Anti-Anti-Dump_and_No...(二)

安卓逆向对抗:三种高效绕过Anti-Frida检测的实战策略 在移动安全与逆向工程领域,动态分析技术是理解应用行为、挖掘漏洞的关键手段。其核心原理在于通过运行时插桩,实时监控和修改目标进程的内存与执行流,从而突破了静态分析的局限。这项技术的核心价值在于为安全研究人员提供了透视应用内部逻辑的“显微镜”,广泛应用于恶意软件分析、协议破解与漏洞挖掘等场景。随着安全防护升级,针对主流动态分析工具Frida的检测(Anti-Frida)日益普遍,成为逆向工作中的常见障碍。本文聚焦于这一对抗前沿,深入剖析了基于进程端口、文件模块、内存线程等多维度的检测机制,并系统性地提出了从源码 阅读详情
标 题: [翻译]Anti-Anti-Dump_and_No...(下)
作 者: aalloverred
时 间: 2006-05-14,18:34
链 接: http://bbs.pediy.com/showthread.php?threadid=25564

[翻译]Anti-Anti-Dump_and_Nonintrusive_tracers(下)

[出处及相关]
kanxue的ARTeam团队优秀文章汇总帖:
http://bbs1.pediy.com:8081/showthread.php?s=&threadid=24875
原文地址:
http://bbs1.pediy.com:8081/attachment.php?s=&attachmentid=1236
上半部分地址(由kkbing翻译):
http://bbs1.pediy.com:8081/showthread.php?s=&threadid=25001
[重要的废话]
实在不好意思贴出来,因为这篇文章着实的超出了我的能力,我只是一只小小小小鸟
因此大家就当帮忙找错误看好了,谢谢!只希望千万不要误人子弟就好了:)

最后的附件是做成了pdf格式的译文,因为我是按照原文的格式翻译的,贴出来的可能看着不舒服.
[译者]aalloverred

[译文]


4. 用于内存管理器的非侵入式跟踪器
一旦我们利用代码强制性的将所有的东西存到了一个可以转存的地方,我们就可以准备访问这些地方了。一些保护壳会在分配的缓存中存储多态的oep,所以我们还要将eip访问权交给这些缓存。有很多方法实现这一点,我这里要使用的是
PAGE_GUARD, 和我在文献[6]中的oepfinder X.Y.Z中介绍的方法一样。
4.1. 编写非侵入式跟踪器
有关非侵入式跟踪器,有很多文章中都有所涉及,而在文献[5,6]中讲解的尤其得多。但我还是要提一些比较重要的东西;不久以后,也就是你读完了这一部分以后,你就会体会到理解这些东西是多么的重要以及它们是多么的有用。我在[5,6]中已经讲述过非侵入式跟踪器和loaders了,所以这里我将只是简要的提及它们。
非侵入式跟踪器的相关概念包括KiUserExceptionDispatcher的hooking,及其我们自己处理所有的异常。如果我们处理异常,我们只是简单的调用NtContinue ;如果我们不处理异常,我们就返回KiUserExceptionDispatcher。我们先来看一个非侵入式跟踪器样板示例:
nonintrusive: mov ecx, [esp+4]
mov ebx, [esp]
pushad
call deltakiuser
deltakiuser:  pop ebp
sub ebp, offset deltakiuser
...
retkiuser0:   popad
mov [ecx.context_dr0], 0
retkiuser:    push 0deadc0deh
ret
不要因为看到了mov [ecx.context_dr0], 0这一句而困惑;当讲述道第4.3节的时候你自然就会理解它。
现在我们先看一看KiUserExceptionDispatcher这个函数:
.text:7C90EAEC mov ecx, [esp+arg_0]
.text:7C90EAF0 mov ebx, [esp+0]
.text:7C90EAF3 push ecx
.text:7C90EAF4 push ebx
.text:7C90EAF5 call _RtlDispatchException@8
.text:7C90EAFA or al, al
.text:7C90EAFC jz short loc_7C90EB0A
.text:7C90EAFE pop ebx
.text:7C90EAFF pop ecx
.text:7C90EB00 push 0
.text:7C90EB02 push ecx
.text:7C90EB03 call _ZwContinue@8
.text:7C90EB08 jmp short loc_7C90EB15
.text:7C90EB0A
.text:7C90EB0A loc_7C90EB0A:
.text:7C90EB0A pop ebx
.text:7C90EB0B pop ecx
.text:7C90EB0C push 0
.text:7C90EB0E push ecx
.text:7C90EB0F push ebx
.text:7C90EB10 call _ZwRaiseException@12
你可能注意到了这与我在[6]中所讲述的东西不是一样的么?别急,接着往下走,你会看到为什么会这样。我们将要hook KiUserExceptionDispatcher的前两条指令,注意如果我们要得到stealth code我们可以hook _RtlDispatchException并将我们的跟踪器插入到那里。有无数种可能。我们hook KiUserExceptionDispatcher 时必须模仿被覆盖的字节它们是:
.text:7C90EAEC mov ecx, [esp+4]
.text:7C90EAF0 mov ebx, [esp]
ecx = 指向CONTEXT
ebx = 指向EXCEPTION_CODE
我们可以通过检查的ebx的值轻松的决定我们是要处理这个异常还是要将控制权返回给KiUserExceptionDispatcher:
nonintrusive: mov ecx, [esp+4]
mov ebx, [esp]
pushad
call deltakiuser
deltakiuser:  pop ebp
sub ebp, offset deltakiuser
cmp dword ptr[ebx], EXCEPTION_BREAKPOINT
je __bp_conditions
cmp dword ptr[ebx], EXCEPTION_GUARD_PAGE
jne retkiuser0
...
retkiuser0:   popad
mov [ecx.context_dr0], 0
retkiuser:     push 0deadc0deh <--- 在hook引擎中改变此值使其指向正确的值
ret           
因为KiUserExceptionDispatcher是ntdll.dll的输出函数,我们可以简单的使用GetProcAddress定位它并且找到我们要hook的地址。同样注意,我一直使用的都是偏移地址,因为所有的代码都是在我注入的代码中执行的:
mov eax, [ebp+KiUserExceptionDispatcher]
lea ebx, [ebp+nonintrusive]
mov byte ptr[eax], 0e9h
mov ecx, eax
add ecx, 5
sub ebx, ecx
mov dword ptr[eax+1], ebx
add eax, 7
mov dword ptr[ebp+retkiuser+1], eax
有了这个钩子,你也许就可以肯定了,所有的异常都会经过你的钩子,你可以处理它们。当然非侵入式跟踪器也有弊端,因为这些理论不适用于两个进程比如当我们使用调试器/被调试程序的时候,因为这些异常可能会被调试器处理。另一方面,如果所有的异常都传递为DBG_EXCEPTION_NOT_HANDLED我们的跟踪器就会毫无问题的工作。只是一个想法,但是为什么不hook WaitForDebugEvent并且将所有的异常传递为DBG_EXCEPTION_NOT_HANDLED呢。只是设想,思考一下,读一下5.3章。 

非侵入式跟踪的第二个也是最大的一个缺陷就是处理堆栈时大量的垃圾代码。如果我们仔细的看看Exception异常的处理过程我们会看到CONTEXT和Exception代码由ring0拷贝到了用户堆栈。如果某些多态指令混淆了堆栈,拷贝就不会被执行,程序也就随之崩溃了。这是一种特别的情况,当我们单步跟踪代码的时候我们必须检查下一条指令是不是要改变堆栈,这样我们就可以阻止单步跟踪或者在我们的跟踪器中模仿这样的指令。这一点,我要推荐一下z0mbie的XDE engine[7]。

好了,希望你了解了基本思路。
4.2. 非侵入式跟踪器中使用PAGE_GUARD
PAGE_GUARD被用来提供简单好用的访问警告,但是也要注意到一旦异常发生了,PAGE_GUARD不会被移除。调试器 (也就是我们的非侵入是跟踪器) 应该在跳转到异常发生的地方执行前应该先改变页的的保护属性。我们也可以使用PAGE_NOACCESS,我们要使用这样或者那样的方法改变错误页的保护属性。但是使用PAGE_GUARD的优点源于这样一个事实:我们同样知道了错误代码传给了我们的跟踪器,这是我们使用的PAGE_GUARD唯一原因。同样也可以在保护器中使用对地址0x00000000h处的内存访问,通常代码是下面这个样子:
xor eax, eax
push offset sehhandle
push dword ptr fs:[eax]
mov dword ptr fs:[eax], esp
mov [eax], eax <-- 异常
我们必须手动确定异常是由于寄存器的错误使用而产生的,还是由于我们的页被设置成了page_noaccess(不可访问属性)而产生的。在 4.3 我还会涉及到如何区分寄存器的错误使用和读/写我们的内存区域。要设置PAGE_GUARD我们得回到内存管理器部分并且将这个属性设置到一定的内存范围,使用下面的方法:
__setpageguard:   or memprotection, PAGE_GUARD
lea eax, dummy_var
          push eax
          push memprotection
          push range
          push virtualaddress
          call [edi+VirtualProtect]
