一个ReverseMe的算法分析
来源:互联网 发布:中学生网络成瘾 编辑:程序博客网 时间:2024/06/04 19:20
这个ReverseMe是我一年前写的,不过现在源代码丢了而且怎么写的也忘了。正好昨天逛一个论坛的时候看到了这个ReverseMe,就顺便下载玩了玩(也算是重温了一下),于是就有了这篇文章。
因为篇幅有限 我就写写关键的地方。
0x0 寻找算法地址
直接来到main函数(0x004019B2)处。
程序首先获取ntdll!ZwContinue函数的地址,然后保存到0x00417F7C处。
004019BE 68 DC504100 push 004150DC ; NtContinue004019C3 68 E8504100 push 004150E8 ; n004019C8 33F6 xor esi, esi004019CA 33DB xor ebx, ebx004019CC C705 787F4100 0>mov dword ptr [0x417F78], 00412000004019D6 FF15 04304100 call dword ptr [<&KERNEL32.GetModuleH>; kernel32.GetModuleHandleW004019DC 50 push eax004019DD FF15 0C304100 call dword ptr [<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress004019E3 68 FC504100 push 004150FC ; E004019E8 A3 7C7F4100 mov dword ptr [0x417F7C], eax
继续往下走,发现程序设置了CONTEXT结构体然后调用ZwContinue函数。
00401ABB 8D05 3D1B4000 lea eax, dword ptr [0x401B3D] ; 返回地址00401AC1 50 push eax00401AC2 9C pushfd00401AC3 8F45 FC pop dword ptr [ebp-0x4]00401AC6 8B45 FC mov eax, dword ptr [ebp-0x4]00401AC9 8945 F0 mov dword ptr [ebp-0x10], eax00401ACC 8965 FC mov dword ptr [ebp-0x4], esp00401ACF 8B45 FC mov eax, dword ptr [ebp-0x4]00401AD2 8945 F4 mov dword ptr [ebp-0xC], eax00401AD5 896D FC mov dword ptr [ebp-0x4], ebp00401AD8 8B45 FC mov eax, dword ptr [ebp-0x4]00401ADB C785 30FFFFFF 0>mov dword ptr [ebp-0xD0], 0x10007 ; CONTEXT_FULL00401AE5 8945 E4 mov dword ptr [ebp-0x1C], eax00401AE8 A1 547F4100 mov eax, dword ptr [0x417F54]00401AED 8945 E8 mov dword ptr [ebp-0x18], eax ; Eip00401AF0 16 push ss00401AF1 8F45 FC pop dword ptr [ebp-0x4]00401AF4 8B45 FC mov eax, dword ptr [ebp-0x4]00401AF7 8945 F8 mov dword ptr [ebp-0x8], eax00401AFA 0E push cs00401AFB 8F45 FC pop dword ptr [ebp-0x4]00401AFE 8B45 FC mov eax, dword ptr [ebp-0x4]00401B01 8945 EC mov dword ptr [ebp-0x14], eax00401B04 1E push ds00401B05 8F45 FC pop dword ptr [ebp-0x4]00401B08 8B45 FC mov eax, dword ptr [ebp-0x4]00401B0B 8945 C8 mov dword ptr [ebp-0x38], eax00401B0E 06 push es00401B0F 8F45 FC pop dword ptr [ebp-0x4]00401B12 8B45 FC mov eax, dword ptr [ebp-0x4]00401B15 8945 C4 mov dword ptr [ebp-0x3C], eax00401B18 0FA0 push fs00401B1A 8F45 FC pop dword ptr [ebp-0x4]00401B1D 8B45 FC mov eax, dword ptr [ebp-0x4]00401B20 8945 C0 mov dword ptr [ebp-0x40], eax00401B23 0FA8 push gs00401B25 8F45 FC pop dword ptr [ebp-0x4]00401B28 8B45 FC mov eax, dword ptr [ebp-0x4]00401B2B 6A 00 push 0x000401B2D 8945 BC mov dword ptr [ebp-0x44], eax00401B30 8D85 30FFFFFF lea eax, dword ptr [ebp-0xD0]00401B36 50 push eax00401B37 FF15 7C7F4100 call dword ptr [0x417F7C] ; ntdll.ZwContinue00401B3D 68 3C514100 push 0041513C ; pause00401B42 E8 A9000000 call 00401BF0
在Eip(0x7781656D)下断,然后F9,分析后容易发现:这个函数也是一个跳转函数,其中配置CONTEXT结构体并转移的代码如下:
778165E3 A1 547F4100 mov eax, dword ptr [0x417F54]778165E8 0105 387B4100 add dword ptr [0x417B38], eax778165EE 16 push ss778165EF 8F05 4C7B4100 pop dword ptr [0x417B4C]778165F5 A1 4C7B4100 mov eax, dword ptr [0x417B4C]778165FA A3 487B4100 mov dword ptr [0x417B48], eax778165FF 0E push cs77816600 8F05 4C7B4100 pop dword ptr [0x417B4C]77816606 A1 4C7B4100 mov eax, dword ptr [0x417B4C]7781660B A3 3C7B4100 mov dword ptr [0x417B3C], eax77816610 1E push ds77816611 8F05 4C7B4100 pop dword ptr [0x417B4C]77816617 A1 4C7B4100 mov eax, dword ptr [0x417B4C]7781661C A3 187B4100 mov dword ptr [0x417B18], eax77816621 06 push es77816622 8F05 4C7B4100 pop dword ptr [0x417B4C]77816628 A1 4C7B4100 mov eax, dword ptr [0x417B4C]7781662D A3 147B4100 mov dword ptr [0x417B14], eax77816632 0FA0 push fs77816634 8F05 4C7B4100 pop dword ptr [0x417B4C]7781663A A1 4C7B4100 mov eax, dword ptr [0x417B4C]7781663F A3 107B4100 mov dword ptr [0x417B10], eax77816644 0FA8 push gs77816646 8F05 4C7B4100 pop dword ptr [0x417B4C]7781664C A1 4C7B4100 mov eax, dword ptr [0x417B4C]77816651 6A 00 push 0x077816653 68 807A4100 push 0x417A8077816658 A3 0C7B4100 mov dword ptr [0x417B0C], eax7781665D FF15 7C7F4100 call dword ptr [0x417F7C] ; ntdll.ZwContinue77816663 90 nop77816664 FF05 507F4100 inc dword ptr [0x417F50]
在0x7781665D处下断,拦截每次的Eip值。
拦截以后发现几个有用的函数:
0x00401B6D 接受用户名(存放在0x00417D50处)
0x00401B8B 接受注册码(存放在0x00417B50处)
0x7781668F(函数地址可变) 检查用户名是否是12个字节
0x778166BE(函数地址可变) 检查注册码是否是12个字节
0x00412057 算法部分
0x4123A9 返回结果
0x1 分析算法
来到0x00412057处,简单看看代码,发现一堆push call pop之类的指令,这里我使用IDA的F5插件来分析。
其中的EncryptData:
这里仅仅调用了两个函数:strlen和sub_401000。
我们目前需要做的就是分析出函数sub_401000是干什么的。
IDA进入401000处,发现代码很简短。
这段代码很简短,就是not not and,如果用一条指令来描述就是nor指令。
其中,有四个指令可以直接被nor模拟。
not(a) = nor(a,a)and(a,b) = nor(nor(a,a),nor(b,b)) = nor(not(a),not(b))or(a,b) = nor(nor(a,b),nor(a,b))xor(a,b) = nor(nor(nor(a,a),nor(b,b)),nor(a,b)) = nor(and(a,b),nor(a,b))
根据这个关系 我们尝试将这段算法给改写成not and or xor的形式
HRESULT __stdcall Decrypt(PINFORMATIONCARD_CRYPTO_HANDLE hCrypto, BOOL fOAEP, DWORD cbInData, PBYTE pInData, DWORD *pcbOutData, PBYTE *ppOutData){ HRESULT result; // eax@1 unsigned int i; // esi@1 int ByteOfSerial1; // ST28_4@2 int ByteOfUsername; // ST28_4@2 int ByteOfSerial; // ebx@2 int ByteOfUsername1; // edi@2 int v12; // ST18_4@2 int v13; // ST14_4@2 int v14; // eax@2 int v15; // eax@2 int v16; // ST18_4@2 int v17; // ST14_4@2 int v18; // ST10_4@2 int v19; // eax@2 int v20; // eax@2 int v21; // eax@2 int v22; // ST18_4@2 int v23; // eax@2 int v24; // eax@2 int v25; // ST18_4@2 int v26; // ST14_4@2 int v27; // ST10_4@2 int v28; // eax@2 int v29; // eax@2 int v30; // ST14_4@2 int v31; // ST10_4@2 int v32; // ST0C_4@2 int v33; // eax@2 int v34; // eax@2 int v35; // eax@2 int v36; // ST14_4@2 int v37; // eax@2 int v38; // eax@2 int v39; // eax@2 int v40; // ST18_4@2 int v41; // ST14_4@2 int v42; // ST10_4@2 int v43; // eax@2 int v44; // eax@2 int v45; // ST14_4@2 int v46; // ST10_4@2 int v47; // ST0C_4@2 int v48; // eax@2 int v49; // eax@2 int v50; // eax@2 int v51; // ST14_4@2 int v52; // eax@2 int v53; // eax@2 int v54; // ST14_4@2 int v55; // ST10_4@2 int v56; // ST0C_4@2 int v57; // eax@2 int v58; // eax@2 int v59; // ST10_4@2 int v60; // ST0C_4@2 int v61; // ST08_4@2 int v62; // eax@2 int v63; // eax@2 int v64; // eax@2 int v65; // ST10_4@2 int v66; // eax@2 int v67; // eax@2 int v68; // eax@2 int v69; // eax@2 char v70; // al@2 int v71; // edi@2 int v72; // ebx@2 int v73; // ST18_4@2 int v74; // ST14_4@2 int v75; // eax@2 int v76; // eax@2 // 初始化加密数据 *(_DWORD *)&SuccessfulData = EncryptData[0]; *((_DWORD *)&SuccessfulData + 1) = EncryptData[1]; *((_DWORD *)&SuccessfulData + 2) = EncryptData[2]; *(&SuccessfulData + 12) = LOBYTE(EncryptData[3]); result = 0; i = 0; if ( strlen(UserName) != 0 ) { do { ByteOfSerial1 = Serial[i]; // Serial[i] ByteOfUsername = UserName[i]; // UserName[i] ByteOfSerial = Serial[i]; // Serial[i] ByteOfUsername1 = UserName[i]; // UserName[i] v12 = nor(ByteOfUsername, ByteOfSerial); v13 = nor(ByteOfSerial, ByteOfSerial); v14 = nor(ByteOfUsername1, ByteOfUsername1); v15 = nor(v14, v13); v16 = nor(v15, v12); // v10 = UserName[i] xor Serial[i] v17 = nor(UserName[i], Serial[i]); v18 = nor(ByteOfSerial, ByteOfSerial); v19 = nor(ByteOfUsername1, ByteOfUsername1); v20 = nor(v19, v18); v21 = nor(v20, v17); // v15 = UserName[i] xor Serial[i] v22 = nor(v21, v16); // v16 = not(UserName[i] xor Serial[i]) v23 = nor(ByteOfUsername1, ByteOfUsername1); v24 = nor(v23, v22); // v18 = UserName[i] and v15 v25 = nor(Serial[i], v24); // v19 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i])) v26 = nor(UserName[i], Serial[i]); v27 = nor(ByteOfSerial, ByteOfSerial); v28 = nor(ByteOfUsername1, ByteOfUsername1); v29 = nor(v28, v27); v30 = nor(v29, v26); // v24 = UserName[i] xor Serial[i] v31 = nor(UserName[i], Serial[i]); v32 = nor(ByteOfSerial, ByteOfSerial); v33 = nor(ByteOfUsername1, ByteOfUsername1); v34 = nor(v33, v32); v35 = nor(v34, v31); // v29 = UserName[i] xor Serial[i] v36 = nor(v35, v30); // v30 = not(UserName[i] xor Serial[i]) v37 = nor(ByteOfUsername1, ByteOfUsername1); v38 = nor(v37, v36); // v32 = nor(not(UserName[i]),v30) v39 = nor(Serial[i], v38); // v33 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i])) v40 = nor(v39, v25); // v34 = not(v33) v41 = nor(UserName[i], Serial[i]); v42 = nor(ByteOfSerial, ByteOfSerial); v43 = nor(ByteOfUsername1, ByteOfUsername1); v44 = nor(v43, v42); v45 = nor(v44, v41); // v39 = UserName[i] xor Serial[i] v46 = nor(UserName[i], Serial[i]); v47 = nor(ByteOfSerial, ByteOfSerial); v48 = nor(ByteOfUsername1, ByteOfUsername1); v49 = nor(v48, v47); v50 = nor(v49, v46); // v44 = UserName[i] xor Serial[i] v51 = nor(v50, v45); // v45 = not(UserName[i] xor Serial[i]) v52 = nor(ByteOfUsername1, ByteOfUsername1); v53 = nor(v52, v51); // v47 = UserName[i] and (UserName[i] xor Serial[i]) v54 = nor(Serial[i], v53); // v48 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i])) v55 = nor(UserName[i], Serial[i]); v56 = nor(ByteOfSerial, ByteOfSerial); v57 = nor(ByteOfUsername1, ByteOfUsername1); v58 = nor(v57, v56); v59 = nor(v58, v55); // v53 = UserName[i] xor Serial[i] v60 = nor(UserName[i], Serial[i]); v61 = nor(ByteOfSerial, ByteOfSerial); v62 = nor(ByteOfUsername1, ByteOfUsername1); v63 = nor(v62, v61); v64 = nor(v63, v60); // v58 = UserName[i] xor Serial[i] v65 = nor(v64, v59); // v59 = not(v58) v66 = nor(ByteOfUsername1, ByteOfUsername1); v67 = nor(v66, v65); // v61 = UserName[i] and (UserName[i] xor Serial[i]) v68 = nor(Serial[i], v67); // v62 = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i])) v69 = nor(v68, v54); // v63 = not(v62) v70 = nor(v69, v40); // v64 = not(v63) -> v64 = v62 UserName[i] = v70; // UserName[i] = v62 v71 = v70; // v65 = v62 v72 = (unsigned __int8)*(&SuccessfulData + i);// v66 = SuccessfulData[i] v73 = nor(v72, v70); v74 = nor(v71, v71); v75 = nor(v72, v72); v76 = nor(v75, v74); // v70 = SuccessfulData[i] and v62 *(&SuccessfulData + i) = nor(v76, v73); // SuccessfulData[i] = SuccessfulDara[i] xor v62 result = 0; ++i; } while ( i < strlen(UserName) ); } return result;}
总结一下算法:
设:用户名为UserName,注册码为Serial,提示信息为SuccessfulData,用户名和注册码的每个字节的关系为x。
则有:
x = nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i]))
SuccessfulData[i] = SuccessfulData[i] xor x
0x2 注册机的编写
知道了算法,这样就可以写一个注册机了。
不过因为算法本身的原因,这个注册机编写起来还是有一定难度的。
因为不是所有的用户名所对应的注册码都能被输入进去,不过又因为算法的关系,导致了x并不是只有一个结果。
根据这个ReverseMe的成功图片,成功会输出”Hello world!”,正好是SuccessfulData的长度。
那么将字符串”Hello world!”和SuccessfulData逐位异或,得到新的SuccessfulData如下:
char SuccessfulData[] = {0x80,0x90,0x9A,0x8A,0x8A,0x92,0x80,0xCD,0xCE,0xC8,0x80,0xA0};
注册机的代码:
#include <stdio.h>#include <string.h>#include <stdlib.h>#define and &#define xor ^#define not ~int nor(int a,int b){ return not a and not b;}int main(void){ char SuccessfulData[] = {0x80,0x90,0x9A,0x8A,0x8A,0x92,0x80,0xCD,0xCE,0xC8,0x80,0xA0}; char UserName[512]; char Serial[13] = {0}; scanf("%s",UserName); if(strlen(UserName) != 12) return 0; for(int i = 0;i < 12;i++) { if(!(UserName[i] >= 0x21 && UserName[i] <= 0x7F)) return 0; } for(int i = 0;i < 12;i++) {ContinueWhile: for(Serial[i] = 0x21;Serial[i] <= 0x7E;Serial[i]++) //scanf函数接受字符串输入时遇到空格截断. { if(nor(Serial[i],UserName[i] and (UserName[i] xor Serial[i])) == SuccessfulData[i]) goto Next; } /* 当前用户名没有对应的可显示的注册码,尝试更改用户名 */ if(UserName[i] == 0x7E) UserName[i] -= (Serial[i] - 0x21); else UserName[i]++; if(UserName[i] == 0x20) //空格截断 UserName[i]++; goto ContinueWhile;Next: _asm nop } printf("------------------------\nUserName:[%s]\n",UserName); printf("Serial:[%s]\n",Serial); system("pause"); return 0;}
运行结果如图所示:
ReverseMe的下载链接:http://pan.baidu.com/s/1gf5YC2B 密码:y093
全文完。
- 一个ReverseMe的算法分析
- 某个ReverseMe的分析
- ReverseMe分析过程
- ReverseMe 不完美分析过程
- 0x04 调试加分析 reverseMe.exe
- 简单CrackMe分析(样本名:ReverseMe)
- 一个算法的分析
- 一个高效的洗牌算法分析
- 一个日期算法的原理分析
- 一个含有crc32算法的CrackMe分析
- 分析一个游戏打包资源文件的算法
- 对一个罗马数字与阿拉伯数字转换算法的分析
- 近日发现一个有趣的数据分析项目 (解析滴滴算法大赛---数据分析过程)
- 一个不是算法的算法
- 如何对一个算法进行复杂度分析
- 一个开源AC算法源码分析
- 算法分析:两个栈实现一个队列
- 一个开源AC算法源码分析
- WPF项目中解决ConfigurationManager不能用
- C编程预备计算机专业知识 _ 变量
- java爬虫之抓取城市数据
- Keil MDK 和 IAR 两款ARM开发工具区别比较
- 整理docker及Hadoop脚本(四)-在docker集群集群中一键式部署hadoop
- 一个ReverseMe的算法分析
- 数据库补充学习之基础补充
- struct和union占用内存详解
- Python入门记——列表2
- Ajax技术
- Mongo数据库安装与入门
- 作业1
- C++11 右值引用和转移语义
- ngrok集成在本地Node.js项目服务器,实现F5调试即可自动打开浏览器且通过外网可访问本地服务器。