好友
阅读权限 10
听众
最后登录 1970-1-1
1.2 分析环境及工具
系统环境:win732位虚拟机
工具:1.OllyDbg (下面简称OD) 动态调试代码
2.CheatEngine(下面简称CE) 内存搜索
3.PEID/exeinfo 查壳,查编译环境
4.DebugView 查看消息返回
5.010Editor 数据查看工具
6.VistualStudio 开发程序,编写Dll
7.IDA 静态调试代码
2.具体分析过程
2.1 信息收集
2.1.1 查看程序环境
010Editor是一款非常强大的文本、十六进制编辑器,除了文本、十六进制编辑外,还包括文件解析、计算器、文件比较等功能,但它真正的强大之处还在于文件的解析功能。当然这么强大的软件也是收费的,不过官方也商业化的给出了30天的试用供用户体验。30天后就没法使用了,为了能够使用也为了技术追求,本文将逐步介绍如何去除使用限制和编写注册机。
通过PEID/exeinfo查找信息:
找出是VC++,12.0版本的判断是 用VS2013版本编写知道返回值一般都是在EAX中
2.2 分析并更改程序
2.2.1.暴力破击分析:
通过暴力破解 修改跳转,将需要跳过的地方暴力修改进行跳过,从而避开检测以获取它的永久使用权限暴力破解分析步骤:找到注册窗口测试注册窗口的反应根据反应做出下一步分析的打算(猜测 API、API下断动态调试、挑出敏感字符串,在程序中搜索)动态分析,定位关键跳转,修改代码动态分析,定位关键 CALL,修改代码安装010后打开010 Editor
软件会弹出一个注册窗口,提示输入注册码
在注册窗口随便输入字符后,点击 Check License 按钮会弹出如下提示:
上述意思是你输入了一个无效的用户名或者是密码,也就是说,你点击那个按钮会弹出这个提示窗口,那么我们可以从这个提示窗口入手,比如对创建这个窗口的 API 下断,或者是寻找提示信息的这段字符串。废话不多说,上 OD,附加进程调试,如下:
附加后先来到主模块,点击E,点击第一个模块进入主模块
进入主模块后先来看下程序主模块导入了哪些函数,右键 -> 查找 -> 当前模块中的名称(标签),或者按下快捷键 Ctrl + N,如下:
看到上图调用了很多的QT库函数,猜测是由Qt编写的,当然不管何种的库最终都是会调用API函数,在其中也找到了和窗口有关的函数user32库函数
打开模块找到user32的库,双击点进去,使用 快捷Ctrl+N 如下图:
也在user32库里发现了弹窗函数:CreatWindow,CreateDialog函数Ctrl+G 找到 CreatWindowW下断点,再次点击 CheckLicense按钮,发现断点断下来了,说明断点下对了:
想知道 Check License是在哪个函数调用的这个窗口,点击上方小按钮K查看栈回溯,如下:
看到有个显眼的Show ,貌似跟显示窗口有关,双击进去看下里面有什么可以利用的信息,但是没有发现什么有价值的东西,不急点击下一个地址,双击进去查看,结果里边还是没有,再跟进去下一个地址,双击进去,会发现如下这种情况:
发现大量可疑字符串,其中Invalid name or password。。。。就是我们随意输入用户名和密码时弹出的信息提示,处于一个PUSH指令中取消之前的断点,在这个位置打上断点,运行到字符串的位置,Ctrl+A分析一下会出现一个跳转来自:xxxx
右键点击跳转,到达调用的上一层,
在跳转上一步有一个跳转判断,EBX !=93就会跳转到刚刚的位置,再往上看还有一个EDI==20C和EDI==ED就会跳转
CMP EDI 0xED处有一个向上的跳转,在这个代码处添加一个断点,去判断这个位置是否可以被运行到,判断最终是否会到刚刚的文字判断的位置位置,发现运行轨迹和分析一致,那就记录一下继续向上跳转分析遍历。
跳转上来后发现上边还有两个从上下来的跳转,一个是 EDI 的值,一个是 ECX 的值,前者需要和 0xDB 这个值作比较,后者则需要跟 一个地址的值作比较,但不确定哪个才是跳转到现在这个位置的跳转,在两个位置下断点,重新运行后发现断在了CMP EDI,0xDB上边,那么就跟随这个位置向上跳转分析。这个位置有两个地址分别去查看,擦看后发现这两个地方实际上是在一起的,打上断点运行查看是哪个模块产生的跳转,运行后发现在0x15D827处EBX和E7对比处跳转,向上查询发现EBX的值是EAX赋予的,而之前信息收集判断,此程序由VS2013版本编写 ,EAX就是上一个函数的返回值,所以判断这个是重要函数;
那么把刚刚找到的分析一下
如果 EBX 为 0xE7 则跳转,跳过之后,再对 EDI 比较,此时 EDI 为 0x177,0x177 不等于 0xDB 再次跳走,接着有两次对 EDI 进行比较,值分别为 0xED 和 0x20C,又因为EDI 值为 0x177,所以两处的 JE 指令不成立,最后对比 EBX 的值,用它跟 0x93 对比,很明显 0xE7 不等于 0x93,所以 JNZ 指令成立,将跳过密码已被接受过程。
那么我们反推一下:后边的EDI和0x20C及EBX和93的对比都是为了跳到其他地方,从EDI和DB的对比开始是最关键的跳转,这个跳转的下面就是正确的位置,那么这个判断就必须要让EDI==DB才是正确的判断,顺着刚刚的位置向上看,发现EDI的值其实是EAX赋予的,而上边分析到EAX的值是函数给予的,那么就应该使这个位置的函数的返回值=DB是正确的
记下重点,继续向上看,来到最后一个判断:EBX 和0xE7的判断,EBX是EAX赋给的,而EAX又是上面函数的返回值,所以 EBX=EAX!=E7 是正确的,分析结果请看图:
真像就在这个函数内了,富贵险中求,留个断点进去看一眼,进入函数后直接深入底部查看:
找到几个返回值一个是0xE7,0x93,0x2D,0x4E是不是感觉这几个数值很熟悉,只知道返回E7是错误的,但是却不知道返回什么是正确的那么在进入在下一个判断返回值是否为DB的函数中查看哪个是正确的
只有当是上一个函数返回的是2D的时候才会返回正确的DB现在已经知道了逻辑,那么直接把关键跳转给NOP掉就可以直接成功了运行一下:
已经显示成功了因为我们只是改了外层的判断,所以在每次重新打开的时候还是会弹出一个注册的窗口,虽然点击一下CheckLicense就可以正常运行可是总归感觉有点别扭,这当然不能满足,,,,不说了继续操作,这次选择去改掉内部的函数,以免再次弹出注册框,2.2.2.去掉注册弹框
刚刚已经分析出一个函数返回是0x2D的话才可以成功的返回,直接去函数里面把返回值改成2D即可
进入函数后发现又随机基址,如果这样改的话返回地址就会是错误的,应该先去其他010中把随机基址改掉
将这里的1改成0成功关闭重定位:
再次回到函数在函数内部直接给EAX赋值为0x2D,让后ret返回即可
点击运行查看结果
发现改掉以后并没有成功,进去运行调试一下发现第二个返回DB的CAll的函数判断存在问题,会和一个01进行判断,直接把JE改成JMP
另存一下运行一下发现运行成功,也去掉了注册窗:
由上可见,已经成功破解了 010 Editor 了,暴力破解已经实现,
2.2.3.初步分析算法
暴力破解时只是修改了一个跳转,但关键点还是在于那两个重要的 CALL,尤其是第一个 CALL,必须让它返回 0x2D 才是正确的,所以后面去跟进第一个关键的 CALL 内去分析注册算法。在跟进之前,我们分析下这个 CALL 有哪些参数,如下:
第一个参数传参为ECX,一般用ECX传参都是this指针,后面两个参数是0x4389和0x9预测账户和密码有关系先分析一下this指针:跟进ECX 地址所在处数据窗口,ECX的值为:002D8EF8如下:
看到注释处写着ArrayData,而且如果是用来验证用户名和密码的话,存储的可能是个支付串,这个this 指针应该是指向一个字符串数组,接着我们需进入到下边地址002D8EFC查看 ,右键数据数据窗口跟随(如果说数据窗口右键有数据窗口跟随这个选项的话,那么一般就是个可以去访问的地址)
第二个地址的数据如下:
第一行单个数据猜测是长度或者是,下边的每一个字节中间都间隔一个00的是unicode字符格式,可以看得出是用户名按一下”-“号返回回去查看第三个地址的数据:
第二个里面存的是密码知道了参数里是什么之后,那么进去函数去分析,前边已经知道用户名和密码的地址了,就贴和这些数据空间去分析:
在检测用户名和密码是否为空后,紧接着将一个局部变量的值压和栈中,右键栈中的地址数据窗口跟随,会发现这个数据是你输入的密码字符串,而且为 16 进制,所以说 PUSH 指令下的这个 CALL,是将密码字符串转为 16 进制字节的数据。
继续向下走看到一个循环,从这里开始就是真正的算法了:其中P[0],P[1],,,,这些对应你输入的密码1234-5678-9009-8765-4321那么P[0]为12,P[1]为34,依此类推
在上图中,有几个关键点地方,第一个圈起来的,它会拿你输入的密码的第 4 组数据去和 0x9C、0xAC、0xFC 这三个数进行比较,如果不是这三个数中的其中一个,那么程序会将 0xE7 赋值给 EAX,而在前面一小节当中,EAX 的值不能为 0XE7,否则失败,所以我们令 JNZ 下方这条指令为新的 EIP,否则JNZ 条件成立跳过去了,就不好分析了,最后我们可以肯定,密码有三个版本,这里只分析下 0x9C 这个版本。第二个关键点是第二个圈起来的地方,跟进这个 CALL 里,如下:
还好这个 CALL 里的代码并不是太复杂,主要处理的是P[0] 和 P[6]。第三个关键点是第三个圈起来的地方,我们跟进这个 CALL 内,如下:
同样,代码也不复杂,主要是判断余数是否为 0,如果为 0,那么就返回商,如果不为 0,那么就返回 0。所以这么一整段下来,在对 k[0]、k[1]、k[2]、k[3]、k[4]、k[5]、k[6]、k[7] 在进行操作,而我们输入的密码有 10 组,OD 中并没看到对 k[8]、k[9] 进行处理,其实这里有个点,那就是当你密码中第 4 组数据为 0x9C,那么生成的密码并没有 10 组,而是 8 组,如下:
写注册机验证:通过刚刚代码中的推演,注册码的生成已经找到,写注册机进行测试:
[C++] 纯文本查看 复制代码
int main()
{
srand(time(NULL));
byte P[10] = {0x12,0x34,0x56,0x9C,0x90,0x09,0x87,0x65,0x99,0xAA};
//AL=(P[0]^P[6]^0x18+0x3d)^0xA7
while (true)
{
byte P0= rand()%0xff;
byte P6= rand()%0xff;
byte al = (P0 ^ P6 ^ 0x18 + 0x3D) ^ 0xA7;
if (al>0)
{
P[0] = P0;
P[6] = P6;
break;
}
}
//push ESI=((P[1]^P[7]&0FF)*0x100)*(P[2]^P[5]&0xff)&0xFFFF
//EAX=(((ESI ^ 0X7892) + 0x4d30)0x3421) & 0FFFF / 0xB
//计算之后 判断榆=余数是否为0,为0返回商,不为0返回0
while (true)
{
byte P1 = rand() % 0xff;
byte P7 = rand() % 0xff;
byte P2 = rand() % 0xff;
byte P5 = rand() % 0xff;
DWORD ESI = (0x100*(P1 ^ P7 & 0xFF) +P2 ^ P5 & 0xff) & 0xFFFF;
DWORD EAX = (((ESI ^ 0X7892) + 0x4d30)^0x3421) & 0xFFFF;
if (EAX %0xB==0&&EAX/0xB<=0x3E8)
{
P[1] = P1;
P[7] = P7;
P[2] = P2;
P[5] = P5;
break;
}
}
printf("%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X", P[0], P[1], P[2], P[3], P[4], P[5], P[6], P[7], P[8], P[9]);
getchar();
return 0;
}
以上代码只是让你遇到那两个 JE 和一个 JA 跳转指令可以使它不成立,这个雏形生成的密码并没有与用户名进行关联,在后面会分析密码与用户名的之间的联系。MFC的注册机雏形诞生了:
2.2.4.深入编写注册机:
前边是通过穷举法来找出符合要求的密码。下面将继续分析下用户名和密码之间的关系。接着向下分析:
将用户名字符串处理后的返回值传给EDX,让后与密码P[4],P[5],P[6],P[7]发生关系
在上图中的 ds:[edi+0x1C] 处其实是前面处理 k[0] 和 k[6] 那个 CALL 后的返回值(即 AL 的值),所以我们需要将代码中的
[C++] 纯文本查看 复制代码
if (AL > 0)
{
k[0] = k0;
k[6] = k6;
break;
}
改为如下:
if (AL >= 9)
{
通过分析知道了,用户名和密码产生了一个对应关系,将对应关系体现在代码中
[C++] 纯文本查看 复制代码
//CMP k[4],RetValue&0xFF
//CMP k[5],RetValue>>8&0xFF //CMP k[6],RetValue>>16&0xFF //CMP k[7],RetValue>>24&0xFF k[4] = dwKey & 0xFF;
k[5] = dwKey >> 8 & 0xFF;
k[6] = dwKey >> 16 & 0xFF;
k[7] = dwKey >> 24 & 0xFF
接下来就是要做的就是那个对用户名处理的 CALL,通过分析知道,这个 CALL 的返回值类似于哈希值,我们需要得到这个值,回为这个值需要和 k[4]、k[5]、k[6]、k[7] 发生关系,我们不妨声明这样一个函数,用来处理 ASCII 码版的用户名字符串,这里教大家一个简便的办法,那就是利用IDA,将 010 Editor 载入IDA 中,我们需要先获取处理这个用户名字符串的 CALL 的地址,这个地址可以通过 OD 找到,找到之后,切换到 IDA,按 g 输入地址回车,再按下 F5 进行翻译,如下:
找到后发现翻译的有些乱,将数组地址保存下来,到OD中复制搜索:在OD数据窗口处果然看起来好多了;
刚刚在IDA里面看到是DWORD格式的,那么我们直接使用C++,DOWORD格式复制下来:
在MFC中写一个数组:
再次回到IDA中,将IDA中F5生成的判断代码揪下来,放到VS中转换一下,更改后的代码如下:
[C++] 纯文本查看 复制代码
int CbmbmDlg::EncodeUserName(const char* a1, int a2, char a3, unsigned __int16 a4)
{
// TODO: 在此处添加实现代码.
const char* v4; // edx
signed int v5; // esi
signed int v6; // edi
unsigned __int8 v7; // bl
int v8; // eax
int v9; // ecx
int v10; // ecx
int result; // eax
unsigned __int8 v12; // [esp+8h] [ebp-10h]
unsigned __int8 v13; // [esp+Ch] [ebp-Ch]
unsigned __int8 v14; // [esp+10h] [ebp-8h]
int v15; // [esp+14h] [ebp-4h]
v4 = a1;
v15 = 0;
v5 = strlen(a1);
v6 = 0;
if (v5 <= 0)
return 0;
v12 = 0;
v13 = 0;
v7 = 15 * a4;
v14 = 17 * a3;
do
{
v8 = toupper((unsigned __int8)v4[v6]);
v9 = v15 + dwEcodeArray[v8];
if (a2)
v10 = dwEcodeArray[v13]
+ dwEcodeArray[v7]
+ dwEcodeArray[v14]
+ dwEcodeArray[(unsigned __int8)(v8 + 47)] * (dwEcodeArray[(unsigned __int8)(v8 + 13)] ^ v9);
else
v10 = dwEcodeArray[v12]
+ dwEcodeArray[v7]
+ dwEcodeArray[v14]
+ dwEcodeArray[(unsigned __int8)(v8 + 23)] * (dwEcodeArray[(unsigned __int8)(v8 + 63)] ^ v9);
result = v10;
v15 = v10;
v13 += 19;
++v6;
v14 += 9;
v7 += 13;
v12 += 7;
v4 = a1;
} while (v6 < v5);
return result;
}
2.2.5.改变网络验证
如果过正确的注册码后会发现还是会弹出一个错误窗口,显示是一个无效地授权:
出现这种情况是 010 Editor 会把该密码上传到它的服务器,如果发现不是通过正常渠道获取得的密码,它会把这个密码拉黑,出现上述这种情况不要慌,回到代码中继续分析,分析后会发现,返回值进行了第二次的验证把EAX给变成了0x113,从而返回了错误。
重新在代码中跟踪进行更改:
看到0x113非常激动啊,就是这里因为老是验证通不过去,果断改掉,改掉后发现还是存在验证,,,先不要慌再次向下遍历,在下边再次发现还有一个判断:
把这里也改掉就成功了,当然还有另外一个方法,找到网络验证的函数,直接更改里边的值:先将上述的标志位0x113改掉,改成jmp,先让标志位返回正确,再找到网络验证函数,,,
当输入的密码正确后,它在调用这个 CALL 进行网络验证,需要等待一下,等它验证完后,EAX 的值变会变成 -1,从而使得 JNS 指令成立,所以解决的办法是进入到网络验证这个 CALL 内,把 EAX 的值修改成不是负数,在这我把它改成 1,如下:
让它永远返回1从新去验证发现成功
经过了这么久,010的逆向终于全部完成! 这三种方法及原理就都告诉大家了,大家可以一起操作吧
免费评分
查看全部评分