太简单了是吧? 这部分要存储在我们每个内存管理器的后面。很简单,现在你应该也明白了为什么所有的分配的内存缓冲区的都是以页为边界的。因为不管你怎么做都VirtualProtect将可访问的起始地址设为:
Address to set access = address and 0FFFFF000h
访问错误的页简单的使用FPI,多亏有了FPI,我们可以容易的找到VirtualAddress以及任何给定区域的大小:
getfpi: push esi
 mov esi, [ebp+memstart]
     add esi, NEW_MEM_RANGE-4000h
     sub eax, [ebp+memstart]
     shr eax, 0ch ;获得当前页的索引
     mov eax, [esi+eax*4]
     shr eax, 2 ;获得 FPI
     pop esi
     retn
;eax=FPI?,返回页的基址
getvafromfpi: 
     shl eax, 0ch
     add eax, [ebp+memstart]
     retn
;eax FPI, 由getfpi中返回。本函数返回大小
getsizefromfpi:
     push esi ecx edx ebx
     mov esi, [ebp+memstart]
     add esi, NEW_MEM_RANGE-4000h
     mov ecx, eax
     xor ebx, ebx
__cycle_fpi: 
     mov edx, [esi+ecx*4]
     shr edx, 2
     cmp edx, eax ;比较各FPI
     jne __gotsizefromfpi
     inc ebx
     inc ecx
     jmp __cycle_fpi
__gotsizefromfpi: 
     shl ebx, 0ch
     mov eax, ebx
     pop ebx edx ecx esi
     retn
现在,要是你不喜欢我的方法,如何实现不同的内存管理也可以取决于你,完全取决于你,本文的主要目的只是想让你理解这个方法的思路。如果你正在像我一样思考,你应该会问了:怎样才能知道是由于对受保护页的执行引发了EXCEPTION_GUARD_PAGE还是对受保护页的读写引发了异常呢?不使用ring0这几乎是不可能的。我说几乎是因为如果肯定了是对受保护页的读写引发了异常,你就必须在你的内存管理器中跟踪受保护页的信息。 
4.3.特别情况下的PAGE_GUARD
就像我在编写自己的小程序的时候一样。我意识到由KiUserExceptionDispatcher传回的数据已经不足以满足我的需要了。我的处境很可能是一个程序员能够碰到的最糟糕的处境了。我想做的是迫使使用了Advanced OEP protection(高级OEP保护手段)的 ASProtect SKE 2.2将分配的所有缓存都存储到一个大缓存以方便以后转储。为了找到ASPR混淆后的多态变形的oep,我计划在每个新分配的缓存上使用PAGE_GUARD,这样我就可以确定什么时候EIP到达缓存并在这个时候登入,但此时一个大问题来了。

因为所有的缓存都位于一个大内存缓冲区中,而我的代码用作了内存管理器(释放/分配内存页),我无法确定什么时候eip到达了某个可能的范围中,因为在缓冲内存中这样的范围实在是太多了。实际上我所需要的不是作为信息传递给KiUserExceptionDispatcher的东西,对,我需要的是cr2寄存器的内容,这样就能得到错误地址,这样如果EIP和cr2的内容相匹配,就说明我们到达了自己的受保护页。
Ring3中这一点的解决方案就是保存每个内存页的信息,这样一旦发生了PAGE_GUARD异常,我们就可以确定这个PAGE_GUARD异常发生时,EIP是在一个没有PAGE_GUARD属性的页中还是在一个标记了PAGE_GUARD的内存页中。是个不错的主意,但是需要编写更多的代码,还需要重新组织我的ring3的内存管理器,增加对每个内存页的保护功能。因为我懒得重新编写,所以我就又为KiUserExceptionDispatcher添加了一小段代码。

当然也可能hook KiTrap0E 并且在context结构中返回cr2寄存器的内容。嗯,不错的想法。cr2寄存器将会保存有错误发生的va(虚拟地址),因为对受保护页的访问只不过是触发异常,我们除了得到cr2寄存器的值还要确定异常是由于执行产生的还是由于读写产生的。当然,KiUserExceptionDispatcher是不会替我们返回出错地址的,就是因为糟糕的这一点我们必须改进它,使它能返回cr2寄存器的值:
cr2 = faulting_address0 //aal注:出错地址
eip = faulting_address0 //aal注:出错地址
exceptioncode = EXCEPTION_GUARD_PAGE//错误代码 = EXCEPTION_GUARD_PAGE
爽,登入它,去除这一页的保护,然后等待下次访问。但是如何得到cr2寄存器的内容呢???是啊,这的确是个头痛的问题,但是实际上确实是可以做到的。来看看KiTrap0E是怎么做的:
.text:804DAF25 _KiTrap0E:
.text:804DAF25 mov word ptr [esp+2], 0
.text:804DAF2C push ebp
.text:804DAF2D push ebx
.text:804DAF2E push esi
...
.text:804DB0ED mov ecx, 3
.text:804DB0F2 mov edi, eax
.text:804DB0F4 mov eax, 0C0000006h
.text:804DB0F9 call CommonDispatchException

到这里所有的KiTrap调用将会调用CommonDispatchException,这个函数会将CONTEXT 和ERROR_CODE的内容保存到堆栈中并且将EIP重新引导到KiUserExceptionDispatcher。继续跟踪CommonDispatchException,我们来到这里:
.text:804D8A8D CommonDispatchException proc near
.text:804D8A8D
.text:804D8A8D
.text:804D8A8D
.text:804D8A8D sub esp, 50h
.text:804D8A90 mov [esp+50h+var_50], eax
.text:804D8A93 xor eax, eax
.text:804D8A95 mov [esp+50h+var_4C], eax
.text:804D8A99 mov [esp+50h+var_48], eax
.text:804D8A9D mov [esp+50h+var_44], ebx
.text:804D8AA1 mov [esp+50h+var_40], ecx
.text:804D8AA5 cmp ecx, 0
.text:804D8AA8 jz short loc_804D8AB6
.text:804D8AAA lea ebx, [esp+50h+var_3C]
.text:804D8AAE mov [ebx], edx
.text:804D8AB0 mov [ebx+4], esi
.text:804D8AB3 mov [ebx+8], edi
.text:804D8AB6
.text:804D8AB6 loc_804D8AB6:
.text:804D8AB6 mov ecx, esp
.text:804D8AB8 test dword ptr [ebp+70h], 20000h
.text:804D8ABF jz short loc_804D8AC8
.text:804D8AC1 mov eax, 0FFFFh
.text:804D8AC6 jmp short loc_804D8ACB
.text:804D8AC8
---------------------------------------------------------------------
.text:804D8AC8
.text:804D8AC8 loc_804D8AC8:
.text:804D8AC8 mov eax, [ebp+6Ch]
.text:804D8ACB
.text:804D8ACB loc_804D8ACB:
.text:804D8ACB and eax, 1
.text:804D8ACE push 1
.text:804D8AD0 push eax
.text:804D8AD1 push ebp
.text:804D8AD2 push 0
.text:804D8AD4 push ecx
.text:804D8AD5 call _KiDispatchException@20
.text:804D8ADA mov esp, ebp
.text:804D8ADC jmp Kei386EoiHelper@0
.text:804D8ADC CommonDispatchException endp
好了,因为知道早晚都会调用_KiDispatchException所以我们就直接跟到这里了:
.text:804F318D ; __stdcall KiDispatchException(x,x,x,x,x)
.text:804F318D _KiDispatchException@20 proc near
.text:804F318D
.text:804F318D push 390h
.text:804F3192 push offset dword_804F3278
.text:804F3197 call __SEH_prolog
.text:804F319C mov eax, ds:___security_cookie
.text:804F31A1 mov [ebp-1Ch], eax
.text:804F31A4 mov esi, [ebp+8]
.text:804F31A7 mov [ebp-2ECh], esi

