cuscuta的草稿 - 2
啊嘞……
我觉得可以……的吧……?
(我其实不知道我为什么要写这些……?)
正片……?
挣脱
这是一个平平无奇的参数 (但是没了它登录不了) ,如下所示:
1 | X-Random-Challenge: 99fkG54BAAC/1sAr/ZD2GhaqPorsBaGyRsgNpeeU1sK72U2FSgB+9u99Cf43ohO+bhKaNgpqk6E= |
解码之后,我们得到了以下内容:
1 | F7D7E41B9E010000BFD6C02BFD90F61A16AA3E8AEC05A1B246C80DA5E794D6C2BBD94D854A007EF6EF7D09FE37A213BE6E129A360A6A93A1 |
这是一个固定56字节的内容,注意到前8字节,是一个UNIX时间戳,而后面还有48字节,我们需要继续揭露……
遮蔽
我尝试了一些可能性,例如这是不是什么SHA256?或者Blowfish?又或者是TEA之类的……后来均以失败告终……没办法,大抵只能硬着头皮逆
在简单的静态分析之后,我找到了这个函数的地址……这是一个非常大的函数,足足有0x161AC长,内部包含了严重的函数内联和循环展平,导致难以分析,所以,我开始用frida试图dump出一些有用的东西
我使用了一个惯用的方法,也就是hook void memcpy(void *dest, const void *src, size_t n),然后dump出src的值,再加上一点调用栈,以此试图找到这段内容发生的地方……
消散
在dump中,我发现了一些有意思的内容,例如:
1 | 0 1 2 3 4 5 6 7 8 9 A B C D E F 0123456789ABCDEF |
前8字节很明显就是时间戳,后面跟上了一个Path,和一个32字节的何意味数据
找到base64发生的地方也比我想象中简单 (不是哥们你怎么把base64生成也内联进去了……) ,第一段内容出现在target.so!0x86dff4(说的更确切一点,是在target.so!fun+0x157f8附近),这样的话,我就可以稍微减少一下工作量……
另外这个函数是真的不当人,它内部采用了比较狗屎的结构,数据流有点难分析(用那啥一点的话来说,就是有一堆不透明谓词),我用stalker打印出了函数运行的路径,并丢到IDA里上色,过滤掉了一堆碍眼的大坨玩意
遏止
经过许多天的打桩 摸鱼 ,我大概是定位到了两段关键 (又长得几乎一模一样) 的汇编 (你说这不是编译器inline我都不信(x)) (以下汇编从IDA导出),已经做好了部分注释:
1 | loc_86C068 |
下面是冗长而无序的汇编,总的来看,它把栈上内存的值读到寄存器之后几乎只做几个操作:MOV,AND,EOR,EXTR(用来循环移位的),然后写到特定的栈空间里
这段汇编的特别之处在于,在loc_86C0D4代表的这一大坨逻辑里,它完全没有对堆空间进行任何操作,所有的操作完完全全都在栈上进行,于是我进而查看了它对栈空间的访问情况……
首先我dump出了它的栈空间(顺便一提这玩意的栈空间有0x5C0长我的天哪),由于函数和栈空间太大了,我又写了一段简单的Kt来稍微过滤一下它到底访问了哪些栈空间……(不要问咱为什么不用Py,因为咱不会哈哈)
1 | import java.io.File |
注意
这么做显然不太合适,因为它直接忽略了寄存器的宽度,所以这段小脚本取到的只是偏移地址,而非每一个被读写的字节;不过在这里,对于大多数操作的寄存器都是W寄存器来看,它已经够用了
最后出来的结果如下:
1 | Ld: [70, 7c, 80, 84, 88, 8c, 90, 94, 98, 9c, a0, b0, c0, d0, d8, e0, e8, f0, f8, 108, 110, 118, 124, 128, 12c, 130, 13c, 140, 144, 148, 14c, 150, 154, 158, 15c, 160, 16c, 478, 47c, 480, 484, 488, 48c, 490, 494, 498, 49c, 4a0, 4a4, 4a8, 4ac, 4b0, 4b4, 4b8, 4bc, 4c0, 4c4, 4c8, 4cc, 4d0, 4d4, 4d8, 4dc, 4e0, 4e4, 4e8, 4ec, 4f0, 4f4, 4f8, 4fc, 500, 504, 508, 50c, 510, 514] |
我们可以发现,除开一堆自己先写再读的临时变量以外,其余大部分都是十分连续的读取,通过读取这些地址的值,结合原有的汇编,我们有以下发现:
- 在LD中,有两个不连续的偏移,
90这个值目前看来永远是0x00000000;而16c这个值只关系到一个根本不会复制内容的memcpy,所以理论上也不重要,这还挺奇怪的,不过更有可能是我没有测试一些特殊情况 - 这些地址可以分为四部分,“只读不写”、“只写不读”、“读了又改”,“写了再读”这四种,其中“写了再读”的应该是临时变量(寄存器不够用了应该是),直接忽略;“只读不写”(
498-514)的我觉得是纯输入;“只写不读”(398-3af)的是纯输出;“读了又改”(478-494)的比较棘手,但是根据静态分析,这一段32字节的内容其实是一个存在so文件中的常量,地址是target.so!0x4BE004,具体值是88 6A 3F 24 D3 08 A3 85 2E 8A 19 13 44 73 70 03 22 38 09 A4 D0 31 9F 29 98 FA 2E 08 89 6C 4E EC,这段常量的意义不明 - 在
loc_86C068这一段内,它向代表连续输入的栈空间的末尾写入了一个常量:29 30 58 02 00 00 00 00 00 00,这个常量的意义不明 - 从
loc_86C0D4这一段内容其实没有依赖任何通用寄存器的初始值,在这一段内容内使用的所有的通用寄存器在使用前都被覆盖上了初值,而这些初值要么是立即数,要么是从栈空间读出来的,所以,我们可以有一个很大胆的想法
……我们就完全可以在unicorn里模拟这段逻辑而无需理解它:
1 | use std::{fs, iter}; |
通过将预定值入栈的特定部位,以上模拟成功地还原产出了那开头48字节的后半段24字节的内容,至于前半段,我需要一些时间来进一步复现
悲怆
那么,代价是什么呢?
很明显,虽然我们复现出了算法,但是有很多细节,我们并没有理解,例如神秘的29 30 58 02 00 00 00 00 00 00和88 6A 3F 24 D3 08 A3 85 2E 8A 19 13 44 73 70 03 22 38 09 A4 D0 31 9F 29 98 FA 2E 08 89 6C 4E EC,那个块内的,似乎取自圆周率的立即数,还有将一些数据向高位-0x80栈空间复制输入的操作,一切都太蹊跷了
并且,我初步推断,这是一个白盒加密……虽然效果可能并不强
下一步,我会分析一下先前版本的算法——有可能很类似,只是改了一个常数,也有可能是改了一堆常数……
我也可能或许会先将这个程序测试一下——当然,很有可能会在下一个版本爆炸掉
碎碎念……
上面的文字看起来很短,流程应该还算清晰,但是其实我在插桩&缩小分析范围&反复调脚本&在很奇怪的网络环境下连接frida(?)&很多杂七杂八的事情上还是花掉了很多时间……毕竟不是专业干这个的……呜喵……
勤工俭学的工资还没发……
体育课的定向跑好好玩但是好累……
上次种的玉米和花生不知道长得怎么样了……
傻逼GIANT一个自行车水壶支架居然要75块钱……
鱼鱼,Temmie,easy,废,滢,嘿嘿……
明天早上还要上课……
不过事到如今,也只能睡觉了ww