【160 crackme 之52 】 注册码算法分析
本帖最后由 6767 于 2017-12-24 21:18 编辑160CM 里面的三个5星难度之一
## 052.esgis.1
`UPX 0.89 - 3.xx -> Markus & Laszlo ver. [ 0.76 ] <- from file.`
A classic viusal C++ programe, compiled at 1998.
### Headers
长文预警
本文大概有三千个字
先上图;
!(SuccessfulCracked.png)
使用linxue的脱壳机去掉packer,或者使用通用oep大法,upx主要是压缩没有加密,反正也不难。
追踪 引用字符串找到函数结点;
```c
int __thiscall sub_401C20(void *this) {
void *_this; // ebp@1
unsigned int len_plus1; // kr20_4@1
char *v3; // edi@1
int v4; // eax@1
int Ntimes64; // esi@3
int ii; // edi@3
int result; // eax@6
signed int j; // esi@7
unsigned int *guessPtr; // edi@7
signed int i; // eax@9
DWORD Type; // @1
DWORD cbData; // @1
HKEY phkResult; // @1
int guessHex; // @5
char v15; // @5
char v16; // @5
char v17; // @5
int C0_FF_0; // @1
int v19; // @3
char userString; // @1
BYTE tmp; // @1
CHAR guessString; // @1
_this = this;
memset(userString, 0, sizeof(userString));
memset(&guessString, 0, 0x1F4u);
memset(&tmp, 0, 0x1F4u);
getStrFromTextBox(this + 92, userString, 100);
getStrFromTextBox(_this + 172, &guessString, 100);
strcpy(&tmp, userString);
_strrev(&tmp); // input1 reversed
strcat(userString, &tmp); // normal+reversed str
RegOpenKeyA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion", &phkResult);
Type = 1;
cbData = 256;
RegQueryValueExA(phkResult, "ProductID", 0, &Type, &tmp, &cbData);
Type = 1;
strcat(userString, &tmp);
cbData = 256;
RegQueryValueExA(phkResult, "RegisteredOwner", 0, &Type, &tmp, &cbData);
strcat(userString, &tmp); // 1234_4321_4321_4321
len_plus1 = strlen(userString) + 1;
setConst_4016E0(C0_FF_0);
memset(&userString, 0, 76u);
v3 = &userString;
*v3 = 0;
v3 = 0;
v4 = 64 - (len_plus1 & 0x3F);
userString = 0x80u;
if ( v4 <= 7 )
v4 += 64;
Ntimes64 = v4 + len_plus1;
ii = 0;
for ( *(&v19 + v4 + len_plus1) = 8 * strlen(userString); ii < Ntimes64; ii += 64 )
MD5like_401700(&userString, C0_FF_0); // MD5 like
//
C0_FF_0 = LOWORD(C0_FF_0);
if ( sscanf(&guessString, "%lx%lx%lx%lx", &guessHex, &v15, &v16, &v17) == 4 ) { // guess text like :'0x12341234 0x12341234 0x12341234 0x12341234'
j = 0;
guessPtr = &guessHex;
do {
rolN_401B90(guessPtr, 0xBADC0DE / (j++ + 0x50));
++guessPtr;
} while ( j < 3 );
i = 0;
while ( *(&guessHex + i * 4) == C0_FF_0 ) {
++i;
if ( i >= 4 )
return msgbox_4100A8("... Man, you're good enough to join CORE! ...", "Welcome", 0x40u);
}
result = msgbox_4100A8("... Better luck next time ...", "Failed", 0x30u);
} else {
result = msgbox_4100A8("... Hmmm, you don't even pass the first threshold ...", "Failed", 0x30u);
}
return result;
}
```
主要函数就在这个block里。
内存地址:0x19ed24
inputtext1.会操作3次得到一个4倍长度的strings1(Win10注册表是空,直接复制3次),inputtext2以`%lx`的方式读入4个,然后调用sub_401B90循环移位N次得到一个16bytes的字符串,与strings1 的类似MD5摘要结果进行比较。
```
input1->(like MD5 digest)——┐
cmp---(Y/N)
input2->(ROL+strange+1)___」
```
### MD5 like func
使用PEid的Krypto 插件检查算法会发现有且仅有md5算法,
地址块 :`dword_41B0B0 `;
所有的常数(F G H I)里面的都是对的,包括`UPX0:setConst_4016E0`函数 sets initial magic number;
但是,怎么看这个函数都不是正常的标准md5,也没有动力去分析一个修改过的md5;
考虑到在`UPX0:00401E56`还and了一个0xffff,表明直接爆破md5是不可能的了;
不过可以在内存地址里得到摘要值;
- Note Here: 注意啦,已经有更好的md5分析了 https://www.52pojie.cn/thread-677861-1-1.html
对应于 "0000"的输入,摘要值是
```
0019ED34|BB A2 00 00 B8 2E 9A A3|42 25 88 E8 70 B8 C4 9D|........B%..p...
```
这16bytes数据;
### ROL strange array
这个函数是最有意思的函数;开始的时候发现它每次循环修改2 int,
`c,c,c`,
循环3次刚刚好全部的16bytes
查看反汇编伪代码
```c
char *__cdecl rolN_401B90(unsigned int *ptr, int const1)
{
char *result; // eax@1
int cnt; // ebx@1
unsigned int Lsi; // esi@1
unsigned int Hdi; // edi@1
unsigned __int64 tt0; // rax@2
int t1; // esi@2
int t2; // edi@2
unsigned __int64 tt; // rax@2
result = ptr;
cnt = const1;
Lsi = *ptr;
Hdi = ptr;
if ( const1 )
{
do
{
tt0 = 2 * __PAIR__(Hdi, Lsi);
LODWORD(tt0) = ((2 * Lsi) | (Hdi >> 31)) & 4;
t1 = tt0 | (Hdi >> 31);
t2 = HIDWORD(tt0);
tt = tt0 << 11;
LODWORD(tt) = t1 & 0x2000 ^ tt;
tt <<= 18;
LODWORD(tt) = t1 & 0x80000000 ^ tt;
tt *= 2i64;
Lsi = tt ^ t1;
Hdi = HIDWORD(tt) ^ t2;
--cnt;//IDA监控点
}
while ( cnt );
result = ptr;
}
*result = Lsi;
*(result + 1) = Hdi;
return result;
}
```
不好意思,研究了好久发现,这个代码是错误的,但是只有试过才大概了解到一大堆位运算在干嘛;
在这个地方卡了好久,花时间修改导出的C代码,发现是错误的计算结果;
经过汇编跟踪比较,发现伪代码错误,放弃;
用IDA dump脚本监控前几百次运行是这样的:
```c
ebx, esi, edi,
0,00255F35,00000001,00000002,0000000200000001
1,00255F35,00000002,00000004,0000000400000002
2,00255F34,00000004,00000009,0000000900000004*
3,00255F33,00000008,00000012,0000001200000008
4,00255F32,00000010,00000024,0000002400000010
5,00255F31,00000020,00000048,0000004800000020
6,00255F30,00000040,00000090,0000009000000040
7,00255F2F,00000080,00000120,0000012000000080
8,00255F2E,00000100,00000240,0000024000000100
9,00255F2D,00000200,00000480,0000048000000200
A,00255F2C,00000400,00000900,0000090000000400
B,00255F2B,00000800,00001200,0000120000000800
C,00255F2A,00001000,00002400,0000240000001000
D,00255F29,00002000,00004801,0000480100002000*
E,00255F28,00004000,00009002,0000900200004000
```
#### Try Brute force
这里走歪了;
这个函数 接受 4 uint,运行后和md5like 的摘要比较,得到是否正确;
前面说到了,直接用导出的fake C code是不能用的,于是我们想要爆破的话,直接用汇编代码来运行就好了嘛;
这里主要写内联汇编,把rolN_401B90 函数的汇编代码提取出来,在vs2015里改写接口部分,
```c
__declspec(naked)
char* __cdecl rolN_401B90(unsigned int *Cptr, int Cconst1) {
//checked match original funcs
#define ptr 4
#define const1 8
__asm {
mov eax,
push ebx
mov ebx,
push esi
mov esi,
push edi
...MORE...
RETZERO:; CODE XREF : __allshl + 3j
xor eax, eax
xor edx, edx
retn
}
#undef ptr
#undef const1
}
```
运行了一个测试,发现是正确的;
于是写了一个爆破Generator function 来初始化输入值,每10000次打个log;
结果是,原始汇编代码运行1W次时间,4~5minutes,
如果要爆破完16bytes,需要集群了,emm.....
有没有土豪赞助我一车GTX1080和i7 7700K 吗
直接爆破不行,就需要分析这个汇编blocks在干嘛,这里现在看来是**最最重要的**;
前面调试可以看到全部是位运行,既然是数学,这里使用符号传递来尝试分析;事实证明这个方法是正确的。
```assembly
; char* __declspec(naked) __cdecl rolN_401B90(unsigned int *ptr, int const1)
rolN_401B90 proc near ; CODE XREF: sub_401C20+284p
;ptr = dword ptr4
;const1 = dword ptr8
mov eax,
push ebx
mov ebx, ;cnt;
push esi
mov esi, ;c
push edi
mov edi, ;c
test ebx, ebx
jbe short loc_401C15
push ebp
loop1: ; CODE XREF: rolN_401B90+7Ej
mov ebp, edi
mov ecx, 1 ;ecx=1;
shr ebp, 1Fh
mov , ebp;get c.bit31;
mov eax, esi
mov edx, edi
xor ebp, ebp ;ebp=0
call __allshl; edx,eax <<1
;eax=c<<1
;edx=c<<1|c>>31;
mov ecx, ;ecx=c>>31
or ebp, edx ; mov ebp,edx;
;ebp=c<<1 | c>>31;
or ecx, eax ;ecx=c>>31|(c<<1);
xor edx, edx ;edx=0;
mov esi, ecx ;esi=c>>31|(c<<1);
mov ecx, 11 ;ecx=11
mov eax, esi ;eax=c>>31|(c<<1);
mov edi, ebp ;edi=c<<1 | c>>31 ;
and eax, 4 ;eax=(c>>31|(c<<1)) & 4;
call __allshl
;eax=((c.bit31|(c<<1)) & 4)<<11
;eax=((c>>31|(c<<1)) & 4)<<11
;eax=((c<<1) & 4)<<11
;eax=( c&2 ) <<12
;edx=0
mov ecx, esi ;ecx=c>>31|(c<<1)
xor ebp, ebp ;ebp=0
and ecx, 2000h ;ecx=(c>>31|(c<<1)) & 0x2000
;ecx=(c<<1) & 0x2000
xor edx, ebp ;NOP;ebp==0
xor eax, ecx ;eax=(((c<<1) & 4)<<11) ^ (c<<1 & 0x2000)
;eax=( ( c&2 ) <<12) ^ (c<<1 & 0x2000)
mov ecx, 12h ;ecx=18;
call __allshl
;eax=( (( c&2 ) <<12) ^ (c<<1 & 0x2000)) <<18
;edx=( (( c&2 ) <<12) ^ (c<<1 & 0x2000)) >>14 Always edx==0
;
mov ecx, esi ;ecx=c>>31|(c<<1);
xor edx, ebp ;NOP,ebp==0;
and ecx, 80000000h ;ecx=c.bit31|(c<<1) & 0x8000_0000;
;ecx=(c<<1) & 0x8000_0000;
xor eax, ecx
;eax=(((( c&2 ) <<12) ^ (c<<1 & 0x2000))<<18 )^( (c<<1) & 0x8000_0000 )
mov ecx, 1 ;ecx=1
call __allshl
;eax=((((( c&2 ) <<12) ^ (c<<1 & 0x2000))<<18 )^( (c<<1) & 0x8000_0000 ))<<1;
;edx=(((( c&2 ) <<12) ^ (c<<1 & 0x2000)) >> 14)<<1 |( (((( c&2 ) <<12) ^ (c<<1 & 0x2000))<<18 )^( (c<<1) & 0x8000_0000 ) )>>31
xor esi, eax
;c=(c>>31|(c<<1)) ^( ((((( c&2 ) <<12) ^ (c<<1 & 0x2000))<<18 )^( (c<<1) & 0x8000_0000 ))<<1)
; ^ ((c.bit1<<31 ^ c.bit12<<31 ^ c.bit30<<31 )<<1)
;c=(c>>31|(c<<1)) ^ 0
;c= c>>31|(c<<1)
xor edi, edx
;c=(c<<1 | c>>31) ^( (((( c&2 ) <<12) ^ (c<<1 & 0x2000)) >> 14)<<1 |( (((( c&2 ) <<12) ^ (c<<1 & 0x2000))<<18 )^( (c<<1) & 0x8000_0000 ) )>>31 )
; ( c.bit1<<13^c.bit12<<13 )>>14<<1
;c=(c<<1 | c>>31) ^( 0 |(c.bit1<<31 ^ c.bit12<<31 ^ c.bit30<<31 )>>31)
;c=(c<<1 | c>>31) ^(c.bit1 ^ c.bit12 ^ c.bit30 )
;c=(c<<1 | c>>31) ^(c>>1 ^c>>12 ^c>>30)&1
;c= c<<1 ^ (c>>31 ^c>>1 ^c>>12 ^c>>30)&1
dec ebx
jnz short loop1
mov eax,
pop ebp
loc_401C15: ; CODE XREF: rolN_401B90+12j
mov , esi
mov , edi
pop edi
pop esi
pop ebx
retn
endp
__allshl proc near ; CODE XREF: rolN_401B90+29p
; rolN_401B90+46p ...
; cmp cl, 40h
; jnb short RETZERO
; cmp cl, 20h
; jnb short MORE32
shld edx, eax, cl
shl eax, cl
retn
; ---------------------------------------------------------------------------
MORE32: ; CODE XREF: __allshl+8j
mov edx, eax
xor eax, eax
and cl, 1Fh
shl edx, cl
retn
; ---------------------------------------------------------------------------
RETZERO: ; CODE XREF: __allshl+3j
xor eax, eax
xor edx, edx
retn
endp
```
简单来说,
被操作的2个uint 会循环N次,
每次操作
```c
c=(c<<1 | c>>31) ^(c>>1 ^c>>12 ^c>>30)&1
c= c>>31|(c<<1)
```
差不多就是两个uint32 相互循环移位,附加c一个特殊的最低位XOR,条件是c的c.bit1 ^ c.bit12 ^ c.bit30 的异或 ;
得到这个算法以后写了个C代码测试,验证正确,结果又尝试了爆破,嗯,在编译器O2优化下 2mins/1W 计数;
不行,这个速度太慢了,于是按照逻辑写了个纯汇编的函数,手写的主要循环体优化后只有9行汇编代码,运行速度1mins/1W 计数,还是不够快
### 柳暗花明又一村
算法已知是迭代的,普通不能爆破,那就只能找算法的特性/漏洞了,比如通项函数什么的。
但是,上面的C表述基本不能看出什么;
于是就画了好几个算法运行的示意图
最后就这个看出端倪了。
!(draft1.jpg)
可以回代运算(N+1)项得到第N项;
核心是 c的n项的0~30bit在c的n+1项的1~31bit, cn项缺少的 31bit可以从c的n+1项的31,13,2和c的n+1项的最低位这四个东西异或得到;
C代码表述
```c
char* rolN_reverse(uint32_t* ptr, int cnt) {
uint32_t n, n_1;
n = ptr;
n = ptr;
printf("[-]Get: %X %X\n", n, n);
for (int i = 0; i < cnt; i++) {
n_1 = (n >> 1 & 0x7fffffff)|((n&1 ^ n>>31^ n>>13^ n>>2)&1)<<31;
n_1 = (n >> 1 &0x7fffffff )|n<<31;
n = n_1;
n = n_1;
}
printf("[-]Rev: %X %X\n", n, n);
ptr = n;
ptr = n;
return ptr;
}
```
写了个函数验证,结果正确;
不考虑奇怪的MD5,
最后把注册机(的½)写一下就是
```c
#define V0 0xA2BB
#define V1 0XA39A2EB8
#define V2 0XE8882542
#define V3 0X9DC4B870
#define CV0 0x255f35
#define CV1 0x24e918
#define CV2 0x2475dd
uint32_t knownArray = {V0,V1,V2,V3};
rolN_reverse(knownArray + 2, CV2 );
rolN_reverse(knownArray + 1, CV1);
rolN_reverse(knownArray + 0, CV0);
for (int i = 0; i < 4; i++) {
printf(" 0x%X", knownArray);
}
/*
[-]Get: E8882542 9DC4B870
[-]Rev: D4787858 54130406
[-]Get: A39A2EB8 D4787858
[-]Rev: 8C0D43AF 50B025BA
[-]Get: A2BB 8C0D43AF
[-]Rev: 2C6B2A6D EF4CABB9
input:0000
input:0x2C6B2A6D 0xEF4CABB9 0x50B025BA 0x54130406
*/
```
所以,
!(https://attach.52pojie.cn/forum/201708/22/071449b2v11o3s2ivvg4vi.png)
成功完成任务;
期间各种奇奇怪怪的bug和错误歧途就不再描述了。。。
### Summary
- 内联汇编,x86 asm
- Z3符号引擎 不知道能不能用上帮助分析,手动分析汇编逻辑还是太慢了;
- 不要一条路走到黑,比如爆破什么的。~( ̄▽ ̄)~*
- 笔和草稿纸需常备案头,in case of something;
- ...
欢迎大家点评/闲聊
防止乱码在这里
撸了一个注册机,
这个CM.052终于完整了
楼主是否可以贴暴力测试的代码。我测试没通过,感觉是指针问题。谢谢
#include <stdio.h>
#define V0 0x12345
#define V1 0X23456
#define V2 0X41A4309F
#define V3 0X6F6A7DCB
#define CV0 0x255f35
#define CV1 0x24e918
#define CV2 0x2475dd
typedef unsigned __int32 uint32_t;
int * rolN_reverse( int *Cptr,int Cconst1)
{
/*
uint32_t n, n_1;
n = ptr;
n = ptr;
printf("[-]Get: %X %X\n", n, n);
for (int i = 0; i < cnt; i++) {
n_1 = (n >> 1 & 0x7fffffff)|((n&1 ^ n>>31^ n>>13^ n>>2)&1)<<31;
n_1 = (n >> 1 &0x7fffffff )|n<<31;
n = n_1;
n = n_1;
}
printf("[-]Rev: %X %X\n", n, n);
ptr = n;
ptr = n;
returnptr;
}
*/
// int b;
//v7=v1;
#define ptr 4
#define const1 8
_asm{
mov eax,
push ebx
mov ebx, ;cnt;
push esi
mov esi, ;c
push esi
mov edi, ;c
test ebx, ebx
push ebp
jbe short loc_401C15
loop1:
mov ebp, edi
mov ecx, 1
shr ebp, 1Fh
mov , ebp;
mov eax, esi
mov edx, edi
xor ebp, ebp
;call __allshl;
shld edx, eax, cl
shl eax, cl
mov ecx,
or ebp, edx
or ecx, eax
xor edx, edx
mov esi, ecx
mov ecx, 0Bh
mov eax, esi
mov edi, ebp
and eax, 4
;call __allshl
shld edx, eax, cl
shl eax, cl
mov ecx, esi
xor ebp, ebp
and ecx, 2000h
xor edx, ebp
xor eax, ecx
mov ecx, 12h
;call __allshl
shld edx, eax, cl
shl eax, cl
;
mov ecx, esi
xor edx, ebp
and ecx, 80000000h
xor eax, ecx
mov ecx, 1
;call __allshl
shld edx, eax, cl
shl eax, cl
xor esi, eax
xor edi, edx
dec ebx
jnz short loop1
mov eax,
pop ebp
loc_401C15:
mov , esi
mov , edi
;popad
;pop edi
;pop esi
;pop ebx
;retn
popad
}
#undef ptr
#undef const1
}
int main()
{
int knownArray = {V0,V1,V2,V3};
rolN_reverse(knownArray + 2, CV2 );
rolN_reverse(knownArray +1, CV1);
rolN_reverse(knownArray +0, CV0);
//for(int i = 0; i < 4; i++) {
// printf(" 0x%X", knownArray);
//}
return 0;
} 代码测试通过,谢谢楼主。
#include <stdio.h>
#define V0 0x12345
#define V1 0X23456
#define V2 0X34567
#define V3 0X45678
#define CV0 0x255f35
#define CV1 0x24e918
#define CV2 0x2475dd
typedef unsigned __int32 uint32_t;
__declspec(naked) char * rolN_reverse(uint32_t *Cptr,int Cconst1)
{
/*
uint32_t n, n_1;
n = ptr;
n = ptr;
printf("[-]Get: %X %X\n", n, n);
for (int i = 0; i < cnt; i++) {
n_1 = (n >> 1 & 0x7fffffff)|((n&1 ^ n>>31^ n>>13^ n>>2)&1)<<31;
n_1 = (n >> 1 &0x7fffffff )|n<<31;
n = n_1;
n = n_1;
}
printf("[-]Rev: %X %X\n", n, n);
ptr = n;
ptr = n;
returnptr;
}
*/
// int b;
//v7=v1;
#define ptr 4
#define const1 8
_asm{
mov eax,
push ebx
mov ebx,
push esi
mov esi,
push edi
mov edi,
test ebx, ebx
jbe short loc_401C15
push ebp
loc_401BA5: ; CODE XREF: sub_401B90+7Ej
mov ebp, edi
mov ecx, 1
shr ebp, 1Fh
mov , ebp
mov eax, esi
mov edx, edi
xor ebp, ebp
;call __allshl
shld edx, eax, cl
shl eax, cl
mov ecx,
or ebp, edx
or ecx, eax
xor edx, edx
mov esi, ecx
mov ecx, 0Bh
mov eax, esi
mov edi, ebp
and eax, 4
;call __allshl
shld edx, eax, cl
shl eax, cl
mov ecx, esi
xor ebp, ebp
and ecx, 2000h
xor edx, ebp
xor eax, ecx
mov ecx, 12h
;call __allshl
shld edx, eax, cl
shl eax, cl
mov ecx, esi
xor edx, ebp
and ecx, 80000000h
xor eax, ecx
mov ecx, 1
;call __allshl
shld edx, eax, cl
shl eax, cl
xor esi, eax
xor edi, edx
dec ebx
jnz short loc_401BA5
mov eax,
pop ebp
loc_401C15: ; CODE XREF: sub_401B90+12j
mov , esi
mov , edi
pop edi
pop esi
pop ebx
retn
}
#undef ptr
#undef const1
}
int main()
{
uint32_t knownArray = {V0,V1,V2,V3};
rolN_reverse(knownArray + 0, CV0 );
rolN_reverse(knownArray +1, CV1);
rolN_reverse(knownArray +2, CV2);
for(int i = 0; i < 4; i++) {
printf(" 0x%X", knownArray);
}
return 0;
} 好厉害啊
对了 五星难度和问号的 哪个难度高一些? 本帖最后由 6767 于 2017-8-22 10:14 编辑
zbnysjwsnd8 发表于 2017-8-22 09:09
好厉害啊
对了 五星难度和问号的 哪个难度高一些?
你可以做做那些带问号的,给出你的评分啊{:301_997:}
不加壳的话,难点就自我校验,暗桩,强力的混淆算法这几个了。
看了看160里面其他两个5星的,都是2000年左右写的,宏观上结构和现在这个差不多。
6767 发表于 2017-8-22 09:15
你可以做做那些带问号的,给出你的评分啊
不加壳的话,难点就自我校验,暗桩,强力的混淆算 ...
我做的几个带问号的注册方式都是serial的
好像都挺简单的
没有这个难(应该是我做的太少了 6767 发表于 2017-8-22 09:15
你可以做做那些带问号的,给出你的评分啊
不加壳的话,难点就自我校验,暗桩,强力的混淆算 ...
第90个和第132个
这个是我做的带问号的
我觉得没有楼主你玩的这个CM难
来学习一下,谢谢 谢谢分享。 就凭那手工图也要加个精华鼓励一下!{:1_926:} 来学习了 这图画的真好