.text:804F31AD mov ecx, [ebp+0Ch]
.text:804F31B0 mov [ebp-2F0h], ecx
.text:804F31B6 mov ebx, [ebp+10h]
.text:804F31B9 mov [ebp-2F8h], ebx
.text:804F31BF db 3Eh
.text:804F31BF mov eax, ds:0FFDFF020h
.text:804F31C5 inc dword ptr [eax+504h]
.text:804F31CB mov dword ptr [ebp-2E8h], 10017h
.text:804F31D5 cmp byte ptr [ebp+14h], 1
.text:804F31D9 jz loc_804F5A76
.text:804F31DF cmp ds:_KdDebuggerEnabled, 0
.text:804F31E6 jnz loc_804F5A76
.text:804F31EC
.text:804F31EC loc_804F31EC:
.text:804F31EC
.text:804F31EC lea eax, [ebp-2E8h]
.text:804F31F2 push eax
.text:804F31F3 push ecx
.text:804F31F4 push ebx
.text:804F31F5 call _KeContextFromKframes@12
.text:804F31FA mov eax, [esi]
.text:804F31FC cmp eax, 80000003h
.text:804F3201 jnz loc_804F5A20
.text:804F3207 dec dword ptr [ebp-230h]
.text:804F320D
.text:804F320D loc_804F320D:
.text:804F320D
.text:804F320D xor edi, edi
.text:804F320F
.text:804F320F loc_804F320F:
.text:804F320F cmp byte ptr [ebp+14h], 0
.text:804F3213 jnz loc_804F58C3
.text:804F3219 cmp byte ptr [ebp+18h], 1
.text:804F321D jnz loc_80516D98
.text:804F3223 mov eax, ds:_KiDebugRoutine
.text:804F3228 cmp eax, edi
.text:804F322A jz loc_80505721
.text:804F3230 push edi
.text:804F3231 push edi
.text:804F3232 lea ecx, [ebp-2E8h]
.text:804F3238 push ecx
.text:804F3239 push esi
.text:804F323A push dword ptr [ebp-2F0h]
.text:804F3240 push ebx
.text:804F3241 call eax
.text:804F3243 test al, al
.text:804F3245 jz loc_80505721
.text:804F324B
.text:804F324B loc_804F324B:
.text:804F324B push dword ptr [ebp+14h]
.text:804F324E push dword ptr [ebp-2E8h]
.text:804F3254 lea eax, [ebp-2E8h]
.text:804F325A push eax
.text:804F325B push dword ptr [ebp-2F0h]
.text:804F3261 push ebx
.text:804F3262 call _KeContextToKframes@20
.text:804F3267
.text:804F3267 loc_804F3267:
.text:804F3267
.text:804F3267 mov ecx, [ebp-1Ch]
.text:804F326A call @xHalReferenceHandler@4

.text:804F326F call __SEH_epilog
.text:804F3274 retn 14h
.text:804F3274 _KiDispatchException@20 endp ; sp = -14h
.text:804F3274
如果不实际的跟踪一下,就看不出这些代码有什么意义,所以我们可以使用Softice开始跟踪,很快我们就会发现我们想要的字节:
.text:804F5959 call _ProbeForWrite@12
.text:804F595E mov ecx, 0B3h
.text:804F5963 lea esi, [ebp+var_2E8]
.text:804F5969 rep movsd
804F5969处的指令负责将CONTEXT拷贝到用户堆栈,当然是为KiUserExceptionDispatcher做这个工作。如果我们能够hook那条指令,我们当然也就能够保存cr2寄存器的内容,并将它的值放到CONTEXT结构的某个域中传回ring3。这些域就是dr0/4 regs。如果我们不处理它,我们的非侵入式跟踪器应该清除cr2的值。
我们将要hook "lea esi, [ebp+var_2E8]" 处,因为它的大小足够保存hook-也就是push/ret组合,注意千万不要hook rep movsd处,否则驱动卸载的时候有可能发生BSOD(aal注:Blue Screen of Death,蓝屏死机)的错误。只是有可能发生,正像Mark Russinovich所描述的,当驱动的地址空间中的代码在执行时,如果打断了某个线程,而我们在这个时候卸载驱动,当被中断的线程打算再次执行时就会返回到空内存而你会得到一个内存页错误(PAGE_FAULT),看到的就是BSOD。我还没有遇到过这种情况,但是这的确是有可能发生的。只要你遭遇到BSOD却一点也不知道是什么原因造成的时候,脑子中想到这一点就行。
同样我们必须标识我们的进程,完成这一点,我使用的是cr3技巧,因为cr3中存储了PDE的物理地址,因为每个进程都有自己的内存空间,那样我们就能毫无问题的标识进程了 [11]。注意我使用的KeStackAttachProcess是不必要的, 我们可以直接在KPROCESS结构(EPROCESS的一部分)中得到cr3的值:
kd> dt nt!_KPROCESS
+0x000 Header : _DISPATCHER_HEADER
+0x010 ProfileListHead : _LIST_ENTRY
+0x018 DirectoryTableBase : [2] Uint4B <-- 就是这里了 
+0x020 LdtDescriptor : _KGDTENTRY
ntoskrnl.exe基址的偏移地址25963h处,我们现在就可以准备完成这个技巧了。开始干吧!!!!你必须在你自己的ntoskrnl.exe中看看这个偏移地址,因为这个偏移地址可能会有所不同。
另外还有一种条件非常非常非常重要,这就是cr2的保存!!!
cr2的值没有被保存,如果其它的内存页错误,cr2就会保存那个错误地址。 这对我们来说使非常危险的,因为内存页错误的处理程序同样也用来将内存页存储在页面文件中。有一种存储cr2的值的方法就是hook int 0eh,它能够在当且仅当cr2的值属于我们的进程时存储它。接下来的代码中,我们的hook将会得到已经存储了的cr2的值,并且将它的值放到context.context_dr0中传给我们的代码。
hookint0eh label dword
push eax
mov eax, cr3
cmp eax, c_cr3
jne __exit_int0e
mov eax, cr2
mov c_cr2, eax
__exit_int0e: 
pop eax
jmp cs:[oldint0eh]
;将 cr2 的内容保存在 context.context_dr0 中
hookmycode label dword
push eax
mov eax, cr3
cmp eax, c_cr3
jne __exithook
lea esi, [ebp-2e8h]
mov eax, c_cr2
mov [esi.context_dr0], eax
__exithook: 
pop eax
lea esi, [ebp-2e8h]
retaddr: 
push 0deac0deh
ret
现在在你的非侵入式跟踪器中你只要测试是否EIP = DR0,如果相等的话我们就登入访问,而如果不是我们就去除PAGE_GUARD然后在错误指令后设置int 3h。我们有改进后的KiUserExceptionDispatcher替我们作我们想做的事。 
同样这也将帮助我们分别访问我们的内存页时产生的ACCESS_VIOLATION时我们是不是在使用PAGE_NOACCESS而不是在使用PAGE_GUARD。
4.4. 进入内存区域并访问
一旦我们知道了如何解决问题,我们就得进入我们的内存页面。这可以通过向文件进行写入操作来实现,或者更好一点的方法是使用www.sysinternals.com 的OutputDebugStringA和DbgView得到输出:
示例代码:
__log: lea ecx, [ebp+format5]
    lea ebx, [ebp+buffer]
    push eax
    push ecx
    push ebx
    call [ebp+wsprintfA]
    add esp, 0ch
    push ebx
    call [ebp+OutputDebugStringA] ;登入 访问
    ...
    format5 db "eip log : 0x%.08X", 13, 10, 0
现在,你应该在DbgView中得到了许多数据,保存下日志数据,研究一下地址,排出掉重复的部分,并且通过再次运行跟踪器缩减日志文件的大小。
4.5. 从跟踪器中触发驱动
这个实际上用来通知驱动要关心什么样PID,此时驱动已经安装好了,但是我们还得告诉它开始跟踪我们:
push 0
push 0
push OPEN_EXISTING
push 0
push 0
push GENERIC_READ or GENERIC_WRITE
push offset driver
callW CreateFileW
mov dhandle, eax
call DeviceIoControl, eax, 20h, o pid, 4, o pid, 4, o dwbytes, 0
push dhandle
callW CloseHandle
...
driver: unis <//./ring0>
pid = pid of process we are tracing//我们所跟踪程序的pid
我还使用了文章Loader from ring0 [11]中讲述的相同的方法来标识进程。
4.6. 制作stealth非侵入式跟踪器
好,这里假定我们对付的是个新壳,它会在KiUserExceptionDispatcher 中寻找我们的hook,我们要做的就是打败它。怎样才能做到这一点呢?的确不是简单的任务,多亏有了yates展示的技巧[8]我们能过继续下去。
KiUserExceptionDispatcher是一个永远永远都不会返回的过程,它将要调用NtContinue或者是NtRaiseException。我们看看都发生了什么:
- 异常发生
- ntoskrnl.exe通过KiTrapXX接手控制权
- KiTraps实际上是IDT的入口(?),并且根据不同的异常在KiTrapXX的入口处有两种可能的堆栈布局:
+---------------+    +---------------+
|     EFLAGS      |    |      EFLAGS     |
+---------------+    +---------------+
|       CS         |   |         CS       |
+---------------+   +---------------+
|       EIP        |   |        EIP     |
+---------------+   +---------------+
            |   Error Code   |
            +---------------+

