第一次写分析文章,第一是分享自己的分析过程,还有一个就是对自己做这道题的一个总结吧。。。
首先打开main函数,发现fgets获取输入,FILE*结构体就是stdin,OD动态跟发现也是我输入的字符串,所以确认这就是输入点
fgets(input, 260, &stru_4090E0);//这里获取输入
inputLen = strlen(input) - 1;//-1是因为fgets会把'\n'也包括进来,所以-1刚好就是输入的长度
if ( inputLen < 8 || inputLen > 20 )// 8<=len<=20
{
printf((int)aKeyLenErrorD__, inputLen);
return 0;
}
v6 = 0;
i = 0;
input[inputLen] = 0; // '\n' -> '\0'
if ( inputLen > 0 ) // i < inputLen
{
do
{
v8 = input[i];
if ( v8 <= '0' || v8 > '9' )
++v6;
++i;
}
while ( i < inputLen ); // 必须是digit 1-9
if ( v6 )
{
printf((int)aKeyFormatError, v22);
return 0;
}
}
这是最基本的判断,接下来调用了一个thiscall的函数,传了局部变量的一个指针,这里猜测这个局部变量是一个类
push esi
mov esi, ecx
mov dword ptr [esi], offset vt1
call ds:GetTickCount
mov ecx, esi
mov [esi+200Ch], eax
mov [esi+2008h], eax
call init_class
进去之后使用了指针后面偏移0x200C和0x2008的DWORD数据,把它赋值为GetTickCount的返回值,然后发现又调用了一个子函数
跟进那个子函数,还有第一个dword是虚表
// about initialization of class CriticalClass:
// 0 vt
// 1 something,
// 2 to 1025 reverse order of arr2
// 1026 to 2049 saves shaffled 0 to 1023
// 2051 to 2050 saves getTickCount
// unit dword
_DWORD *__thiscall init_class(CriticalClass *this)
{
CriticalClass *this_1; // esi@1
signed int i; // eax@1
_DWORD *i_this_p1026_1; // edi@1
_DWORD *i_this_p1026; // ecx@1
int v5; // edx@2
signed int j; // ebp@3
int v7; // ebx@4
int v8; // eax@4
_DWORD *arr1; // eax@5
int *p; // ecx@5
signed int i_; // edx@5
int v12; // esi@6
this_1 = this;
i = 0;
i_this_p1026_1 = this->arr2;
i_this_p1026 = this->arr2;
do
{
++i_this_p1026;
v5 = i++ & 0x3FF; // 0011 1111 1111
*(i_this_p1026 - 1) = v5; // assign from this[1026] to this[1026+1023]
}
while ( i < 1024 ); // traverse from 0 to 0x3FF(1023)
//
// assign 0-1023 to this[1026] to this[1026+1023]
j = 1024;
do
{
v7 = *i_this_p1026_1;
v8 = bit_proc_toTickCount((char *)this_1); // find a random number from 0 to 1023
// and regard it as index,
// exchange thing in it with i_this__
*i_this_p1026_1 = this_1->arr2[v8];
++i_this_p1026_1;
--j;
this_1->arr2[v8] = v7; // exchange the number
}
while ( j ); // j from 1024 to 1
// from [1026] to [1026+1023]
//
// shuffle the array
arr1 = this_1->arr1;
p = &this_1->arr2[1023];
i_ = 1024;
do
{
v12 = *p;
--p;
*arr1 = v12;
++arr1;
--i_;
}
while ( i_ ); // _i from 1024 to 1
// p from [2049] to [1026]
// arr1 from [2] to [1025]
return arr1; // cpy arr1 as the reverse order of arr2
}
第一步,这个函数把[1026](把this看成dword*)往后1024个DWORD赋值为了0-1023
然后bit_proc_toTickCount根据之前用GetTickCount存储在这个class里面的值做了一些位处理,姑且算他是伪随机生成器,然后进行一个数组打乱的算法,用OD看一下也的确是乱着的0-1023
最后,倒序复制[1026]后1024个dword到[2]后的1024个dword
初步分析这个类的结构如下
00000000 CriticalClass struc ; (sizeof=0x2010, mappedto_22) ; XREF: _main/r
00000000 ; _main/r ...
00000000 vt dd ? ; char
00000004 unknown dd ?
00000008 arr1 dd 1024 dup(?)
00001008 arr2 dd 1024 dup(?)
00002008 tickCount1 dd ?
0000200C tickCount2 dd ?
00002010 CriticalClass ends
constructor(&critical_class);
v26 = 0;
init_cc_accToInput(&critical_class, input);
nullsub_1();
调用完刚才的函数之后(八成就是构造函数了),又调用了一个函数,传入了输入作为参数
void __thiscall init_cc_accToInput(CriticalClass *this, const char *input)
{
CriticalClass *this_1; // ebx@1
int i; // esi@1
unsigned int inputLen_plus1; // kr04_4@1
char c; // al@2
signed int v; // eax@4
int v7; // ecx@6
this_1 = this;
i = 0;
inputLen_plus1 = strlen(input) + 1;
this_1->rec = 0;
if ( (signed int)(inputLen_plus1 - 1) > 0 ) // i < len
{
do
{
c = input[i];
if ( c >= '0' && c <= '9' ) // always true
{
v = c - '0';
if ( v )
{
if ( i )
{
v7 = i;
do
{
v *= 10;
--v7;
}
while ( v7 ); // convert to number according to position
// 123
// convert to
// 1
// 20
// 300 in each loop
}
critical_401580(this_1, v);
}
}
++i;
}
while ( i < (signed int)(inputLen_plus1 - 1) );// i < len
}
make_e_smaller_than_10(this_1);
}
这个就是遍历输入字符串,然后根据下标转换成数字,比方说输入“123...”,第一次循环得到的就是1,第二次20,第三次300...
然后把这个v跟着this传到另外一个函数里面去
接下来看critical_401580的内容,顺便看了看xref发现这个函数只有一处引用
void __thiscall critical_401580(CriticalClass *arr, signed int v)
{
signed int v_; // eax@1
int i; // ebx@1
DWORD *i_arr2; // edi@2
int rec; // edx@3
DWORD arr2_1st; // edx@5
signed __int64 v_now; // rtt@5
v_ = v;
i = 0;
if ( v )
{
i_arr2 = arr->arr2;
do
{
rec = arr->rec;
if ( i >= rec ) // 所以i这里最高为9,rec最多被加到10,因为只有32位
{
arr->arr1[arr->arr2[rec]] = 0; // when v is 300, rec is 2 here
++arr->rec; // finnally rec is equal to the length of input
// (not completely true for overflow case)
}
arr2_1st = *i_arr2;
++i_arr2;
v_now = v_;
v_ /= 10;
++i;
arr->arr1[arr2_1st] += v_now % 10; // eg 300, arr1[arr2[2]] will add 3
// the cell that is added to 3 is always the cell that is cleared to zero in the beginning
//
// case is different for overflow
// for overflow, it is possible fro v to not be multiple of 10
}
while ( v_ ); // 循环的次数等于v的位数
// 但是最多循环十次,因为只有32位
}
make_e_smaller_than_10(arr);
} // arr1[arr2[i]]是input的第i个数
这个循环呢仔细看看其实也不难分析,比方说进来一个1234,实际会是4,30,200,1000这样进来(注意这样的话实际在控制台的输入是4321,当然不够八位这里不考虑就举个栗子),他会把4放在[0],3放在[1],2放在[2],1放在[3],而且下标永远不能超过结构体偏移为4的那个整形,如果位数大于了那个数,就会把它++。比方说rec是2,输入1234,循环到2的时候,i>=rec(我们姑且先把+4命名为rec),会把[2]清0,然后rec++。
这里要注意的是,这个存储的方式不是用单个数组。比方说[0],真正用到的是arr1[arr2[0]]来存放,因为arr2里面的值永远在0-1023之间,所以不会越过下标,可以把它理解为两次映射,这可能是作者的一种混淆方式。
实际调用这个函数的时候,传进来的v会是x x0 x00 x000 x0000。。。x是1-9的数字
所以如果不溢出的话,如果我输入192837465
这个类就会按如下方式保存
arr1[arr2[0]] 1
arr1[arr2[1]] 9
arr1[arr2[2]] 2
arr1[arr2[3]] 8
......
溢出的话就不一定是x00...的形式了,因为那样的话要对0x100000000取模,现在暂时不考虑溢出的情况,但不排除溢出的可能性,因为最多能有20位。不过就算溢出,也会使得这个类保存某个数,只不过这个数不直接是输入的数了
接下来再看看这个make_e_smaller_than_10
void __thiscall make_e_smaller_than_10(CriticalClass *this)
{
int rec; // edx@1
int i; // ebx@1
DWORD *p; // esi@2
signed int arr1_arr2_i; // eax@3
int v5; // edx@6
rec = this->rec;
i = 0;
if ( rec > 0 ) // i < rec
{
p = &this->arr2[1];
do
{
arr1_arr2_i = this->arr1[*(p - 1)]; // arr1[arr2[i]]
if ( arr1_arr2_i >= 10 )
{
if ( i == rec - 1 ) // only do this for the last time of loop
{
if ( rec >= 1024 )
return; // probable unlikely to occur
this->rec = rec + 1;
v5 = arr1_arr2_i % 10;
this->arr1[*p] = arr1_arr2_i / 10;
}
else
{
v5 = arr1_arr2_i % 10;
this->arr1[*p] += arr1_arr2_i / 10;
}
this->arr1[*(p - 1)] = v5;
}
rec = this->rec;
++i;
++p;
}
while ( i < rec ); // 使[0..rec)都小于10
// 若大于10,则把多的部分/10放在后面
}
}
如注释所写,他会把所有0到rec-1的数都弄得小于10,如果大了,就把多出来的部分/10放在后面,然后自己%10,如果rec要爆了,就把它++
现在就可以有灵感了,这个类很有可能是一个表示大数的类,表示方法如下
arr1[arr2[i]]存储第i位数字
如数字4567,7就存在[0]处,6在[1],以此类推,其中,rec是数字的位数
那么前面的那个函数,
critical_401580
就是把那个大数加上参数v,因为这样有可能会使个别数字大于等于10,所以调用make_e_smaller_than_10
好的回到main继续分析
multiply2(&critical_class, 9); // v9没用
nullsub_1();
看下multiplty2这个函数(当然现在分析到这里的时候还不知道是multiply。。因为我这个已经分析好了,所以就重命名了,,算剧透一波了。。。。)
signed int __userpurge multiply2@<eax>(CriticalClass *this@<ecx>, signed int a3)
{
int v2; // eax@0
void *v3; // esp@1
CriticalClass *_this; // ebp@1
int i; // edi@1
int v6; // edx@1
signed int num; // eax@1
int num_mod10; // esi@2
int num_div10; // ebx@2
signed int result; // eax@5
CriticalClass v11; // [sp+0h] [bp-402Ch]@1
CriticalClass sum; // [sp+2010h] [bp-201Ch]@1
int v13; // [sp+4020h] [bp-Ch]@1
int (*v14)(); // [sp+4024h] [bp-8h]@1
int v15; // [sp+4028h] [bp-4h]@1
v15 = -1;
v14 = sub_4075D6;
v13 = v2;
v3 = alloca(0x4020); // 两个CriticalClass
_this = this;
constructor(&v11);
i = 0;
v15 = 0;
constructor3(&sum, 0);
num = a3;
LOBYTE(v15) = 1;
if ( a3 )
{
while ( 1 ) // 循环次数是Num的位数
{
v6 = num % 10;
num_mod10 = num % 10;
num_div10 = num / 10;
if ( num % 10 )
{
cc_cpy(&v11, v6, _this);
lsr_critical_class(&v11, i);
multiply_byConst(&v11, num_mod10);
add(&sum, &v11);
if ( getRec(&sum) >= 1024 )
break;
}
num = num_div10; // num /= 10
++i;
if ( !num_div10 ) // if (num == 0)
goto LABEL_5;
}
setVt(&sum);
setVt(&v11);
result = 1;
}
else
{
LABEL_5:
cc_cpy(_this, v6, &sum);
setVt(&sum);
setVt(&v11);
result = 0;
}
return result;
}
这里又出现了一个constructor3,其实里面内容和constructor基本一样,只不过这个函数会直接把rec清0
CriticalClass *__thiscall constructor3(CriticalClass *this, int zero)//cref看,第二个参数永远是0
{
CriticalClass *_this; // esi@1
DWORD v3; // eax@1
_this = this;
this->vt = (int)&vt1;
v3 = GetTickCount();
_this->tickCount2 = v3;
_this->tickCount1 = v3;
init_class(_this);
setRecZero(_this, zero);//进去这个函数,发现如果第二个参数是0的话,做的事情也就是把rec=0而已
return _this;
}
接着分析这4个函数,在循环里面的
cc_cpy(&v11, v6, _this);
lsr_critical_class(&v11, i);
multiply_byConst(&v11, num_mod10);
add(&sum, &v11);
一个一个来,第一个发现这个hexray瞎**给我反编译,所以自己看asm。。。
.text:00401540 ; this->arr1[arr2[i]] = cc3->arr1[arr2[i]]
.text:00401540 ; for i [0..cc3.rec)
.text:00401540 ; this->rec=cc3->rec
.text:00401540 ;
.text:00401540 ; 2nd arg not useful
.text:00401540
.text:00401540 ; int __fastcall cc_cpy(CriticalClass *this, int a2, CriticalClass *cc3)
.text:00401540 cc_cpy proc near ; CODE XREF: multiply2+6Cp
.text:00401540 ; multiply2+BAp ...
.text:00401540
.text:00401540 cc3 = dword ptr 4
.text:00401540
.text:00401540 push esi
.text:00401541 mov esi, [esp+4+cc3] ; esi is cc3
.text:00401545 xor edx, edx
.text:00401547 mov eax, [esi+4] ; eax=cc3->rec
.text:0040154A test eax, eax
.text:0040154C mov [ecx+4], eax ; this->rec=cc3->rec
;还有这个,使this的rec等于cc3的rec
.text:0040154F jle short loc_401579 ; if (cc3->rec > 0)
.text:00401551 push ebx
.text:00401552 push ebp
.text:00401553 push edi
.text:00401554 mov edi, esi
.text:00401556 lea eax, [ecx+4104] ; eax=this->arr2
.text:0040155C sub edi, ecx ; edi is cc3-this
.text:0040155E
.text:0040155E loc_40155E: ; CODE XREF: cc_cpy+34j
.text:0040155E mov ebx, [edi+eax] ; take element in cc3->arr2
.text:00401561 mov ebp, [eax] ; take element in this->arr2
.text:00401563 inc edx ; i++
.text:00401564 add eax, 4 ; eax used to traverse
.text:00401567 mov ebx, [esi+ebx*4+8] ; this->arr1[arr2[i]] = cc3->arr1[arr2[i]]
;这句是关键,说明是把cc3赋给this
.text:00401567 ; for i [0..cc3.rec)
.text:00401567 ; this->rec=cc3->rec
.text:0040156B mov [ecx+ebp*4+8], ebx
.text:0040156F mov ebx, [ecx+4]
.text:00401572 cmp edx, ebx ;循环到this,也就是cc3的rec
.text:00401574 jl short loc_40155E ; do_while(edx < this->rec)
.text:00401576 pop edi
.text:00401577 pop ebp
.text:00401578 pop ebx
.text:00401579
.text:00401579 loc_401579: ; CODE XREF: cc_cpy+Fj
.text:00401579 pop esi
.text:0040157A retn 4
.text:0040157A cc_cpy endp
分析一下发现很简单,他就是使this所存储的数等于cc3所存储的数,相当于一个赋值
// 总结一下,就是把this所存储的数乘上10^a2
signed int __thiscall lsr_critical_class(CriticalClass *this, int a2)
{
signed int result; // eax@2
int this_rec_p_a2; // eax@3
int v4; // eax@5
int *i_arr2_a2; // esi@6
int this_rec_minus1; // eax@6
int *i_arr2; // edx@6
int i; // eax@6
int v9; // ebx@7
int v10; // ebp@7
int *v11; // eax@9
int j; // edx@9
int v13; // edi@10
if ( a2 > 0 ) // 总是 true
{
this_rec_p_a2 = a2 + this->rec;
if ( this_rec_p_a2 <= 1024 ) // 基本上是 true
{
this->rec = this_rec_p_a2;
v4 = this_rec_p_a2 - 1;
if ( v4 >= a2 )
{
i_arr2_a2 = &this->arr2[v4];
this_rec_minus1 = v4 - a2;
i_arr2 = &this->arr2[this_rec_minus1];
i = this_rec_minus1 + 1;
do
{
v9 = *i_arr2;
v10 = *i_arr2_a2;
--i_arr2;
--i_arr2_a2;
--i;
this->arr1[v10] = this->arr1[v9]; // 赋值
}
while ( i ); // i 从1到this->rec
// i_arr2 = arr2[rec-1] 到 arr2[0]
// i_arr2_a2 = arr2[this->rec + a2 - 1] 到 arr2[a2]
//
// 这个循环"右移"CriticalClass里面的数据
}
if ( a2 - 1 >= 0 )
{
v11 = &this->arr2[a2 - 1];
j = a2;
do
{
v13 = *v11;
--v11;
--j;
this->arr1[v13] = 0; // 将 arr1[arr2[0]] 到 arr1[arr2[a2-1]] 清零
}
while ( j ); // j从1到a2
}
result = 0;
}
else
{
result = 1;
}
}
else
{
result = -1;
}
return result;
}
signed int __thiscall multiply_byConst(CriticalClass *this, signed int a2)
{
int i; // edx@3
int *p_arr2; // esi@4
int v4; // eax@5
signed int result; // eax@6
if ( a2 < 0 || a2 >= 10 )
{
result = 1;
}
else
{
i = 0;
if ( this->rec > 0 )
{
p_arr2 = this->arr2;
do
{
v4 = *p_arr2;
++p_arr2;
this->arr1[v4] *= a2; // arr1[arr2[i]] *= a2
//很明显了,把所有位都乘上a2,相当于把这个数乘上a2
++i;
}
while ( i < this->rec );
}
make_e_smaller_than_10(this);
//防止出现一位大于10的情况
result = 0;
}
return result;
}
void __thiscall add(CriticalClass *a1, CriticalClass *a2)
{
int max_rec; // edx@1
int i; // ebx@3
int *p_a1_arr2; // eax@4
signed int diff2_1; // edi@4
int a1_rec; // edx@5
int max_rec_; // [sp+8h] [bp-4h]@1
max_rec = a1->rec;
max_rec_ = max_rec;
if ( max_rec < a2->rec )
{
max_rec = a2->rec;
max_rec_ = a2->rec;
}
i = 0;
if ( max_rec > 0 ) // i < max_Rec
{
p_a1_arr2 = a1->arr2;
diff2_1 = (char *)a2 - (char *)a1;
do
{
a1_rec = a1->rec;
if ( i < a1_rec )
{
if ( i < a2->rec )
a1->arr1[*p_a1_arr2] += a2->arr1[*(int *)((char *)p_a1_arr2 + diff2_1)];// a1->arr1[arr2[i]] += a2->arr1[arr2[i]]
}
else//如果没过a1的rec的话,就直接把对应位加起来,如果过了,就要把a1的rec++,然后直接赋值
{
a1->arr1[a1->arr2[a1_rec]] = a2->arr1[*(int *)((char *)p_a1_arr2 + diff2_1)];//
// a1->arr1[arr2[i]] = a2->arr1[arr2[i]]
// a1->rec++
++a1->rec;
}
++i;
++p_a1_arr2;
}
while ( i < max_rec_ );//本质上呢,就是干的a1+=a2这事
}
make_e_smaller_than_10(a1);//同样,防止大于等于10的数字位
}
那么这4个函数都分析完了,那么来看看这个multiply2为什么是乘吧。。。
signed int __userpurge multiply2@<eax>(CriticalClass *this@<ecx>, signed int a3)
{
int v2; // eax@0
void *v3; // esp@1
CriticalClass *_this; // ebp@1
int i; // edi@1
int v6; // edx@1
signed int num; // eax@1
int num_mod10; // esi@2
int num_div10; // ebx@2
signed int result; // eax@5
CriticalClass v11; // [sp+0h] [bp-402Ch]@1
CriticalClass sum; // [sp+2010h] [bp-201Ch]@1
int v13; // [sp+4020h] [bp-Ch]@1
int (*v14)(); // [sp+4024h] [bp-8h]@1
int v15; // [sp+4028h] [bp-4h]@1
v15 = -1;
v14 = sub_4075D6;
v13 = v2;
v3 = alloca(0x4020); // 两个CriticalClass
_this = this;
constructor(&v11);
i = 0;
v15 = 0;
constructor3(&sum, 0);
num = a3;
LOBYTE(v15) = 1;
if ( a3 )
{
while ( 1 ) // 循环次数是Num的位数
{
v6 = num % 10;
num_mod10 = num % 10;
num_div10 = num / 10;
if ( num % 10 )
{
cc_cpy(&v11, v6, _this);
lsr_critical_class(&v11, i);
multiply_byConst(&v11, num_mod10);
add(&sum, &v11);
if ( getRec(&sum) >= 1024 )
break;
}
num = num_div10; // num /= 10
++i;
if ( !num_div10 ) // if (num == 0)
goto LABEL_5;
}
/*
我直接把解释写注释里了,,,更方便更好看。。。
首先这个循环是根据num这个数字的位数在循环,比方说a3是1234,那么会分别把4 3 2 1取出来
(通过%10,然后/10进行下一次循环,直到Num变成0)
然后我们设this所存储的数是x
那么第一次循环,x放到v11,然后乘上4*10^0,加到sum
第二次,乘3*10^1,加到sum
所以数学式子就是x*4*1 + x*3*10 + x*2*100 + x*1*1000
提公因式,变成x*(4+30+200+1000)
就是1234x
所以以此类推,任何常数都一样
*/
setVt(&sum);
setVt(&v11);
result = 1;
}
else
{
LABEL_5:
cc_cpy(_this, v6, &sum);//this*=a3
setVt(&sum);
setVt(&v11);
result = 0;
}
return result;
}
嗯。。所以这玩意就是乘,至于返回值,就是基本上就会返回0,不正常的情况下(rec>=1024了,明显不会有那么大的数。。。)才会返回1
而且再看看main,0也是我们想要的返回值。。。原因待会说。。。
接着就继续看main
while ( 1 )
{
constructor2(&cc_input, input);//这个其实就是constructor+init_cc_accToInput,很简单不说了。。。
LOBYTE(v26) = 1;
v9 = multiply3(&cc_input, &critical_class, &cc_input);//这个很重要,待会分析
criticalRet2 = multiply2(&critical_class, 9) + v9;
nullsub_1();
if ( criticalRet2 || getRec(&critical_class) % 2 != 1 )// 我们想让这个为false,所以这两个都要是0
//想让criticalRet2为0,那么就要multiply2,3都返回0
// 而且critical_class的rec必须要是奇数
goto LABEL_16; // getRec很简单就直接返回的是this->rec
cc_rec = getRec(&critical_class);
v12 = getDigit(&critical_class, cc_rec >> 1);
v13 = getDigit(&cc_input, 0);//这个getDigit也很简单,就是return this->arr1[this->arr2[a2]]
v14 = &cc_input;
if ( v12 == v13 )
break;//我们想让他break,所以v12要等于v13,
LABEL_17:
LOBYTE(v26) = 0;
setVt(v14);
if ( criticalRet2 )
{
printf((int)aWrongKey___, v22);
goto LABEL_19;
}
}
v15 = getRec(&cc_input) - 1;
v16 = 1 - getRec(&cc_input);
v17 = getRec(&critical_class);
v18 = critical_4013E0(&critical_class, &cc_input, v17 + v16, 1, v15, 0);
v19 = getRec(&cc_input);
if ( critical_4013E0(&critical_class, &cc_input, 0, 1, v19 - 1, 1) + v18 )
//我们想让他为false,所以这两个critical_4013E0必须要是相反数
{
criticalRet2 = 0;
LABEL_16:
v14 = &cc_input;
goto LABEL_17;
}
printf((int)aWellDone, v22);
来看看multiply3
// need this to return 0
// need to see how this affect the cc1 and cc2
signed int __userpurge multiply3@<eax>(CriticalClass *cc1@<eax>, CriticalClass *cc2@<ecx>, CriticalClass *cc1_)
{
void *v3; // esp@1
CriticalClass *cc2_1; // esi@1
int i; // edi@1
int cc2Rec; // ecx@1
int cc1Rec; // eax@1
DWORD *cc1_arr2; // edx@4
signed int v10; // ebp@5
int v11; // edx@7
DWORD *i_cc1_arr2; // [sp+0h] [bp-4030h]@4
CriticalClass cc2_tmp; // [sp+4h] [bp-402Ch]@1
CriticalClass sum; // [sp+2014h] [bp-201Ch]@1
CriticalClass *cc1_1; // [sp+4024h] [bp-Ch]@1
int (__cdecl *v16)(EXCEPTION_RECORD *, struct EHRegistrationNode *, struct _CONTEXT *, void *); // [sp+4028h] [bp-8h]@1
int v17; // [sp+402Ch] [bp-4h]@1
v17 = -1;
v16 = sub_4075F6;
cc1_1 = cc1;
v3 = alloca(0x4024);
cc2_1 = cc2;
constructor(&cc2_tmp);
i = 0;
v17 = 0;
constructor3(&sum, 0);
cc2Rec = cc2_1->rec;
LOBYTE(v17) = 1;
cc1Rec = cc1_->rec;
if ( cc1Rec + cc2Rec > 1024 ) // cc1->rec + cc2->rec <= 1024
{
LABEL_2:
setVt(&sum); // 出问题,就返回1,但基本上不会有那么大的数。。。
setVt(&cc2_tmp);
return 1;
}
if ( cc1Rec > 0 ) // i < ccRec
{
cc1_arr2 = cc1_->arr2;
i_cc1_arr2 = cc1_->arr2;
do
{
v10 = cc1_->arr1[*i_cc1_arr2];
// 在循环中,i_cc1_arr2 总是 &cc1->arr2[i]
cc_cpy(&cc2_tmp, (int)cc1_arr2, cc2_1);//第二个参数没用,IDA抽了
lsr_critical_class(&cc2_tmp, i);
multiply2(&cc2_tmp, v10); // cc1->arr1[arr2[i]]
add(&sum, &cc2_tmp);
if ( cc2_1->rec >= 1024 )
goto LABEL_2;
++i;
++i_cc1_arr2;
}
while ( i < cc1_->rec ); // i从0到cc1->rec
/*
这个跟前面那个multiply2很像,只不过是乘上另外一个CriticalClass
设this为x
也是从最低位开始,x*10^i*cc1的第i位,加起来加到sum
一样的,也是把x提出来,最后发现结果就是cc1*x
*/
}
init_class(cc2_1);
cc_cpy(cc2_1, v11, &sum);//this*=cc1
setVt(&sum);
setVt(&cc2_tmp);
return 0;
}
回到main,看下逻辑
设input输入为x
x先乘上9,然后再乘x,然后再乘9,就是81x^2
设这玩意为y
1.y一定是奇数位数
2.y的(位数>>1)所存的数字一定要跟x的第0位的数字一样
3.如果不一样,还有几次循环的机会,到目前为止就只能分析到这么多
最后看这个critical_4013E0,这个耗了我最久的时间。。。首先反编译效果很差,必须要用asm自己看,而且他分两种情况第一种是第一次调用第二种是第二次调用,我们要一次一次地看。其中this是81x^2,input是x
; unsigned int __thiscall critical_4013E0(CriticalClass *critical_class, CriticalClass *cc_input, int a3, int one, int j_, char a6)
critical_4013E0 proc near
var_4= dword ptr -4
cc_input= dword ptr 4
a3= dword ptr 8
one= dword ptr 0Ch
j_= dword ptr 10h
a6= byte ptr 14h
i = esi
j = edx
this = edi
input = ecx
push input
mov j, [esp+4+j_] ; edx=input_rec
push i
push this
mov this, input ; edi=this
mov input, [esp+0Ch+cc_input] ; ecx=input
mov [esp+0Ch+var_4], 0 ; v4=0
test j, j
jle short loc_401477 ; if (rec > 0),小于等于直接飞走return,这个不可能<=0
mov eax, [esp+0Ch+one] ; eax=1
push ebx
push ebp
mov ebp, [esp+14h+a3] ; ebp=a4
lea ebx, [eax+j-1] ; ebx=input_rec
mov i, eax ; esi = 1
mov [esp+14h+j_], ebx ; 编译器抽风没用系列
sub ebp, eax ; ebp = a4 - 1
进dowhile循环
先看看dowhile循环的变量范围
loc_401468:
mov eax, [esp+14h+j_]
dec eax
inc i ; i 从 1 到 input->rec - 1
dec j ; j 从 input->rec - 1 到 1
; j == input->rec - i
mov [esp+14h+j_], eax ; j_--
jnz short DOWHILE ; j == j_ always
; 循环次数为 input->rec - 1 次
DOWHILE:
mov ebx, [this+4] ; ebx=this->rec
lea eax, [i+ebp] ; eax=a4 - 1 + i
cmp eax, ebx
jl short loc_401426
mov eax, [esp+14h+var_4] ; a4-1+i >= this->rec
; 不可能到这里
;因为i最大是input->rec-1,a4有两种情况0(第二次调用)或者this->rec-input->rec + 1(第一次)
;所以a4-1+i最大是trec-irec+1-1+irec-1,等于trec-1,所以不可能大于等于trec
;PS:待会为了方便起见this->Rec就是trec,input->rec就是irec
inc eax
xor ebx, ebx
mov [esp+14h+var_4], eax ; ebx=0
; v4++
jmp short loc_401428
loc_401426: ; 会永远到这里
; a4-1+i < this->rec
mov ebx, eax ; ebx=a4-1+i
; 第1次: ebx=cc-input+i
; 第2次: ebx=i-1
;ebx待会会用来作为下标,待会说
loc_401428:
call getRec
cmp i, eax
jl short loc_40143E
mov eax, [esp+14h+var_4] ; if (i >= input->rec)
; 同样不可能到达,因为i最大为irec-1
inc eax
mov [esp+14h+var_4], eax ; ++v4
xor eax, eax
jmp short loc_40144C
loc_40143E: ; if (i < input->rec),永远到这
mov al, [esp+14h+a6];a6第一次是0,第二次是1
test al, al
mov eax, [esp+14h+j_] ; 2nd time第二次,eax会是j
jnz short loc_40144C
mov eax, i ; 1st time第一次,eax会是i
loc_40144C:
mov ebx, [this+ebx*4+1008h]
mov eax, [input+eax*4+1008h]
mov ebx, [this+ebx*4+8] ; this->arr1[arr2[ebx]]
cmp ebx, [input+eax*4+8] ; input->arr1[arr2[eax]]
; 现在分析这个就差不多了,之前说我想让两次调用返回相反数
;因为这里只有加,而且不大可能溢出,所以只有让他们都返回0
;所以我们不能让v4++,(v4是返回值的一部分,待会说)
;所以一定要让他们总是相等
; 第一次 :input->arr1[arr2[i]]
; this-> arr1[arr2[cc_Rec-input_rec + i]]
; i = [1..input->rec-1]
;所以x的1位到irec-1位要 和 y(81x^2)的trec-irec+1位到trec-1位一致
;
; 第二次 :input->arr1[arr2[input_rec-i]]
; this-> arr1[arr2[i - 1]]
; i = [1..input->rec-1]
;所以x的irec-1位到1位要 和 y的0位到irec-2位一致,注意这里是倒序一致
jz short loc_401468
inc [esp+14h+var_4] ; 爆破点,nop掉直接过,当然要满足最前面的那两条规则
最后看看返回值
loc_401477:
mov eax, [input+200Ch];还记得那个GetTickCount不
mov i, [this+200Ch]
mov input, [esp+0Ch+var_4]
xor eax, i ;取出来xor右移7位
shr eax, 7 ;基本上这个会是0,因为电脑很快的,但是main里面的while循环跑太多次就不一定了。。。所以上面我分析的要求最好一次达标,就是呢,在y=81x^2的时候达标
pop this
add eax, input;是0的话,就返回v4的值
pop i
pop input
retn 14h
好的,结合前面的两条标准,加上最后分析出的那条标准,我们写出Python爆破器。。。
def takeReverseOrder(num, x):
ret = 0
i = 0
while (i <= x):
ret = ret * 10
ret = ret + (num % 10)
i = i + 1
num = num / 10
return ret
def getNumDigit(x):
ret = 0
while (x != 0):
ret = ret + 1
x = x / 10
return ret
def getDigit(x, n):
return (x / pow(10,n)) % 10
def ifMatch(number, nextNum):
irec = getNumDigit(number)
trec = getNumDigit(nextNum)
tmp = number / 10
nnumD = getNumDigit(nextNum)
if (tmp == nextNum / pow(10, trec - irec + 1)
and tmp == takeReverseOrder(nextNum, irec - 2)
and nnumD % 2 == 1
and number % 10 == getDigit(nextNum, nnumD / 2)):
return True
return False
number = 10000000
while True:
nextNum = number * number * 81
if (ifMatch(number, nextNum)):
print ("correct: %d" % number)
break
else:
if (number % 100000 == 0):
print ("wrong: %d" % number)
number = number + 1
最后输出为
wrong: 11800000
wrong: 11900000
wrong: 12000000
wrong: 12100000
wrong: 12200000
wrong: 12300000
correct: 12345679
PS:汇编太菜,F5用的多,略脚本小子
而且这第三题一出我一看就知道不在我的能力范围内。。。所以直接give up。。。就写了这篇分析。。。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课