整个程序大致流程:(所有重要字符串都已经过RC6解密处理,达到加密的效果)
- 解密出fnGetRegSnToVerify和fnCalcUserInputRegSnAfterEnc两个函数名并注册为lua函数
- 获取输入并进行RC6解密处理
- 解密出lua编译后的脚本并执行该脚本
- 在该脚本中调用了fnGetRegSnToVerify和fnCalcUserInputRegSnAfterEnc函数
- fnGetRegSnToVerify返回一串固定密文,fnCalcUserInputRegSnAfterEnc对输入再次加密
- 比较fnGetRegSnToVerify返回的密文与fnCalcUserInputRegSnAfterEnc加密后的数据
逆向流程:
通过GetWindowText函数找到处理输入的关键点,计算长度后接下来又调用了两个函数,跟进查看,第一个是检查字符串的有效性,内部存在一个反调试,patch掉后看下一个函数;这个函数对输入进行了处理,跟进则会找到关键算法处,
根据一些函数特征,可以识别为RC6加密的密钥生成过程,既然生成了密钥,那么接下来不远处应该会进行加密了
结果是解密函数,意思是这个程序先对我们的输入进行了RC6解密操作。。密钥生成通常是在加密和解密之前来调用,所以考虑在加密函数调用前也进行了密钥生成,对该函数进行交叉引用就可来到RC6加密函数的地方,标识后返回。
2.回溯到调用GetWindowText函数处,输入处理完成后,就ret了,继续向上回溯,把这个函数名标识为输入处理,然后继续往下看,接下来会看到我们标识过的RC6加密函数,这里将一串数据作为参数传入,为了了解发生了什么,直接下断点动态跟踪,观察传入数据的变化情况,函数调用后,发现出现了明文,
由于对称加密与解密是互逆的,而这里又用RC6加密函数来解密,那么数据本身应该是经过RC6解密来加密的,为了顺应逻辑,我们把RC6的加密与解密反着说,那么这里调用的就是解密函数,对解密函数进行交叉引用,发现有很多调用处,猜测该程序的重要字符串都是进行加密的,没有明文,每次要使用时都进行了解密操作,跟踪到每个解密函数的调用点,查看并记录所有解密字符串,按照程序的执行流程,依次为
这也验证的我们的猜测,接下来一段就不知道是啥了,而且很长。
不过可以看到一些之前出现过的字符串也出现在里面,看名字像函数名之类的,搜索一下这个PE文件中的字符串,拿一些去搜索,发现是与lua脚本语言相关,那估计这就是个lua脚本编译后的脚本文件了,dump出来。github上有lua的反编译工具,根据特征确认版本是lua5.3,修复文件头后反编译得到源码
分析这个脚本整个验证过程fnGetRegSnToVerify()==fnCalcUserInputRegSnAfterEnc(RC6(input))
可以知道fnGetRegSnToVerify和fnCalcUserInputRegSnAfterEnc两个函数是关键,但是函数体并未定义,说明这两个函数是用C语言去注册的。当前函数解密了这个脚本,而之后又调用luaL_loadbufferx函数(根据特征识别),那么当前函数的作用应该就是解密并执行这个脚本,所以下一步就是找到fnGetRegSnToVerify和fnCalcUserInputRegSnAfterEnc的函数体,去看看它们做了什么。
这里有几种方法:
- C语言调用lua函数时,会使用lua_pcall函数,所以可以先找到lua_pcall函数,再动态跟踪程序的执行即可找到这两个函数
- 语言注册函数时会调用lua_pushfunction和lua_setglobal,根据特征码可以找到lua_setglobal,向上回溯即可找到注册点
总之是找到这两个函数了,无论输入什么fnGetRegSnToVerify都返回一段固定数据,这应该就是密文了;
fnCalcUserInputRegSnAfterEnc就比较复杂了,内部包含了一个修改过的AES,看得脑壳痛,有时间再来继续写吧。。