随便写点什么

GRIS 的文章呢?

别急,不会鸽的。


二月份都比较忙,所以除了试着玩了一下基础的光线追踪以及用它尝试了一把 OpenCL/SYCL 之外,没做什么自己的玩意儿。啊说到 OpenCL,用过之后觉得,它的限制和坑还是挺多的。也许有时间我写一篇文章讲讲这些;毕竟关于如何“正常地”使用,许多人都写过了。

那就讲讲最近的事情吧。(本来想显示全文的,结果写完发现太长了,就写了简单的关键词。)

反向工程:东京偶像计划、无线屏幕;狂欢节;杂事。


昨晚又小小地反向了一把,对象是京东娃娃项目东京偶像计划(プロジェクト東京ドールズ)。关于结果 esterTion 应该已经更新在他的文章里了。

其实只是蹲坑的时候刷 Google Play,给我推荐了一个这玩意儿。看着画风还行,打开看看。嘛游戏内容(小人打架加一些反应要素)我就不感兴趣了(而且世界观的建构更是让人一脸问号),但是一些图啊模型啊还是不错的。(“穿着偶像衣服却不干偶像的事。” by 群友)饰品分离目前我只见过 Square Enix(SGS、京东娃娃)和 Konami(心跳凉凉)做过,大概还有一些参考价值?心跳偶像本来我想着跟土豆一样反向的,但是安卓严重混淆,我又没有越狱的 iPhone,没办法;后来……它就停服了。

有点远了。这次反向其实还挺简单的,esterTion 已经找到了关键的类和方法,所以省了很多事,只要耐心读就行了。他大概只是心急失手了而已。如果他又想吹我,您别听他的;我只是个弱渣。

IL2CPP 的反向比一般的原生代码要简单的一点是,它保留了很多 IL 的特征,C++ 编译器无法完全将它们优化。比如在这次挨个设置密钥数组元素的时候,就出现了很明显的一种模式:

if (array.metadata.length < 0) throw_index_out_of_range_exception();
array.elements[0] = elem0;
if (array.metadata.length < 1) throw_index_out_of_range_exception();
array.elements[1] = elem1;
if (array.metadata.length < 2) throw_index_out_of_range_exception();
array.elements[2] = elem2;
if (array.metadata.length < 3) throw_index_out_of_range_exception();
array.elements[3] = elem3;

在反编译的代码中,上面都是用偏移表示的。虽然在没看 IL2CPP Array 的源代码的情况下,并不清楚这个结构每个偏移是什么(快去看,懒虫),但是根据这么几个连续的偏移规律,也能摸个大概。再想想对应的 C# 是怎么样的:

arr[0] = elem0;
arr[1] = elem1;
arr[2] = elem2;
arr[3] = elem3;

在实际使用中,这个索引测试一般不是手工做的。因此可以猜测是在 IL2CPP 变换的过程中自动插入的片段,以保持语义一致(ldelem.*)。

与此类似的还有 IL2CPP 类型初始化(不是 .cctor)、IL2CPP 实例初始化(不是 .ctor)、加载静态的引用类型字段等等,生成的代码一看就知道是干什么的。这些重复的模式比起“一般的” C++ 来说要仁慈多了。

当然 C++ 编译器也不是吃素的,明显能优化的地方一定会优化。比如简单的 getter/setter,尤其是简单类型的自动属性,会被直接内联。我就说怎么一些属性的 getter/setter 无法被 xref,结果一看只要是简单赋值,全都跑到实际使用位置去了。而且恶心的是,这通常指的是指针+偏移访问。这就防住了简单的使用检索(find usage),如果是关键属性的话会非常头疼。

IDA 的 F5 并不能完美地反编译,有时候需要进入函数再出来,让它更新函数签名,特别是变参函数和看上去参数奇怪的函数调用。再比如,在 MakeUrlCore() 的关键调用签名更新完毕后,会发现有返回值没被使用的情况。而这些函数并没有修改 call site 所在实例的内部状态,这时就根据下文推断返回值使用的地方,而且可以回到汇编窗口验证。对,我说的是获取 UTF-8 字节数组之后计算 MD5 hash 的那一块。

