Chain
You know sometimes you just run out of ideas for challenge descriptions...
ghidraによる解析
main()が見つからなかったので,Decompileを見ながらメイン処理っぽい関数をSymbol TreeのFunctionsから探す.
FUN_000106a0 Decompileソースコード
undefined4 FUN_000106a0(void)
{
size_t sVar1;
int iVar2;
int local_1c4;
uint local_1c0 [45];
byte abStack_10c [256];
int local_c;
local_c = 0;
memset(local_1c0,0,0xb4);
local_1c0[0] = 0xc2;
local_1c0[1] = 0x9c;
local_1c0[2] = 0x65;
local_1c0[3] = 0x83;
local_1c0[4] = 0x95;
local_1c0[5] = 0x66;
local_1c0[6] = 0xfa;
local_1c0[7] = 0x15;
local_1c0[8] = 0x5e;
local_1c0[9] = 0x58;
local_1c0[10] = 0x2f;
local_1c0[11] = 0x23;
local_1c0[12] = 0xac;
local_1c0[13] = 0x4f;
local_1c0[14] = 0xa1;
local_1c0[15] = 0x4c;
local_1c0[16] = 0x7d;
local_1c0[17] = 0x1e;
local_1c0[18] = 0x69;
local_1c0[19] = 0x80;
local_1c0[20] = 0x8c;
local_1c0[21] = 0x4a;
local_1c0[22] = 0x26;
local_1c0[23] = 0x5b;
local_1c0[24] = 0x5f;
local_1c0[25] = 0x91;
local_1c0[26] = 0x30;
local_1c0[27] = 0xcf;
local_1c0[28] = 0xc0;
local_1c0[29] = 0x4d;
local_1c0[30] = 0x97;
local_1c0[31] = 0x9b;
local_1c0[32] = 0xba;
local_1c0[33] = 0x20;
local_1c0[34] = 0x77;
local_1c0[35] = 0x4c;
local_1c0[36] = 0xf5;
local_1c0[37] = 0xef;
local_1c0[38] = 0x97;
local_1c0[39] = 0x96;
local_1c0[40] = 0x31;
local_1c0[41] = 0x30;
local_1c0[42] = 0x8c;
local_1c0[43] = 0xe2;
puts("Password? ");
fgets((char *)abStack_10c,0xff,stdin);
sVar1 = strlen((char *)abStack_10c);
if (sVar1 == 0x2d) {
for (local_1c4 = 0; local_1c4 < 0x2c; local_1c4 = local_1c4 + 1) {
if ((uint)((byte)FUN_000105ac[*(int *)(&DAT_00021040 + local_1c4 * 4)] ^
abStack_10c[local_1c4]) != (local_1c0[local_1c4] & 0xff)) {
iVar2 = printf("Wrong!");
goto LAB_00010914;
}
}
iVar2 = puts("Correct!");
}
else {
iVar2 = puts("Wrong!");
}
LAB_00010914:
if (local_c != 0) {
/* WARNING: Subroutine does not return */
__stack_chk_fail(iVar2,local_c,0);
}
return 0;
}
入力abStack_10c
がforループ中のif条件を満たし続ければ"Correct!"を表示するので,正しい入力自体がflagになりそう.
-
if(sVar1 == 0x2d)
入力長は45文字 -
local_1c4 = 0; local_1c4 < 0x2c; local_1c4 = local_1c4 + 1
ループ変数local_1c4
をインクリメント -
if((uint)((byte)FUN_000105ac[*(int *)(&DAT_00021040 + local_1c4 * 4)] ^ abStack_10c[local_1c4]) != (local_1c0[local_1c4] & 0xff))
変数を整理すると,入力とXORする値について,
-
(&DAT_00021040 + local_1c4 * 4)
配列DAT_00021040
の先頭アドレスにループ変数local_1c4*4
(配列がint型配列のため*4)を加算
→出力はポインタ -
(int *)(&DAT_00021040 + local_1c4 * 4)
上記で出たポインタをint型ポインタ(int *)にキャスト -
*(int *)(&DAT_00021040 + local_1c4 * 4)
上記で出たポインタが指すアドレスに格納されている値を取得 -
(byte)FUN_000105ac[*(int *)(&DAT_00021040 + local_1c4 * 4)]
関数に[]?
結論を述べると,関数FUN_000105acのアドレス+配列DAT_00021040のlocal_1c4番目の要素の値
が指すアドレスから1バイト分読みとった値になる.
読み取ってくる値は関数FUN_000105acに記載されているアセンブラ命令に対応するマシン語の一部.
(byte)FUN_000105ac[*(int *)(&DAT_00021040 + local_1c4 * 4)]
に該当するアセンブリコードの詳細は以下 -
アセンブリコード詳細
[レジスタ]はレジスタが指すアドレスに格納されている値を取得.
ldr r3,[PTR_PTR_FUN_0001094c]
PTR_PTR_FUN_0001094c
はFUN_000105ac
(イメージできなかった関数)を指すポインタのポインタ.
[]より,r3
← FUN_000105acを指すアドレスが
ロードされる.
ldr r3,[r3,#0x0]
1行目を踏まえると,r3
← FUN_000105acの先頭アドレス
ldr r1,[PTR_DAT_00010950]
PTR_DAT_00010950
はDAT_00021040
(別箇所で定義されていた配列)を指すポインタ.
よって,r1
← DAT_00021040の先頭アドレス
ldr r2,[r11,#local_1c4]
[r11,#local_1c4]
には事前に0が格納されているため,r2
← 0
.
ldr r2,[r1,r2,lsl #offset DAT_00021040]
[r1, r2, lsl #offset DAT_00021040]
はr1+(r2<<#offset)
のアドレスに格納されている値をロードしてくる.
r1=DAT_00021040の先頭アドレス, r2=0
より,ここでは配列DAT_00021040
の最初の要素(0x0E)になる.
add r3,r3,r2
ldrb r3,[r3,#0x0]
r3=関数FUN_000105acのアドレス, r2=0x0E
より,関数がマップされている先頭アドレス+0x0E
のアドレスから1バイトロードしている.(ここがDecompileの関数名[]の処理と推測)
FUN_000105ac付近のアセンブリコードは以下
[000105ac+0x0E=000105ba]
の値は0xa0(画像赤枠)
0xa0
とlocal_1c0[local_1c4]
のXORを計算すると(1ループ目と仮定してlocal_1c4=0
),0xa0 ^ 0xc2 = 0x62
asciiコードだとb
になり,byuctfの頭文字.
解答用ソースコード
ソースコード
#!/usr/bin/env python3
local_1c0 = [0xc2, 0x9c, 0x65, 0x83, 0x95, 0x66, 0xfa, 0x15, 0x5e, 0x58, 0x2f, 0x23, 0xac, 0x4f, 0xa1, 0x4c, 0x7d, 0x1e, 0x69, 0x80, 0x8c, 0x4a, 0x26, 0x5b, 0x5f, 0x91, 0x30, 0xcf, 0xc0, 0x4d, 0x97, 0x9b, 0xba, 0x20, 0x77, 0x4c, 0xf5, 0xef, 0x97, 0x96, 0x31, 0x30, 0x8c, 0xe2]
# 配列 local_1c0
DAT_00021040 = [0x0E, 0x03, 0x1C, 0x13, 0x17, 0x21, 0x12, 0x04, 0x27, 0x09, 0x0D, 0x22, 0x1E, 0x15, 0x0B, 0x24, 0x1D, 0x0A, 0x18, 0x2B, 0x19, 0x00, 0x1B, 0x2A, 0x08, 0x1F, 0x20, 0x25, 0x02, 0x1A, 0x0C, 0x29, 0x07, 0x05, 0x11, 0x28, 0x14, 0x16, 0x23, 0x0F, 0x01, 0x10, 0x2C, 0x06, 0x26]
# 配列 DAT_00021040(4バイト単位で1バイト分しか値が格納されていないため,非ゼロの値を抽出)
FUN_000105ac = [0x24, 0x00, 0x9f, 0xe5, 0x24, 0x10, 0x9f, 0xe5, 0x00, 0x30, 0x41, 0xe0, 0xa3, 0x1f, 0xa0, 0xe1, 0x43, 0x11, 0x81, 0xe0, 0xc1, 0x10, 0xb0, 0xe1, 0x1e, 0xff, 0x2f, 0x01, 0x10, 0x30, 0x9f, 0xe5, 0x00, 0x00, 0x53, 0xe3, 0x1e, 0xff, 0x2f, 0x01, 0x13, 0xff, 0x2f, 0xe1, 0xf8, 0x10, 0x02, 0x00, 0xf8, 0x10, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00]
# 関数 FUN_000105acの先頭から格納されているマシン語16進表記を適当な分とってくる.
for i in range(44):
print(chr(FUN_000105ac[DAT_00021040[i]]^local_1c0[i]), end="")
print("")
flag
byuctf{1_h0p3_ARM_wasn't_t00_b4d_0f_4_tw1st}