您的位置:首页 > 其它

算法逆向第1篇——简单算法逆向

2017-12-26 18:09 351 查看


本文作者:icq5f7a075d


Music起~

“啊门 啊前 一棵葡萄树,啊嫩 啊嫩 绿地刚发芽,蜗牛背着那重重地壳呀,一步一步地往上爬~”

上周分析勒索病毒(GlobeImposter),笔者感觉自己算法的逆向能力很弱,于是找了些题目练手。本文逆向程序主要来源于《加密与解密实战攻略》,算法将用Python/C++实现。

既然有了算法逆向1,自然会有2、3、4……叶问说“我要打十个!”,笔者说“我要写十篇!”,甭管行不行,先立Flag! ——>文末有福利

1. 简单的算法逆向

预计时间:2h

1.1. 定位关键位置先随便输入,弹出了错误提示:





搜索字符串:Invalid!此时已经找到了关键位置。








上下翻找一番,0×00404167就是关键跳转,如果是crackme,那么直接把这里修改为jmp就可以了。而我们要进行算法逆向,就要追踪分析ebx和esi这两个值怎么来的。

1.2. 算法逆向

往上回溯,ebx的值来自于下图,过程还是比较简单,对输入name字符串进行计算,得到一个值,以这个值为编号,在表0x4050B8中取值:








往上回溯,esi的值来自于此,也比较简单对输入密码字符串进行计算:





算法逆向源码(python):

calc_table=[0x19791126,0x19791007,0x11261979,0x10071979,\

0x12345678,0x9abcdef0,0x12123434,0x78787878,\

0xccc6ccc6,0xcc00cc00,0xffefefff,0xddcc5555,\

0x67678787,0xcececbcc,0x778899ab,0x44337766]

 

def calc_name(name):

sum_name=0

for i  in range(0,len(name)):

sum_name=sum_name+i*ord(name)

#print hex(sum_name)

calc_name1=(sum_name&0xFFFF)+((((2*len(name)+0×63)&0xFFFF)<<0×10))

#print hex(calc_name1)

calc_name2=calc_name1&0xF

return calc_name2

def calc_pass(password):

str_pass='0'+password

edx=0

for i in range(0,len(str_pass)):

edx=edx*2

edx=edx+edx*4

edx=edx+ord(str_pass[i])-0×30

return edx

name_in = raw_input("Input your name:")

pass_in=raw_input("Input your pass:")

print hex(calc_table[calc_name(name_in)])

print hex(calc_pass(pass_in))


2. 解方程算法逆向

预计时间:4h

本程序虽然叫解方程算法,但是解方程算法只是本程序算法中的一小部分。

2.1. 定位关键算法

运行程序,随便输入name和serial,点击check没有任何反应。直接拖入IDA,程序的函数很少,一眼就可以看到,我们要逆向的算法在DialogFunc和sub_401141中:





DialogFunc中流程很简单,检测到Check按钮点击事件就会进入如下的处理过程:





第一次调用GetDlgItemTextA获取输入的Serial,第二次调用GetDlgItemTextA获取输入的Name,对Name的长度进行判断,当Name长度大于5并小于32,则调用sub_401141进行计算,计算结果正确则弹出正确对话框,否则,重新等待用户输入。

要分析的算法处于sub_401141,第一次参数是Name的长度,第二次参数是Serial的长度,0×403084存放输入的Serial,0×403044存放输入的Name。

2.2. 算法分析

(本小节中的代码尽量遵循汇编语句,所以比较凌乱,下小节有整合后的源码)

首先是一个先循环,倒序取Name中的字符进行计算,结果存放在dword_4030C4中:





这段代码有循环移位操作,需要使用自定义函数实现,在下文中会对移位指令进行介绍,

本部分源码(C++):

unsigned ebx=0×1;

unsigned d_4030c4=0×0;

char cl;

for(int i=len_name;i>0;){

i–;

unsigned edx=name[i];

edx=edx ^ ebx;

edx=edx * ebx;

ebx+=0×5;

d_4030c4=d_4030c4 ^ edx;

d_4030c4=leftrot(d_4030c4,5);

}


d_4030c4=0-d_4030c4-1;//这里必须要减去1

d_4030c4=right_rot(d_4030c4,len_name);

char b_4030c8='\0';

