st98 の日記帳


[ctf] TUCTF 2017 の write-up

チーム Harekaze で TUCTF 2017 に参加しました。最終的にチームで 2176 点を獲得し、順位は得点 948 チーム中 44 位でした。うち、私は 3 問を解いて 750 点を入れました。

以下、解いた問題の write-up です。

[Web 300] iFrame and Shame

与えられた 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}

[Rev 200] Unknown

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 という非常に長く複雑な関数が見つかりました。0x5a8279990x6ed9eba1 のような特徴的な数値があることから、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!}

[Rev 250] Future

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!}
このエントリーをはてなブックマークに追加
st98.github.io / st98 の日記帳