最初に
picoDTF2024のReverse Engineeringをやっていきます!!
Writeup
Windowsのデバックの以下3問は初めて取り組む内容だったことと、Writeupを読んでも理解できなかったので、ちょっとまた今度取り組もう。。。
- WinAntiDbg0x100
- WinAntiDbg0x200
- WinAntiDbg0x300
weirdSnake
Pythonバイトコードで記載されたファイルが与えられます
Copilotで元のPythonコードすると以下になりますが(コメント付き)、その出力がNiF3jF^wJ_V▒]ok:2M1K;▒Mne7"a9otZeFj:
で文字化けしている。。。。
# 定数をロードしてリストを構築
input_list = [4, 54, 41, 0, 112, 32, 25, 49, 33, 3, 0, 0, 57, 32, 108, 23, 48, 4, 9, 70, 7, 110, 36, 8, 108, 7, 49, 10, 4, 86, 43, 102, 126, 92, 0, 16, 58, 41, 89, 78]
# キー文字列を構築
key_str = "J"
key_str += "_"
key_str += "o"
key_str += "3"
key_str += "t"
# キー文字列を文字コードのリストに変換
key_list = [ord(char) for char in key_str]
# キーリストの長さが入力リストの長さ以上になるまでキーリストを拡張
while len(key_list) < len(input_list):
key_list.extend(key_list)
# 入力リストとキーリストをXORで結合し結果を得る
result = [a ^ b for a, b in zip(input_list, key_list)]
# 結果を文字列に変換
result_text = ''.join(map(chr, result))
ここから分からなくなったので、以下のWriteupを見てみると、key_strの連結方法が違っていた。。。。
確かに、よくよくsnake
を見ると、5文字がすべて同じではないですね、、、
2 84 LOAD_CONST 30 ('J')
86 STORE_NAME 1 (key_str)
3 88 LOAD_CONST 31 ('_')
90 LOAD_NAME 1 (key_str)
92 BINARY_ADD
94 STORE_NAME 1 (key_str)
4 96 LOAD_NAME 1 (key_str)
98 LOAD_CONST 32 ('o')
100 BINARY_ADD
102 STORE_NAME 1 (key_str)
5 104 LOAD_NAME 1 (key_str)
106 LOAD_CONST 33 ('3')
108 BINARY_ADD
110 STORE_NAME 1 (key_str)
6 112 LOAD_CONST 34 ('t')
114 LOAD_NAME 1 (key_str)
116 BINARY_ADD
118 STORE_NAME 1 (key_str)
これに注意してプログラムを書き換えたら、うまくいきました!
packer
outという実行ファイルが与えられる
実行してみるとパスワードを求められるので、逆コンパイルした実行ファイルからパスワードを見つける問題と思われる
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ ./out
Enter the password to unlock this file: aaa
You entered: aaa
Access denied
Ghidraを使ったりしていたのですが、手がかりがなく、あきらめて以下のWriteupをみたところ、upxでアンパックするみたいでした、、、そうかタイトルがpackerだからか、、、
実際にやってみると、見つかりました!
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ upx -d out
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
[WARNING] bad b_info at 0x4b718
[WARNING] ... recovery at 0x4b714
877724 <- 336520 38.34% linux/amd64 out
Unpacked 1 file.
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ strings out | grep pico
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/packer]
└─$ strings out | grep flag
Password correct, please see flag: 7069636f4354467b5539585f556e5034636b314e365f42316e34526933535f62646438343839337d
(mode_flags & PRINTF_FORTIFY) != 0
WARNING: Unsupported flag value(s) of 0x%x in DT_FLAGS_1.
version == NULL || !(flags & DL_LOOKUP_RETURN_NEWEST)
flag.c
_dl_x86_hwcap_flags
_dl_stack_flags
FactCheck
binという実行ファイルが一つ与えられます
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ file bin
bin: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=f837edf11d5c6ec47ce468ef605df9f1015af9f2, for GNU/Linux 3.2.0, not stripped
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ strings bin | grep flag
packerの問題の反省からupxでパッキングされているかも確認します
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ upx -d bin bin_unpack
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2024
UPX 4.2.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 3rd 2024
File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: bin: NotPackedException: not packed by UPX
upx: bin_unpack: FileNotFoundException: bin_unpack: No such file or directory
Unpacked 0 files.
実行してみても何も起こらなかったです。。。
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$ ./bin
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/FactCheck]
└─$
Ghidraを使ってみると、picoCTF{wELF_d0N3_mate_
というフラグの前半らしき文字列が。。。!
std::string::string(local_248,"picoCTF{wELF_d0N3_mate_",&local_249);
この文字列が使われている関数で、文字列が定義された後に、複数の文字列が定義されていて、フラグの前半の文字列に文字を追加していくのですが、4つはIf文で条件に合えば追加するようになっています。
条件に合致しているかどうかの判定はそこまで難しくなく、前半2つがTrueで、後半2つがFalseです。
後は文字列をつなげればフラグが手に入ります!
picoCTF{wELF_d0N3_mate_********}
Classic Crackme 0x100
crackme100 という実行ファイルが与えられました
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ file crackme100
crackme100: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1ea93b145c2407916c88a68aa90f0a373edeb42a, for GNU/Linux 3.2.0, with debug_info, not stripped
Here is your flag: %s
からフラグは動的に作成しているみたい
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ strings crackme100| grep flag
picoCTF{sample_flag}
SUCCESS! Here is your flag: %s
_flags
_flags2
試しに実行してみると、パスワードが求められますね
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ ./crackme100
Enter the secret password: aa
FAILED!
Ghidraを使うとmain関数にパスワードの判定ロジックがありました!
入力したinputがいろいろ変換されて、outputと等しくなればいいみたいです。
とりあえずoutputの文字列をそのまま入れてみましたが、ダメでした(念のための確認!)
┌──(kali㉿kali)-[~/…/PicoCTF/picoCTF_2024/ReverseEngineering/ClassicCrackme0x100]
└─$ ./crackme100
Enter the secret password: xjagpediegzqlnaudqfwyncpvkqneusycourkguerjpzcbstcc
FAILED!
判定ロジックがわかったから、あとはもとに戻すだけだ!と思ったのですが、これが難しい。。。。
Writeupをみてましたが、正直ビット計算が入るとよくわからなくなってしまう、、、
これは今後の課題としておいておきます。。。
最後に
今回学んだことは以下
- 生成AIに書かせたプログラムは結構間違っている・・・
- IDAは習得しないといけない。。。
備忘録
個人的に試した結果を記載しておきます。
問題を解くうえではあまり関係ないです。。。
WinAntiDbg0x300の実行ファイルをGhidraで逆コンパイルした結果
/* WARNING: Control flow encountered bad instruction data */
void entry(void)
{
char cVar1;
undefined uVar2;
char cVar3;
byte bVar4;
undefined4 uVar5;
uint uVar6;
HMODULE hModule;
FARPROC pFVar7;
int iVar8;
int *piVar9;
undefined4 *puVar10;
uint uVar11;
uint uVar12;
uint uVar13;
uint *puVar14;
UINT unaff_EDI;
undefined4 *puVar15;
char *pcVar16;
int *lpProcName;
int *piVar17;
int *piVar18;
byte *pbVar19;
byte *pbVar20;
bool bVar21;
bool bVar22;
bool bVar23;
undefined local_80 [72];
undefined4 uStackY_38;
puVar14 = &DAT_00416000;
puVar15 = (undefined4 *)&DAT_00401000;
uVar13 = 0xffffffff;
do {
uVar11 = *puVar14;
bVar21 = puVar14 < (uint *)0xfffffffc;
puVar14 = puVar14 + 1;
bVar22 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar21);
uVar11 = uVar11 * 2 + (uint)bVar21;
do {
if (bVar22) {
uVar2 = *(undefined *)puVar14;
puVar14 = (uint *)((int)puVar14 + 1);
*(undefined *)puVar15 = uVar2;
puVar15 = (undefined4 *)((int)puVar15 + 1);
}
else {
uVar6 = 1;
do {
do {
bVar21 = CARRY4(uVar11,uVar11);
uVar12 = uVar11 * 2;
if (uVar12 == 0) {
uVar11 = *puVar14;
bVar22 = puVar14 < (uint *)0xfffffffc;
puVar14 = puVar14 + 1;
bVar21 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar22);
uVar12 = uVar11 * 2 + (uint)bVar22;
}
uVar6 = uVar6 * 2 + (uint)bVar21;
uVar11 = uVar12 * 2;
} while (!CARRY4(uVar12,uVar12));
if (uVar11 != 0) break;
uVar12 = *puVar14;
bVar21 = puVar14 < (uint *)0xfffffffc;
puVar14 = puVar14 + 1;
uVar11 = uVar12 * 2 + (uint)bVar21;
} while (!CARRY4(uVar12,uVar12) && !CARRY4(uVar12 * 2,(uint)bVar21));
if (2 < uVar6) {
uVar2 = *(undefined *)puVar14;
puVar14 = (uint *)((int)puVar14 + 1);
uVar13 = CONCAT31((int3)uVar6 + -3,uVar2) ^ 0xffffffff;
if (uVar13 == 0) {
pcVar16 = &DAT_00401000;
iVar8 = 0x193;
do {
cVar3 = *pcVar16;
pcVar16 = pcVar16 + 1;
while (((byte)(cVar3 + 0x18U) < 2 && (*pcVar16 == '\0'))) {
uVar5 = *(undefined4 *)pcVar16;
cVar3 = pcVar16[4];
*(undefined1 **)pcVar16 =
&DAT_00401000 +
(CONCAT31(CONCAT21((ushort)uVar5 >> 8,(char)((uint)uVar5 >> 0x10)),
(char)((uint)uVar5 >> 0x18)) - (int)pcVar16);
pcVar16 = pcVar16 + 5;
iVar8 = iVar8 + -1;
if (iVar8 == 0) {
lpProcName = &DAT_00418000;
do {
if (*lpProcName == 0) {
puVar15 = (undefined4 *)0x400ffc;
pbVar19 = (byte *)(lpProcName + 1);
while( true ) {
bVar4 = *pbVar19;
uVar13 = (uint)bVar4;
pbVar20 = pbVar19 + 1;
if (uVar13 == 0) break;
if (0xef < bVar4) {
uVar13 = CONCAT12(bVar4,*(undefined2 *)pbVar20) & 0xff0fffff;
pbVar20 = pbVar19 + 3;
}
puVar15 = (undefined4 *)((int)puVar15 + uVar13);
uVar5 = *puVar15;
*puVar15 = &DAT_00401000 +
CONCAT31(CONCAT21(CONCAT11((char)uVar5,(char)((uint)uVar5 >> 8)),
(char)((uint)uVar5 >> 0x10)),
(char)((uint)uVar5 >> 0x18));
pbVar19 = pbVar20;
}
uStackY_38 = 0x419548;
VirtualProtect(&IMAGE_DOS_HEADER_00400000,0x1000,4,(PDWORD)&stack0xffffffdc);
/* WARNING: Read-only address (ram,0x00400217) is written */
IMAGE_SECTION_HEADER_004001f0.Characteristics._3_1_ = 0x60;
/* WARNING: Read-only address (ram,0x0040023f) is written */
IMAGE_SECTION_HEADER_00400218.Characteristics._3_1_ = 0x60;
uStackY_38 = 0x41955d;
VirtualProtect(&IMAGE_DOS_HEADER_00400000,0x1000,uVar13,
(PDWORD)&stack0xffffffdc);
do {
} while (&stack0x00000000 != local_80);
/* WARNING: Bad instruction - Truncating control flow here */
halt_baddata();
}
puVar15 = (undefined4 *)(&DAT_00401000 + lpProcName[1]);
piVar18 = lpProcName + 2;
hModule = LoadLibraryA((LPCSTR)((int)&DWORD_0041c9b0 + *lpProcName));
while( true ) {
cVar3 = *(char *)piVar18;
lpProcName = (int *)((int)piVar18 + 1);
if (cVar3 == '\0') break;
piVar9 = lpProcName;
piVar17 = lpProcName;
do {
piVar18 = piVar17;
if (piVar9 == (int *)0x0) break;
piVar9 = (int *)((int)piVar9 + -1);
piVar18 = (int *)((int)piVar17 + 1);
cVar1 = *(char *)piVar17;
piVar17 = piVar18;
} while ((char)(cVar3 + -1) != cVar1);
pFVar7 = GetProcAddress(hModule,(LPCSTR)lpProcName);
if (pFVar7 == (FARPROC)0x0) {
/* WARNING: Subroutine does not return */
ExitProcess(unaff_EDI);
}
*puVar15 = pFVar7;
puVar15 = puVar15 + 1;
}
} while( true );
}
}
} while( true );
}
}
bVar21 = CARRY4(uVar11,uVar11);
uVar11 = uVar11 * 2;
if (uVar11 == 0) {
uVar11 = *puVar14;
bVar22 = puVar14 < (uint *)0xfffffffc;
puVar14 = puVar14 + 1;
bVar21 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar22);
uVar11 = uVar11 * 2 + (uint)bVar22;
}
bVar22 = CARRY4(uVar11,uVar11);
uVar11 = uVar11 * 2;
if (uVar11 == 0) {
uVar11 = *puVar14;
bVar23 = puVar14 < (uint *)0xfffffffc;
puVar14 = puVar14 + 1;
bVar22 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar23);
uVar11 = uVar11 * 2 + (uint)bVar23;
}
iVar8 = (uint)bVar21 * 2 + (uint)bVar22;
if (iVar8 == 0) {
iVar8 = 1;
do {
do {
bVar21 = CARRY4(uVar11,uVar11);
uVar6 = uVar11 * 2;
if (uVar6 == 0) {
uVar11 = *puVar14;
bVar22 = puVar14 < (uint *)0xfffffffc;
puVar14 = puVar14 + 1;
bVar21 = CARRY4(uVar11,uVar11) || CARRY4(uVar11 * 2,(uint)bVar22);
uVar6 = uVar11 * 2 + (uint)bVar22;
}
iVar8 = iVar8 * 2 + (uint)bVar21;
uVar11 = uVar6 * 2;
} while (!CARRY4(uVar6,uVar6));
if (uVar11 != 0) break;
uVar6 = *puVar14;
bVar21 = puVar14 < (uint *)0xfffffffc;
puVar14 = puVar14 + 1;
uVar11 = uVar6 * 2 + (uint)bVar21;
} while (!CARRY4(uVar6,uVar6) && !CARRY4(uVar6 * 2,(uint)bVar21));
iVar8 = iVar8 + 2;
}
uVar6 = iVar8 + 1 + (uint)(uVar13 < 0xfffff300);
puVar10 = (undefined4 *)((int)puVar15 + uVar13);
if (uVar13 < 0xfffffffd) {
do {
uVar5 = *puVar10;
puVar10 = puVar10 + 1;
*puVar15 = uVar5;
puVar15 = puVar15 + 1;
bVar21 = 3 < uVar6;
uVar6 = uVar6 - 4;
} while (bVar21 && uVar6 != 0);
puVar15 = (undefined4 *)((int)puVar15 + uVar6);
}
else {
do {
uVar2 = *(undefined *)puVar10;
puVar10 = (undefined4 *)((int)puVar10 + 1);
*(undefined *)puVar15 = uVar2;
puVar15 = (undefined4 *)((int)puVar15 + 1);
uVar6 = uVar6 - 1;
} while (uVar6 != 0);
}
}
bVar22 = CARRY4(uVar11,uVar11);
uVar11 = uVar11 * 2;
} while (uVar11 != 0);
} while( true );
}
Classic Crackme 0x100
Ghidraで出力されたパスワード判定ロジックを記載しておきます
/* WARNING: Unknown calling convention */
int main(void)
{
uint uVar1;
int iVar2;
size_t sVar3;
char input [51];
char output [51];
int random2;
int random1;
char fix;
int secret3;
int secret2;
int secret1;
int len;
int i_1;
int i;
builtin_strncpy(output,"xjagpediegzqlnaudqfwyncpvkqneusycourkguerjpzcbstcc",0x33);
setvbuf(stdout,(char *)0x0,2,0);
printf("Enter the secret password: ");
__isoc99_scanf(&DAT_00402024,input);
i = 0;
sVar3 = strlen(output);
for (; i < 3; i = i + 1) {
for (i_1 = 0; i_1 < (int)sVar3; i_1 = i_1 + 1) {
uVar1 = (i_1 % 0xff >> 1 & 0x55U) + (i_1 % 0xff & 0x55U);
uVar1 = ((int)uVar1 >> 2 & 0x33U) + (uVar1 & 0x33);
iVar2 = ((int)uVar1 >> 4) + input[i_1] + -0x61 + (uVar1 & 0xf);
input[i_1] = (char)iVar2 + (char)(iVar2 / 0x1a) * -0x1a + 'a';
}
}
iVar2 = memcmp(input,output,(long)(int)sVar3);
if (iVar2 == 0) {
printf("SUCCESS! Here is your flag: %s\n","picoCTF{sample_flag}");
}
else {
puts("FAILED!");
}
return 0;
}