whyCTF - gameboy
0x01 运行
使用file查看文件类型,显示是Game Boy ROM文件
1
2
(venv13) woc@myarch:why25/gamerboy ‹master*›$ file chall.gb
chall.gb: Game Boy ROM image (Rev.01) [ROM ONLY], ROM: 256Kbit
尝试下载一些模拟器执行(最好能带有调试功能),最后我选择了GearBoy
然而在安装的时候,make阶段报错:
1
2
3
4
/usr/bin/ld: /tmp/ccinz5GX.ltrans15.ltrans.o: in function `SoundQueue::FillBufferCallback(void*, unsigned char*, int)':
<artificial>:(.text+0x1189): undefined reference to `SDL_SemPost'
collect2: error: ld returned 1 exit status
make: *** [../desktop-shared/Makefile.common:76: gearboy] Error 1
即使用pacman安装了sdl包还是不行.
打开Makefile:
1
2
3
4
5
6
7
include ../desktop-shared/Makefile.sources
SOURCES_CXX += $(DESKTOP_SRC_DIR)/nfd/nfd_gtk.cpp
CPPFLAGS += `pkg-config --cflags gtk+-3.0`
LDFLAGS += `pkg-config --libs gtk+-3.0`
include ../desktop-shared/Makefile.common
加上LDFLAGS += -lSDL2这一行,然后make就行了(自行修复link问题)
0x02 分析调试
现在需要更加细致的分析。尝试寻找能够分析GameBoy格式的插件。 刚开始找到了一个ghidraGBA插件,然而无法正确识别;仔细检查后发现 chall.gb 是GB ROM 和GBA是两种不同的格式。后来改成ghidraBoy成功识别
搜索字符串引用找到:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void FUN_036b(void)
{
char cStack_10;
undefined2 uStack_f;
undefined1 auStack_d [8];
char local_5;
byte local_4;
undefined1 local_1;
local_1 = 0;
uStack_f = 0xb;
FUN_04fa(&UNK_023c,auStack_d);
FUN_0200(&uStack_f);
FUN_05e1("Press START");
FUN_05a6(0x80);
FUN_05e1("NOW THE CHEAT PATTERN!!\n");
do {
local_5 = FUN_057e();
if (local_5 != '\0') {
FUN_0247();
}
if (local_5 != '\0') {
if (local_5 == (&cStack_10)[local_4]) {
local_4 = local_4 + 1;
if (local_4 == 0xb) {
FUN_02c9(&cStack_10);
}
}
else {
local_4 = 0;
}
}
FUN_0606(0x50);
} while( true );
}
关注这里的循环,挨个进入,找到一个可疑的函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
void FUN_0247(char param_1)
{
if (param_1 == '\x01') {
FUN_05e1("RIGHT");
return;
}
if (param_1 == '\x02') {
FUN_05e1(&UNK_02a7);
return;
}
if (param_1 == '\x04') {
FUN_05e1(&UNK_029f);
return;
}
if (param_1 == '\b') {
FUN_05e1(&UNK_02a2);
return;
}
if (param_1 == '\x10') {
FUN_05e1(&UNK_02b2);
return;
}
if (param_1 == ' ') {
FUN_05e1(&UNK_02b4);
return;
}
if (param_1 == '@') {
FUN_05e1("SELECT");
return;
}
if ((char)(param_1 + -0x80) != '\0') {
FUN_05e1(param_1 + -0x80,"MULTI");
return;
}
FUN_05e1(0,"START");
return;
}
可以猜出这里是对输入的按键进行映射,然后返回相应的结果. 再回头看上面的函数,有明显的比较计数逻辑。
1
2
3
4
5
6
7
8
9
10
11
if (local_5 != '\0') {
if (local_5 == (&cStack_10)[local_4]) {
local_4 = local_4 + 1;
if (local_4 == 0xb) {
FUN_02c9(&cStack_10);
}
}
else {
local_4 = 0;
}
}
因为比较的对象是栈上数值,但是目前并没有找到明显的赋值位置,尝试用动态调试 为了知道要在哪里下断点,对比汇编代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
03ba f8 0c LD HL,SP+0xc
03bc 6e LD L,(HL=>local_4)
03bd 26 00 LD H,0x0
03bf 19 ADD HL,DE
03c0 4d LD C,L
03c1 44 LD B,H
03c2 0a LD A,(BC)
03c3 4f LD C,A
03c4 f8 0b LD HL,SP+0xb
03c6 7e LD A,(HL=>local_5)
03c7 91 SUB C
03c8 20 15 JR NZ,LAB_03df
03ca f8 0c LD HL,SP+0xc
03cc 34 INC (HL=>local_4)
03cd 7e LD A,(HL=>local_4)
03ce d6 0b SUB 0xb
03d0 20 11 JR NZ,LAB_03e3
其中寄存器的名称非常有特色:有寄存器B, C, L, H,但是也有 BC, HL的用法,猜测是类似于现代寄存器不同位宽命名方式 最后的比较是通过 SUB + JR的方法,所以可以在这附近下断点,然后查看 C 寄存器的值.
dump相关的寄存器数值,最后得到一串特定顺序的按键序列,输入获得flag
This post is licensed under CC BY 4.0 by the author.

