一个ReverseMe的算法分析
前几天写到博客的 然后昨天写了一个和这个很相似的KeygenMe.ReverseMe:https://www.52pojie.cn/thread-647291-1-1.html
KeygenMe:https://www.52pojie.cn/thread-647830-1-1.html
这个ReverseMe是我一年前写的,不过现在源代码丢了而且怎么写的也忘了。正好昨天逛一个论坛的时候看到了这个ReverseMe,就顺便下载玩了玩(也算是重温了一下),于是就有了这篇文章。
因为篇幅有限 我就写写关键的地方。
**0x0 寻找算法地址**
直接来到main函数(0x004019B2)处。
程序首先获取ntdll!ZwContinue函数的地址,然后保存到0x00417F7C处。
```
004019BE 68 DC504100 push 004150DC ; NtContinue
004019C3 68 E8504100 push 004150E8 ; n
004019C8 33F6 xor esi, esi
004019CA 33DB xor ebx, ebx
004019CC C705 787F4100 0>mov dword ptr , 00412000
004019D6 FF15 04304100 call dword ptr [<&KERNEL32.GetModuleH>; kernel32.GetModuleHandleW
004019DC 50 push eax
004019DD FF15 0C304100 call dword ptr [<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress
004019E3 68 FC504100 push 004150FC ; E
004019E8 A3 7C7F4100 mov dword ptr , eax
```
继续往下走,发现程序设置了CONTEXT结构体然后调用ZwContinue函数。
```
00401ABB 8D05 3D1B4000 lea eax, dword ptr ; 返回地址
00401AC1 50 push eax
00401AC2 9C pushfd
00401AC3 8F45 FC pop dword ptr
00401AC6 8B45 FC mov eax, dword ptr
00401AC9 8945 F0 mov dword ptr , eax
00401ACC 8965 FC mov dword ptr , esp
00401ACF 8B45 FC mov eax, dword ptr
00401AD2 8945 F4 mov dword ptr , eax
00401AD5 896D FC mov dword ptr , ebp
00401AD8 8B45 FC mov eax, dword ptr
00401ADB C785 30FFFFFF 0>mov dword ptr , 0x10007 ; CONTEXT_FULL
00401AE5 8945 E4 mov dword ptr , eax
00401AE8 A1 547F4100 mov eax, dword ptr
00401AED 8945 E8 mov dword ptr , eax ; Eip
00401AF0 16 push ss
00401AF1 8F45 FC pop dword ptr
00401AF4 8B45 FC mov eax, dword ptr
00401AF7 8945 F8 mov dword ptr , eax
00401AFA 0E push cs
00401AFB 8F45 FC pop dword ptr
00401AFE 8B45 FC mov eax, dword ptr
00401B01 8945 EC mov dword ptr , eax
00401B04 1E push ds
00401B05 8F45 FC pop dword ptr
00401B08 8B45 FC mov eax, dword ptr
00401B0B 8945 C8 mov dword ptr , eax
00401B0E 06 push es
00401B0F 8F45 FC pop dword ptr
00401B12 8B45 FC mov eax, dword ptr
00401B15 8945 C4 mov dword ptr , eax
00401B18 0FA0 push fs
00401B1A 8F45 FC pop dword ptr
00401B1D 8B45 FC mov eax, dword ptr
00401B20 8945 C0 mov dword ptr , eax
00401B23 0FA8 push gs
00401B25 8F45 FC pop dword ptr
00401B28 8B45 FC mov eax, dword ptr
00401B2B 6A 00 push 0x0
00401B2D 8945 BC mov dword ptr , eax
00401B30 8D85 30FFFFFF lea eax, dword ptr
00401B36 50 push eax
00401B37 FF15 7C7F4100 call dword ptr ; ntdll.ZwContinue
00401B3D 68 3C514100 push 0041513C ; pause
00401B42 E8 A9000000 call 00401BF0
```
在Eip(0x7781656D)下断,然后F9,分析后容易发现:这个函数也是一个跳转函数,其中配置CONTEXT结构体并转移的代码如下:
```
778165E3 A1 547F4100 mov eax, dword ptr
778165E8 0105 387B4100 add dword ptr , eax
778165EE 16 push ss
778165EF 8F05 4C7B4100 pop dword ptr
778165F5 A1 4C7B4100 mov eax, dword ptr
778165FA A3 487B4100 mov dword ptr , eax
778165FF 0E push cs
77816600 8F05 4C7B4100 pop dword ptr
77816606 A1 4C7B4100 mov eax, dword ptr
7781660B A3 3C7B4100 mov dword ptr , eax
77816610 1E push ds
77816611 8F05 4C7B4100 pop dword ptr
77816617 A1 4C7B4100 mov eax, dword ptr
7781661C A3 187B4100 mov dword ptr , eax
77816621 06 push es
77816622 8F05 4C7B4100 pop dword ptr
77816628 A1 4C7B4100 mov eax, dword ptr
7781662D A3 147B4100 mov dword ptr , eax
77816632 0FA0 push fs
77816634 8F05 4C7B4100 pop dword ptr
7781663A A1 4C7B4100 mov eax, dword ptr
7781663F A3 107B4100 mov dword ptr , eax
77816644 0FA8 push gs
77816646 8F05 4C7B4100 pop dword ptr
7781664C A1 4C7B4100 mov eax, dword ptr
77816651 6A 00 push 0x0
77816653 68 807A4100 push 0x417A80
77816658 A3 0C7B4100 mov dword ptr , eax
7781665D FF15 7C7F4100 call dword ptr ; ntdll.ZwContinue
77816663 90 nop
77816664 FF05 507F4100 inc dword ptr
```
在0x7781665D处下断,拦截每次的Eip值。
拦截以后发现几个有用的函数:
> 0x00401B6D 接受用户名(存放在0x00417D50处)
> 0x00401B8B 接受注册码(存放在0x00417B50处)
> 0x7781668F(函数地址可变) 检查用户名是否是12个字节
> 0x778166BE(函数地址可变) 检查注册码是否是12个字节
> 0x00412057 算法部分
> 0x4123A9 返回结果
**0x1 分析算法**
来到0x00412057处,简单看看代码,发现一堆push call pop之类的指令,这里我使用IDA的F5插件来分析。
![这里写图片描述](http://img.blog.csdn.net/20170926221714323?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
其中的EncryptData:
![这里写图片描述](http://img.blog.csdn.net/20170926221656708?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这里仅仅调用了两个函数:strlen和sub_401000。
我们目前需要做的就是分析出函数sub_401000是干什么的。
IDA进入401000处,发现代码很简短。
![这里写图片描述](http://img.blog.csdn.net/20170926221953251?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这段代码很简短,就是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;
*((_DWORD *)&SuccessfulData + 1) = EncryptData;
*((_DWORD *)&SuccessfulData + 2) = EncryptData;
*(&SuccessfulData + 12) = LOBYTE(EncryptData);
result = 0;
i = 0;
if ( strlen(UserName) != 0 )
{
do
{
ByteOfSerial1 = Serial; // Serial
ByteOfUsername = UserName; // UserName
ByteOfSerial = Serial; // Serial
ByteOfUsername1 = UserName; // UserName
v12 = nor(ByteOfUsername, ByteOfSerial);
v13 = nor(ByteOfSerial, ByteOfSerial);
v14 = nor(ByteOfUsername1, ByteOfUsername1);
v15 = nor(v14, v13);
v16 = nor(v15, v12); // v10 = UserName xor Serial
v17 = nor(UserName, Serial);
v18 = nor(ByteOfSerial, ByteOfSerial);
v19 = nor(ByteOfUsername1, ByteOfUsername1);
v20 = nor(v19, v18);
v21 = nor(v20, v17); // v15 = UserName xor Serial
v22 = nor(v21, v16); // v16 = not(UserName xor Serial)
v23 = nor(ByteOfUsername1, ByteOfUsername1);
v24 = nor(v23, v22); // v18 = UserName and v15
v25 = nor(Serial, v24); // v19 = nor(Serial,UserName and (UserName xor Serial))
v26 = nor(UserName, Serial);
v27 = nor(ByteOfSerial, ByteOfSerial);
v28 = nor(ByteOfUsername1, ByteOfUsername1);
v29 = nor(v28, v27);
v30 = nor(v29, v26); // v24 = UserName xor Serial
v31 = nor(UserName, Serial);
v32 = nor(ByteOfSerial, ByteOfSerial);
v33 = nor(ByteOfUsername1, ByteOfUsername1);
v34 = nor(v33, v32);
v35 = nor(v34, v31); // v29 = UserName xor Serial
v36 = nor(v35, v30); // v30 = not(UserName xor Serial)
v37 = nor(ByteOfUsername1, ByteOfUsername1);
v38 = nor(v37, v36); // v32 = nor(not(UserName),v30)
v39 = nor(Serial, v38); // v33 = nor(Serial,UserName and (UserName xor Serial))
v40 = nor(v39, v25); // v34 = not(v33)
v41 = nor(UserName, Serial);
v42 = nor(ByteOfSerial, ByteOfSerial);
v43 = nor(ByteOfUsername1, ByteOfUsername1);
v44 = nor(v43, v42);
v45 = nor(v44, v41); // v39 = UserName xor Serial
v46 = nor(UserName, Serial);
v47 = nor(ByteOfSerial, ByteOfSerial);
v48 = nor(ByteOfUsername1, ByteOfUsername1);
v49 = nor(v48, v47);
v50 = nor(v49, v46); // v44 = UserName xor Serial
v51 = nor(v50, v45); // v45 = not(UserName xor Serial)
v52 = nor(ByteOfUsername1, ByteOfUsername1);
v53 = nor(v52, v51); // v47 = UserName and (UserName xor Serial)
v54 = nor(Serial, v53); // v48 = nor(Serial,UserName and (UserName xor Serial))
v55 = nor(UserName, Serial);
v56 = nor(ByteOfSerial, ByteOfSerial);
v57 = nor(ByteOfUsername1, ByteOfUsername1);
v58 = nor(v57, v56);
v59 = nor(v58, v55); // v53 = UserName xor Serial
v60 = nor(UserName, Serial);
v61 = nor(ByteOfSerial, ByteOfSerial);
v62 = nor(ByteOfUsername1, ByteOfUsername1);
v63 = nor(v62, v61);
v64 = nor(v63, v60); // v58 = UserName xor Serial
v65 = nor(v64, v59); // v59 = not(v58)
v66 = nor(ByteOfUsername1, ByteOfUsername1);
v67 = nor(v66, v65); // v61 = UserName and (UserName xor Serial)
v68 = nor(Serial, v67); // v62 = nor(Serial,UserName and (UserName xor Serial))
v69 = nor(v68, v54); // v63 = not(v62)
v70 = nor(v69, v40); // v64 = not(v63) -> v64 = v62
UserName = v70; // UserName = v62
v71 = v70; // v65 = v62
v72 = (unsigned __int8)*(&SuccessfulData + i);// v66 = SuccessfulData
v73 = nor(v72, v70);
v74 = nor(v71, v71);
v75 = nor(v72, v72);
v76 = nor(v75, v74); // v70 = SuccessfulData and v62
*(&SuccessfulData + i) = nor(v76, v73); // SuccessfulData = SuccessfulDara xor v62
result = 0;
++i;
}
while ( i < strlen(UserName) );
}
return result;
}
```
总结一下算法:
> 设:用户名为UserName,注册码为Serial,提示信息为SuccessfulData,用户名和注册码的每个字节的关系为x。
> 则有:
> x = nor(Serial,UserName and (UserName xor Serial))
> SuccessfulData = SuccessfulData 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;
char Serial = {0};
scanf("%s",UserName);
if(strlen(UserName) != 12)
return 0;
for(int i = 0;i < 12;i++)
{
if(!(UserName >= 0x21 && UserName <= 0x7F))
return 0;
}
for(int i = 0;i < 12;i++)
{
ContinueWhile:
for(Serial = 0x21;Serial <= 0x7E;Serial++) //scanf函数接受字符串输入时遇到空格截断.
{
if(nor(Serial,UserName and (UserName xor Serial)) == SuccessfulData)
goto Next;
}
/* 当前用户名没有对应的可显示的注册码,尝试更改用户名 */
if(UserName == 0x7E)
UserName -= (Serial - 0x21);
else
UserName++;
if(UserName == 0x20) //空格截断
UserName++;
goto ContinueWhile;
Next:
_asm nop
}
printf("------------------------\nUserName:[%s]\n",UserName);
printf("Serial:[%s]\n",Serial);
system("pause");
return 0;
}
```
运行结果如图所示:
![这里写图片描述](http://img.blog.csdn.net/20170926223456278?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMTk0NTk4NTM=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
ReverseMe的下载链接:http://pan.baidu.com/s/1gf5YC2B 密码:y093
全文完。 帮忙顶一下666 帮忙顶一下 感觉算法好难分析
页:
[1]