0.前言
1.全部是自己实现的,有问题欢迎讨论
2.调试过程中的文件及相关idb
https://github.com/gxkyrftx/kxctf/tree/master/2016.12.%E7%AC%AC28%E9%A2%98
3.教程参考 https://www.kanxue.com/book-35-374.htm
1.程序流程分析
1.0 关闭aslr
使用CFF
把relocation info stripped from file 选上,意思就是从文件中删除的重新定位信息。
然后定位到main函数,f5如下
1.1 main
整体的逻辑 1.获取输入字符串 2.判断长度是否符合要求 3.md5算法,初始化四个参数,计算64轮 4.rc4算法,以一字节为密钥,解密一些东西 具体分析见注释
printf("--- CrackMe By Try2Crack ---\n");
printf("Serial:");
sn = 0;
memset(&Dst, 0, 0x63u); // 内存空间初始化
scanf("%s", &sn); // 获取输入的字符串
length_sn = strlen(&sn); // 获取输入字符串的长度,判断范围在不在[0x3D-0x64]之间
if ( length_sn > 0x64 )
return 1;
if ( length_sn <= 0x3C )
return 1;
v4 = (char *)&v16 + 15;
do
v5 = (v4++)[1];
while ( v5 );
*(_WORD *)v4 = '==';//在sn末尾+“==”
v4[2] = 0;
_mm_storeu_si128((__m128i *)&v16, (__m128i)0i64);//变量赋值
memset(&v11, 0, 0x54u);
v10 = 0;
v11 = 0;
v12 = 0x67452301;
v13 = 0xEFCDAB89;
v14 = 0x98BADCFE; // md5实现过程,与https://github.com/leimingshan/MD5/blob/master/md5c.c基本相同
v15 = 0x10325476; // md5的参数,小端序排列
md5_updata(&sn, (unsigned int *)&v10, strlen(&sn));// 分成512位的块,每个块生成128bit的向量,经过64轮变换
md5_final((unsigned int *)&v10); // 填充
rc4(1, (int)&v20, (int)&word_405020, 0x34A00, &word_405020);// 此处判断为rc4算法,是对word_405020中一些文本进行运算。sn不直接参与运算,因为sn没有出现
v7 = load(v6);
v8 = v7;
if ( !v7 || sub_4012EE(v19 - 48, (int)v7, v21 - 48, v22 - 48) )
return 1;
sub_40143D((unsigned __int8 *)&sn);
sub_402849(v8);
return 0;
}
1.2 md5函数的判断
1.根据四个参数初步确定
2.跟进md5_updata函数,与md5实现算法比较,如下图所示。
3.可以看到,函数结构,参数与md5是相同的,因此确定为md5算法。
1.3 rc4算法判断
1.rc4原理
RC4的原理分为三步:
1、初始化S和T
for i=0 to 255 do
S[i]=i;
T[i]=K[ imodkeylen ];
2、初始排列S
j=0;
for i=0 to 255 do
j= ( j+S[i]+T[i])mod256;
swap(S[i],S[j]);
3、产生密钥流
i,j=0;
for r=0 to len do //r为明文长度,r字节
i=(i+1) mod 256;
j=(j+S[i])mod 256;
swap(S[i],S[j]);
t=(S[i]+S[j])mod 256;
k[r]=S[t];
2.题目中的函数部分内容
do // 以下是KSA(密钥调度算法)过程
v21[v7++] = v6++; // 初始化s[i]
while ( v7 <= 255 );
v8 = 0;
do
{
v20[v8] = *(_BYTE *)(v8 % a1 + v5); // 重复使用密钥
++v8;
}
while ( v8 <= 255 );
v9 = 0;
LOBYTE(v10) = 0;
v11 = 256;
do // 交换s[i],s[j]
{
v12 = v9;
v13 = v21[v9];
v10 = (unsigned __int8)(v10 + v20[v9++] + v13);
v21[v12] = v21[v10];
v21[v10] = v13;
--v11;
}
while ( v11 ); // 以下是prga(伪随机子密码生成算法)及解密过程
if ( a4 - 1 >= 0 )
{
v14 = 0;
v15 = a5;
v19 = a4;
do
{
v11 = (unsigned __int8)(v11 + 1);
v16 = v21[v11]; // 交换s[i]和s[j]
v17 = (unsigned __int8)(v14 + v16);
v21[v11] = v21[v17];
v21[v17] = v16;
v14 += v16;
*v15 = v15[a3 - (_DWORD)a5] ^ v21[(unsigned __int8)(v16 + v21[v11])];//直接把密钥流与明文异或
++v15;
--v19;
}
while ( v19 );
}
return 0;
可以看出两者完成的功能一致,基本可判定为rc4算法。
1.4 rc4运算后的内容
rc4(1, (int)&v20, (int)&word_405020, 0x34A00, &word_405020) 首先我们知道,rc4,是把[word_405020,word_405020+0x34A00]这段空间内的字符做了解密。继续往下看load函数,部分伪代码如下。
v1 = 0;
if ( word_405020 != 'ZM' ) // 判断是不是MZ头
{
SetLastError(0xC1u);
return 0;
}
if ( !length_cmp_34A00((int)this, dword_40505C + 0xF8) )
return 0;
v3 = (char *)&word_405020 + dword_40505C;
if ( *(_DWORD *)((char *)&word_405020 + dword_40505C) != 'EP' || *((_WORD *)v3 + 2) != 0x14C || v3[56] & 1 )// 判断pe头
{
SetLastError(0xC1u);
return 0;
}
可以看到,在这个函数中,判断解密的内容,首部是不是MZ,紧接着还有PE,可以判断解密出来的东西,是一个PE文件。根据这一点,对[word_405020,word_405020+0x34A00]这段空间内的字符,使用rc4算法,进行穷举,只有一个字节的密钥,而且将输入字符集限制在”0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”,穷举可行。穷举代码如下:
#coding : utf-8
class RC4:
def __init__(self, key):
self.K = []
self.S = []
self.key = key
self.S = [s for s in range(256)]
for i in range(256):
self.K.append(self.key[i % len(self.key)])
j = 0
for i in range(256):
j = (j + self.S[i] + self.K[i]) % 256
self.S[i], self.S[j] = self.S[j], self.S[i]
def encrypt(self, data):
i, j = 0, 0
keystream = []
out = []
dataLength = len(data)
for k in range(dataLength):
i = (i + 1) % 256
j = (j + self.S[i]) % 256
tmp = (self.S[i] + self.S[j] % 256) % 256
keystream.append(self.S[tmp])
out.append(data[k] ^ keystream[k])
return out
if __name__ == '__main__':
keystream='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
for x in keystream:
a = RC4([ord(x)])
encryptData = a.encrypt([0xB2, 0xB4])
showList = []
for i in encryptData:
showList.append(hex(i))
print(showList),x
调了一个小时。。哎,水平一天不如一天。。 最终结果如下:密钥为I
关于密钥位置的确定,可以通过ida动态调试的方法,例如,将“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”作为序列号直接输入,可以看到,经过rc4函数后,单步步过f8,然后双击v20,可以看到我输入之后,v20,到底被赋了什么样的值。我的如下图:
然后下面这张图是密钥输入时被加载的起始位置
将0x0019fef8-0x0019fed0=0x28=40,也就是说,输入的序列号sn[40]是rc4的密钥。
1.5 求解lua.dll
刚才穷举除了密钥为“I”,如何解密这段内容?我使用了ida动态调试,在rc4函数的地方下断点,然后run,输入序列号(位数在[0x3D-0x64]之间),为了方便,我就输70个I,结果如下。
然后f8,一直f8,等到调用rc4之后,会发现,解密出来了。如下图
然后将这段pe文件dump出来,具体操作:file——>script commend,输入idc脚本如下
#include<idc.idc>
static main(void)
{
auto i,fp;
fp=fopen("e:\\dump_encode.bin","wb");
for(i=0x405020;i<0x405020+0x34A00;i++)
fputc(Byte(i),fp);
}
得到了dump_encode.bin,因为是pe文件,直接上ida,可以看到导出表中的导出函数,所以它是lua.dll
1.6 load函数
这个需要动态调试,可以看到,这个函数里面一直在不停的调用alloc等这样的分配空间的函数,结合自己调试与大佬的分析,这是在进行内存分段加载,text,data,rdata等
单步步过load函数,看到ecx中已经加载了lua.dll,所以此函数功能为,分段加载lua.dll
1.7 sub_4012EE
在此处中调用的:if ( !v7 || sub_4012EE(v19 - 48, (int)v7, v21 - 48, v22 - 48) ),其中v7来源于load函数,返回了到了lua.dll。v19,v21,v22的内容,通过动态调试也可以找到,只介绍v19,如下图:
0x0019feee-0x0019fed0=0x1e=30,也就是sn[30]。 下面开始分析函数功能,函数内容如下:
BOOL __usercall sub_4012EE@<eax>(int a1@<edx>, int a2@<ecx>, int a3, int a4)
{
int v4; // esi
_DWORD *v5; // edi
int (__cdecl *v6)(_DWORD, _DWORD); // eax
v4 = a1;
v5 = (_DWORD *)a2;
dword_443240 = (int (*)(void))sub_4027A5((_DWORD *)a2, (const char *)1);
dword_443220 = (int (__cdecl *)(_DWORD, _DWORD))sub_4027A5(v5, (const char *)(v4 - 5));
dword_44221C = (int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD))sub_4027A5(v5, (const char *)5);
dword_442214 = (int (__cdecl *)(_DWORD, _DWORD))sub_4027A5(v5, (const char *)7);
dword_44323C = (int (__cdecl *)(_DWORD, _DWORD))sub_4027A5(v5, (const char *)8);
dword_443234 = (int (__cdecl *)(_DWORD))sub_4027A5(v5, (const char *)9);
dword_442218 = (int (__cdecl *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD))sub_4027A5(v5, (const char *)(2 * a4 + 4));
dword_443230 = (int (__cdecl *)(_DWORD, _DWORD))sub_4027A5(v5, (const char *)0xA);
dword_442210 = (int (__cdecl *)(_DWORD, _DWORD))sub_4027A5(v5, (const char *)0xB);
dword_443224 = (int (__cdecl *)(_DWORD, _DWORD, _DWORD))sub_4027A5(v5, (const char *)0xC);
dword_443228 = (int (__cdecl *)(_DWORD))sub_4027A5(v5, (const char *)0xD);
dword_443244 = (int (__cdecl *)(_DWORD, _DWORD, _DWORD))sub_4027A5(v5, (const char *)(a3 + 2));
v6 = (int (__cdecl *)(_DWORD, _DWORD))sub_4027A5(v5, (const char *)2);
dword_44322C = v6;
return !dword_443240
|| !v6
|| !dword_443220
|| !dword_442218
|| !dword_44221C
|| !dword_443244
|| !dword_442214
|| !dword_44323C
|| !dword_443234
|| !dword_443230
|| !dword_442210
|| !dword_443224
|| !dword_443228;
}
1.8 sub_4027A5
观察到其中多次调用sub_4027A5函数,他有两个参数参数a1,dll中的一个位置;a2是一个常量,但是不停的在变化,1,7,8,9,A,B,C,D之类的。可以联想到pe中的基础知识,既然加载了dll,那么肯定要使用它的导出函数,这个是pe中最重要的,导出函数的使用有两种方式,一种是按函数名,一种是按函数序号。既有地址,又有常量,猜测是按序号使用dll中的导出函数。下面看看它在干嘛
int __fastcall sub_4027A5(_DWORD *a1, const char *a2)
{
int v2; // ebx
unsigned int v3; // edi
_DWORD *v4; // esi
unsigned int v5; // ecx
_DWORD *v6; // ecx
const char *v8; // [esp+Ch] [ebp-Ch]
int v9; // [esp+10h] [ebp-8h]
unsigned __int16 *v10; // [esp+14h] [ebp-4h]
v2 = a1[1];
v3 = 0;
v8 = a2;
if ( !*(_DWORD *)(*a1 + 0x7C) )
goto LABEL_10;
v4 = (_DWORD *)(v2 + *(_DWORD *)(*a1 + 0x78));
if ( !v4[6] && !v4[5] )
goto LABEL_10;
if ( !HIWORD(a2) )
{
if ( (unsigned int)(unsigned __int16)a2 >= v4[4] )
{
v5 = (unsigned __int16)a2 - v4[4];
goto LABEL_13;
}
LABEL_10:
SetLastError(0x7Fu);
return 0;
}
v6 = (_DWORD *)(v2 + v4[8]);
v9 = v2 + v4[8];
v10 = (unsigned __int16 *)(v2 + v4[9]);
if ( v4[6] <= 0u )
goto LABEL_10;
while ( stricmp(a2, (const char *)(v2 + *v6)) )
{
++v3;
++v10;
v6 = (_DWORD *)(v9 + 4);
a2 = v8;
v9 += 4;
if ( v3 >= v4[6] )
goto LABEL_10;
}
v5 = *v10;
LABEL_13:
if ( v5 > v4[5] )
goto LABEL_10;
return v2 + *(_DWORD *)(v4[7] + 4 * v5 + v2);
}
a1中的内容,由于按小端徘地址,a1的内容为0x000000F0。
debug017:00474538 db 0F0h
debug017:00474539 db 0
分别加了0x7c=0x16c,加了0x78=0x168之后,得到结果如图所示,确定是对lua.dll导出表的操作无疑,然后也大体确定了1.7 sub_4012EE中,多次调用sub_4027A5是在按函数序号导出函数。这些dword
字段,实际就是导出函数的名字。
1.9 判断sub_4012EE中的参数内容
将sub_4012EE中dword字段的函数名字,与lua.dll导出函数中的名字一一对应,发现最后又三个函数名称是无法确定的,需要根据sub_4012EE中的参数内容分析。如下图所示
其中三个未确定的函数,是因为他们的序号由输入决定。现在需要根据函数名称,去寻找函数序号。如何寻找?这三个函数的参数的数量是不一样的,根据这一点判断出来,分别对应参数个数为5,3,2.
1.10 sub_40143D
然后进入sub_40143D,可以看到,里面在各种调用lua.dll中的函数,把不知道的函数,和参数一对比,就可确定函数名称,从而确定函数序号,最后确定sn某一位的值,在此以上面的v19也就是sn[30]为例,sub_40143D关于by_a1_sn_30_的部分代码如下:
v1 = this;
v2 = luaL_newstate();//分配空间
luaL_openlibs(v2);//加载标准类库
dword_44322C(v2, v1);这是lua_pushstring,没有改过来,功能:将这个值设置到栈,赋值
by_a1_sn_30_(v2, &unk_404138);//这是lua_setglobal,功能为设置全局变量,点进去unk_404138就是lua脚本中的L
两个参数,对应lua_setglobal,
int __cdecl lua_setglobal(int a1, int a2)
函数序号为3,根据
dword_443220 = (int (__cdecl *)(_DWORD, _DWORD))sub_4027A5(v5, (const char *)(v4 - 5));
倒推得到v4为8, 根据
if ( !v7 || lua_call(sn[30] - 0x30, (int)v7, sn[46] - 0x30, sn[58] - 0x30) )
倒退的到sn[30]的asill码为0x38,就是‘8’ 同理可得sn[46] == ‘4’ ,sn[58] == ‘0’
1.10 luaL_loadbufferx
luaL_loadbufferx(v2, &unk_439A60, 33876, &unk_40413C, 0) 从439A60开始的33876字节,里面又是一大串文本,结合上一步刚调用了rc4,动态调试一下,在luaL_loadbufferx处下断点,然后输入一串满足要求: sn[30] == ‘8’ && sn[40] == ‘I’ && sn[46] == ‘4’ && sn[58] == ‘0’的字符串 可以看到如下结果
把这一段dump出来,脚本如下。
#include<idc.idc>
static main(void)
{
auto i,fp;
fp=fopen("e:\\stepa.luac","wb");
for(i=0x439A60;i<0x439A60+0x8454;i++)
fputc(Byte(i),fp);
}
然后使用工具,对stepa.luac文件进行编译,工具使用如下
https://github.com/viruscamp/luadec
git clone https://github.com/viruscamp/luadec
cd luadec
git submodule update --init lua-5.3
cd lua-5.3
make linux
cd ../luadec
make LUAVER=5.3
编译完成后,进入luadec找到可执行文件luadec,然后输入,进行编译
./luadec stepa.luac
得到结果如图所示,得到了一个lua脚本。
代码如下
-- params : ...
-- function num : 0 , upvalues : _ENV
ZZBase64 = {}
-- DECOMPILER ERROR at PC70: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.__code = {"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "+", "/"}
-- DECOMPILER ERROR at PC73: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.__decode = {}
for k,v in pairs(ZZBase64.__code) do
-- DECOMPILER ERROR at PC87: Confused about usage of register: R5 in 'UnsetPending'
(ZZBase64.__decode)[(string.byte)(v, 1)] = k - 1
end
-- DECOMPILER ERROR at PC92: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.encode = function(text)
-- function num : 0_0 , upvalues : _ENV
local len = (string.len)(text)
local left = len % 3
len = len - left
local res = {}
local index = 1
for i = 1, len, 3 do
local a = (string.byte)(text, i)
local b = (string.byte)(text, i + 1)
local c = (string.byte)(text, i + 2)
local num = a * 65536 + b * 256 + c
for j = 1, 4 do
local tmp = (math.floor)(num / 2 ^ ((4 - j) * 6))
local curPos = tmp % 64 + 1
res[index] = (ZZBase64.__code)[curPos]
index = index + 1
end
end
if left == 1 then
(ZZBase64.__left1)(res, index, text, len)
else
if left == 2 then
(ZZBase64.__left2)(res, index, text, len)
end
end
return (table.concat)(res)
end
-- DECOMPILER ERROR at PC95: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.__left2 = function(res, index, text, len)
-- function num : 0_1 , upvalues : _ENV
local num1 = (string.byte)(text, len + 1)
num1 = num1 * 1024
local num2 = (string.byte)(text, len + 2)
num2 = num2 * 4
local num = num1 + num2
local tmp1 = (math.floor)(num / 4096)
local curPos = tmp1 % 64 + 1
res[index] = (ZZBase64.__code)[curPos]
local tmp2 = (math.floor)(num / 64)
curPos = tmp2 % 64 + 1
res[index + 1] = (ZZBase64.__code)[curPos]
curPos = num % 64 + 1
res[index + 2] = (ZZBase64.__code)[curPos]
res[index + 3] = "="
end
-- DECOMPILER ERROR at PC98: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.__left1 = function(res, index, text, len)
-- function num : 0_2 , upvalues : _ENV
local num = (string.byte)(text, len + 1)
num = num * 16
tmp = (math.floor)(num / 64)
local curPos = tmp % 64 + 1
res[index] = (ZZBase64.__code)[curPos]
curPos = num % 64 + 1
res[index + 1] = (ZZBase64.__code)[curPos]
res[index + 2] = "="
res[index + 3] = "="
end
-- DECOMPILER ERROR at PC101: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.decode = function(text)
-- function num : 0_3 , upvalues : _ENV
local len = (string.len)(text)
local left = 0
if (string.sub)(text, len - 1) == "==" then
left = 2
len = len - 4
else
if (string.sub)(text, len) == "=" then
left = 1
len = len - 4
end
end
local res = {}
local index = 1
local decode = ZZBase64.__decode
for i = 1, len, 4 do
local a = decode[(string.byte)(text, i)]
local b = decode[(string.byte)(text, i + 1)]
local c = decode[(string.byte)(text, i + 2)]
local d = decode[(string.byte)(text, i + 3)]
local num = a * 262144 + b * 4096 + c * 64 + d
local e = (string.char)(num % 256)
num = (math.floor)(num / 256)
local f = (string.char)(num % 256)
num = (math.floor)(num / 256)
res[index] = (string.char)(num % 256)
res[index + 1] = f
res[index + 2] = e
index = index + 3
end
if left == 1 then
(ZZBase64.__decodeLeft1)(res, index, text, len)
else
if left == 2 then
(ZZBase64.__decodeLeft2)(res, index, text, len)
end
end
return (table.concat)(res)
end
-- DECOMPILER ERROR at PC104: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.__decodeLeft1 = function(res, index, text, len)
-- function num : 0_4 , upvalues : _ENV
local decode = ZZBase64.__decode
local a = decode[(string.byte)(text, len + 1)]
local b = decode[(string.byte)(text, len + 2)]
local c = decode[(string.byte)(text, len + 3)]
local num = a * 4096 + b * 64 + c
local num1 = (math.floor)(num / 1024) % 256
local num2 = (math.floor)(num / 4) % 256
res[index] = (string.char)(num1)
res[index + 1] = (string.char)(num2)
end
-- DECOMPILER ERROR at PC107: Confused about usage of register: R0 in 'UnsetPending'
ZZBase64.__decodeLeft2 = function(res, index, text, len)
-- function num : 0_5 , upvalues : _ENV
local decode = ZZBase64.__decode
local a = decode[(string.byte)(text, len + 1)]
local b = decode[(string.byte)(text, len + 2)]
local num = a * 64 + b
num = (math.floor)(num / 16)
res[index] = (string.char)(num)
end
dk = function(k, l)
-- function num : 0_6 , upvalues : _ENV
(ZZBase64.decode)(text)
end
t1{一个数组,很长~} //上面的都是base64相关的,有解码,有编码。
_ENV.cp = (ZZBase64.decode)(L)//对l解码,l来源于sub_40143D,中的设置全局变量,其实就是输入的sn,对sn进行base64解码
if ((_ENV.string).len)(_ENV.cp) ~= 46 then//解码后的长度不是46,退出
_ENV.g_v1 = nil
end
if (_ENV.cp)[1] ~= "." then//解码后的[1]的位置不是.,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[2] ~= "t" then//解码后的[2]的位置不是t,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[3] ~= "r" then//解码后的[3]的位置不是r,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[4] ~= "y" then//解码后的[4]的位置不是y,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[5] ~= "2" then//解码后的[5]的位置不是2,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[6] ~= "c" then//解码后的[6]的位置不是c,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[7] ~= "r" then//解码后的[7]的位置不是r,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[8] ~= "a" then//解码后的[8]的位置不是a,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[9] ~= "c" then//解码后的[9]的位置不是c,退出
_ENV.g_v1 = nil
else
if (_ENV.cp)[10] ~= "k" then//解码后的[10]的位置不是k,退出
_ENV.g_v1 = nil
end
end
end
end
end
end
end
end
end
end
_ENV.sk = ((_ENV.string).sub)(_ENV.cp, 2, 10)//要求全部满足,sk就是2-10位“try2crack”
_ENV.g_v1 = ((_ENV.string).sub)(_ENV.cp, 11)//g_v1是11位后面的
if _ENV.g_v1 ~= 0 then//判断一下不是0,return t1{一个大数组}
return t1
else
return nil
end
1.11 lua赋值
1.10说到,最后luaL_loadbufferx,如果满足条件,返回了一个大的数组,随后的这个for循环,就是在给byte_442220进行赋值,动态调试可以看到,进入for循环,一直f7,可以看到byte_442220的 值一直在改变。for循环里调用了lua的函数
for ( i = lua_next(v2, -2); i; i = lua_next(v2, -2) )
{
v4 = lua_tointeger(v2, -1, 0);
v5 = dword_443238;
byte_442220[dword_443238] = v4;
dword_443238 = v5 + 1;
lua_settop(v2, -2);
}
结果如下图所示
1.12 lua_getglobal
紧接下来的lua_getglobal
if ( !lua_getglobal(v2, "sk") )
{
v6 = lua_tolstring(v2, -1, 0);
printf("error\n", v6);
return 1;
}
寻找了sk这个变量,在lua脚本里有,也就是“try2crack”
随后的主要代码,v7来源于v2,是字符串“try2crack”,可以静态看到,以下功能就是对“try2crack”求md5
v14 = 0;
v16 = 0x67452301;
Dst = 0;
v17 = 0xEFCDAB89;
v18 = 0x98BADCFE;
v19 = 0x10325476;
md5_updata(v7, (unsigned int *)&v14, strlen(v7));
md5_final((unsigned int *)&v14);
验证通过,则调用rc4算法,解密另外一个脚本
1.13 rc4第二次解密
rc4(16, (int)&v20, (int)byte_442220, dword_443238, byte_442220);
下断点,输入满足上述所有条件要求的sn,然后可以看到,这个rc4又解密了一个stepb.lua,把它dump出来,上面的脚本改一下就好了。其余流程一样。可以得到stepb.lua,他的内容为
-- params : ...
-- function num : 0 , upvalues : _ENV
getn = function(t)
-- function num : 0_0 , upvalues : _ENV
local x = 0
for k,v in pairs(t) do
x = x + 1
end
return x
end
bs = function(ar, mode)
-- function num : 0_1 , upvalues : _ENV
local l = getn(ar)//获取ar的长度,ar是一个table,表
for i = 1, l - 1 do//有点像冒泡法排序,一个一个向后走
for j = 1, l - i do
-- DECOMPILER ERROR at PC23: Unhandled construct in 'MakeBoolean' P1
if mode ~= 0 and ar[j + 1] < ar[j] then//mode==1返回最小值
ar[j] = ar[j + 1]
end
if ar[j] < ar[j + 1] then//mode==0返回最大值
ar[j] = ar[j + 1]
end
end
end
end
vc = function(k)
-- function num : 0_2 , upvalues : _ENV
local x = (math.sqrt)((string.len)(k))//参数k的长度然后sqrt,根号36=6,x=6
local r = {}
local idx = 1
local s = 0
for i = 1, x do//二维的,看一下就想起了6*6矩阵,计算各行的值
for j = 1, x do//j循环,计算一行的值
s = s + (string.byte)(k, j + (i - 1) * x)//
end
r[idx] = s//计算出一行的值,放入另外的一个数组r里
s = s - (s)//归零
idx = idx + 1//行+1,下一行
end
for i = 1, x do//同上,计算各列的值
for j = 1, x do
s = s + (string.byte)(k, i + (j - 1) * x)
end
r[idx] = s
s = s - (s)
idx = idx + 1
end
for i = 1, x do//这是在计算主对角线之和
s = s + (string.byte)(k, i + (i - 1) * x)
end
r[idx] = s
s = s - (s)
idx = idx + 1
for i = 1, x do//这是在计算副对角线之和
s = s + (string.byte)(k, (x - i) * x + i)
end
r[idx] = s
s = s - (s)
idx = idx + 1
//以上的vc函数,是一个六阶幻方。
local a = {}
local b = {}
bs(r, 1)//最小值表
for i = 1, getn(r) do
a[i] = r[i]
end
bs(r, 0)//最大值表
for i = 1, getn(r) do
b[i] = r[i]
end
for i = 1, getn(a) do//对于表中的每一个元素,判断是否相等,也就是判断幻方是不是成立
if a[i] ~= b[i] then
return 1
end
end
return 0
end
r = vc(g_v1)//g_v1是作为参数,调用vc函数,g_v1是啥?1.10中的lua脚本出现的,它是base64解码后的字符串,2-10位为sk,11位之后的都是g_v1,11-46,总计36个字符。vc函数的功能在上面。
if r == 0 then
g_r = "锟斤拷证通锟斤拷"//这里应该是“验证通过”,解码出问题了,因为下面的是nil,退出。
else
g_r = nil
end
没解过幻方,大佬提供了一个思路,手动构造一个幻方,然后进行穷举,找到满足条件的幻方。
1.14 其他函数
也没其他了,最后就剩下一个了,sub_402849,这是内存释放的。算法分析完毕。
2.破解代码
先说整题的验证要求:
1.sn[30] == ‘8’ && sn[40] == ‘I’ && sn[46] == ‘4’ && sn[58] == ‘0’
2.输入序列号后判断长度在不在[0x3D-0x64]之间
3.末尾加上“==”
4.base64解码后的结果分为两部分,一部分解码后是“.try2crack”,另外一部分的36个字符,满足6阶幻方
#include "stdafx.h"
#include <string.h>
const char * base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
char * base64_encode(const unsigned char * bindata, char * base64, int binlength)
{
int i, j;
unsigned char current;
for (i = 0, j = 0; i < binlength; i += 3)
{
current = (bindata[i] >> 2);
current &= (unsigned char)0x3F;
base64[j++] = base64char[(int)current];
current = ((unsigned char)(bindata[i] << 4)) & ((unsigned char)0x30);
if (i + 1 >= binlength)
{
base64[j++] = base64char[(int)current];
base64[j++] = '=';
base64[j++] = '=';
break;
}
current |= ((unsigned char)(bindata[i + 1] >> 4)) & ((unsigned char)0x0F);
base64[j++] = base64char[(int)current];
current = ((unsigned char)(bindata[i + 1] << 2)) & ((unsigned char)0x3C);
if (i + 2 >= binlength)
{
base64[j++] = base64char[(int)current];
base64[j++] = '=';
break;
}
current |= ((unsigned char)(bindata[i + 2] >> 6)) & ((unsigned char)0x03);
base64[j++] = base64char[(int)current];
current = ((unsigned char)bindata[i + 2]) & ((unsigned char)0x3F);
base64[j++] = base64char[(int)current];
}
base64[j] = '\0';
return base64;
}
int _tmain(int argc, _TCHAR* argv[])
{
unsigned char buf[256] = { 0 };
char key[20] = { 0 };
#define N 6
unsigned char xx[N][N] = {
1, 32, 31, 33, 5, 9,
12, 8, 31, 27, 11, 22,
20, 17, 15, 18, 18, 23,
17, 19, 21, 22, 18, 14,
35, 26, 5, 9, 29, 7,
26, 9, 8, 2, 30, 36,
};
unsigned char ss[128] = { ".try2crack" };
for (unsigned char x = 0; x < 0xff - 36; x++)
{
int k = 10;
for (int i = 0; i < N; i++)
{
int s = 0;
for (int j = 0; j < N; j++)
{
ss[k++] = xx[i][j] + x;
}
}
base64_encode(ss, (char *)buf, k);
if (buf[30] == '8' && buf[40] == 'I' && buf[46] == '4' && buf[58] == '0')
break;
}
printf("%s", buf);
return 0;
}
完结,程序输出结果,及验证结果如下图