声明:本研究只作为个人兴趣,绝没用于商业用途。读者阅读后所用于的其他应用本人概不负责!
1、背景
公司一直在做天网监控的项目,需要将监控摄像头的rtsp转成h264封装的实时视频,然后在电视端通过H5播放,平台是浪潮7585的高清机顶盒和其他几个厂家的安卓机顶盒,但都统一了标准。但是推流转码用的程序是厂家安装的,哪个厂家就不说了,启动时需要一个19的数字作为license,为了个人兴趣,所以研究了程序的license认证算法。
2、思路和准备
2.1 愚蠢的想法
首先既然需要一个19位的license作为授权码,那我用程序去跑就行了,每遍历一次就去启动转码程序,检测到程序启动成功了,那么肯定就是这个数字,然后写入到文件里,,于是我跑的程序都写好了,如下:
#coding:utf-8
import time
import commands
#import subprocess
def AddStr(s):
if(len(str(s))==1):
s='0000'+str(s)
elif(len(str(s))==2):
s='000'+str(s)
elif(len(str(s))==3):
s='00'+str(s)
elif(len(str(s))==4):
s='0'+str(s)
elif(len(str(s))==5):
s=str(s)
return s
#print(type(s))
for i in range(0,9999):
for m in range(0,99999):
for n in range(0,99999):
for j in range(0,99999):
#i
if(len(str(i))==1):
i='000'+str(i)
elif(len(str(i))==2):
i='00'+str(i)
elif(len(str(i))==3):
i='0'+str(i)
#print(type(i))
#m
AddStr(m)
#n
AddStr(n)
#j
AddStr(j)
MyStr=str(i)+AddStr(m)+AddStr(n)+AddStr(j)
with open('monitor_license','w+') as f:
f.write(MyStr+'\n')
status,output = commands.getstatusoutput('./Sdgd_Monitor')
if 'Monitor Server Running...' in output:
with open('license.txt','w+') as f:
f.write(MyStr)
exit('已成功获取license')
跑了大约两天我发现我做了一个很愚蠢的事情,假如1毫秒尝试一次,那么需要9999999999999999999/1000秒/3600/24/365=31709791.983764585年,即便1纳秒运算一次,还需要317年,,那时候估计已经天荒地老了……没办法,我想到了IDA,那个神奇的反编译工具,想不如做,于是走起。。。。
2.2 思路
既然需要本地的一个授权码,那么程序执行时肯定会生成一个去和放到服务器上的对比,那么只分析生成过程的算法就可以了
2.3 代码分析
反编译后按F5查看伪代码,本人学的电子信息工程,虽然汇编是专业课,但是都还给老师了,还是看伪代码靠谱。反编译后的主要函数列表如下:
解释如下:Main:主函数,即入口函数
GetMainbroadSerID:获取服务器序列号的函数
GetLocalProgramName:检测服务器执行的程序名函数
GetLocalAuthorrizeCode:生成授权码的函数,这个也是主要分析的函数
GetAuthorizeFileCode:获得服务器上已存在的授权码,即和生成的对比的授权码,一样才执行程序
checkAuthorize:检查认证的函数,即对比生成的和已存在的是否一样的函数
str2hash:生成授权码的算法函数
主要函数伪代码如下:
2.4 执行流程分析
综合以上伪代码可以看出,程序执行时的流程如下:
1)进入main函数,传的参数是程序名字,去checkAuthorize检查认证。
2)进入到checkAuthorize后先去生成授权码,去GetLocalAuthorrizeCode。
3)进入到GetLocalAuthorrizeCode后,先获取服务器的序列号,即去GetMainbroadSerID函数
4)到GetMainbroadSerID后,用dmidecode -s system-serial-number生成的序列号写入到一个文件,然后读文件入内存,存取读到的内容后,删除文件,然后把读到的内容作为参数去str2hash执行算法。
5)重点就在str2hash算法,过程是遍历参数字符串,用33乘以无符号数h,再加上遍历的字符串的ascii码,需要注意的是h的类型,一旦超过LL类型的范围就会溢出。最终生成一个usign的LL类型数,溢出仍然需要处理,取绝对值,和求编码一个原理。
6)回到3),用程序名加下划线拼接刚生成的无符号数,再作为参数传给str2hash,最终生成的值即为要获取的license。
7)回到2),然后读取本地的以license结尾的文件名,即放在服务器上的文件名,读取后和生成的对比。
8)回到1),对比的结果一样,正常执行程序,对比不一样,报错,执行失败。
3、用程序模拟license的生成
基于以上生成流程,用python模拟生成即可,主要代码如下:
def str2hash(str0):
h=123456789098765
for i in str0:
h=33*h+ord(i)
while h>2**64-1:
h=h-2**64
#print(hex(h))
if h>2**63-1:
h=abs(h-2**64)
return h
str2='sdgd_Monitor_'+str(str2hash(str1))
print("序列号="+str1+"\n"+"code="+str(str2hash(str2)))
遍历流程如下图:
经测试,生成的数值可使程序成功执行。
4、总结
本人是做网络,兼做前端出身,有时也设计后端,主要是php,参加过多次网络安全比赛,只要遇到逆向的题一般直接放弃,这次逆向分析折腾了三天,包括IDA的使用、远程调试、函数查找、代码分析等等,但总算成功了。
其实刚开始分析时出过好多错,主要浪费在了hash函数上,没有弄清h的类型,导致生产的总不行,后来通过linux远程调试才明白hash的执行过程。
总之,通过这次的逆向分析让我学到了好多东西,也为以后的学习工作带来了很大的方便。