Post

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.