对Name的计算完毕之后,对Serial进行计算,但是计算Serial计算是将其分为三部分:'-'字符、'-'字符前的字符,'-'字符后的字符,下图是计算是否存在'-'字符,并取'-'字符前的字符:





这部分源码(C++):

for(int eax=0;eax!=len_serial;){

char edx_c=serial[eax];

eax++;

if(edx_c=='-'){//Serial中必须有'-'字符

char al=(char)eax;

b_4030c8=–al;

eax=len_serial;}

}

if(b_4030c8!=0){

cl='\0';

cl= b_4030c8;

}

else{

return -1;

}


'-'字符前的字符的字符ASCII区间(0x3F,0x5B)





这部分源码(C++):

int cl_i=(int)cl;

for(;cl_i!=0;)

{

cl_i=cl_i-1;

char edx_c=serial[cl_i];

if(edx_c<=0x3F || edx_c>=0x5B)

{

return -1;}

}


计算’-’前的字符,判断得到的值和最初计算Name得到的值是否相同,且Serial中’-’后的字符个数是3





这部分源码(C++):

cl=b_4030c8;

cl_i=(int)cl;

int ebx_2=0;

for(;cl_i!=0;)

{

cl_i=cl_i-1;

char edx_c=serial[cl_i];

ebx_2*=0x1A;

edx_c=serial[cl_i];

edx_c=edx_c-'\x41';

ebx_2=ebx_2+int(edx_c);

}

if(ebx_2!=d_4030c4)

{return -1;}

int eax=len_serial;

eax=(eax/0×100)*0×100+(eax%0×100-(int)b_4030c8)%0×100;

if(eax!=4){

return -1;

}


计算’-’后的3个字符









这部分源码(C++):

int j=(int)b_4030c8;

char b_4030c9=serial[j+1];

char b_4030ca=serial[j+2];

char b_4030cb=serial[j+3];

eax=(int)b_4030c9;

eax*=3;

unsigned d_4030cc=eax;

eax=0;

ebx=(int)b_4030c9;

ebx*=7;

unsigned d_4030d0=eax-ebx;

unsigned d_4030d4=(int)b_4030c9;

d_4030cc=d_4030cc-(int)b_4030ca;

d_4030d0=(int)b_4030ca*2+d_4030d0;

d_4030d4=d_4030d4+(int)b_4030ca;

d_4030cc=d_4030cc+(int)b_4030cb*5;

d_4030d0=d_4030d0+(int)b_4030cb*7;

d_4030d4=d_4030d4-(int)b_4030cb*2;

if(d_4030cc==0×204 && d_4030d0==0×19 &&d_4030d4==0x0D)

{

printf("OK!\n");

return 0;

}


2.3. 源码整合

[size=18.6667px]源码(C++)

#include <iostream>

#include <string.h>

using namespace std;

 

//elfbin:0x8501675c

char* str_n="elfbin";//test 

unsigned len_n=strlen(str_n);

char* str_s="YWSCVFH-RAC";//test

unsigned len_s=strlen(str_s);

 

 

unsigned leftrot(unsigned a,int n) //RCL

{

        unsigned b;

        b=(a >> (32 – n)) | (a << n);

        return b;

}

unsigned rightrot(unsigned a,int n)

{

        unsigned b;

        b=(a << (32 – n)) | (a >> n);

        return b;

}

