0x01 前言
函数符号对于逆向分析来说是十分重要,如果符号被混淆将会加大分析的难度,这次遇到的是一个cocos2dx的游戏,关键的函数名被混淆。
0x02 分析
本来不打算解密混淆的函数名,但是在分析过程中发现一个解密关键字符串的函数。
它用的是循环异或来解密加密后的字符串,会不会混淆的函数名也使用了这种加密方式?可能性非常大。
但是用这个函数的秘钥去解密函数名解出来的是乱码,秘钥可能是不同的,那么如何获取密码呢?网上的方式是基于文本字符统计的方式去猜所使用的循环异或秘钥,
但是有没有比较好的方法呢?
0x03 猜秘钥
在浏览函数名我发现一个比较有意思的类,它有一个Encode的函数,那会不会有一个Decode函数呢?
看下图那个框起来的函数,刚好6字节,参数也对得上,或许就是我们要找的函数,因为是循环异或,所以可以通过明文和密文直接算出秘钥
秘钥:0702050003,通过该秘钥去解密其他字符串,都能解密成功,所以猜测是正确的。{:1_912:}
0x04 批量重命名
可以方便的通过IDAPython去重命名类似“_ZN26BAGA516B7669616B675765607312BAGA6B676374Ev”的c++修饰的函数名。
主要思路是获取所有导出函数名,再通过相应的解析规则去替换函数名称,代码如下:
[Python] 纯文本查看 复制代码 import idaapi
import idautils
import idc
import binascii
import string
#解密字符串
def dec_name(str):
str
key=binascii.a2b_hex('0702050003')
keylen=len(key)
str=binascii.a2b_hex(str)
strlen=len(str)
de_str=''
for i in range(0,strlen):
de_str+=chr((ord(key[i%keylen])^ord(str[i]))&0xff)
return de_str
#获取长度前缀
def getint(str):
pos=0
for i in range(0,len(str)):
if(str[0:i+1].isdigit()):
pos=i+1
else:
if pos>0:
return pos
return 0
#获取改名后的符号
def getsysm(str):
rep_str=[]
if(str[0:3]=='_ZN'):
i=3
while i<len(str):
pos=getint(str[i:])
if pos<=0:
break;
pre_len=str[i:i+pos]
int_len=string.atoi(pre_len)
ba_str=str[i+pos:i+pos+int_len]
if(ba_str[0:4]=='BAGA'):
dec_str=dec_name(ba_str[4:])
else:
i+=int_len+len(pre_len)
continue
rep_str+=[[pre_len+ba_str,"%d%s"%(len(dec_str),dec_str)]]
i+=int_len+len(pre_len)
for i in rep_str:
str=str.replace(i[0],i[1]) #替换相应的字符串
return str
else:
return None
funlist=list(idautils.Entries()) #获取导出函数
for i in funlist:
str=getsysm(i[3])
if str:
idc.MakeNameEx(i[2],str,idc.SN_NOWARN)
跑完脚本后,基本上重命名完成了,发送和接受,加密和解密的地方都可以看出来,一般不用分析加解密函数,直接call发包函数就能发包,因为用于连接服务器的对象的this指针一般是不变的(单例模式),
如何获取上述指针能?简单的打个hook就能获取了。
0x05 结束
如果碰上那种符号被混淆的很厉害或者根本没符号的那应该怎么办?发送函数最终还是调用send(tcp ),可以从send入手,一直往上,但是很可能跟不到发送的地方。
因为一般封装的用于处理发送和接受的消息的类,都不是直接发的,而是插入一个队列或者类似队列的地方,由update函数进行发送,这种异步的发送方式可以从队列下手
就能发现sendMsg的函数了。
|