趣旨
デコンパイルの練習のため、最近(2022年7月時点)のCTFのRev問にいくつか取り組んでみました。rev問のほとんどは、入力を受け付けるプログラムが与えられ、適切な入力を渡すと成功を出力するというものです。プログラムのどこかに入力が正しいかどうかを判定する処理が実装されています。この処理を手動でデコンパイルしていきます。
CTF問題は、CTF-archives で公開されているものを使用しました。ここにある最近のRev問を難易度を気にせず気ままに解いていきました。
所感
改めてRev問に取り組んでみて、何をやったら良いか分からないまま時間が過ぎていく、ということは殆どなかったのが印象的でした。出題の意図は明白で、(競技中に解答できるかはさておき)ひたすら時間をかければデコンパイルは可能で、デコンパイルさえできれば後は解を導くだけ、という感じです。
デコンパイル自体は何問かチャレンジしていくうちに速くなるかなと思いましたが、これくらいの練習量だと体感としてはあまり変わりませんでした。ただ諦めなければ大抵の問題は解ける、ということが実感できたのは成果でした。あとRev問は手元にダウンロードしたバイナリだけで完結するので、過去問にチャレンジしやすいです。Pwn問のようにサーバーの環境(ASLRの有無やlibcなど)がきちんと再現できているかを意識する必要もありません。コンテスト後でも正しいフラグが取得できるのが良いですね。
BCACTF 2.0
- BCACTF 2.0(CTF Time)
- 2021/6/11~14
最近のCTFってどんな問題があるのかな、と思ったときにちょうど開催していたのでチャレンジしてみました。教育的な問題が多い印象で、pwn問は全てソースコードが提供されていて、一番難しい問題でも67チームが解答しています。手始めにチャレンジするには手ごろなCTFでした。
Wait, this isn't C (150 points, 118 solves)
Fortranでコンパイルされた小さいプログラムで、正しい入力を与えると成功します。デコンパイルすると、入力した各文字に対して値を加算して判定していることが分かります。
encoded = L"cedgyl\x82n9| ~A\\nyFqvD\x84eG\x84\x96";
for (i = 1; i <= 0x19; i++) {
buf[(i-1)*2] = input[i];
}
for (i = 1; i <= 0x19; i++) {
buf[(i-1)*2] += i;
if (buf[(i-1)*2] != encoded[(i-1)*2]) {
write("Sorry, flag does not match.");
exit();
}
}
write("Congrats, that was the flag!");
デコンパイルできれば後は簡単で、判定文字列から逆に減算するとフラグが得られます。
#!/usr/bin/python
s = "cedgyl\x82n9| ~A\\nyFqvD\x84eG\x84\x96"
buf = ""
for i in range(len(s)):
buf += chr(ord(s[i]) - (i+1))
print(buf)
これを実行すると、フラグを取得することができます。
bcactf{f0rr4N_i5_c0oO0l}
Wait, this isn't C 2 (The Sequel) (350 points, 14 solves)
同じくFortranでコンパイルされたプログラムで、正しい入力を与えると成功します。急に難しくなった気がしますが、以下のCFGを紐解くと、2重ループ × 2回 × 4ブロックに分割して考えられることが分かります。この中では、それぞれ行列に対して、転置、シフト、和、積といった操作を行っています。
encrypted = {0xac0, 0x779, 0xa43, 0x859, 0x982, 0xcda, 0x92c, 0xc10, 0x9d5, 0xb5e, 0xfc9, 0xba1, 0xe65, 0xbc2, 0xdfc, 0x1465, 0xf9e, 0x120c, 0xf08, 0x1200, 0x1b6e, 0x15c2, 0x17de, 0x13d5, 0x183b};
count = 0;
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
matrix1[i+j*5] = input[count];
count++;
}
}
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
matrix2[i*5+j] = matrix1[i+j*5];
}
}
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
matrix1[i*5+j] = matrix2[i*5+j];
}
}
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
matrix1[i+j*5] += (i+1)*(j-2);
}
}
gfortran_cshift(matrix2, matrix1, 2, 0);
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
matrix1[i*5+j] = matrix2[i*5+j];
}
}
count = 1;
for (i = 0; i < 5; i++) {
for (j = 0; j <= i; j++) {
matrix3[i+j*5] = count;
matrix3[i*5+j] = matrix3[i+j*5];
count++;
}
}
gfortran_matmul_i4(result, matrix1, matrix3, 0);
for (i = 0; i < 5; i++) {
for (j = 0; j < 5; j++) {
assert(result[j+i*5] == encoded[j+i*5]);
}
}
これもデコンパイルできると変換の処理が分かります。逆順に行列計算を行うことで解答できます。
#!/usr/bin/python
import sys, os
import numpy
encrypted = numpy.matrix([
[0xac0, 0x779, 0xa43, 0x859, 0x982],
[0xcda, 0x92c, 0xc10, 0x9d5, 0xb5e],
[0xfc9, 0xba1, 0xe65, 0xbc2, 0xdfc],
[0x1465, 0xf9e, 0x120c, 0xf08, 0x1200],
[0x1b6e, 0x15c2, 0x17de, 0x13d5, 0x183b],
])
count = 1
matrix1 = numpy.matrix([[0] * 5] * 5)
for i in range(5):
for j in range(i+1):
matrix1[i, j] = count
matrix1[j, i] = matrix1[i, j]
count += 1
flag1 = (matrix1 ** -1) * encrypted
# do cshift -3
flag2 = numpy.matrix([[0] * 5] * 5)
for i in range(5):
for j in range(5):
flag2[i, (j-3)%5] = round(flag1[i, j])
for i in range(5):
for j in range(5):
flag2[i, j] -= (j+1)*(i-2)
buf = ""
for i in range(5):
for j in range(5):
buf += chr(flag2[i, j]%256)
print(buf)
これを実行すると、フラグを取得することができます。
bcactf{m4tRlce5_aRe_co0l}
Trailblazer (450 points, 27 solves)
入力を受け取る小さなプログラムです。フラグ判定処理のデコンパイルは以下のようになっています。
check_flag(input) {
if (strlen(input) == 0x2F) {
return 0x2F;
}
rev_addr = loc_401262;
if (perms(rev_addr) == 0) {
puts("My innocent flag ...");
return 1;
}
*rev_addr ^= input[0];
/*
loc_401262:
0x662B573C6B32E82A
0x63245020640CFC27
0x6D3F48176F3CE317
0x410F5432413FE917
0x100F5D214921FF17
*/
}
perms(addr) {
pagesize = getpagesize(addr);
addr += ~(addr % pagesize);
if (mprotect(addr, pagesize, 7) != -1) {
return 0;
}
return -1;
}
命令は 0x40125F で終わっていて適当に動かすとここで失敗します。この最後の命令の [rax] はその次のアドレスを指していて、適切な入力を与えることでこのデータを変更することができます。すなわち、任意の 8バイトまでの命令を実行することができます。
どのような命令に書き換えれば良いのか明確な指針がありませんが、最初の 7バイトの入力はフラグの規則から "bcactf{" と推測できますので、とりあえずこれを送ってみます。すると、以下の命令が復元できます。
0x401262 mov rdx, [rbx+0x8]
ここから命令列を推測しますが、その次のアドレスに命令を書き込むためには [rax+0x8] に対する操作が必要ですので、以下のように推測されます。データは40バイトなので、ここで終わるように ret などをセットします。
xor QWORD PTR [rax], rdx
mov rdx, [rbx+0x8]
xor QWORD PTR [rax+0x8], rdx
mov rdx, [rbx+0x10]
xor QWORD PTR [rax+0x10], rdx
mov rdx, [rbx+0x18]
xor QWORD PTR [rax+0x18], rdx
mov rdx, [rbx+0x20]
xor QWORD PTR [rax+0x20], rdx
mov rdx, [rbx+0x28]
xor QWORD PTR [rax+0x28], rdx
mov eax, 0
leave
ret
このような命令列を書き込むための入力は以下のスクリプトで取得できます。
#!/usr/bin/python
# coding:UTF-8
from pwn import *
context(os='linux', arch='amd64')
b = asm("""
mov rdx, [rbx+0x8]
xor QWORD PTR [rax+0x8], rdx
mov rdx, [rbx+0x10]
xor QWORD PTR [rax+0x10], rdx
mov rdx, [rbx+0x18]
xor QWORD PTR [rax+0x18], rdx
mov rdx, [rbx+0x20]
xor QWORD PTR [rax+0x20], rdx
mov rdx, [rbx+0x28]
xor QWORD PTR [rax+0x28], rdx
mov eax, 0
leave
ret
""")
target = b""
target += p64(0x662B573C6B32E82A)
target += p64(0x63245020640CFC27)
target += p64(0x6D3F48176F3CE317)
target += p64(0x410F5432413FE917)
target += p64(0x100F5D214921FF17)
target += p64(0xF3BEF079323565DF)
ans = b"".join([(x^y).to_bytes(1, 'little') for x, y in zip(target, b)])
print(ans)
このスクリプトを実行すると、以下のフラグを取得できます。
bcactf{now_thats_how_you_blaze_a_trail_8ge52y9}
zer0pts CTF 2021
- zer0pts CTF 2021 (CTF Time)
- 2021/3/6~7
次に何をやろうかなと思って見つけたCTFで、評判も良かったのでチャレンジしてみました。作問者様の解説をはじめ、多数のWriteupがありましたので、あまり触れられていない部分を中心に説明します。
Not Beginner's Rev (239 points)
入力を受け取るバイナリが与えられます。試しに実行してみると、以下のような 8バイト整数の列が出力されます。
# ./encoder
Text: aaa
5aaf63e5c1f262bf
a967ff06da3b455c
ee8d95ecc2ae3151
5aaf63e5c19303de
a967ff06da3b455c
8484738b01a248d9
6336aaa1c30b9137
ee2beb0b334c6392
0123ec4f4691d2c6
同梱された output.txt には同じようなデータが含まれています。ここで、同じ出力を行う入力を解析する問題ということが分かります。
# head output.txt
5aaf63e5c1f262bf
a967ff06da3b455c
ee8d95ecc2ae3151
20804abbaeac4ae3
f01c8c72aa0b3739
59a22a5a76e6b680
4dfd0de827989bbe
19b3437732734c33
94b33d96023c5d20
0c7ccebc25e54e9d
# wc -l output.txt
92 output.txt
また、出力行は入力の長さに依存して増えていきますので、出力を確認しながら入力サイズを増やしていくことで、入力は 86文字であると想像できます。
Not Beginner's Stackという入門者向けのPwn問があったので、これも簡単な問題かと思ったらそうでもありませんでした。Not Beginner's Stackと同様、独自のstack_shadowでリターンアドレスを管理していて、callやretが全てjmpに置き換えられていますが、IDAで見ると全ての処理がひとつの関数に収まってしまっているので、構造が良く分かりません。とりあえず地道にデコンパイルしてみましたが、量が多いのと構造がうまく復元できないので諦めました。ちなみに、main() の導入部分は以下のようになっています。
main() {
long stack_shadow[];
env = rdx;
var_F4 = r8d;
var_100 = r11;
buf1 = malloc(0x400);
buf2 = calloc(0x86, 8);
stack_shadow[stack_depth] = loc_C58;
printf_chk(1, "Text: ");
return_value = scanf("%127s", buf1);
r11 = var_100;
r8d = var_F4;
rdx = env;
var_E8 = &var_48 + 4;
jmp stack_shadow[stack_depth]; // jmp loc_C58
}
全てデコンパイルするのは諦めて、入出力の部分を動的に追いながら解析範囲を限定することにしました。とりあえず最初の 3行は固定なので、4行目の 20804abbaeac4ae3
を出力するにはどうしたら良いか考えます。
output.txt の出力は(手元の環境だと)0x5555557576b0 に格納されていましたので、watch *0x5555557576c8
で監視したところ、0xd2b の命令で書き込みが行われていました。そこで、少し遡って以下のアセンブリを確認します。
.text:0000000000000E7A 000 mov rax, [rsp+arg_30]
.text:0000000000000E7F 000 mov rdi, [rsp+arg_8]
.text:0000000000000E84 000 movsxd rcx, cs:__stack_depth
.text:0000000000000E8B 000 mov [rax], rdx
.text:0000000000000E8E 000 mov [rax+8], rdi
.text:0000000000000E92 000 mov [rax+10h], r12
.text:0000000000000E96 000 mov rax, [rsp+arg_38]
.text:0000000000000E9B 000 mov rsi, [rax]
.text:0000000000000E9E 000 mov rax, [rax+8]
.text:0000000000000EA2 000 xor rdi, rax ; output[4] = output[1] ^ input[1]
.text:0000000000000EA5 000 xor rdx, rsi ; output[3] = output[0] ^ input[0]
.text:0000000000000EA8 000 lea eax, [rcx+1]
.text:0000000000000EAB 000 mov [rsp+arg_8], rdi
.text:0000000000000EB0 000 lea rdi, loc_DE0
.text:0000000000000EB7 000 rol rsi, 7
.text:0000000000000EBB 000 mov [rbx+rcx*8], rdi
.text:0000000000000EBF 000 jmp loc_B79
.text:0000000000000B79 loc_B79:
.text:0000000000000B79 148 sub eax, 1
.text:0000000000000B7C 148 mov cs:__return_value, rsi
.text:0000000000000B83 148 mov cs:__stack_depth, eax
.text:0000000000000B89 148 cdqe
.text:0000000000000B8B 148 jmp qword ptr [rbx+rax*8]
.text:0000000000000DE0 000 mov rax, 6A09E667F3BCC908h
.text:0000000000000DEA 000 xor rax, cs:__return_value
.text:0000000000000DF1 000 lea rdi, loc_D10
.text:0000000000000DF8 000 xor r12, rax
.text:0000000000000DFB 000 mov rax, [rsp+arg_38]
.text:0000000000000E00 000 mov rax, [rax+8]
.text:0000000000000E04 000 mov [rsp+arg_40], rax
.text:0000000000000E09 000 movsxd rax, cs:__stack_depth
.text:0000000000000E10 000 lea esi, [rax+1]
.text:0000000000000E13 000 mov [rbx+rax*8], rdi
.text:0000000000000E17 000 mov rax, [rsp+arg_40]
.text:0000000000000E1C 000 ror rax, 7
.text:0000000000000E20 000 jmp loc_B21
.text:0000000000000B21 loc_B21:
.text:0000000000000B21 000 sub esi, 1
.text:0000000000000B24 000 mov cs:__return_value, rax
.text:0000000000000B2B 000 mov cs:__stack_depth, esi
.text:0000000000000B31 000 movsxd rsi, esi
.text:0000000000000B34 000 jmp qword ptr [rbx+rsi*8]
.text:0000000000000D10 loc_D10:
.text:0000000000000D10 148 mov rax, cs:__return_value
.text:0000000000000D17 148 mov rdi, [rsp+148h+var_138]
.text:0000000000000D1C 148 xor ebp, ebp
.text:0000000000000D1E 148 xor r12, rax
.text:0000000000000D21 148 mov [rsp+148h+var_128], rax
.text:0000000000000D26 148 mov rax, [rsp+148h+var_110]
.text:0000000000000D2B 148 mov [rax+18h], rdx ; output[3]
.text:0000000000000D2F 148 mov [rax+20h], rdi ; output[4]
.text:0000000000000D33 148 mov [rax+28h], r12
.text:0000000000000D37 148 jmp short loc_CEC
ここで 0xd2b の rdx は 0xea5 で計算していますが、ここで output[3] = output[0] ^ input[0] という関係が成り立つことに気づきます。同様に 0xd2f の rdi は 0xea5 で output[3] = output[0] ^ input[0] という計算を行っています。
0x5aaf63e5c1f262bf^0x20804abbaeac4ae3
⇒ "\(^o^)/z"
0xa967ff06da3b455c^0xf01c8c72aa0b3739
⇒ "er0pts{Y"
最初の 8文字は分かりましたので、ここで、ダミーのフラグ \(^o^)/zer0pts{Yaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
を入力して、出力を確認してみます。
5aaf63e5c1f262bf
a967ff06da3b455c
ee8d95ecc2ae3151
20804abbaeac4ae3
f01c8c72aa0b3739
59a22a5a76e6b680
4dfd0de827989bbe
19b3437732734c33
94b33d96023c5d20
0c7ccebc25e54e9d
705f15907157aff5
917d2313d08804f5
b1eef51660432705
68288330bca1cccd
d8e8330399835a28
0bae631fb770c9a2
2861e01b0ea2eb90
96b17f6b92cca8ef
a440ff9f3b70bc02
b030b45f3296b9c2
5abc59091bc7cf3b
002c0066edad214c
3b9d8f4c92f4f0d4 ⇒ 正しくは ..f0d0
90001647bc8a674e ⇒ 正しくは ..675c
835c980e129e53b0 ⇒ 正しくは ..538e
394bfa98b73bfff7 ⇒ 正しくは ..ffdf
...
22行目まで正しい出力が得られました。その後は正しくありませんが、最後の 1バイトのみ異なっていてほぼ正解です。そこで、watch *(0x5555557576b0+22*8)
でこの処理についてウオッチしてみると、0x1044 の命令で書き込みをしていることが分かりました。
.text:000000000000102C loc_102C:
.text:000000000000102C 148 mov rsi, [rsp+148h+var_120]
.text:0000000000001031 148 lea ecx, [rbp+6]
.text:0000000000001034 148 movsxd rcx, ecx
.text:0000000000001037 148 movsx rsi, byte ptr [rsi+rdi]
.text:000000000000103C 148 mov rdi, [rsp+148h+var_110]
.text:0000000000001041 148 xor rsi, r12
.text:0000000000001044 148 mov [rdi+rcx*8], rsi
0x1037 の rsi は 1バイトで、これをxorしているため、この入力が正しくないと下1バイトが合いません。そこで、1文字ずつブルートフォースを行い、フラグを取得しました。
\(^o^)/zer0pts{es_Its_https://www.hitachi.com/rd/yrl/crypto/mugi/mugi_spe.pdf}\(^o^)/
spotlight (239 points)
入力を受け取るプログラムです。IDAで開くと所々命令が解釈できていない部分がありますが、気にせず頭からデコンパイルしていきます。
この sub_960 をデコンパイルすると、データを命令列にデコードする関数であることが分かります。つまり、関数はエンコードされていて、関数の冒頭でこの関数を呼び出すことで、本来の処理が復元できます。
expand(addr, magic) {
pg = addr;
while (mprotect(pg & 0xFFFFFFFFFFFFF000, 0x1000, 7) != -1) {
pg += 0x1000;
}
while (*addr != 0xDF0ADAB) {
addr++;
}
expand_stack[g_count*3] = addr;
*addr = 0x90909090; // nop, nop, nop, nop
end = addr + 4;
if (*end != 0xABADF00D) {
end++;
}
expand_stack[g_count*3+1] = end;
expand_stack[g_count*3+2] = magic;
*end = 0x90909090; // nop, nop, nop, nop
expand_sub();
g_count++;
if (mprotect(addr, 0x1000, 5) != -1) {
addr += 0x1000;
}
}
expand_sub() {
addr = expand_stack[g_count*3];
end = expand_stack[g_count*3+1];
magic = expand_stack[g_count*3+2];
tmp = end - addr
for (addr += 4; addr <= end - 4; addr++) {
tmp = tmp * magic + magic >> 0x20;
*addr ^= tmp;
}
}
最初は関数がデコードされたタイミングでメモリダンプしてデコンパイルしようとしましたが、関数は終了時にデコード関数を呼び出して元のデータに再エンコードしています。全ての関数にこのような難読化処理がされているため、全ての関数がデコードされた瞬間が存在しません。そこで、いったん以下のように手元でデコード処理を行い、IDA上でパッチを当てました。入れ子の関数もエンコードされているため、全ての処理がデコードされるまで繰り返します。
#!/usr/bin/python
import sys
from binascii import hexlify, unhexlify
from pwn import *
context(os='linux', arch=['amd64', 'i386'][0])
addr_5555555550BF = """
AB AD F0 0D AE 86 20 5F F8
0D C0 73 E5 A6 E1 9E 42 52 C5 94 DB AB BD 1A 33
91 8A B3 48 A1 3D 45 C8 C2 94 B9 50 B7 2D 57 6D
2D 58 5F 7B 7B FA 65 D3 98 D9 27 5D 28 AE BF 3C
54 44 98 C1 CC 6F E0 A3 2E C2 1F BB AA 45 65 77
0D E0 40 87 49 50 19 0C F9 50 83 42 BA FD ED DD
89 86 9B 64 07 49 A2 FE 20 D1 A0 83 5B A9 35 74
B4 5A 83 50 F6 F9 2D A7 FC 6D D8 3F 46 16 31 9A
18 F2 44 39 2F 51 43 EE 1D B1 70 DB B7 16 E4 76
39 3C B0 C4 92 48 6A 36 D7 D6 8E 66 02 8E D6 5D
D2 E1 86 83 E8 94 E8 20 FB BC 37 BA 90 C1 C7 FB
51 96 1A 43 2D 9E B9 75 D5 2E 95 F6 8F F9 17 0C
51 DC 3F 5E 5F F1 A2 EE 9A DC 4F B1 73 59 C0 87
01 00 94 C4 33 2D DA 3F 92 6E 64 C9 5D A4 F8 5C
7D 58 32 F4 9B C4 D8 4C CD 72 31 04 0D F0 AD AB
"""
addr_5555555550BF = b"".join([unhexlify(x) for x in addr_5555555550BF.split()])
addr_555555554D1B = """
AB AD F0 0D 34
18 79 1F 30 19 BA B1 C7 4C 86 C4 AF FE 35 BF 7B
BA 84 38 85 B7 12 61 9F 59 9E 51 02 16 8B B3 65
7E DC A7 88 A9 06 89 B8 9E 28 FC 4E AD A4 FD 1B
3C F4 EA 36 B4 0C B7 6A 9F 6E 2E 4B 14 37 C1 AD
21 2A C7 8D 69 C6 C3 11 77 2F 11 C8 5D 49 77 C3
97 2E 14 8F 21 13 05 66 F7 62 21 A2 26 DB 2B 52
6C 13 C2 14 7C F4 94 5E 58 A0 3D 6F 6D 07 54 C2
FA 59 00 94 16 57 74 9D 44 BE 8E 58 B2 75 5A BA
7C 46 06 A1 63 93 00 5B 2E A0 BF 6A 10 6A C2 A2
0D F0 AD AB
"""
addr_555555554D1B = b"".join([unhexlify(x) for x in addr_555555554D1B.split()])
addr_555555554CAD = """
AB AD F0
0D AD 4D 4B 2D 7C BF 00 8C A5 E1 F5 55 62 1A 9F
8C 63 CB 7C 6B 9C 6B A2 1A EF 80 A2 FB 0D F0 AD
AB
"""
addr_555555554CAD = b"".join([unhexlify(x) for x in addr_555555554CAD.split()])
addr_555555554C0E = """
AB AD
F0 0D 9F F0 C7 DC 2F 4C E9 BA A7 1C BF FF E8 A4
8C 0C 86 E1 F1 29 70 B9 CF E1 66 2F 85 1A 5B 8B
52 6D 6D 1B EC BA 49 11 CD 52 F2 AA B1 0F 7B 60
DB D0 7B ED B1 57 A2 75 AC 59 0B 2B 6C 29 0A 42
A2 75 54 E5 C6 0D 85 1C 10 0E 84 5E 65 8A EA F6
60 8E F9 D8 42 D5 6F 94 A6 6F 28 E8 7C 28 E7 0D
F0 AD AB
"""
addr_555555554C0E = b"".join([unhexlify(x) for x in addr_555555554C0E.split()])
addr_555555554EC3 = """
AB AD F0 0D 4F 50 28 DB 98 C0 79 5F CB
51 70 2F F1 84 D2 5F 70 DE E7 05 51 50 90 B7 59
58 A3 37 D8 45 50 19 D2 91 53 19 D2 A9 41 24 B1
60 91 EB 72 50 51 51 19 DC 25 75 49 19 DC 6C 6C
52 51 51 BA 56 5E 4E 51 5E E6 45 56 37 62 45 50
37 D8 45 57 19 D2 91 53 19 D2 A9 41 24 BB 1D D8
9E E9 50 51 51 51 37 7F 5E 4E D5 51 51 51 51 51
5E E6 05 10 AF 19 D2 96 55 D8 87 50 83 37 90 BF
5E 5E E6 A7 58 A3 D0 B3 AE AE 51 51 D8 06 AD DC
01 50 D2 B3 56 5E E6 05 05 49 D8 06 4D 19 D8 93
D2 B3 56 5E E6 05 05 59 D8 87 90 B3 54 37 90 BF
5A 5E E6 A7 58 A3 D0 B3 AE AE 51 51 D8 06 6D DC
01 55 D2 B3 56 5E E6 05 05 59 D8 82 90 B3 59 5E
E7 8E 58 8B D0 B3 AE AE 51 51 D8 06 0D DC 01 54
D2 B3 56 5E E6 05 05 59 D8 87 90 B3 5C 37 90 BF
52 5E E6 A7 58 A3 D0 B3 AE AE 51 51 D8 06 2D DC
01 52 D2 B3 56 5E E6 05 05 49 D8 C6 CD 51 51 51
DC 01 53 D2 B3 56 5E E6 05 05 49 D8 C6 ED 51 51
51 DC 01 57 19 D2 91 50 D2 B3 56 5E E6 05 05 49
D8 C6 8D 51 51 51 19 D2 A9 58 5E D4 61 01 5C 1F
0D F0 AD AB
"""
addr_555555554EC3 = b"".join([unhexlify(x) for x in addr_555555554EC3.split()])
addr_555555554B7C = """
AB AD F0 0D
18 1A 07 DF 4F E1 AB DF 02 91 69 4A 67 E1 82 9F
61 60 60 E3 81 1F 28 ED 55 A3 66 60 60 24 6F D7
64 37 21 51 A8 E3 81 1F 24 E9 A2 E3 82 1F 06 53
74 2E E9 B1 E9 A2 06 45 9F 61 24 51 A0 06 A1 8A
69 6F D7 A0 51 AA 6F D7 6C 27 51 B1 6F D7 B2 E9
A8 E3 80 1F 06 43 99 A1 0D F0 AD AB
"""
addr_555555554B7C = b"".join([unhexlify(x) for x in addr_555555554B7C.split()])
def unexpand(a, start, end, magic):
import struct
tmp = end - start - 8
a = b"".join([struct.pack("B", x) for x in a])
a = b"\x90"*4 + a[4:]
a = a[:-4] + b"\x90"*4
for i in range(4, tmp + 1):
tmp = (tmp * magic + (magic >> 0x20)) & 0xFFFFFFFF
a = a[:i] + p32((u32(a[i:i+4]) ^ tmp) & 0xFFFFFFFF) + a[i+4:]
for c in a:
sys.stdout.write("{:02x} ".format(c))
print()
#print(disasm(a[4:]))
unexpand(addr_5555555550BF, 0x555555555067, 0x555555555150, 0x989E105BA25F98EB)
unexpand(addr_555555554D1B, 0x555555554D1B, 0x555555554DB4, 0x6AB9591947053767)
unexpand(addr_555555554CAD, 0x555555554CAD, 0x555555554CD1, 0x7D9D585E6E989A8)
unexpand(addr_555555554C0E, 0x555555554C0E, 0x555555554C73, 0x2194D29E7590998D)
unexpand(addr_555555554EC3, 0x555555554EC3, 0x555555555004, 0x0D5DF98ABDAC7303C)
unexpand(addr_555555554B7C, 0x555555554B7C, 0x555555554BDC, 0x926F8490C6064008)
全てのデコードが完了したらIDAでパッチを当て、アセンブリに変換した後で再びデコンパイルします。すると、判定ルーチンは以下のような処理であることが分かります。
check_flag(flag) {
init_iv(iv, "zer0pts CTF 2021");
magic = 0x786F4620797A614C;
memset(output, 0, 64);
encode(iv, &magic, flag, output, 0x40);
rax = 0x2DE23334718727BB ^ output[0] | 0xB298DE796E944115 ^ output[1] |
0xE45EF960FBEC841B ^ output[2] | 0x314FC3F835E2958E ^ output[3] |
0xDC115329AA177509 ^ output[4] | 0x7CB37E8F516BC981 ^ output[5] |
0x2D9D9AD4ADAF9925 ^ output[6] | 0x1808A242A2A693E1 ^ output[7] |
//assert(rax == 0);
return rax;
}
encode(iv, *magic, flag, output, arg5) {
mgc = *magic;
// if (arg5 < 0) arg5 += 7;
// arg5 = (arg5 >> 3) | (arg5 << 61); // sar arg5, 3
assert(arg5 == 8);
for (ptr = flag; ptr != flag + 8*8; ptr++) {
mgc ^= *ptr;
encode_1(iv, mgc, output);
output++;
mgc = *output;
}
return;
}
encode_1(iv, mgc, out_qw) {
var1 = mgc & 0xFFFFFFFF;
var2 = (mgc >> 0x20) & 0xFFFFFFFF;
bswap(var1);
bswap(var2);
for (i = 0; i != 8; i += 2) {
ret = encode_1_1(iv, var1, i);
ret = encode_1_2(iv, ret, i);
var2 ^= ret;
ret = encode_1_2(iv, var2, i+1);
ret = encode_1_1(iv, ret, i+1);
var1 ^= ret;
}
out_qw[0] = (var1 >> 0x18) % 0xFF;
out_qw[1] = (var1 >> 0x10) % 0xFF;
out_qw[2] = (var1 >> 0x08) % 0xFF;
out_qw[3] = (var1 >> 0x00) % 0xFF;
out_qw[4] = (var2 >> 0x18) % 0xFF;
out_qw[5] = (var2 >> 0x10) % 0xFF;
out_qw[6] = (var2 >> 0x08) % 0xFF;
out_qw[7] = (var2 >> 0x00) % 0xFF;
}
encode_1_1(iv, arg2, n) {
esi = iv[n] & (arg2 >> 0x10);
esi = ((esi << 1) & 0xFFFF) | (esi >> 15) & 0xFFFF)
esi = esi ^ arg2;
eax = iv[n+0x8] | esi;
eax = ((eax << 1) & 0xFFFF | (eax >> 15) & 0xFFFF);
eax = ((eax ^ (arg2 >> 0x10)) << 0x10) + esi;
return eax;
}
encode_1_2(iv, arg2, n) {
edi = ((arg2 >> 0x10) ^ iv[n+0x10]) & 0xFFFF;
eax = encode_1_2_1(edi, iv[n+0x28]);
r10 = eax ^ arg2;
r9 = (arg2 ^ iv[n+0x18]) & 0xFFFF;
eax = encode_1_2_1(r9, iv[n+0x30]);
r9 = eax ^ r10;
r10 = (r10 ^ iv[n+0x20]) & 0xFFFF;
eax = encode_1_2_1(r10, iv[n+0x38]);
eax = eax ^ r9;
return (eax + (r9 << 0x10)) & 0xFFFFFFFF;
}
encode_1_2_1(arg1, arg2) {
dword1 = table512[((arg1 >> 0x7) & 0x1FF)] ^ (arg1 & 0x7F);
dword2 = ((dword1 & 0x7F) ^ table128[(arg1 & 0x7F)]) & 0xFFFF;
dword2 = table512[((arg2 & 0x01FF) ^ dword1)] ^ (((arg2 >> 9) & 0xFFFF) ^ dword2);
ret = (((dword2 & 0x7F) ^ table128[(arg2 >> 9) ^ dword2]) & 0xFFFF) << 0x9 + dword2;
return ret;
}
#!/usr/bin/python
# coding:UTF-8
from binascii import unhexlify
from pwn import *
table512 = "A7 00 EF 00 A1 00 7B 01 87 01 4E 01 09 00 52 01 26 00 E2 00 30 00 66 01 C4 01 81 01 5A 00 8D 01 B7 00 FD 00 93 00 4B 01 9F 01 54 01 33 00 6A 01 32 01 F4 01 06 01 52 00 D8 00 9F 00 64 01 B1 00 AF 00 F1 00 E9 01 25 00 CE 00 11 00 00 00 4D 01 2C 00 FE 00 7A 01 3A 00 8F 00 DC 00 51 00 90 01 5F 00 03 00 3B 01 F5 00 36 00 EB 00 DA 00 95 01 D8 01 08 01 AC 00 EE 01 73 01 22 01 8F 01 4C 00 A5 00 C5 00 8B 01 79 00 01 01 E0 01 A7 01 D4 00 F0 00 1C 00 CE 01 B0 00 96 01 FB 01 20 01 DF 00 F5 01 97 01 F9 00 09 01 59 00 BA 00 DD 00 AC 01 A4 00 4A 00 B8 01 C4 00 CA 01 A5 01 5E 01 A3 00 E8 00 9E 00 86 00 62 01 0D 00 FA 00 EB 01 8E 00 BF 00 45 00 C1 00 A9 01 98 00 E3 00 6E 01 87 00 58 01 2C 01 14 01 F2 00 B5 01 40 01 71 00 16 01 0B 00 F3 00 57 00 3D 01 24 00 5D 00 F0 01 1B 00 E7 01 BE 01 E2 01 29 00 44 00 9C 00 C9 01 83 00 46 01 93 01 53 01 14 00 27 00 73 00 BA 01 7C 00 DB 01 80 01 FC 01 35 00 70 00 AA 00 DF 01 97 00 7E 00 A9 00 49 00 0C 01 17 01 41 01 A8 00 6C 01 6B 01 24 01 2E 00 F3 01 89 01 47 01 44 01 18 00 C8 01 0B 01 9D 00 CC 01 E8 01 AA 01 35 01 E5 00 B7 01 FA 01 D0 00 0F 01 5D 01 91 01 B2 01 EC 00 10 00 D1 00 67 01 34 00 38 00 78 00 C7 00 15 01 D1 01 A0 01 FC 00 1F 01 F6 00 06 00 53 00 31 01 A4 01 59 01 99 00 F6 01 41 00 3D 00 F4 00 1A 01 AD 00 DE 00 A2 01 43 00 82 01 70 01 05 01 65 00 DC 01 23 01 C3 00 AE 01 31 00 4F 00 A6 00 4A 01 18 01 7F 01 75 01 80 00 7E 01 98 01 9B 00 EF 01 6F 01 84 01 12 01 6B 00 CB 01 A1 01 3E 00 C6 01 84 00 E1 00 CB 00 3C 01 EA 00 0E 00 2D 01 5B 00 F7 01 1E 01 A8 01 D3 00 5B 01 33 01 8C 00 76 01 23 00 67 00 7D 00 AB 01 13 00 D6 00 C5 01 92 00 F2 01 3A 01 BC 01 E6 00 00 01 49 01 C6 00 1D 01 32 00 74 00 4E 00 9A 01 0A 00 CD 00 FE 01 AB 00 E7 00 2D 00 8B 00 D3 01 1D 00 56 00 F9 01 20 00 48 00 1A 00 56 01 96 00 39 01 EA 01 AF 01 EE 00 9B 01 45 01 95 00 D9 01 28 00 77 00 AE 00 63 01 B9 00 E9 00 85 01 47 00 C0 01 11 01 74 01 37 00 6E 00 B2 00 42 01 0C 00 D5 01 88 01 71 01 BE 00 01 00 6D 00 77 01 89 00 B5 00 58 00 4B 00 34 01 04 01 E4 01 62 00 10 01 72 01 13 01 9C 01 6F 00 50 01 3E 01 04 00 F8 01 EC 01 03 01 30 01 4D 00 51 01 B3 01 15 00 65 01 2F 01 4C 01 E3 01 12 00 2F 00 55 00 19 00 F1 01 DA 01 21 01 64 00 0D 01 28 01 DE 01 0E 01 6A 00 1F 00 68 00 B1 01 54 00 9E 01 E6 01 8A 01 60 00 63 00 9A 00 FF 01 94 00 9D 01 69 01 99 01 FF 00 A2 00 D7 00 2E 01 C9 00 0A 01 5F 01 57 01 90 00 B9 01 6D 01 6C 00 2A 01 FB 00 22 00 B6 00 FD 01 8A 00 D2 00 4F 01 85 00 37 01 60 01 48 01 8D 00 8C 01 5A 01 7B 00 3F 01 C2 01 19 01 AD 01 E4 00 BB 01 E1 01 5C 00 94 01 E5 01 A6 01 F8 00 29 01 17 00 D5 00 82 00 D2 01 16 00 D9 00 1B 01 46 00 26 01 68 01 A3 01 7F 00 38 01 79 01 07 00 D4 01 C2 00 02 00 75 00 27 01 CF 01 02 01 E0 00 BF 01 F7 00 BB 00 50 00 8E 01 1C 01 61 01 69 00 86 01 2B 01 D7 01 D6 01 B8 00 39 00 C8 00 5C 01 3F 00 CC 00 BC 00 21 00 C3 01 61 00 1E 00 36 01 DB 00 5E 00 A0 00 81 00 ED 01 40 00 B3 00 07 01 66 00 BD 00 CF 00 72 00 92 01 B6 01 DD 01 83 01 7A 00 C0 00 2A 00 7D 01 05 00 91 00 76 00 B4 00 C1 01 25 01 43 01 88 00 7C 01 2B 00 42 00 3C 00 C7 01 55 01 BD 01 CA 00 B0 01 08 00 ED 00 0F 00 78 01 B4 01 D0 01 3B 00 CD 01".split()
table512 = [int(table512[i+1] + table512[i], 16) for i in range(0, len(table512), 2)]
table128 = "36 00 32 00 3E 00 38 00 16 00 22 00 5E 00 60 00 26 00 06 00 3F 00 5D 00 02 00 12 00 7B 00 21 00 37 00 71 00 27 00 72 00 15 00 43 00 41 00 0C 00 2F 00 49 00 2E 00 1B 00 19 00 6F 00 7C 00 51 00 35 00 09 00 79 00 4F 00 34 00 3C 00 3A 00 30 00 65 00 7F 00 28 00 78 00 68 00 46 00 47 00 2B 00 14 00 7A 00 48 00 3D 00 17 00 6D 00 0D 00 64 00 4D 00 01 00 10 00 07 00 52 00 0A 00 69 00 62 00 75 00 74 00 4C 00 0B 00 59 00 6A 00 00 00 7D 00 76 00 63 00 56 00 45 00 1E 00 39 00 7E 00 57 00 70 00 33 00 11 00 05 00 5F 00 0E 00 5A 00 54 00 5B 00 08 00 23 00 67 00 20 00 61 00 1C 00 42 00 66 00 1F 00 1A 00 2D 00 4B 00 04 00 55 00 5C 00 25 00 4A 00 50 00 31 00 44 00 1D 00 73 00 2C 00 40 00 6B 00 6C 00 18 00 6E 00 53 00 24 00 4E 00 2A 00 13 00 0F 00 29 00 58 00 77 00 3B 00 03 00".split()
table128 = [int(table128[i+1] + table128[i], 16) for i in range(0, len(table128), 2)]
def encode_1_2_1(arg1, arg2):
dword1 = table512[((arg1 >> 0x7) & 0x1FF)] ^ (arg1 & 0x7F)
dword2 = ((dword1 & 0x7F) ^ table128[(arg1 & 0x7F)]) & 0xFFFF
dword3 = table512[((arg2 & 0x01FF) ^ dword1)] ^ ((arg2 >> 9) & 0xFFFF) ^ dword2
ret = (dword3 & 0x7F) ^ table128[(arg2 >> 9 ^ dword2) & 0x7F] & 0xFFFF
ret = (ret << 0x9) + dword3
return ret
#print(hex(encode_1_2_1(0xbcd1, 0xbd88))) # should be 0xd9e2
iv = b"ca f4 00 00 60 e4 00 00 e8 e0 00 00 40 e6 00 00 a8 86 00 00 40 8c 00 00 60 64 00 00 62 64 00 00 df f9 00 00 cf be 00 00 88 bd 00 00 b8 fc 00 00 64 44 00 00 21 00 00 00 46 7b 00 00 57 37 00 00 0e 46 00 00 8e 0e 00 00 0e 64 00 00 88 6a 00 00 08 c4 00 00 06 46 00 00 26 46 00 00 af 4c 00 00 46 20 00 00 32 30 00 00 32 31 00 00 7a 65 00 00 72 30 00 00 70 74 00 00 73 20 00 00 43 54 00 00 46 06 00 00 46 26 00 00 4c af 00 00 46 0e 00 00 0e 8e 00 00 64 0e 00 00 6a 88 00 00 c4 08 00 00 88 bd 00 00 b8 fc 00 00 64 44 00 00 21 00 00 00 46 7b 00 00 57 37 00 00 df f9 00 00 cf be 00 00 cf be 00 00 88 bd 00 00 b8 fc 00 00 64 44 00 00 21 00 00 00 46 7b 00 00 57 37 00 00 df f9 00 00 21 00 00 00 46 7b 00 00 57 37 00 00 df f9 00 00 cf be 00 00 88 bd 00 00 b8 fc 00 00 64 44 00 00".split()
iv = [u32(unhexlify(b"".join(iv[i:i+4]))) for i in range(0, len(iv), 4)]
def encode_1_2(iv, arg2, n):
edi = ((arg2 >> 0x10) ^ iv[n+0x10]) & 0xFFFF
eax = encode_1_2_1(edi, iv[n+0x28])
r10 = eax ^ arg2
r9 = (arg2 ^ iv[n+0x18]) & 0xFFFF
eax = encode_1_2_1(r9, iv[n+0x30])
r9 = eax ^ r10
r10 = (r10 ^ iv[n+0x20]) & 0xFFFF
eax = encode_1_2_1(r10, iv[n+0x38])
eax = eax ^ r9
return ((r9 << 0x10) + (eax & 0xFFFF)) & 0xFFFFFFFF
#print(hex(encode_1_2(iv, 0xfadf3338, 0))) # should be 0x773a74b6
def encode_1_1(iv, arg2, n):
esi = iv[n] & (arg2 >> 0x10)
esi = ((esi << 1) & 0xFFFF) | ((esi >> 15) & 0xFFFF)
esi = esi ^ arg2
eax = iv[n+0x8] | esi
eax = (((eax & 0xFFFF) << 1) & 0xFFFF) | (((eax & 0xFFFF) >> 15) & 0xFFFF) | (eax & 0xFFFF0000)
eax = (((eax ^ (arg2 >> 0x10)) << 0x10) + (esi & 0xFFFF)) & 0xFFFFFFFF
return eax
#print(hex(encode_1_1(iv, 0xd203b38, 0))) # should be 0xfadf3338
def encode_1(iv, mgc, out_qw):
var1 = mgc & 0xFFFFFFFF
var2 = (mgc >> 0x20) & 0xFFFFFFFF
def bswap(b):
from struct import pack, unpack
return unpack("<I", pack(">I", b))[0]
var1 = bswap(var1)
var2 = bswap(var2)
for i in range(0, 8, 2):
ret = encode_1_1(iv, var1, i)
ret = encode_1_2(iv, ret, i)
var2 ^= ret
ret = encode_1_2(iv, var2, i+1)
ret = encode_1_1(iv, ret, i+1)
var1 ^= ret
out_qw[0] = (var1 >> 0x18) & 0xFF
out_qw[1] = (var1 >> 0x10) & 0xFF
out_qw[2] = (var1 >> 0x08) & 0xFF
out_qw[3] = (var1 >> 0x00) & 0xFF
out_qw[4] = (var2 >> 0x18) & 0xFF
out_qw[5] = (var2 >> 0x10) & 0xFF
out_qw[6] = (var2 >> 0x08) & 0xFF
out_qw[7] = (var2 >> 0x00) & 0xFF
return u64("".join([chr(x) for x in out_qw]))
out_qw = [0] * 8
#print(hex(encode_1(iv, 0x392e0761383b200d, out_qw))) # should be 0x9aaf4ea1498bf23e
def encode_1_inv(iv, mgc, result):
from struct import pack, unpack
var1 = unpack(">I", p64(result)[:4])[0]
var2 = unpack(">I", p64(result)[4:])[0]
for i in range(6, -1, -2):
ret = encode_1_2(iv, var2, i+1)
ret = encode_1_1(iv, ret, i+1)
var1 = var1 ^ ret
ret = encode_1_1(iv, var1, i)
ret = encode_1_2(iv, ret, i)
var2 = var2 ^ ret
def bswap(b):
from struct import pack, unpack
return unpack("<I", pack(">I", b))[0]
var1 = bswap(var1)
var2 = bswap(var2)
return (var2 << 0x20) + var1
def solve():
buf = b""
magic = 0x786F4620797A614C
crypt = [0x2DE23334718727BB, 0xB298DE796E944115, 0xE45EF960FBEC841B, 0x314FC3F835E2958E,
0xDC115329AA177509, 0x7CB37E8F516BC981, 0x2D9D9AD4ADAF9925, 0x1808A242A2A693E1]
for i in range(8):
msg = encode_1_inv(iv, 0x392e0761383b200d, crypt[i])
buf += (msg ^ magic).to_bytes(8, "little")
magic = crypt[i]
print(buf)
solve()
これを実行すると、フラグを取得することができます。
zer0pts{KASUMI_c1ph3r_c4m3_fr0m_th3_J4p4n3s3_w0rd_f0r_MISTY}
Zh3r0 CTF 2021
- Zh3r0 CTF 2021(CTF Time)
- 2021/6/4~6
このあたりからCTF Timeを順に遡って適当にRev問に手を付けていきます。
BabyRe (100 points)
プログラムを実行すると、TUIなログイン画面でパスワードが要求されます。
アセンブリを少し眺めると、0x14D0 の位置に入力文字をエンコードしている処理を見つけられます。ビット単位で入力文字列を変換しています。
encode(str_8byte) {
encoded = 0;
for (i = 0; i != 8; i++) {
ch = str_8byte[i];
for (ptr = &encoded; ptr != &canary; ptr++) {
*ptr = ((ch & 1) << i) | encoded;
encoded = *ptr;
ch >>= 1;
}
}
return encoded;
}
デコンパイルで変換処理が分かったら、後は逆順に計算するだけです。
#!/usr/bin/python
from binascii import unhexlify
table = "A4 AD C0 A3 FD 7F AB 00 E8 D5 E2 48 DA BF FD 00 D1 40 F2 C4 7B BF 76 00 87 07 D5 AD AE 82 FD 00".split()
table = [unhexlify("".join(table[x:x+8])) for x in range(0, len(table), 8)]
def encode(str_8byte):
encoded = bytearray(b"\x00"*8)
for i in range(8):
ch = str_8byte[i]
for j in range(8):
encoded[j] = ((ch & 1) << i) | int(encoded[j])
ch >>= 1
return encoded
def decode(str_8byte):
decoded = bytearray(b"\x00"*8)
for i in range(8):
ch = str_8byte[i]
for j in range(8):
decoded[j] |= ((ch & (1 << j)) >> j) << i
return decoded
buf = b""
for i in range(4):
buf += decode(table[i])
print(buf)
これを実行すると、フラグを取得することができます。。
zh3r0{4_b4byre_w1th0ut_-O3_XDXD}
wtf CTF
a2Z_Pl4yGr0und
入力を 2回受け取るプログラムです。問題文に "ihavealotoffunplayingwithu" という文字列がヒントとして与えられています。
a2z is a happy little kid in a program. He's like any other kid and he likes playing games, candy, comic books, frequency, puppies, balloons and ice cream like any other normal kid. He also likes a particular string "ihavealotoffunplayingwithu". See if you can win his favour and get the flag.
このプログラムをデコンパイルすると以下のようになります。1回目は文字列を入力として受け取り、文字列が出力されます。2回目は 26個の数値を受け取り、文字列が出力されます。1回目の入力に対して、アルファベット 26文字の出現頻度を計算しています。
main() {
cout << "Welcome to the playground! Fool around and have fun :)\n");
while (1) {
if (c == 'y') {
cout << "Yay! Now pick up some lowercase alphabets and give them to me!\n");
cin >> input;
fun::fun(freq_array);
fun::Frequency(freq_array, input);
fun::Num2alp(freq_array, freq_array);
cout << "\nOh! You have to go?....okay just one last game and you can go\nGive me the numbers from the last game\n"
for (i = 0; i < 26; i++) {
cin >> input2 + i;
}
fun::flagify(freq_array, input2);
return 0;
}
cout << "Would you like to play with me?(y/n)\n");
cin >> &c;
if (c == 'n') break;
if (c != 'y') {
cout << "Man.....it says (y/n) so give me y or n\n";
}
}
cout << "Wow, mean! No flag for you >:(\n";
exit(1);
}
fun::fun(*freq_array) {
for (i = 0; i < 26; i++) {
freq_array[i] = 0;
}
for (i = 0; i < 26; i++) {
freq_array[i+0x68] = 0x0;
}
}
fun::Frequency(*freq_array, char *input) {
len = strlen(input);
for (i = 0; i < len; i++) {
freq_array[input[i]-0x61]++;
}
}
fun::Num2alp(*freq_array, *arg2) {
for (i = 0; i < 26; i++) {
n = arg2[i] + 0x60;
if ((0x60 < n) && (n < 0x7b)) {
freq_array[i+0x68] = n & 0xFF;
}
}
cout << endl;
for (i = 0; i < 26; i++) {
cout << freq_array[i+0x68];
}
}
fun::flagify(*freq_array, *input2) {
arr[0] = 0xe;
arr[1] = 0xc;
arr[2] = 5;
arr[3] = 0xffffffcd;
arr[4] = 0xffffffef;
arr[5] = 0xffffffe5;
arr[6] = 0xf;
arr[7] = 4;
arr[8] = 0xffffffc0;
arr[9] = 0xfffffff5;
arr[10] = 0xfffffff9;
arr[11] = 0xe;
arr[12] = 0xffffffbb;
arr[13] = 0xfffffff1;
arr[14] = 3;
arr[15] = 0xffffffc7;
arr[16] = 0xffffffd2;
arr[17] = 0xffffffe6;
arr[18] = 0xc;
arr[19] = 0xfffffff1;
arr[20] = 0;
arr[21] = 0xffffffb9;
arr[22] = 0xfffffff6;
arr[23] = 0xffffffc6;
arr[24] = 0xffffffc0;
arr[25] = 8;
for (int i = 0; i < 26; i++) {
ans[i] = arr[i] + input2[i] + 0x60;
}
for (int i = 0; i < 26; i++) {
cout << ans[i];
}
}
問題分に与えられたヒントをどう活用するかは明示されていませんが、1回目の出力が "ihavealotoffunplayingwithu" となるような入力を見つけます。この時の 26個のアルファベットの出現頻度を 2回目の入力に渡すことでフラグを取得できます。
>>> a="ihavealotoffunplayingwithu"
>>> b= [ord(x)-ord("a") for x in a]
>>> [x+1 for i, x in enumerate(b)]
[9, 8, 1, 22, 5, 1, 12, 15, 20, 15, 6, 6, 21, 14, 16, 12, 1, 25, 9, 14, 7, 23, 9, 20, 8, 21]
2回目の入力に対して、以下のフラグが出力されました。
wtfCTF{s4d_t0_s33_u_g0_:(}
ICHSA CTF 2021
- ICHSA CTF 2021(CTF Time)
- 2021/5/31~6/2
Anti (450 points)
アンチデバッグが施された ELF バイナリで、正しいフラグを入力すると Success が表示されます。非常に奇妙なバイナリで、普通に実行することはできますが、gdb から実行すると動きません。IDA の解析結果も色々と問題があります。少しずつ正常に解析できるように修正していきます。
- gdbで実行できないため、ELF ヘッダーを次のように修正する。
7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
⇒7f 45 4c 46 02 5c 78 30 32 00 00 00 00 00 00 00
- IDAで開いた時の命令のアドレスが 0x7F だけずれるため、バイナリのオフセット 0x4930 を
0x0040107F
⇒0x00401000
に変更する。 - 子プロセスはダミーなので、親プロセスを追跡する。
set follow-fork-mode parent
- IDAが .text の最後の一部(init()) が読み込めていない(データの存在すら認識しない)が、実はここに条件判定ルーチンへの関数呼び出しが隠されている。main() にも条件判定ルーチンがあるがこれはダミーで、実際は main() に到達するまえにプログラムが終了する。.text のサイズを修正する。
- セクションヘッダの一部が削除されており、IDA で 0x403000 以降が認識できない(この問題は無視した)。
- gdbのステップ実行の途中でプログラムが突然走り出す。
- 後々になって分かったが、一部の命令(push rsp、sub rsp,0x8)は ni で実行が止まらないので si を使うべきようだ。意図的にこのような命令を多く含んでいる?
ひととおりアンチデバッグ機能を解除した後は、プログラムをデコンパイルします。main() は実際は呼び出されませんので注意します。
// dummy code
main() {
ret1 = encode(input1 ^ input2);
ret2 = encode(input1);
ret = check_flag(ret1 | ret2, "\xba\xd5\x5f\x0a\x03\x3d\x3c\xb6", 8);
assert(ret == 0);
}
shadow_main() {
char input_buf[16]; // input_buf == local_138
__printf_chk(0, "Enter flag here:", 1, &local_e8);
scanf("%16s", input_buf);
ret1 = encode(u64(input_buf[0:8]));
if (fork() == 0) {
prctl(4,0);
} else {
ret2 = encode(u64(input_buf[8:16]));
ret = check_flag(0xee117ed4d156d844, &ret2, 8);
assert(ret == 0);
ret = check_flag(0x6d06f38c0be87c2d, &ret1, 8);
assert(ret == 0);
__printf_chk(1, "Success! Flag is ICHSA_CTF{%s}\n", input_buf); //
}
exit(0);
}
encode(arg1) {
cnt = arg1 & 0x3FFFFFFFFFFFFFFF;
assert(cnt != 0);
a = 5;
b = 1;
while (cnt != 0) {
if (bl == 1) {
b = mul(b, a);
}
cnt = cnt >> 1;
a = mul(a, a);
}
return (arg1 >> 0x3E) - 1 + b;
}
mul(arg1, arg2) {
for (rsp != &var_10000) {
rsp -= 0x1000;
}
var_FFF8 = arg1;
var_10000 = arg2;
memset(&var_FFD0, 0, 0xFFBD);
// dummy code?
var_FFC1 = *(byte *)(0x405080 + 0x1F) ^ 0xFFFFFFAA;
var_FFC2 = *(byte *)(0x405080 + 0x1E) ^ 0xFFFFFFAA;
var_FFC3 = *(byte *)(0x405080 + 0x1D) ^ 0xFFFFFFAA;
var_FFC4 = *(byte *)(0x405080 + 0x1C) ^ 0xFFFFFFAA;
var_FFC5 = *(byte *)(0x405080 + 0x1B) ^ 0xFFFFFFAA;
var_FFC6 = *(byte *)(0x405080 + 0x1A) ^ 0xFFFFFFAA;
var_FFC7 = *(byte *)(0x405080 + 0x19) ^ 0xFFFFFFAA;
var_FFC8 = *(byte *)(0x405080 + 0x18) ^ 0xFFFFFFAA;
var_FFC9 = *(byte *)(0x405080 + 0x17) ^ 0xFFFFFFAA;
var_FFCA = *(byte *)(0x405080 + 0x16) ^ 0xFFFFFFAA;
var_FFCB = *(byte *)(0x405080 + 0x15) ^ 0xFFFFFFAA;
var_FFCC = *(byte *)(0x405080 + 0x14) ^ 0xFFFFFFAA;
var_FFCD = *(byte *)(0x405080 + 0x13) ^ 0xFFFFFFAA;
var_FFCE = *(byte *)(0x405080 + 0x12) ^ 0xFFFFFFAA;
var_FFCF = *(byte *)(0x405080 + 0x11) ^ 0xFFFFFFAA;
var_FFD0 = *(byte *)(0x405080 + 0x10) ^ 0xFFFFFFAA;
var_FFD1 = *(byte *)(0x405080 + 0x0F) ^ 0xFFFFFFAA;
var_FFD2 = *(byte *)(0x405080 + 0x0E) ^ 0xFFFFFFAA;
var_FFD3 = *(byte *)(0x405080 + 0x0D) ^ 0xFFFFFFAA;
var_FFD4 = *(byte *)(0x405080 + 0x0C) ^ 0xFFFFFFAA;
var_FFD5 = *(byte *)(0x405080 + 0x0B) ^ 0xFFFFFFAA;
var_FFD6 = *(byte *)(0x405080 + 0x0A) ^ 0xFFFFFFAA;
var_FFD7 = *(byte *)(0x405080 + 0x09) ^ 0xFFFFFFAA;
var_FFD8 = *(byte *)(0x405080 + 0x08) ^ 0xFFFFFFAA;
var_FFD9 = *(byte *)(0x405080 + 0x07) ^ 0xFFFFFFAA;
var_FFDA = *(byte *)(0x405080 + 0x06) ^ 0xFFFFFFAA;
var_FFDB = *(byte *)(0x405080 + 0x05) ^ 0xFFFFFFAA;
var_FFDC = *(byte *)(0x405080 + 0x04) ^ 0xFFFFFFAA;
var_FFDD = *(byte *)(0x405080 + 0x03) ^ 0xFFFFFFAA;
var_FFDE = *(byte *)(0x405080 + 0x02) ^ 0xFFFFFFAA;
var_FFDF = *(byte *)(0x405080 + 0x01) ^ 0xFFFFFFAA;
var_FFE0 = *(byte *)(0x405080 + 0x00) ^ 0xFFFFFFAA;
var_FFE8 = var_FFC1;
if (var_FFE8 == 0x0FFFFEFADDC14E) {
rax = (arg1 * arg2) & 0xFFFFFFFFFFFFFFFF;
} else {
rax = -1;
}
return rax;
}
check_flag(arg1, arg2, n) {
if (fork() != 0) {
// parent
i = 0;
r9d = 0;
// ignore
/*
if (n & 0x7 != 0) {
if (rax != 1) {
if (rax != 2) {
if (rax != 3) {
if (rax != 4) {
if (rax != 5) {
if (rax != 6) {
i++;
r9d = arg1[0] ^ arg2[0];
}
i++;
r9d |= arg1[i] ^ arg2[i];
}
i++;
r9d |= arg1[i] ^ arg2[i];
}
i++;
r9d |= arg1[i] ^ arg2[i];
}
i++;
r9d |= arg1[i] ^ arg2[i];
}
i++;
r9d |= arg1[i] ^ arg2[i];
}
i++;
r9d |= arg1[i] ^ arg2[i];
}
*/
for (; i != n; i += 8) {
r9d = r9d |
(arg1[i] ^ arg2[i]) |
(arg1[i+1] ^ arg2[i+1]) |
(arg1[i+2] ^ arg2[i+2]) |
(arg1[i+3] ^ arg2[i+3]) |
(arg1[i+4] ^ arg2[i+4]) |
(arg1[i+5] ^ arg2[i+5]) |
(arg1[i+6] ^ arg2[i+6]) |
(arg1[i+7] ^ arg2[i+7]);
}
} else {
// child
prctl(PR_SET_DUMPABLE, 0);
eax = ptrace(PTRACE_ATTACH, getppid(), 0, 0);
assert(eax == 0);
ptrace(PTRACE_SETOPTIONS, getppid(), 0, 2); // 0x4200
rdi = var_24+0x28;
var_24 = 0;
sub_401170(rdi);
ptrace(PTRACE_CONT, getppid(), 0, 0); // 7
r9d = -1;
}
return r9d;
}
#!/usr/bin/python
import sys
from binascii import hexlify, unhexlify
def encode(n):
m = n & 0x3FFFFFFFFFFFFFFF
a = 5
b = 1
while (m != 0):
if (m & 0x01 == 1):
b = (b * a) & 0xFFFFFFFFFFFFFFFF
m = m >> 1
a = (a * a) & 0xFFFFFFFFFFFFFFFF
return (n >> 0x3E) - 1 + b
def get_base():
a = 5
b = 1
c = []
for i in range(64):
print("{:016x}".format(a))
b = (a * b) & 0xFFFFFFFFFFFFFFFF
a = (a * a) & 0xFFFFFFFFFFFFFFFF
c.append(b)
#get_base()
a = [
[0x0000000000000005, 0x0000000000000019, 0x0000000000000271, 0x000000000005f5e1, 0x0000002386f26fc1, 0x2d6d415b85acef81],
[0x6e38ed64bf6a1f01, 0x03df99092e953e01, 0xbed3875b982e7c01, 0x77f27267fc6cf801],
[0xf55b2b722919f001, 0xe30968651333e001, 0xd4724e8d2a67c001, 0x9f34522664cf8001],
[0x41b5687d099f0001, 0x190d61bb133e0001, 0x4c21067a267c0001, 0x1c3b19044cf80001],
[0x275a624899f00001, 0x0245859133e00001, 0x92ce0f2267c00001, 0x5ea82e44cf800001],
[0xa1809c899f000001, 0xd3c239133e000001, 0xea8872267c000001, 0xe120e44cf8000001],
[0xf281c899f0000001, 0xa6039133e0000001, 0x50072267c0000001, 0xb00e44cf80000001],
[0xa01c899f00000001, 0x4039133e00000001, 0x8072267c00000001, 0x00e44cf800000001],
[0x01c899f000000001, 0x039133e000000001, 0x072267c000000001, 0x0e44cf8000000001],
[0x1c899f0000000001, 0x39133e0000000001, 0x72267c0000000001, 0xe44cf80000000001],
[0xc899f00000000001, 0x9133e00000000001, 0x2267c00000000001, 0x44cf800000000001],
[0x899f000000000001, 0x133e000000000001, 0x267c000000000001, 0x4cf8000000000001],
[0x99f0000000000001, 0x33e0000000000001, 0x67c0000000000001, 0xcf80000000000001],
[0x9f00000000000001, 0x3e00000000000001, 0x7c00000000000001, 0xf800000000000001],
[0xf000000000000001, 0xe000000000000001, 0xc000000000000001, 0x8000000000000001],
[0x0000000000000001, 0x0000000000000001]
]
def solve(flag):
ans = 1
buf = ""
for i, b in enumerate(a):
for n in range(2**len(b)):
bits = bin(n)[2:]
bits = bits.rjust(len(b), "0")[::-1]
tmp = 1
m = 0
while n > 0:
if (n & 1) == 1:
tmp = (tmp * b[m]) & 0xFFFFFFFFFFFFFFFF
n >>= 1
m += 1
if hex(ans * tmp)[-i-2:] == hex(flag)[-i-2:]:
ans = (ans * tmp) & 0xFFFFFFFFFFFFFFFF
buf += bits
break
else:
exit(1)
ret = ""
for i in range(0, len(buf), 8):
ret += chr(int(buf[i:i+8][::-1], 2))
return ret
flag1 = 0x6d06f38c0be87c2d
flag2 = 0xee117ed4d156d844+1
# solve
print(solve(flag1)) # => "Gr8J0b7("
print(solve(flag2)) # => "15sUrf19"
# check encode
print(hex(encode(0x683762304a387247))) # => "0x6d06f38c0be87c2d" (== flag1)
print(hex(encode(0x3931667255733531))) # => "0xee117ed4d156d844" (== flag2)
# get flag
print(unhexlify(b"4772384a306237683135735572663139")) # => "Gr8J0b7h15sUrf19"
ICHSA_CTF{Gr8J0b7h15sUrf19}
NorzhCTF 2021
- NorzhCTF 2021(CTF Time)
- 2021/5/21~23
Secure Auth v0 (13 points)
入力を受け取るプログラムです。大量のnop(0x90)が埋め込まれた 70 MBくらいのELFファイルで、IDAで開くとハングします。objdumpの出力からnop行を除いたものを出力します。
# objdump -D -M intel chall | grep -v nop
この出力をデコンパイルします。
main() {
scanf("%64s", var_0x50);
function1(var_0x50);
}
function1(arg1) {
len = strlen(arg1);
function2(arg1, len);
function3(arg1, len);
if (strcmp(arg1, "c-n|TD^zJFp|I'q\"VCj7.mNj4") != 0) {
puts("Failed !");
return 1;
}
return 0;
}
function2(arg1, n) {
for (i = 0; i < n/2; i++) {
tmp = arg1[i];
arg1[i] = arg1[n-1-i];
arg1[n-1-i] = tmp;
}
}
function3(arg1, n) {
for (i = 0; i < n; i++) {
if (arg1[i] > 0x20) {
if (arg1[i] < 0x7f) {
eax = ((arg1[i] - 0x21) + (*0x49ca050[i%4] - 0x21)) // 66 34 7c 3c
arg1[i] = (eax % 0x5f + 0x21) & 0xFF;
}
}
}
}
a = r"""c-n|TD^zJFp|I'q"VCj7.mNj4"""
c = [0x66, 0x34, 0x7c, 0x3c]
buf = ""
for i, b in enumerate(a):
if ord(b) - c[i%4] + 0x21 < 0x20:
buf += chr(ord(b) - c[i%4] + 0x21 + 0x5f)
else:
buf += chr(ord(b) - c[i%4] + 0x21)
print(buf[::-1])
スクリプトを実行すると、フラグを取得できます。
NORZH{n0pfuscat3d_b1nary}
おわりに
他にもいくつか解いた問題がありますので、余力があったら追記します。