0x00 前言这次分析的是一个exe +dll文件,很明显,在exe执行的时候应该要动态链接该dll的,那就一个个分析,逐一攻破。 详细分析 0x01 DLL文件几个导入函数。包括 ‘Cre…
0x00 前言
这次分析的是一个exe +dll文件,很明显,在exe执行的时候应该要动态链接该dll的,那就一个个分析,逐一攻破。
详细分析
0x01 DLL文件
几个导入函数。包括 CreateProcessA
以及WS2_32.dll
的通过网络接收和发送数据的函数。
但是该dll文件的字符串很有意思,其中还包括了一个 IP地址 127.26.152.13
另一点比较奇怪的是该dll文件并没有导出函数。
那就先从入口点分析吧。但是……指令贼多,一句一句分析效率太慢了。
先看call 指令调用的函数吧。
10001015callalloca_ probe //调用库函数__alloca_probe分配栈空间
10001059callds:OpenMutexA //打开互斥量
1000106Ecallds:CreateMutexA //创建互斥量 这两个在一起保证同一时间只有这个程序的一个实例在运行
1000107Ecallds:WSAStartup //WS2_32.dll的一个函数
10001092callds:socket
100010AFcallds:inet addr
100010BBcallds:htons
100010CEcallds:connect
10001101callds:send
10001113cal1ds:shutdown
10001132callds:recv //一直到这里,都是为了建立网络连接socket通信
1000114Bcallebp;strncmp
10001159callds:Sleep
10001170callebp ; strncmp
100011AFcallebx ; CreateProcessA//创建进程
100011C5callds:Sleep
到这里大概可以猜一下,该dll文件建立通信后创建进程,很像我们建立shell的行为。
接下来看函数的具体参数。
connect
连接的是 127.26.152.13
这个IP地址,并且端口是 50h,即80端口。emm80端口,猜测可能走http协议进行通信,找一下通信的流量
这里 buf数组存入了 hello 字符串……好像cobalt strike里的娱乐弹窗……
看下recv
接收的流量。在 10001124
处,lea 指令访问 buf,指针指向buf这块缓冲区,接着 push
了3个参数,调用 recv
指令,但这里好像也看不出来什么
接着往下看。1000114B
cmp 前面是不是 sleep
字符串,它会在 10001150
处检查是否返回值是否为0,如果是0,调用 sleep
函数
也就是说如果远程shell终端发送的命令是sleep,则执行sleep函数
到这里并没有结束,buf 缓冲区还在被使用。首先检查指令是不是 exec
,如果是,strncpy
函数返回0,顺序执行,直到 100011AF
处创建进程。看到CreateProcessA
有很多参数,不过最重要的还是 lpCOmmandLine
,它来自1000119B
处的 CommandLine
,双击追踪这个参数发现它在栈空间中
.text:10001161 loc_10001161: ; CODE XREF: DllMain(x,x,x)+142↑j
.text:10001161 lea edx, [esp+1208h+buf]
.text:10001168 push4 ; MaxCount
.text:1000116A pushedx ; Str2
.text:1000116B pushoffset aExec; "exec"
.text:10001170 callebp ; strncmp
.text:10001172 add esp, 0Ch
.text:10001175 testeax, eax
.text:10001177 jnz short loc_100011B6
.text:10001179 mov ecx, 11h
.text:1000117E lea edi, [esp+1208h+StartupInfo]
.text:10001182 rep stosd
.text:10001184 lea eax, [esp+1208h+ProcessInformation]
.text:10001188 lea ecx, [esp+1208h+StartupInfo]
.text:1000118C pusheax ; lpProcessInformation
.text:1000118D pushecx ; lpStartupInfo
.text:1000118E push0 ; lpCurrentDirectory
.text:10001190 push0 ; lpEnvironment
.text:10001192 push8000000h; dwCreationFlags
.text:10001197 push1 ; bInheritHandles
.text:10001199 push0 ; lpThreadAttributes
.text:1000119B lea edx, [esp+1224h+CommandLine]
.text:100011A2 push0 ; lpProcessAttributes
.text:100011A4 pushedx ; lpCommandLine
.text:100011A5 push0 ; lpApplicationName
.text:100011A7 mov [esp+1230h+StartupInfo.cb], 44h ; 'D'
.text:100011AF callebx ; CreateProcessA
.text:100011B1 jmp loc_100010E9
追踪到dllmain函数这里,发现它的初始值是 0FFBh,同时buf
的初始值是1000h
,说明缓冲区从这里开始。
这里大概清楚了这个dll文件会创建进程来实现远程socket通信,对于攻击者来说就是弹shell,也就是后门。但是这个dll文件并没有导出函数,它怎么被调用执行啊……
先放一放,看看exe文件
0x02 EXE文件
先看exe的导入表,其中几个重点关注下,当然不止这几个,这里我懒得敲了,等下逐个分析
CreateFileMappingA
CreateFileA
CopyFileA
很明显,这里exe文件并没有在运行时导入该dll文件,导入函数中没有 LoadLibrary/ GetProcAddress
,与前面分析dll文件正好对应
这几个字符串也是很有意思
来看一看main函数
.text:00401440 mov eax, [esp+argc]
.text:00401444 sub esp, 44h
.text:00401447 cmp eax, 2
.text:0040144A pushebx
.text:0040144B pushebp
.text:0040144C pushesi
.text:0040144D pushedi
.text:0040144E jnz loc_401813
.text:00401454 mov eax, [esp+54h+argv]
.text:00401458 mov esi, offset aWarningThisWil ;"WARNING_THIS_WILL_DESTROY_YOUR_MACHINE"
.text:0040145D mov eax, [eax+4]
.text:00401460
.text:00401460 loc_401460: ; CODE XREF: _main+42↓j
.text:00401460 mov dl, [eax]
.text:00401462 mov bl, [esi]
.text:00401464 mov cl, dl
.text:00401466 cmp dl, bl
.text:00401468 jnz short loc_401488
.text:0040146A testcl, cl
.text:0040146C jz short loc_401484
.text:0040146E mov dl, [eax+1]
.text:00401471 mov bl, [esi+1]
.text:00401474 mov cl, dl
.text:00401476 cmp dl, bl
.text:00401478 jnz short loc_401488
.text:0040147A add eax, 2
.text:0040147D add esi, 2
.text:00401480 testcl, cl
.text:00401482 jnz short loc_401460
.text:00401484
.text:00401484 loc_401484: ; CODE XREF: _main+2C↑j
.text:00401484 xor eax, eax
.text:00401486 jmp short loc_40148D
先分析关键指令,在 401447
处会比较 eax/argc (即传递的参数的个数)是否为2,如果不是2,跳转到 101813
,程序终止,这里启动程序时添加参数是为了防止意外启动造成不必要的后果。否则继续执行。在 401458
处将 "WARNING_THIS_WILL_DESTROY_YOUR_MACHINE" 字符串放入 esi 寄存器中,在 40145D
中,eax 存储 argv[1]。接着比较 argv[1] 的值是不是 "WARNING_THIS_WILL_DESTROY_YOUR_MACHINE" ,如果不是,跳转到 401488
,程序终止。注意在 401482
处的跳转到 401460
,往回跳转,很明显是个循环。
接下来分析 40148D
.text:0040148D loc_40148D: ; CODE XREF: _main+46↑j
.text:0040148D testeax, eax
.text:0040148F jnz loc_401813
.text:00401495 mov edi, ds:CreateFileA
.text:0040149B pusheax ; hTemplateFile
.text:0040149C pusheax ; dwFlagsAndAttributes
.text:0040149D push3 ; dwCreationDisposition
.text:0040149F pusheax ; lpSecurityAttributes
.text:004014A0 push1 ; dwShareMode
.text:004014A2 push80000000h ; dwDesiredAccess
.text:004014A7 pushoffset FileName ; "C:\\Windows\\System32\\Kernel32.dll"
.text:004014AC calledi ; CreateFileA
.text:004014AE mov ebx, ds:CreateFileMappingA
.text:004014B4 push0 ; lpName
.text:004014B6 push0 ; dwMaximumSizeLow
.text:004014B8 push0 ; dwMaximumSizeHigh
.text:004014BA push2 ; flProtect
.text:004014BC push0 ; lpFileMappingAttributes
.text:004014BE pusheax ; hFile
.text:004014BF mov [esp+6Ch+hObject], eax
.text:004014C3 callebx ; CreateFileMappingA
.text:004014C5 mov ebp, ds:MapViewOfFile
.text:004014CB push0 ; dwNumberOfBytesToMap
.text:004014CD push0 ; dwFileOffsetLow
.text:004014CF push0 ; dwFileOffsetHigh
.text:004014D1 push4 ; dwDesiredAccess
.text:004014D3 pusheax ; hFileMappingObject
.text:004014D4 callebp ; MapViewOfFile
.text:004014D6 push0 ; hTemplateFile
.text:004014D8 push0 ; dwFlagsAndAttributes
.text:004014DA push3 ; dwCreationDisposition
.text:004014DC push0 ; lpSecurityAttributes
.text:004014DE push1 ; dwShareMode
.text:004014E0 mov esi, eax
.text:004014E2 push10000000h ; dwDesiredAccess
.text:004014E7 pushoffset ExistingFileName ; "Lab07-03.dll"
.text:004014EC mov [esp+70h+argc], esi
.text:004014F0 calledi ; CreateFileA
.text:004014F2 cmp eax, 0FFFFFFFFh
.text:004014F5 mov [esp+54h+var_4], eax
.text:004014F9 push0 ; lpName
.text:004014FB jnz short loc_401503
.text:004014FD callds:exit
在4014AC
处调用了 CreateFileA
,接着还有 CreateFileMappingA/MapViewOfFile/
,所以这么多函数到底干了什么?毋庸置疑的是 创建了 Kernel32.dll
这个文件,CreateFileMappingA
这是一个共享内存函数,会 创建一个文件映射对象,目的是为了写入内存中,这里的参数就是 kernel32.dll
,接着利用MapViewOfFile
将文件映射到进程地址空间
接着往下看,在 4017D4
这里,调用了两个 CloseHandle
,因为对前面的文件已经操作完毕。接着在 4017F4
处调用 CopyFileA
,将 恶意dll文件 copy为 kernel32.dll,这样就可以理解为什么 该恶意dll文件没有被导出了,很常规的一次dll劫持
紧接着传入了C盘的盘符,调用了 4011E0
,
来到 4011E0
处,只调用了 FindFirstFileA
,来搜索C盘符,
接下来的call指令就是 stricmp
,找一下它push的参数,会比较字符串是否是.exe
,
在这之后会调用 FindNextFileA
,查找下一个文件,然后在 401427
处jmp 到 401210
,往前跳转,说明这是一个循环,然后在40142E
处调用 FindClose
函数,终止。
到这里,梳理一下,这个函数在找C盘里的exe文件,并且匹配相应的dll,接着进行一系列操作。
接着call 4011A0
,看到 4010A0
处的函数调用。
依旧是调用了 CreateFileA/ CreateFileMappingA/ MapViewOfFile
用来将文件映射到内存中。
接着调用 IsBadReadPtr
函数,检查调用进程是否具有读取指定内存区域的权限。下面都是对该函数的一些算术运算,直接pass
.text:004010A0 sub esp, 0Ch
.text:004010A3 pushebx
.text:004010A4 mov eax, [esp+10h+lpFileName]
.text:004010A8 pushebp
.text:004010A9 pushesi
.text:004010AA pushedi
.text:004010AB push0 ; hTemplateFile
.text:004010AD push0 ; dwFlagsAndAttributes
.text:004010AF push3 ; dwCreationDisposition
.text:004010B1 push0 ; lpSecurityAttributes
.text:004010B3 push1 ; dwShareMode
.text:004010B5 push10000000h ; dwDesiredAccess
.text:004010BA pusheax ; lpFileName
.text:004010BB callds:CreateFileA
.text:004010C1 push0 ; lpName
.text:004010C3 push0 ; dwMaximumSizeLow
.text:004010C5 push0 ; dwMaximumSizeHigh
.text:004010C7 push4 ; flProtect
.text:004010C9 push0 ; lpFileMappingAttributes
.text:004010CB pusheax ; hFile
.text:004010CC mov [esp+34h+var_4], eax
.text:004010D0 callds:CreateFileMappingA
.text:004010D6 push0 ; dwNumberOfBytesToMap
.text:004010D8 push0 ; dwFileOffsetLow
.text:004010DA push0 ; dwFileOffsetHigh
.text:004010DC push0F001Fh ; dwDesiredAccess
.text:004010E1 pusheax ; hFileMappingObject
.text:004010E2 mov [esp+30h+hObject], eax
.text:004010E6 callds:MapViewOfFile
.text:004010EC mov esi, eax
.text:004010EE testesi, esi
.text:004010F0 mov [esp+1Ch+var_C], esi
.text:004010F4 jz loc_4011D5
.text:004010FA mov ebp, [esi+3Ch]
.text:004010FD mov ebx, ds:IsBadReadPtr
.text:00401103 add ebp, esi
.text:00401105 push4 ; ucb
.text:00401107 pushebp ; lp
.text:00401108 callebx ; IsBadReadPtr
.text:0040110A testeax, eax
.text:0040110C jnz loc_4011D5
.text:00401112 cmp dword ptr [ebp+0], 4550h
.text:00401119 jnz loc_4011D5
.text:0040111F mov ecx, [ebp+80h]
.text:00401125 pushesi
.text:00401126 pushebp
.text:00401127 pushecx
.text:00401128 callsub_401040
.text:0040112D add esp, 0Ch
.text:00401130 mov edi, eax
.text:00401132 push14h ; ucb
.text:00401134 pushedi ; lp
.text:00401135 callebx ; IsBadReadPtr
.text:00401137 testeax, eax
.text:00401139 jnz loc_4011D5
.text:0040113F add edi, 0Ch
继续往下看,找到了 stricmp
函数调用,来检查 字符串是否是 kernel32.dll
,接着在401186
处调用 repne scasb
,用来重复扫描特定字符串的长度, 在401196
处调用 rep movsd
。这里的用处和 strlen+memcpy
函数是等价的。至于往内存中写入的是什么东西,看下edi寄存器里存的是什么,
定位到403010
处,
这里存储的是 kernel32.dll
这个字符串,按下A键,可以看到转换为了该字符串。
总结
至此,梳理下,这个exe文件遍历C盘查找所有的 exe文件,并且找到其中 kernel32.dll
的位置,并且用我们的恶意dll文件替换它,简单来说就是劫持 kernel32.dll
。但是也不对啊,这个恶意dll只是实现了后门的功能,并没有正常kernel32.dll
的功能,按理说劫持后exe文件会运行失败。
动态分析,在恶意代码运行后,正常kernel32.dll
的md5并没有被改变,说明该dll没有被修改。而当我们再次看我们的恶意dll时,发现它导出了所有的kernel32.dll
的导出函数,这些导出函数是重定向后的,相当于做了一次转发。功能还在原来的kernel32.dll
上,只是程序运行时会加载我们的恶意dll。所以在main函数中访问 kernel32.dll
和我们的恶意dll,是在解析kernel32.dll
中的导出段并且在恶意dll中创建一个导出段,用来导出并转发函数。这是一个简单的重定向转发dll劫持。
- 本文作者: 0r@nge
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/857
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!