考虑到是树莓派驱动程序方向的逆向,我会简单的介绍一下如何分析此类程序的方式以及此题的细节过程。
这道题的解题思路非常清楚,逆向树莓派驱动程序获得其中写入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
sub_100003A4
即是我们需要重点分析的函数。
可以看到其中包含的字符串"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存在于最后一帧中。
以上,简单介绍了相关部分的分析方式以及处理办法。流程绝对算不上简洁,但也不失为一种办法。
但我们真的需要大费周章去分析这道题吗?
答案是否定的。只要你手上有一块Raspberry Pi Pico和st7789 lcd,根据题目提供的电路图连接后,通过sdk中的elf2uf2
将elf转换为uf2,使用开发板加载运行后放映,那么分析以上内容又有什么必要呢?
逃(
笔者注:这次尝试换了一种风格写文章,手笔比较稚嫩,望看到这篇文的各位海涵。
发表回复