如果对 .NET 比较了解,少数虚函数表里的调用也可以根据上下文推断。string + Encoding.MethodX() -> A; HashAlgorithm.ComputeHash(A),那么这个 MethodX() 就极有可能是 GetBytes(),即使是虚函数,而且(在 F5 中)缺少实参来判断。再比如,StringBuilder.MethodY() -> B; string.Concat(B, ...),那么这个 MethodY() 就极有可能是 ToString(),即使因为虚函数表+静态分析的原因无法看到实际调用的是哪个函数。这种思路跟中学的化合物推断是很相似的。


说到反向,还有有趣的事情。上一周有一次组会的时候,我们预订的是学校最新的大楼的一个房间。那里面就有一个无线屏幕,可以把设备的屏幕无线地投射到显示器上。一个组员(本科生)说,他在另一门课(好像是 IoT Security 还是什么的)的小组就对这个屏幕使用的客户端下手了,写了一个非官方的客户端,能绕过一些限制来登录。

这肯定让我玩心大发啦。简单看了一下客户端,是一个无任何混淆的 WPF 应用程序。迅速扒光。然后跟着进入 P/Invoke 的原生库,稍微摸了一下结构。另一个是 C++/CLI 的 wrapper,依赖的是 C++ 的。在找不到公开文档和调试信息的情况下,后者基本是不可能静态分析的。

根据我原来看到的信息,我觉得这个系统的验证是存在漏洞的,理论上可以很容易地伪造用户登录和播放任意内容。不过刚才我看了更多的资料和代码之后否定了之前的想法。任意登录下降为可能的,在服务器配置存在特定配置缺陷的情况下;同时对登录有比之前所见的更大的限制。这就带来了攻击成本的上升和回报的下降,导致显得不划算。不过,反跟踪仍然是可以使用低成本的方式做到。

关于这个 WPF 程序的软件质量,我要称赞它,是软件工程的范本。遍地都是契约检查、防御性编程、打印日志(而且模糊了隐私信息),很专业嘛。

啊,本来还想着也许能做个白帽黑客的,不过现在看起来这个案例可能没什么能让我出手的地方了。

不过等到假期结束,我还是想亲手试试。

毕竟人家本科生都能做到的事情,我要是做不到,就太丢脸了。我虽然不知道他们做到了哪一步,但是必须要假设我想到的都已经完成了。


说到假期,这个星期是狂欢节假期。因为艾因霍温在南部嘛,而狂欢节是南部地区在庆祝,所以我们就有了这么一个假期咯。

上周六和昨天晚上去超市的时候,路上偶尔会看见一些穿着“奇装异服”的人。在上面的维基百科链接中可以看到大概的样子。你也可以在这里读到去年的样子。不过我没事又不经常出门,而且这还是在晚上,所以只能看见整个节日的一点点。狂欢游行什么的根本没有参与过,但是的确能在市中心看到不少散发着啤酒味的一次性杯子和几个临时的 live house。


前几天开始我的左耳突然出毛病了。最开始的时候是时不时堵着,就跟坐飞机一样;但无法通过活动咽喉来解决,只能手动按压通气。而后一天晚上玩了一下 The Witness,不过饱和度太高,不久就中等恶心(我玩大多数游戏都没有3D眩晕),受不了了。第二天起来还没发现什么异常,但是联机打了一局黑魂之后就发现对敌人的听觉定位不准了——正中的声源,会听起来偏右。再自言自语的时候就感觉颅内传导的声音,右耳出去了,但左耳好像碰到了什么,反射回来一部分,形成了混响。

虽说现在已经要找医生了,但是还得先处理别的麻烦事——验证邮件收不到,就无法完成电子身份认证,就无法预约。而现在正是狂欢节假期,不知道会不会影响。即使是能预约了,按照我的经验,也得等至少一个星期。这时候我就很怀念国内的挂号了。

所以……没办法咯,只好先习惯目前的处境,调整大脑的处理方式,希望之后不会耽误什么。

哇,如果听不见声音的话(或者只能借助于骨传导),我可会很难过的。

分享到 评论