因为一些异常并不引发错误,同时也为了从ring0中退出来时更容易些,不管什么异常发生了一些KiTrapXX都将0压入堆栈模仿代码,比如KiTrap01和KiTrap03:
_KiTrap01
0008:804D8D7C PUSH 00 <--- dummy Error Code
0008:804D8D7E MOV WORD PTR [ESP+02],0000
0008:804D8D85 PUSH EBP
0008:804D8D86 PUSH EBX
0008:804D8D87 PUSH ESI
0008:804D8D88 PUSH EDI
0008:804D8D89 PUSH FS
_KiTrap03
0008:804D915B PUSH 00 <--- dummy Error Code
0008:804D915D MOV WORD PTR [ESP+02],0000
0008:804D9164 PUSH EBP
0008:804D9165 PUSH EBX
0008:804D9166 PUSH ESI
0008:804D9167 PUSH EDI
0008:804D9168 PUSH FS
但是KiTrap0E (内存页错误处理程序) 并没有将0压入堆栈因为错误代码存在了堆栈中。
_KiTrap0E
0008:804DAF25 MOV WORD PTR [ESP+02],0000
0008:804DAF2C PUSH EBP
0008:804DAF2D PUSH EBX
0008:804DAF2E PUSH ESI
0008:804DAF2F PUSH EDI
0008:804DAF30 PUSH FS
0008:804DAF32 MOV EBX,00000030
从中断中返回是由一个简单的IRETD指令完成的,它与ret指令相近,也是跳转到堆栈中所保存的EIP。异常处理完毕之后,ring0确定要调用KiUserExceptionDispatcher时它就会将KiUserExceptionDispatcher的地址存储在堆栈中,所以IRETD只是简单的返回了KiUserExceptionDispatcher :
0008:804F5A0F MOV EAX,[_KeUserExceptionDispatcher]
0008:804F5A14 MOV [EBX+68],EAX
:dd ebx+68
0010:EEC21DCC 7C90EAEC 0000001B 00000246 0013FCD0 ìê |....F.......
0010:EEC21DDC 00000023 00000000 00000000 00000000 #...............
正如你所看到的,EIP被KiUserExceptionDispatcher的地址覆盖了以及堆栈中保存的CS,Eflags,esp 和 SS。因为我们要做的是hook这些指令,所以他会指向ntdll.dll中其他的代码,就是我们使用yates展示的方法所存储的那些代码。 同样,也有更好的方法应该尽量不要扫描磁盘上的ntdll.dll,而是使用内存中已经载入的文件直接重新引导至UserSharedData,在用户模式下被设置成了只读:
kd> ? SharedUserData
Evaluate expression: 2147352576 = 7ffe0000
kd>
但是在ring0它被映射到了:
#define KI_USER_SHARED_DATA 0xffdf0000
所以我们可以在ring0中向那里写入,并且重新引导我们的异常(Exceptions)到达负责跳转到KiUserExceptionDispatcher 的地方,或者只是简单的调用我们存储在被调试程序内存空间某处的非侵入式跟踪器。见[ring0stealthtracer]文件夹。在你运行之前确定你理解了这些代码将要做什么。我这次使用的是PID标识进程。我们首先定位ntoskrnl.exe的基址: 
iMOV esi, ZwCreateFile
and esi, 0FFFFF000h
__find_base: cmp word ptr[esi],'ZM'
je __ntoskrnlbase
sub esi, 1000h
jmp __find_base
__ntoskrnlbase: mov ntoskrnlbase, esi
然后我们必须定位_KeUserExceptionDispatcherVariable(变量),它没有被输出。对我们来说,幸运的是它被存储在一个以双字(dword)对齐的边界处,而且在ntoskrnl.exe中KiUserExceptionDispatcher只出现了一次,所以我们可以使用KiUserExceptionDispacther的地址搜索它:
mov edi, esi
mov ebx, esi
add ebx, dword ptr[ebx+3ch]
mov ecx, [ebx.NT_OptionalHeader.OH_SizeOfImage]
shr ecx, 2
cld
mov eax, kiuser
repnz scasd
sub edi, 4
一旦我们定位在未输出的的_KeUserExceptionDispatcher我们就可以将我们的代码保存在UserSharedData然后将_KeUserExceptionDispatcher覆盖成我们代码的地址:
push edi
mov KiUserExceptionDispatcher, kiuser
mov edi, kiusershareddata+100h
mov esi, offset shareddatahook
mov ecx, shareddatahooksize
cld
rep movsb
pop edi
mov dword ptr[edi], kiusershareddataring3+100h
...
kiusershareddata equ 0ffdf0000h
kiusershareddataring3 equ 07ffe0000h

完工!
让我们看看如果用softICE跟踪的话是什么样子:
_KiTrap03
0008:804D915B PUSH 00
0008:804D915D MOV WORD PTR [ESP+02],0000
0008:804D9164 PUSH EBP
0008:804D9165 PUSH EBX
0008:804D9166 PUSH ESI
0008:804D9167 PUSH EDI
0008:804D9168 PUSH FS
...
0008:804F5A0F MOV EAX,[_KeUserExceptionDispatcher]
0008:804F5A14 MOV [EBX+68],EAX
0008:804F5A17 OR DWORD PTR [EBP-04],-01
… :
dd _KeUserExceptionDispatcher
0023:80552AF0 7FFE0100 7C90EAD0 7C90EAC0 00002626 ... .ê |.ê |&&..
这里是iretd之前的堆栈:
0023:F0E9DDCC 7FFE0100 0000001B 00000246 0013FCC8 ... ....F.......
0023:F0E9DDDC 00000023 00000000 00000000 00000000 #...............
然后我们就到了我们存在UserSharedData中的代码:
001B:7FFE0100 CALL 7FFE0105
001B:7FFE0105 POP EDX
001B:7FFE0106 SUB EDX,F7129409
001B:7FFE010C MOV ECX,FS:[0020] <--从TEB中得到PID
001B:7FFE0112 CMP DWORD PTR [EDX+F712943B],-01 <-- 不跟踪
001B:7FFE0119 JZ ntdll!KiUserExceptionDispatcher   到KiUser跳转
001B:7FFE011B CMP [EDX+F712943B],ECX <-- 被跟踪的程序的PID么?
001B:7FFE0121 JNZ ntdll!KiUserExceptionDispatcher 不是,我们调用KiUser
001B:7FFE0123 JMP [EDX+F7129437] <-- 我们调到nonintrusive
001B:7FFE0129 JMP [EDX+ntdll!KiUserExceptionDispatcher] <-- 回到KiUser
不是被跟踪的程序,所以我们继续对KiUserExceptionDispatcher的访问:
ntdll!KiUserExceptionDispatcher
001B:7C90EAEC MOV ECX,[ESP+04]
001B:7C90EAF0 MOV EBX,[ESP]
001B:7C90EAF3 PUSH ECX
001B:7C90EAF4 PUSH EBX
希望你理解了stealth技术? 祝未来的保护器好运!!
注意这个技巧是 Barnaby Jack 在他关于ring0shell code著名的文章 [9] 。.

4.7. 非侵入式跟踪器结论
到这里,希望你已经明白了基本思路,我提供的所有的代码和示例都是以在ntoskrnl.exe 中放置hook为目的的,所以你需要将我的驱动源代码中的偏移地址修改为你自己的所有的驱动代码都只是未经修正过的,如果没有提前修改而因为错误的偏移地址导致了蓝屏死机,系统崩溃,数据丢失等均由使用者本人负责。







