算法逆向第1篇——简单算法逆向
2017-12-26 18:09
351 查看
![](http://static.blog.csdn.net/xheditor/xheditor_emot/default/smile.gif)
本文作者:icq5f7a075d
Music起~
“啊门 啊前 一棵葡萄树,啊嫩 啊嫩 绿地刚发芽,蜗牛背着那重重地壳呀,一步一步地往上爬~”
上周分析勒索病毒(GlobeImposter),笔者感觉自己算法的逆向能力很弱,于是找了些题目练手。本文逆向程序主要来源于《加密与解密实战攻略》,算法将用Python/C++实现。
既然有了算法逆向1,自然会有2、3、4……叶问说“我要打十个!”,笔者说“我要写十篇!”,甭管行不行,先立Flag! ——>文末有福利
1. 简单的算法逆向
预计时间:2h
1.1. 定位关键位置先随便输入,弹出了错误提示:
![](http://www.secist.com/wp-content/uploads/2017/12/1-5-300x133.png)
搜索字符串:Invalid!此时已经找到了关键位置。
![](http://www.secist.com/wp-content/uploads/2017/12/2-9-300x36.png)
上下翻找一番,0×00404167就是关键跳转,如果是crackme,那么直接把这里修改为jmp就可以了。而我们要进行算法逆向,就要追踪分析ebx和esi这两个值怎么来的。
1.2. 算法逆向
往上回溯,ebx的值来自于下图,过程还是比较简单,对输入name字符串进行计算,得到一个值,以这个值为编号,在表0x4050B8中取值:
![](http://www.secist.com/wp-content/uploads/2017/12/3-6-300x201.png)
往上回溯,esi的值来自于此,也比较简单对输入密码字符串进行计算:
![](http://www.secist.com/wp-content/uploads/2017/12/4-5-300x141.png)
算法逆向源码(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中:
![](http://www.secist.com/wp-content/uploads/2017/12/5-5.png)
DialogFunc中流程很简单,检测到Check按钮点击事件就会进入如下的处理过程:
![](http://www.secist.com/wp-content/uploads/2017/12/6-4-300x154.png)
第一次调用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中:
![](http://www.secist.com/wp-content/uploads/2017/12/7-4-300x194.png)
这段代码有循环移位操作,需要使用自定义函数实现,在下文中会对移位指令进行介绍,
本部分源码(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计算是将其分为三部分:'-'字符、'-'字符前的字符,'-'字符后的字符,下图是计算是否存在'-'字符,并取'-'字符前的字符:
![](http://www.secist.com/wp-content/uploads/2017/12/8-5-300x151.png)
这部分源码(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)
![](http://www.secist.com/wp-content/uploads/2017/12/9-1-300x242.png)
这部分源码(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
![](http://www.secist.com/wp-content/uploads/2017/12/10-1-300x233.png)
这部分源码(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个字符
![](http://www.secist.com/wp-content/uploads/2017/12/11-1-145x300.png)
![](http://www.secist.com/wp-content/uploads/2017/12/12-1-300x216.png)
这部分源码(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. 注册机
![](http://www.secist.com/wp-content/uploads/2017/12/13-1-300x200.png)
计算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,高位进CF | b=a<<n |
SHR | 逻辑右移:每位右移,低位进CF,高位补0 | b=a>>n |
SAL | 算术左移:每位左移,低位补0,高位进CF | b=a<<n |
SAR | 算术右移动:每位右移,低位进CF,高位不变 | c=0;for(i=n;i>0;i–){ c=c|((a>>(i-1))/(0xFFFFFFFF>>(i)));}b=c|(a>>n); |
ROL | 循环左移:高位到低位并送CF | b=(a >> (32 – n)) | (a << n) |
ROR | 循环右移:低位到高位并送CF | b=(a << (32 – n)) | (a >> n) |
RCL | 带进位循环左移:循环左移,进位值(CF)到低位,高位进CF | 未实现 |
RCR | 带进位循环右移:循环右移,进位值(CF)到高位,低位进CF | 未实现 |
本文如果出现错误,欢迎指出,感激不尽!
本文中的所有程序请在虚拟机中运行。
夜深人静,未完待续…..
解方程算法程序、简单算法程序>>>>>戳我回原文下载
>>>>>>黑客入门必备技能 带你入坑,和逗比表哥们一起聊聊黑客的事儿,他们说高精尖的技术比农药都好玩!
相关文章推荐
- 简单的逆向最大匹配算法实现中文分词(Python)
- 算法逆向第一篇——简单算法逆向
- 几个简单的数据点平滑处理算法
- 一些简单但是常见的算法题目(笔记类)
- 实用算法实践-第1篇 排序
- 和菜鸟一起学算法之递归和分治简单实例
- 字符串方面的几道简单算法题
- 简单算法学习一
- KNN(K近邻)算法的简单入门
- 一款简单的QQ盗号蠕虫逆向分析 --发文于2013-7-23
- 算法与数据结构——排序(四)简单插入排序
- 算法学习之不那么简单的排序(1)
- 2.2 简单的算法举例
- 【Unity3d】简单的物体漂浮算法
- 简单的高效算法分析初步?
- 抽奖简单算法
- 题目:最简单的逆向
- 图像基本处理算法的简单实现(三)
- 【算法】散列表及散列函数的java简单实现
- P****cms简单去版权,不逆向dll,只是简单的隐藏掉而已