这个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 [0x417F78], 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 [0x417F7C], eax
继续往下走,发现程序设置了CONTEXT结构体然后调用ZwContinue函数。
00401ABB 8D05 3D1B4000 lea eax, dword ptr [0x401B3D] ; 返回地址
00401AC1 50 push eax
00401AC2 9C pushfd
00401AC3 8F45 FC pop dword ptr [ebp-0x4]
00401AC6 8B45 FC mov eax, dword ptr [ebp-0x4]
00401AC9 8945 F0 mov dword ptr [ebp-0x10], eax
00401ACC 8965 FC mov dword ptr [ebp-0x4], esp
00401ACF 8B45 FC mov eax, dword ptr [ebp-0x4]
00401AD2 8945 F4 mov dword ptr [ebp-0xC], eax
00401AD5 896D FC mov dword ptr [ebp-0x4], ebp
00401AD8 8B45 FC mov eax, dword ptr [ebp-0x4]
00401ADB C785 30FFFFFF 0>mov dword ptr [ebp-0xD0], 0x10007 ; CONTEXT_FULL
00401AE5 8945 E4 mov dword ptr [ebp-0x1C], eax
00401AE8 A1 547F4100 mov eax, dword ptr [0x417F54]
00401AED 8945 E8 mov dword ptr [ebp-0x18], eax ; Eip
00401AF0 16 push ss
00401AF1 8F45 FC pop dword ptr [ebp-0x4]
00401AF4 8B45 FC mov eax, dword ptr [ebp-0x4]
00401AF7 8945 F8 mov dword ptr [ebp-0x8], eax
00401AFA 0E push cs
00401AFB 8F45 FC pop dword ptr [ebp-0x4]
00401AFE 8B45 FC mov eax, dword ptr [ebp-0x4]
00401B01 8945 EC mov dword ptr [ebp-0x14], eax
00401B04 1E push ds
00401B05 8F45 FC pop dword ptr [ebp-0x4]
00401B08 8B45 FC mov eax, dword ptr [ebp-0x4]
00401B0B 8945 C8 mov dword ptr [ebp-0x38], eax
00401B0E 06 push es
00401B0F 8F45 FC pop dword ptr [ebp-0x4]
00401B12 8B45 FC mov eax, dword ptr [ebp-0x4]
00401B15 8945 C4 mov dword ptr [ebp-0x3C], eax
00401B18 0FA0 push fs
00401B1A 8F45 FC pop dword ptr [ebp-0x4]
00401B1D 8B45 FC mov eax, dword ptr [ebp-0x4]
00401B20 8945 C0 mov dword ptr [ebp-0x40], eax
00401B23 0FA8 push gs
00401B25 8F45 FC pop dword ptr [ebp-0x4]
00401B28 8B45 FC mov eax, dword ptr [ebp-0x4]
00401B2B 6A 00 push 0x0
00401B2D 8945 BC mov dword ptr [ebp-0x44], eax
00401B30 8D85 30FFFFFF lea eax, dword ptr [ebp-0xD0]
00401B36 50 push eax
00401B37 FF15 7C7F4100 call dword ptr [0x417F7C] ; ntdll.ZwContinue
00401B3D 68 3C514100 push 0041513C ; pause
00401B42 E8 A9000000 call 00401BF0
在Eip(0x7781656D)下断,然后F9,分析后容易发现:这个函数也是一个跳转函数,其中配置CONTEXT结构体并转移的代码如下:
778165E3 A1 547F4100 mov eax, dword ptr [0x417F54]
778165E8 0105 387B4100 add dword ptr [0x417B38], eax
778165EE 16 push ss
778165EF 8F05 4C7B4100 pop dword ptr [0x417B4C]
778165F5 A1 4C7B4100 mov eax, dword ptr [0x417B4C]
778165FA A3 487B4100 mov dword ptr [0x417B48], eax
778165FF 0E push cs
77816600 8F05 4C7B4100 pop dword ptr [0x417B4C]
77816606 A1 4C7B4100 mov eax, dword ptr [0x417B4C]
7781660B A3 3C7B4100 mov dword ptr [0x417B3C], eax
77816610 1E push ds
77816611 8F05 4C7B4100 pop dword ptr [0x417B4C]
77816617 A1 4C7B4100 mov eax, dword ptr [0x417B4C]
7781661C A3 187B4100 mov dword ptr [0x417B18], eax
77816621 06 push es
77816622 8F05 4C7B4100 pop dword ptr [0x417B4C]
77816628 A1 4C7B4100 mov eax, dword ptr [0x417B4C]
7781662D A3 147B4100 mov dword ptr [0x417B14], eax
77816632 0FA0 push fs
77816634 8F05 4C7B4100 pop dword ptr [0x417B4C]
7781663A A1 4C7B4100 mov eax, dword ptr [0x417B4C]
7781663F A3 107B4100 mov dword ptr [0x417B10], eax
77816644 0FA8 push gs
77816646 8F05 4C7B4100 pop dword ptr [0x417B4C]
7781664C A1 4C7B4100 mov eax, dword ptr [0x417B4C]
77816651 6A 00 push 0x0
77816653 68 807A4100 push 0x417A80
77816658 A3 0C7B4100 mov dword ptr [0x417B0C], eax
7781665D FF15 7C7F4100 call dword ptr [0x417F7C] ; ntdll.ZwContinue
77816663 90 nop
77816664 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
全文完。