5. 装载器的装载器
装载器的装载器实际上是处理双进程保护的装载器。一些软件使用装载器装载真正的游戏,我已经做了一个处理这种软件的装载器。因为这样就存在了两个装载器所以我把它称作装载器的装载器。网上很可能已经有我写的关于这方面的文章,但是在这里我还是想描述一下,主要是因为我们所对付的就有一些双进程的保护壳。我们必须获得第二个进程的控制权,为了达到这个目的我们将要hook的是CreateProcessA,这样等它完成了自己的任务时,我们就可以得到一个PROCESS_INFORMATION结构,这个结构中包含了ProcessID和ThreadID。
- ProcessId 有了它我们就可以打开进程然后在它的缓存地址空间写东西等等…
- ThreadId 有了它我们才能控制某些东西,Get/SetThreadContex,Resume/SuspendThread。
注意:如果我们是从注入的偏移地址无关代码中hook我们就不需要了,因为我们已经有了保存在PROCESS_INFORMATION结构中的ProcessHandle和ThreadHandle。
我将讲述3种方法,其中一种有可能还没有在RCE领域中被讨论过。
5.1. 使用注入代码实现的装载器的装载器
这种方法的思路是将我们的内存管理器注入到第二个进程中,整个过程涉及到在返回(ret)时hook CreateProcessA,此时我们会得到一个错误代码(如果进程的执行没有问题的话)。我们还会得到一个填充好了的PROCESS_INFORMATION结构,其中就包含了进程和主线程的句柄。从根本上讲,就会有两套偏移无关代码 。示例代码在[lflinjected]文件夹中。
我们来看一眼我们的例子,在第二个装载器的装载器中我还要用到这个测试(test)程序,所以脑子里记住这里我们是怎么处理的:
push offset mutexname
push 0
push MUTEX_ALL_ACCESS
callW OpenMutexA
test eax, eax
jnz __2ndprocess
push offset mutexname
push 0
push 0
callW CreateMutexA
push offset pinfo
push offset sinfo
push 0
push 0
push 0
push 0
push 0

push 0
push offset progy
push 0
callW CreateProcessA
push -1
push pinfo.pi_hProcess
callW WaitForSingleObject
jmp __exit
__2ndprocess: push 40h
push offset mtitle
push offset mtext
push 0
callW MessageBoxA
__exit: push 0
callW ExitProcess
看到了吧,这个小程序是要生成一个互斥对象(mutex),然后根据互斥对象的情况来决定是输出信息还是产生一个新的进程实例。为了实现装载器的装载器,我们用注入代码的第一部分hook函数CreateProcessA,第二部分用来注入。从根本上讲这里出现了两个偏移无关代码,索性的是获得kernel的基址(getkernelbase)和从kernel32.dll搜索输出函数的工作,我们只需要进行一次,因为kernel32.dll偏移地址是不会因为不同的进程而有所不同的。
思路:
- 使用CreateProcess生成挂起状态的进程
- 注入偏移无关代码
- 使用偏移无关代码hook我们目标进程中的CreateProcessA
- 一旦运行到了CreateProcessA返回(retn)处的hook就得到了进程和线程句柄
- 到这里我们的偏移无关代码就要使用WriteProcessMemory和VirtualAllocEx将一个新的装载器注入到第二个进程中,此处从根本上讲我们是在重复我们的原始的装载器中注入偏移无关代码的工作
由于第一个装载器的大小的问题,源代码就不在这里贴出了,你可以在[lflinjected]文件夹中找到已经经过注释的源代码文件。
5.2. 不使用注入代码实现的装载器的装载器
这是更为简单的一个解决方案,也仅需要较少的偏移无关代码的编写和理解。但是如果进程不是使用CREATE_SUSPENDED创建的,这种方法就不太好了,因为我们必须手动的加入CREATE_SUSPENDED标志,这样甚至反倒需要更多的代码。
思路:
- 使用CREATE_SUSPENDED标志载入第一个程序
- 在CreateProcessA中插入jmp $
- 运行到我们的hook时,从堆栈中读取PROCESS_INFORMATION的地址,dwCreationFlags和返回地址
- 将dwCreationFlags与CREATE_SUSPENDED进行或操作并且将它存回堆栈,清除前面的hook并在堆栈的所指向的返回地址插入新的指令
- 运行到我们的第二个hook时,读取PROCESS_INFORMATION我们会得到pid和tid
- 使用OpenProcess和OpenThread处理新进程
在[lflwoinjected]文件夹中查看完整的实现代码,已注释。
5.3.用于被调试进程的非侵入式跟踪器 
我这里用的是一个使用Armadillo 4.3的Standard Protection + Debug Blocker加壳的crackme来注入我的跟踪器。当然这对内存管理器也适用,但为了简单明了的说明问题,我只把非侵入式的跟踪器注入到其中并且中断在OEP处。. Lol, guess what you got DebugBlocker
armadillo oep finder with this tutorial.
其实实践起来并不难,你所需要的只是一点点想象力,以及一些如何使用Windows Debug APIs [10]实现ring3级的调试的知识,你可以参考一些调试装载器的教程。要实现这一点我们要hook WaitForDebugEvent以便我们第一时间得到它的输出值然后才能够检查DEBUG_EVENT结构的内容,我们只对传给我们的代码的EXCEPTION_DEBUG_EVENT感兴趣,而所有其他的事件都直接传回给程序。我使用带DBG_EXCEPTION_NOT_HANDLED参数的ContinueDebugEvent,它会在我们的被跟踪程序中调用KiUserExceptionDispatcher。为了达到目的,我们还得在armadillo中CreateProcessA的返回处(retn)hook,这样才能将我们的非侵入式跟踪器注入。