int main(){

        //计算name,得到32位数值; 

        unsigned f_n=0×0;

        char cl;

        int i=len_n;

        int j=1;

        for(;i>0;){

                i–;

                f_n^=(str_n^j)*j;

                j+=5;

                f_n=leftrot(f_n,5);

        }

        f_n=0-f_n-1;//这里必须要减去1 ,取反 

        f_n=rightrot(f_n,len_n);

 

 

        //Serial中必须有'-'字符,第一个 字符不能是'-'字符,'-'字符前的字符应该是大写或@ 

        char l_s='\0';        

        for(i=0;i!=len_s;i){

                if(str_s=='-'){

                        l_s=(char)i;

                        i=len_s;

                        }

                        else

                          i++;

        }

        if(l_s==0){

                return -1;

        }

 

        for(i=(int)l_s;i!=0;)

        {

                i–;

                if(str_s<=0x3F || str_s>=0x5B)

                        {return -1;} 

        } 

 

        //计算Serial, 倒序取"-"字符前的字符,其在倒序字符串中的索引*在字母表中的位置 求和 

        int f_s=0;        

        for(i=(int)l_s;i!=0;)

        {

                i–;

                f_s*=0x1A;//=26

                f_s=f_s+int(str_s)-'\x41';//int(serial)-'\x41',在字母表中的位置 

                 } 

        if(f_s!=f_n) // 计算Serial得到的值等于计算Name得到的值 

                {;return -1;} 

 

 

        //serial '-'字符后面还有3个字符 

        if((len_s-(int)l_s)!=4){

                return -1;

        }

 

        //计算Serial后三位 

        unsigned A,B,C;

        char a=str_s[(int)l_s+1];

        char b=str_s[(int)l_s+2];

        char c=str_s[(int)l_s+3];

        A=(int)a*3;

        B=-(int)a*7;

        C=(int)a;

        A=A-(int)b;

        B=(int)b*2+B;

        C=C+(int)b;

        A=A+(int)c*5;

        B=B+(int)c*7;

        C=C-(int)c*2;

        //0×204=a*3-b+c*5 

        //0×19=a*7+b*2+c*7

        //0x0D=a+b-c*2

        if(A==0×204 && B==0×19 &&C==0x0D)

        {

                printf("OK!\n");

                return 0;

        } 

        printf("%x,%x,%x\n",A,B,C);

        return -1;

 

} 

[i]


2.4. 注册机





计算Serial最后三字节才是真正的解方程算法,方程组如下:

3a-b+5c=0×204

7a+2b+7c=0×19

a+b-2c=0x0D

注册机源码(C++)

#include <iostream>

#include <string.h>

using namespace std;

 

unsigned leftrot(unsigned a,int n)

{

unsigned b;

b=(a >> (32 – n)) | (a << n);

return b;

}

unsigned rightrot(unsigned a,int n)

{

unsigned b;

b=(a << (32 – n)) | (a >> n);

return b;

}

int main(){

 

char str_n[30];//test

char str_s[30];

unsigned len_n;

cout<<"Name:";

cin>>str_n;

len_n=strlen(str_n);

//计算name,得到32位数值;

unsigned f_n=0×0;

char cl;

int i=len_n;

int j=1;

for(;i>0;){

i–;

f_n^=(str_n[i]^j)*j;

j+=5;

f_n=leftrot(f_n,5);

}

f_n=0-f_n-1;

f_n=rightrot(f_n,len_n);

//计算str_s

for(i=0;f_n>0x1a;i++){

str_s[i]=(char)(f_n%0x1a+0×41);

f_n/=0x1a;

}

str_s[i]=(char)(f_n+0×41);

str_s[i+1]='-';

str_s[i+2]='R';

str_s[i+3]='A';

str_s[i+4]='C';

cout<<"Serial:"<<str_s<<endl;

 

return 0;

}


2.5. 移位运算

算是本节的彩蛋吧,在本算法逆向过程中,移位指令花费了笔者大量时间,于是就总结出了下面这些东西:

移位指令解释C++实现
SHL逻辑左移:每位左移,低位补0,高位进CFb=a<<n
SHR逻辑右移:每位右移,低位进CF,高位补0b=a>>n
SAL算术左移:每位左移,低位补0,高位进CFb=a<<n
SAR算术右移动:每位右移,低位进CF,高位不变c=0;for(i=n;i>0;i–){         c=c|((a>>(i-1))/(0xFFFFFFFF>>(i)));}b=c|(a>>n);
ROL循环左移:高位到低位并送CFb=(a >> (32 – n)) | (a << n)
ROR循环右移:低位到高位并送CFb=(a << (32 – n)) | (a >> n)
RCL带进位循环左移:循环左移,进位值(CF)到低位,高位进CF未实现
RCR带进位循环右移:循环右移,进位值(CF)到高位,低位进CF未实现
 

本文如果出现错误,欢迎指出,感激不尽!

本文中的所有程序请在虚拟机中运行。

夜深人静,未完待续…..

解方程算法程序、简单算法程序>>>>>戳我回原文下载



>>>>>>黑客入门必备技能   带你入坑,和逗比表哥们一起聊聊黑客的事儿,他们说高精尖的技术比农药都好玩!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: