Mas0n
to be reverse engineer🐧
翻车鱼

DSCTF2022 Final - bad_apple

考虑到是树莓派驱动程序方向的逆向,我会简单的介绍一下如何分析此类程序的方式以及此题的细节过程。

这道题的解题思路非常清楚,逆向树莓派驱动程序获得其中写入st7789这块lcd的内容。

我们拿到手的是一个elf后缀文件,并且去除了符号。没有符号是一件很头疼的事情,好比盲人抓瞎。尝试使用bindiff恢复符号。

第一步,动手编译一个类似项目。

在此之前,也许我们会有疑问,如何编译?这究竟是什么?

有了这些问题,我们就需要着手去解决这些问题。

根据二进制文件中存在的少量字符串符号,我们能够得到一条关键线索: Raspberry Pi Pico

这很重要,因为接下来的内容就是围绕其展开。

当然,这里有一个彩蛋,或是作者疏忽,其符号中存在一个网址,链接到了某位师傅的blog(

回到正题,我们了解到这是一个使用Raspberry Pi Pico这一开发板作为运行环境。

官方文档:Raspberry Pi Documentation – Raspberry Pi Pico and Pico W

从官方文档中,了解到其软件开发有两种,一种为C/C++另一种则为MicroPython,显而易见我们需要查看的是C/C++。

https://datasheets.raspberrypi.com/pico/getting-started-with-pico.pdf

这份getting-started文档,能够帮助我们快速构建出所需要的文件。

SDK的具体部署这里不再赘述,请参考上述文档中的Chapter 2。

待运行环境部署完成后,我们需要尽可能的接近题目文件编译所使用到的一些API。在这题中,即使用st7789显示内容。

官方恰巧给了相关的example:pico-examples/st7789_lcd.c

在文档中也提供了详细的使用方法,参见Chapter 3。但这里需要注意开启Debug。

cmake -DCMAKE_BUILD_TYPE=Debug

这在文档中,也有提及。

待编译完成后,我们能在pico-examples/build/pio/st7789_lcd目录下找到所需要的符号文件pio_st7789_lcd.elf

此时只需要使用bindiff就能还原出部分符号。但依旧无从下手。

下一步是什么?

自己动手编译的用途不仅是为了使用bindiff,我们还能够从中通过类比源码与IDA decompiler得到的pseudo code之间的差异,识别出更多的信息或特征,帮助我们函数定位,数据提取等。

回到此题,通过上述的方法,我们最终能够确定main入口在0x10000424

https://cdn.shi1011.cn/2022/08/d2409fbf9de262202c0cd3490d5586d2.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

sub_100003A4即是我们需要重点分析的函数。

https://cdn.shi1011.cn/2022/08/9404f1273412936f6fa62c8ce2b4d877.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

可以看到其中包含的字符串"frame: %d"等较为明显的特征。

从中可以提取出总帧数0x890,以及画面的大小:240×180。

而在sub_1000035C中,其实现了从数据段中数据的提取。

int __fastcall sub_1000035C(int *a1)
{
  int v1; // r1
  _BYTE *i; // r3
  int v3; // r4
  int v4; // r6
  _BYTE *addr; // r7

  while ( 1 )
  {
    v1 = a1[2];
    if ( v1 )
      break;
    v4 = *a1;
    addr = &byte_100068CC[*a1];
    v3 = 0;
    for ( i = addr; ; ++i )
    {
      v3 |= (*i & 0x7F) << v1;
      if ( (char)*i >= 0 )
        break;
      LOBYTE(v1) = v1 + 7;
    }
    a1[2] = v3;
    *a1 = v4 + i + 1 - addr;
    *((_WORD *)a1 + 2) = ~*((_WORD *)a1 + 2);
  }
  a1[2] = v1 - 1;
  return *((unsigned __int16 *)a1 + 2);
}

byte_100068CC即为我们需要提取的数据,但或许后续的处理令人困惑。

要知道其究竟在做些什么,就不得不提及LEB128编码了,为此我单独写了一篇文章来介绍LEB128。

是的,这一函数中,正是使用了ULEB128对数据解码。

但这还不够,我们需要关注的是返回值从何而来。

分析后,其返回值操作仅限于*((_WORD *)a1 + 2) = ~*((_WORD *)a1 + 2),对此而言,这步操作目的即是为了在黑白像素之间变换。

至此,重点部分分析完成。

下面便是实现其逻辑部分,得到我们的”影片“了。

from PIL import Image
from leb128 import u as uleb128
from io import BytesIO
from ddd import data

data = bytearray(data)

dw = 0
idx = 0
pixel = 0


def sub_1000035C():
    global dw, idx, pixel
    while 1:
        if dw:
            break
        value, offset = uleb128.decode_reader(BytesIO(data[idx:]))
        dw = value
        idx += offset

        pixel = ~pixel

    dw -= 1
    return pixel


image_size = (240, 180)
for c in range(0x890):
    img = Image.new("RGB", image_size)
    for i in range(180):
        for j in range(240):
            r = sub_1000035C()
            rgb = (0, 0, 0) if r == 0 else (0xff, 0xff, 0xff)
            img.putpixel((j, i), rgb)

    img.save(f'outs/{c}.jpg')

最后,flag存在于最后一帧中。

https://cdn.shi1011.cn/2022/08/c2ce33de0536ee1d52b4525bb6c14204.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

以上,简单介绍了相关部分的分析方式以及处理办法。流程绝对算不上简洁,但也不失为一种办法。

但我们真的需要大费周章去分析这道题吗?

答案是否定的。只要你手上有一块Raspberry Pi Pico和st7789 lcd,根据题目提供的电路图连接后,通过sdk中的elf2uf2将elf转换为uf2,使用开发板加载运行后放映,那么分析以上内容又有什么必要呢?

逃(

笔者注:这次尝试换了一种风格写文章,手笔比较稚嫩,望看到这篇文的各位海涵。

本文链接:https://blog.shi1011.cn/ctf/2333
本文采用 CC BY-NC-SA 4.0 Unported 协议进行许可

Mas0n

文章作者

发表回复

textsms
account_circle
email

翻车鱼

DSCTF2022 Final - bad_apple
考虑到是树莓派驱动程序方向的逆向,我会简单的介绍一下如何分析此类程序的方式以及此题的细节过程。 这道题的解题思路非常清楚,逆向树莓派驱动程序获得其中写入st7789这块lcd的内容…
扫描二维码继续阅读
2022-08-02