代码和目标程序都在文件夹[armadillo_oep]中,既然到了这里,还要感谢一下Teddy Rogers和他收集unpackme的网站http://www.tuts4you.com/unpackme/ ,因为我们将要使用的unpackme就来自这里
我们来看看调用了CreateProcessW后armadillo又干了什么:
004949EC . 52 PUSH EDX
004949ED . 6A 02 PUSH 2
004949EF . 68 A4B44C00 PUSH armadill.004CB4A4
004949F4 . 8B45 10 MOV EAX,DWORD PTR SS:[EBP+10]
004949F7 . 50 PUSH EAX
004949F8 . 8B4D 08 MOV ECX,DWORD PTR SS:[EBP+8]
004949FB . 8B11 MOV EDX,DWORD PTR DS:[ECX]
004949FD . 52 PUSH EDX
004949FE . FF15 D0304C00 CALL DWORD PTR DS:[<&KERNEL32.ReadProcessMem>
这里,Armadillo从入口处读取了2个字节,这样它就能够在这里插入jmp $ hook然后再使用DebugActiveProces附加进程了,当然,armadillo也使用Sleep/GetThreadContext来确定程序何时到达入口点,在无限循环中:
0048FD8D > /A1 A0B54C00 MOV EAX,DWORD PTR DS:[4CB5A0]
0048FD92 . 8B48 08 MOV ECX,DWORD PTR DS:[EAX+8]
0048FD95 . 51 PUSH ECX
0048FD96 . FF15 C4314C00 CALL DWORD PTR DS:[<&KERNEL32.DebugActivePro>

从这里开始我们就进入了一个使用WaitForDebugEvent和ContinueDebugEvent制造的调试循环(Debug Loop)。 “Hybrid” hooking of WaitForDebugEvent在这里是不行的,因为这里的ret附近没有填充的nop指令,所以我们要使用更巧妙一些的hook方法。
这个方法就是我们覆盖堆栈中所存储的返回地址,这样运行WaitForDebugEvent,当它试图返回时就会跳转到我们的代码了。


好,还没跑吧?或许一些图例将会帮助理解得更清楚些
+---------------+---------------+---------------+
|     返回地址     |     调试事件     |      毫秒      |
+-------+-------+---------------+---------------+
     |
  被钩子覆盖
     |
+-------+-------+---------------+---------------+
|      我的代码    |     调试事件     |      毫秒      |
+---------------+---------------+---------------+
现在一旦WaitForDebugEvent打算返回到它的调用者是就会到达我的代码,而我们就可以随心所欲的处理异常了: 
hooking代码示例:
; ;
首先我们需要几个字节插入我们的hook
;
mov esi, [edi+WaitForDebugEvent]
xor ecx, ecx
push esi
__get5bytes: push esi
call ldex86
add ecx, eax
add esi, eax
cmp ecx, 5
jb __get5bytes
pop esi
push edi
lea edi, [edi+rippedbytes]
cld
rep movsb
pop edi
mov dword ptr[edi+goback+1], esi
;;
将字节拷贝到可以安全运行的缓存区域
;
mov esi, [edi+WaitForDebugEvent]
mov ecx, esi
lea ebx, [edi+hookwaitfordebugevent]
mov byte ptr[esi], 0e9h
add ecx, 5
sub ebx, ecx
mov dword ptr[esi+1], ebx

被覆盖部分的字节保存在了rippedbytes变量中:
hookwaitfordebugevent label dword
pusha
call deltahook
deltahook: pop ebp
sub ebp, offset deltahook
mov eax, [esp+8*4]
;保存原来的返回地址
mov [ebp+waitfordebugeventretaddress], eax
mov dword ptr[ebp+retorig+1], eax
lea ebp, [ebp+mywaitfordebugevent]
mov [esp+8*4], ebp
popa
rippedbytes db 30 dup(90h)
goback: push 0deac0deh <- WaitForDebugEvent + ripped bytes
ret
经过我们的工作,WaitForDebugEvent完成后我们就会到达这里了: imywaitfordebugevent proc
mov ecx, [esp-8] ;这里是debugevent
pusha
同样要注意偏移地址是负值,因为ret和leave指令已经将堆栈对齐了,因为windows API使用的是stdcall约定(除了几个函数如wsprintfA,DbgPrint,还有其他几个???)。
这种情况下我访问变量的时候我们必须计算出负的偏移地址,同样还有小心,此时不要将任何值压入堆栈,因为每次入栈都将会破环存在堆栈中的数据,这些数据对我们使用的这种方法是至关重要的。
现在我们关心的就是异常的处理了,只处理EXCEPTION_DEBUG_EVENT:
mywaitfordebugevent proc
mov ecx, [esp-8] ;这里是debugevent
pusha
call deltamydebug
deltamydebug: pop edi
sub edi, offset deltamydebug
mov ebx, ecx
cmp [ebx.de_code], EXCEPTION_DEBUG_EVENT
jne __return_to_original
cmp [ebx.de_u.ER_ExceptionCode], EXCEPTION_BREAKPOINT
jne __passexception
cmp [edi+firstint3h], 1
je __passexception
;在远进程中设置页面保护
pushv <dd ?>
push PAGE_EXECUTE_READWRITE or PAGE_GUARD
push [edi+c_range]
push [edi+c_start]
push [edi+phandle]
call [edi+VirtualProtectEx]
push DBG_CONTINUE
push [ebx.de_ThreadId]
push [ebx.de_ProcessId]
call [edi+ContinueDebugEvent]
mov [edi+firstint3h], 1
jmp __l33t
__passexception: push DBG_EXCEPTION_NOT_HANDLED
push [ebx.de_ThreadId]
push [ebx.de_ProcessId]
call [edi+ContinueDebugEvent]
;;现在的问题是我们返回哪里呢?我们可以把修改过的错误代码保存在debug_event.code
;中,然后继续执行,因为我们知道再没有什么会在破坏我们的代码了。 
; 
__l33t: mov [
ebx.de_code], 0deadc0deh ;l33t
__return_to_original: popa
retorig: push 0deadc0deh ;改成返回地址
ret
endp
我们只对第一个int 3h感兴趣,它产生于DebugBreak,它应该以ContinueDebugEvent(DBG_CONTINUE)的形式传递,其他的情况我们都使用ContinueDebugEvent(DBG_EXCEPTION_NOT_HANDLED)的形式将它传回被调试程序,让我们的非侵入式的跟踪器来处理被跟踪进程的内存区,同样,为了避免破坏我们的代码,我们将debug_event.code设置成0deadc0deh,这样它就不会再处理debug_event了, 或者我们也可以将ThreadId或ProcessId的值设成毫无意义的数值,这样ContinueDebugEvent就对我们程序什么都不做了。
现在一切都交给我们存在被调试程序的非侵入式的跟踪器了,等待知道你得到一个信息告诉你oep找到了。另外MessageBoxA的“Ok”按钮被点击后,我还在非侵入式跟踪器中插入了一条jmp $,所以现在你要做的就是转出进程并且,当然得从任务管理器或者是Mark Russinovish的进程管理器中结束它了。

祝你好运 
代码位于 [armadillo_oep]文件夹中。




6. 调试注入的代码 ? 几条建议
调试这种代码的要点就是能够看到第二个进程里的代码,尤其是我们面对的是loader的loader或者跟踪被调试程序的时候。
有几个技巧需要提一下:
- 在你的代码中使用int 3h或者在SoftICE中使用bpint 3 或者i3here on来中断在代码的可疑部分
- 对于装载器的装载器的情况,使用ADDR来看第二个进程中到底发生了什么
- 编写非侵入式跟踪器时使用jmp $和ctrl+d中断在softice,因为此时bpint 3和i3here on会因为非侵入式跟踪器的关系中断很多次,或者可以使用drX寄存器跳过错误指令然后代码中的int 3h就会起作用了

这些都是针对调试类似的装载器的几点建议,祝你好运当然这些技巧对调试注入的DLL时也适用。






























7. 结论
这里我展示我所能想到的一些技巧以及我编写的一些代码。希望这能够帮到一些人,特别希望能让那些入门级的逆向者明白在RCE的世界里编程的能力是多么重要。我的意见可能有人赞成,有人反对,有人喜欢,也有人唾弃,或者是破口大骂说我照你的代码怎么编译不成功?不管怎么说,我留下了许多东西都需要思考,要记住熟能生巧。如果你开始没有成功,那就再试一次,答案肯定就在附近的某个地方。为了搞清楚内存管理器和Delphi代码的关系,我用了两天的时间,而编译成功armadillo_oep的代码只花了4天。


我在这里展示的所有技巧的相关代码都同本文附在了一起,为了更好的理解文章,我建议你读以下源代码。另外,Tasm32 DDK也包含在了其中。















随同这篇教程的所有代码都可以随意公开使用,但使用时请在致谢里提及作者以及ARTeam。请不要使用文中的理论进行非法行为,文中的所有信息都是以学习以及帮助更好的理解程序代码的安全技术为目的的。


8. 参考文献
[1] Optimization of 32 bit code, Benny/29a, http://vx.netlux.org/29a/29a-4/29a-4.215
[2] Gaining important datas from PEB under NT boxes, Ratter/29a,
http://vx.netlux.org/29a/29a-6/29a-6.224
[3] Billy Belcebu Virus Writing Guide 1.0 for Win32, Billy Belcebu,
http://vx.netlux.org/29a/29a-4/29a-4.202
[4] Retrieving API’s addresses. LethalMind, http://vx.netlux.org/29a/29a-4/29a-4.227
[5] Solution to The Amazing Picture downloader, deroko,
http://www.crackmes.de/users/warrantyvoider/the_amazing_picture_downloader/
[6] Unpacking and Dumping ExeCryptor and coding loader for it, deroko,
http://tutorials.accessroot.com
[7] eXtended (XDE) disassembler engine, z0mbie, http://vx.netlux.org/29a/magazines/29a-8.rar
[8] Anti-Anti-Bpm, yates, http://www.yates2k.net/syscode/bpm.rar
[9] Remote Windows Kernel Exploatation - Step into ring0, Barnaby Jack,
http://www.eeye.com/~data/publish/whitepapers/research/OT20050205.FILE.pdf
[10] Win32 Debug API Part 1/2/3, Iczelion, http://win32asm.cjb.net/
[11] Loader from ring0, deroko, ARTeam eZine #1, http://ezine.accessroot.com
Some useful tutorials to learn about loaders theory:
[12] Shub-Niggurath and ThunderPwr coding loader series, http://tutorials.accessroot.com
[13] Createing Loaders & Dumpers ? Crackers Guide to Program Flow Control, yates,
http://www.yates2k.net/lad.txt
[14] Using Memory Breakpoints, Shub-Niggurath, http://tutorials.accessroot.com
一些很有用的文章:
[15] Dll Injection Tutorial, Darawk, http://www.edgeofnowhere.cc/viewtopic.php?p=2441382
[16] Three Ways to Inject Your Code into Another Process, Robert Kuster, The Code Project
http://www.codeproject.com/threads/winspy.asp
[17] InjLib ? A Library that implements remote code injection for all Windows versions,
Antonio Feijao, The Code Project, http://www.codeproject.com/library/InjLib.asp
有用的工具:
[18] OllyAdvanced plug-in, MaRKuS TH-DJM,
http://www.tuts4you.com/forum/index.php?showtopic=7092
http://omega.intechhosting.com/~access/forums/index.php?showtopic=2542
[19] LordPE plug-in, deroko, http://deroko.phearless.org/dumpdll/
[20] Ice-Ext, Stan, http://stenri.pisem.net/




9. 致谢
这里我要感谢所有的ARTeam的成员将他们的知识无私奉献,感谢29a病毒团队出品的最好的电子杂志,感谢我phearless电子杂志的朋友,感谢我在Reversing Labs论坛上的所有朋友,感谢所有伟大的程序员… …当然,还有感谢阅读本文的你。




 
http://cracking.accessroot.com
© [ARTeam] 2006 
iOS应用脱壳实战:原理、工具与逆向分析入门 在iOS应用安全与逆向工程领域,理解应用加密与解密机制是核心技术基础。苹果的FairPlay DRM系统会对App Store分发的应用进行加密保护,使得静态分析工具无法直接读取代码逻辑。脱壳技术正是针对这一保护机制,通过从内存中提取已解密的代码段,生成可供分析的明文二进制文件,从而为后续的漏洞挖掘、代码审计与学习提供可能。其核心原理涉及Mach-O文件结构、内存映射与动态调试,常见于安全研究、应用审计与开发学习等场景。本文聚焦于iOS应用脱壳,详细解析了frida-ios-dump等工具的使用方法,并探讨 阅读详情

相关推荐

安卓逆向进阶:Frida-dexdump 对抗加固与内存 Dex 提取实战

在安卓应用安全分析领域,动态分析技术是应对代码混淆、虚拟化等加固方案的关键。其核心原理在于,无论加固如何保护,应用在运行时必须将可执行代码加载到内存中,这为动态提取提供了可能。通过内存扫描等技术,可以定位并提取出这些运行时数据,其技术价值在于能够绕过静态分析的限制,直接获取应用的真实逻辑。这一技术广泛应用于安全评估、漏洞挖掘和恶意软件分析等场景。本文聚焦于使用 Frida-dexdump 这一工具,并针对其常见的对抗点,如反调试和内存混淆,分享一套经过验证的进阶提取技巧,旨在实现稳定、完整的 Dex 文件获

diankui6178的博客 407

实用网站大全

它提供的视频教程非常丰富并且质量很高,包括:PS 教程、手机摄影教程、Ai 做图教程、Excel 教程、Word 教程、PPT 教程、Pr 视频剪辑教程、Ae 视频特效教程、Au 音频教程教程、Flash 教程、做网页教程、css 样式教程、js 教程、Cad 制图教程、电路制作教程、模电教程、Windows 教程、色彩搭配教程、Prezi 演示教程、Keynote 教程、做图标教程、玩字体教程。的确,网站诚意十足,只为告诉你这是一个没有套路、免费下载、海量模板的网站。多频的使用、深度的依赖、长久的受益。

ltqdxl的博客 6万+

macOS逆向工程:从工具链配置到安全分析的完整实战指南

逆向工程是分析软件内部工作原理、数据流和控制流的关键技术,广泛应用于安全研究、漏洞挖掘和软件调试。其核心原理是通过静态与动态分析,将编译后的二进制程序还原为可理解的逻辑。在macOS平台,这一技术价值尤为突出,既能用于学习优秀的代码实现,也能有效检测恶意软件与安全漏洞。应用场景包括分析Objective-C/Swift运行时、探索Mach-O文件格式以及进行动态插桩。本文聚焦于macOS逆向工程的完整工具链配置,详细介绍了Hopper Disassembler、LLDB和Frida等核心工具的使用,并深入探

weixin_30621711的博客 345

IT工程师学习方法和发展路线

前言: 从需求,即招聘,面试入手,了解行业所需,再到个人部分,学习大佬们的学习方法,书写自己的简历,提升自我技能,或通过考证锻炼学习的学习方法,或通过技能提高工作效率,简述几个方面的特点,梳理下发展路线,通过利器提高办公效率。 Tips:Markdown语法学习:改字体颜色、该字体底色、画图、各种图、语法大全根据我国发布的《国民经济和社会发展第十四个五年规划和2035年远景目标纲要》 从招聘平台获取自己想要的招聘信息,进而找到应对之道,换言之,做好押题工作。 1、Boss直聘:https://www.zhi

stqer的博客 1万+

AI辅助逆向分析:破解VMP保护与X-s签名算法的实战记录

在移动应用安全与数据爬取领域,逆向工程是理解客户端与服务器通信协议、分析加密算法的核心技术。其原理在于通过静态反编译与动态调试,解析应用程序的二进制代码,定位关键逻辑。这项技术的核心价值在于安全研究、漏洞挖掘以及合规的数据交互分析。典型的应用场景包括移动App接口分析、协议还原与风控机制研究。随着AI大模型的兴起,传统的逆向工作流正经历变革。以小红书App的X-s签名逆向为例,面对采用VMP(虚拟机保护)技术混淆的Native层代码,工程师可借助AI工具辅助分析汇编指令、解释加密特征并生成Frida脚本框架

weixin_34183910的博客 314

iOS应用防护绕过实战:基于Frida的动态插桩与反调试技术

在移动安全与逆向工程领域,动态代码插桩技术是分析应用运行行为、理解内部逻辑的核心手段。其原理是通过在目标进程运行时注入自定义代码,实现对函数调用、内存访问等操作的实时监控与修改。这项技术的核心价值在于能够穿透应用的多层防护机制,为安全审计、漏洞挖掘和自动化测试提供动态分析能力。在iOS平台,由于系统封闭性和安全机制严格,应用常采用代码混淆、反调试、越狱检测等多重防护。Frida作为主流的动态插桩框架,通过JavaScript注入实现了对防护点的精准绕过,例如拦截ptrace系统调用以对抗反调试,或Hook文

weixin_34204722的博客 430

基于开源飞控pix的无人机装调与测试,零基础入门到精通,收藏这篇就够了

适用人群:希望进一步掌握无人机装调的飞手从零搭建无人机硬件平台的学生/教师无人机爱好者,开发者其他尚未入门开源PX4固件调试的学习者新手学习路线:1,组装组装之前需要了解每个元件的作用,每个元件的关键参数。硬件的选型等直播回放无人机装调与测试(一):无人机基本常识/预备知识固件是是烧录在飞控硬件中的软件,pixhawk飞控支持PX4和ArduPilot两种版本的固件。本次课程重点以PX4固件为基础进行讲解PX4 1.10以上固件QGC固件版本1.11。

Libra1313的博客 4108

Vcenter虚拟机磁盘转换实战:如何用StarWind V2V将vmdk转为qcow2(附空间优化技巧)

本文详细介绍了如何使用StarWind V2V Converter将Vcenter中的vmdk虚拟机磁盘高效转换为qcow2格式,并提供了空间优化技巧。从准备工作、导出vmdk文件到转换过程中的性能调优,帮助管理员在迁移虚拟机时减少资源消耗,确保数据完整性和性能。

dell8的博客 204

Linux基础

①Linux 系统部署 一、GNU/Linux 的历史 1.自由软件之父:Richard M. Stallman 理查德·斯托曼(GNU的创始人) 2.Linux 之父 :林纳斯•本纳第克特•托瓦兹(Linus Benedict Torvalds) 二、Linux发行版介绍 1.RHEL(Red Hat Enterprise Linux) Red Hat公司的Linux系统,商业版 2、CentOS(Community Enterprise Operating System) 社区企业操作系统,不包含封闭

xiaowosss的博客 2万+

Unity URP半透明物体渲染避坑指南:双面渲染与深度写入的实战技巧

本文深入探讨了Unity URP中半透明物体自交叠渲染问题的核心根源与解决方案。针对模型自身交叠导致的排序错误,文章提供了双面渲染与深度写入两种实战技巧,详细分析了其实现原理、性能开销及适用场景,帮助开发者根据项目需求选择最优方案,有效提升玻璃、水体等材质的视觉表现。

sunny的博客 545

Apollo学习笔记(4)坐标系

百度Apollo项目用到了多种坐标系,其中帮助文档提及的坐标系包括: 全球地理坐标系(The Global Geographic coordinate system ) 局部坐标系—东-北-天坐标(The Local Frame – East-North-Up,ENU) 车身坐标系—右-前-天坐标(The Vehicle Frame —Right-Forward-Up,RFU) 车身坐标系—前-左-天坐标(The Vehicle Frame —Front-Left-Up,FLU) 还有一种Frenet坐标

qq_24649627的博客 5798

前后端分别部署教程 ---- 第二部分相关软件安装

2. 相关软件安装 2.1 JDK 我安装的是 Open JDK 11. 2.1.1 下载解压 Open JDK 官网:http://jdk.java.net/ ,根据需要下载对应的版本。我下载的是 11版的压缩包,解压后即可用。 2.1.2 配置环境变量 选中计算机右击,点击属性: 在弹出的系统对话框中,点击左边的 ”高级系统设置“ ----》 ”环境变量“ ----》 ”用户变量“ : 配置变量: 1. 变量名: JAVA_HOME 变量值: D:\Java\jdk-11 (

qq_41547882的博客 702

【安全加固】应对Sweet32攻击:TLS/SSL协议3DES漏洞深度解析与修复指南

本文深度解析了CVE-2016-2183(Sweet32攻击)漏洞,该漏洞利用TLS/SSL协议中3DES加密算法的64位块大小缺陷,通过大量数据流量实施“生日攻击”导致信息泄露。文章提供了从原理分析、系统排查到跨平台修复的完整指南,重点指导如何在Windows、Linux服务器及网络设备中禁用不安全的3DES密码套件,并采用AES-GCM等更安全的加密方式,以彻底修复此TLS/SSL漏洞。

weixin_29230649的博客 586

ACProtect's Anti-Cracking Features

Anti-Cracking FeaturesCompress EXE/DLL/OCX/SCRProtected application files automatically compressed to minimize bandwidth and increase download speeds . Embedded software protectionYou can specify

CryButNoTear的专栏 1434

Anaconda详细安装及使用教程(带图文)

Anacond的介绍 Anaconda指的是一个开源的Python发行版本,其包含了conda、Python等180多个科学包及其依赖项。因为包含了大量的科学包,Anaconda的下载文件比较大(约531 MB),如果只需要某些包,或者需要节省带宽或存储空间,也可以使用Miniconda这个较小的发行版(仅包含conda和Python)。 Conda是一个开源的包、环境管理器,可以用于...

代码帮 186万+

手推反向传播+numpy实现

反向传播的重要性不必多说,手推也是必备基础,大厂面试要求用numpy实现一下BP也是经常的事。下面以一个简单的两层网络为例(简单到连bias都没有的那种),真·手推反向传播+numpy实现。 说明: input:输入 net_h:隐层 S(x):sigmoid简写 SD(x):sigmoid导数的简写 out_h:隐层经过sigmoid激活的输出 net_o:输出层 out_o:经过经过sigmoid激活的输出层,也就是最后的输出 target(简写t1、t2):Ground...

xian0710830114的专栏 1238

Android Native逆向进阶:从ELF静态分析到反调试对抗实战

在移动安全与软件逆向工程领域,ELF(可执行与可链接格式)文件分析是理解程序底层逻辑的核心基础。其原理在于解析二进制文件的结构、符号表与代码段,从而还原出程序的执行流程与算法逻辑。这项技术的价值在于能够深入分析应用的安全机制、发现潜在漏洞,并广泛应用于恶意软件分析、软件保护评估与漏洞挖掘等场景。针对Android平台,原生库(.so文件)的逆向常涉及反调试与加壳保护,需结合动态分析工具进行对抗。例如,通过Frida进行运行时注入可绕过基于ptrace的检测,而GDB则用于内存取证与脱壳操作。本文以Crack

weixin_33806914的博客 391

Coze智能体网页集成实战:从零搭建免登录AI助手

本文详细介绍了如何利用Coze Chat SDK从零开始为网站集成免登录AI助手。通过获取访问令牌和智能体ID,仅需几行JavaScript代码即可将功能完整的对话窗口嵌入网页,实现开箱即用的智能交互。文章重点讲解了核心部署步骤、会话隔离方案以及UI深度定制方法,帮助开发者快速打造贴合品牌、提升用户体验的网页AI助手。

gold8的专栏 560

安卓动态脱壳实战:基于Frida与frida-dexdump的内存DEX提取技术

在移动安全与逆向工程领域,加壳技术是保护应用代码逻辑不被轻易分析的核心手段。其原理是通过加密、混淆或隐藏真实的DEX文件,使静态分析难以直接获取原始代码。动态脱壳技术则从运行时内存入手,利用安卓应用运行时必须将解密后的标准DEX格式加载至内存这一关键点,通过内存操作提取已解密的数据结构。这项技术的核心价值在于能够有效对抗复杂的运行时加密和代码混淆,广泛应用于安全评估、漏洞挖掘、恶意软件分析和合规性检测等场景。本文聚焦于使用Frida动态插桩框架及其专用工具frida-dexdump,深入讲解如何精准定位并导

weixin_30675247的博客 391

基于THUCNews数据集bert bert_wwm的新闻文本分类

基于THUCNews数据集bert bert_wwm的新闻文本分类

984

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

Ubuntu 14.04下Nginx安全安装与遗留系统维稳实践

Nginx作为轻量级HTTP服务器与反向代理,其核心价值在于低资源占用、高稳定性及强兼容性。在老旧Linux发行版(如Ubuntu 14.04)中,因glibc 2.19、OpenSSL 1.0.1f等底层组件锁定,标准APT安装易引发CVE漏洞(如Heartbleed)、TLS协议降级与模块缺失等问题。此时需转向源码编译或静态二进制方案,通过自定义OpenSSL链接、禁用高危模块、适配内核参数(如somaxconn)等手段实现安全加固。该实践广泛应用于工业网关、嵌入式边缘设备及离线ERP等遗留系统场景,是

weixin_30699955的博客 1469

pscad IEEE标准系统搭建说明

pscad IEEE标准系统搭建说明,包含典型的IEEE 9节点、14节点、30节点、39节点和118节点系统。内含标准测试系统的原始数据和潮流结果。

SQLFluff 3.4.2新特性:Doris方言深度测评

你是否在处理Doris数据库的SQL代码时遇到过格式混乱、语法错误难以发现的问题?SQLFluff 3.4.2版本带来了对Apache Doris方言的支持,这一功能将彻底改变你处理Doris SQL代码的方式。本文将深入测评SQLFluff 3.4.2版本中Doris方言的各项功能,帮助你快速掌握如何利用SQLFluff提升Doris SQL代码的质量和可维护性。 读完本文,你将能够: - 了...

gitblog_00811的博客 702

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

SystemPrompt 智能体系统提示词

智能体提示词版本1.0,引入逻辑运算简化整理公理和公式推导层面的复杂运算逻辑。这套提示词应该可以把AI复杂的思考逻辑转换为查表和推公式,增加Agent的运行效率,降低成本。@已有项目代码优化指令描述 ai\@.clinerules

qq_45424503的博客 1325

DeepFace人脸对齐性能优化实战指南:5个技巧解决卡顿问题

DeepFace是一个轻量级Python人脸识别和面部属性分析库,支持年龄、性别、情感和种族识别。在实际应用中,人脸对齐作为关键预处理步骤,直接影响识别精度和系统性能。本文将深入探讨DeepFace人脸对齐的性能瓶颈,并提供从参数调优到架构设计的完整优化方案,帮助开发者构建高性能的人脸识别应用。 ## 一、人脸对齐性能瓶颈深度分析 人脸对齐是将检测到的人脸区域进行标准化处理,确保眼睛、鼻子等关

gitblog_00359的博客 489

IPVS direct routing on top of openstack (by quqi99)

IPVS是Linux内核态的一个L4 LB (KTCPVS, Kernel TCP Virtual Server则是L7 LB), IPVS通过在Netfilter框架中的不同位置注册自己的处理函数来捕获数据包,并根据与IPVS相关的信息表对数据包进行处理,按照IPVS规则中定义的不同的包转发模式,对数据包进行不同的转发处理。IPVS有哪些包转发模式:NAT、IP tunneling和Direct Routing。

技术并艺术着 1229

Self-Remapping-Code 内存布局前后对比:一张图看懂 IMG 与 MAP 页面的关键区别

Self-Remapping-Code 是一款面向 Windows 的 PE 映像自我重映射安全工具,它的核心能力是:程序启动后将自己"搬进"一个全新的内存位置,并让 **NtProtectVirtualMemory 再也无法修改映像内任何页面的保护属性**。本文将用内存布局的前后对比表,带你从零看懂 `VirtualQuery` 输出中 **IMG 与 MAP 两种页面的关键区别**,理解这项反

gitblog_00846的博客 406
上一篇: Anti-Anti-Dump and nonintrusive tracers
下一篇: Pinczakko的AwardBIOS逆向工程指导
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值