想学习ASM破解想了N多时间,也尝试过好几次,但都是半途而废,但是这次决定要好好的学习一下,度过最艰难的开始期。
这次学习破解的例子是GBA的日版《鬼眼狂刀》,请参见《破解日记by落木孤叶 》(连接来自TGB论坛,上面有破解只读区,很不错,有很多的例子可以学习)。当然孤叶已经写的很明白了,大致的顺序是先找字库,然后找文本,确定文本和控制符的码表,导入等,我这里只是想通过ASM来找到字库,因为自己先前也想破解过一些NDS的rom但都是在找字库这关卡住。
具体内容请参见孤叶的文章,我这里只是想强调几点我自己的觉得重要的东西。
1.如何找到定义02018000H的程序段:
先让字库解压完毕,然后Ctrl+B下断r0 = 02018000,这时在出现了几次对话后,程序就停在了下面的地方:
往上看就有r2,=2018000h。这里的想法是如果程序中用到了字库的第一个字,那r0就肯定会等于02018000,而幸运的是在开始的几句话中确实有用到字库的第一个字(na,发音是这样的吧~)。但是如果对话没用到第一个字,确定字库地址时(解压后)肯定需要一个基址(2018000h),所以这样想的话肯定能再程序中找到定义2018000h的程序段。
08018ABE 4008 and r0,r1 08018AC0 0180 lsl r0,r0,6h 08018AC2 4A0B ldr r2,=2018000h(**) 08018AC4 1883 add r3,r0,r2 08018AC6 2080 mov r0,80h 08018AC8 0100 lsl r0,r0,4h 08018ACA 4008 and r0,r1 08018ACC 0400 lsl r0,r0,10h 08018ACE 0D40 lsr r0,r0,15h 08018AD0 0400 lsl r0,r0,10h 08018AD2 0C00 lsr r0,r0,10h 08018AD4 1C02 mov r2,r0 08018AD6 3240 add r2,40h 08018AD8 0892 lsr r2,r2,2h 08018ADA 1C18 mov r0,r3 08018ADC>1C21 mov r1,r4 (*) 08018ADE F06EFB5D bl 808719Ch 08018AE2 BC10 pop r4 08018AE4 BC01 pop r0 08018AE6 4700 bx r0 08018AE8 E484 b 80183F4h
2.关于压缩,字库的确是被压缩了的,重新开始游戏,下断:r4 = 02018000,会停在下面的地方
也就是说r0存放被压缩的内容的起始地址,r4存放解压后的内容的起始地址
0801822E 0400 lsl r0,r0,10h 08018230 B510 push r4,lr 08018232 4806 ldr r0,=8704E2Ch(*) 08018234 4C06 ldr r4,=2018000h(*) 08018236>1C21 mov r1,r4(停的地方) 08018238 F7EAFE88 bl 8002F4Ch 0801823C 1C01 mov r1,r0 0801823E 4805 ldr r0,=8707200h 08018240 1909 add r1,r1,r4
用CT2打开rom,定位到地址704e2cH(这要去掉最前面的08是因为,0x08000000是运行时卡带被映射到的地址范围):
可以看到全面两个字节为“LE”,刚开始我还不知道这是什么压缩,然后想起来以前在ACG的破解招聘考题有一道就是LE的压缩解压缩。这里的解压缩内容如下(摘自ACG考题'LZE压缩',后来我也一句句看了ASM的代码,确实是这样的,看来以后碰到压缩或加密的东西,只好老老实实去看代码,代码是最好的文档,呵呵)
======== 文件结构 ======== 00000000h:文件头"Le"(2h) 00000002h:原文件大小(4h) 00000006h:压缩数据流 ======== 解压流程 ======== 与lz77类似,读取1字节,控制之后若干数据,再读取一个控制字节,如此下去直到写到原文件长度。 ============ 控制数据分析 ============ 从低位开始一次读取两位,即一个控制字节对应4段数据输出。 例如: 00 01 11 10 其中, 10:原样输出1字节; 11:原样输出3字节; 01:读取1字节,按“XX XX XX|YY”分开,指针回溯YY+1字节,读写XX+2字节; 00:读取2字节,第一个字节“XXXX XXXX”,第二个按“YYYY|ZZZZ”分开,指针回溯XX+5,读写YY+3,ZZ无用。
这里解压程序是比较好写的,关键是压缩程序。以前学习lz77和lzss时也写过解压缩程序。总体来说解压缩程序是比较好写的,关键还是压缩程序。我还没写过,想花点时间写一下。
在网上搜了一下,ms是lzss一个很经典的实现是一个日本professor写的一个c语言的版本,看了半个小时,还是不怎么通,看来只好调式看看了,由此也发现自己阅读代码的能力还是很弱啊~
11.18:
在网友realjoeeye的帮助下总算是看懂了代码,而且realjoeeye在他的空间里也写了一篇日志关于这个代码(http://58439527.qzone.qq.com ),在这里感谢realjoeeye。同时我也写了LZE的解压缩程序,发现自己N久没写c程序了,小错误还是不少。另外在写程序的时候发现,《鬼眼狂刀》中的LZE和ACG考题中的还是有所不同,上次也没仔细的看ASM代码,后来发现当控制位是'00'时,程序读两个字节b1和b2,然后令t=b2<<8 | b1, 这时back=t&0xFFF,cnt=(t>>12)&0XFF。由这里可以发现,就算是同一个压缩,变种还是有可能出现的。但是奇怪的是我的程序只能正确解压0x3E88个字节,后面的又不一样了,又看了一遍ASM代码,也没发现有2中解压代码,很奇怪,先不考虑这个了。
核心代码如下:
for(i=0; i<4; i++) { uint temp, temp2, back, cnt; //t is control 2 bits t = (byte)((ctl>>(i*2)) & 0x3); switch (t) { //'10' case 2: outBuffer[output++] = inBuffer[input++]; break; //'11' case 3: for(j=0; j<3 && output<outFileSize; ++j) outBuffer[output++] = inBuffer[input++]; break; //'01' case 1: //XXXXXX|YY //cnt = XX + 2 //back = YY + 1 temp = inBuffer[input++]; cnt = (byte)((temp>>2) & 0x3f) + 2; back = (byte)(temp & 0x3) + 1; temp2 = output - back; for(j=0; j<cnt && output<outFileSize; ++j) { outBuffer[output++] = outBuffer[temp2++]; } break; //'00' case 0: //back = XXXXXXXX + 5 //YYYY|ZZZZ, cnt = YY+3 /* back = (uint)((byte)inBuffer[input++] + 5); cnt = (byte)((inBuffer[input++]>>4)&0xf) + 3; temp2 = output - back; */ //XXXXXXXX(b2)|XXXX(b1)YYYY(b1) //back = XXXXXXXX(b2)|XXXX(b1) + 5 //cnt = YYYY(b1) + 3 back = (((uint)(inBuffer[input+1]<<8)|inBuffer[input])&0xfff) + 5; cnt = (byte)((inBuffer[input+1]>>4)&0xf) + 3; temp2 = output - back; input += 2; for(j=0; j<cnt && output<outFileSize; ++j) outBuffer[output++] = outBuffer[temp2++]; break; }
下面就是考虑压缩的方法了,待续...