芒果教你写壳之代码虚拟化(二)
本帖最后由 ssjjdns 于 2021-8-27 22:46 编辑大家好我是芒果
芒果验证好久没更新了
壳子好久没更新了,因为以前基本啥也不会,所以写的东西也没有什么技术含量
现在和某些老师学了一点东西
现在准备重新写一下这个代码虚拟化系列
{:5_121:}
好进入正题了那就!
众所周知,这个代码虚拟化呢,就是一个壳的手段。将一段汇编代码移交至壳内部的虚拟机中执行,防止逆向分析的手段。
那么我们考虑对一段汇编代码进行虚拟化
我的帖子(一)解答了进入虚拟机前和退出虚拟机时后要做什么
现在帖子(二)将展示对一段汇编指令进行虚拟处理时,必要的操作
0x1将一段汇编代码 移至虚拟机执行 存在的问题</br>【我们把一段要移植到虚拟机内部执行的指令,称之为块】
我们期望,一种完美的情况就是,要被VM的汇编指令,不存在任何跳转。这样这一段指令我们就可以舒心的让虚拟机从头执行到尾。
但是,被VM的汇编指令中存在跳转,这些跳转,有可能跳到被VM的代码块之外的。这时候我们必须要退出虚拟机,然后去执行块外代码。
但是,又有一些跳转,是跳转到被VM的代码块内部的。
1.这些跳转来自于被vm的指令块内,即块内跳到块内。
2.也有可能是来自于被VM的指令块外部,即块外部跳到块内
对于情况1我们可以让虚拟机内部逻辑处理就好了
但是对于情况2
情况有点棘手比如下面这样
[如果你不知道下面在说什么,可以去看一下我的虚拟化(一)]
原来代码:
401000 push 12345678
40100A push xxxxxxxxx
40100C push xxxx
40100D call xxxx
............
401244 jmp 40100C or call 40100C
VM后:
401000 JMP xxxxxxxx//进入虚拟机
40100A xxxxxxxx //一些死代码,本来就是随机的字节,没有意义
40100C xxxxxxxx//一些死代码,本来就是随机的字节,没有意义
40100D xxxxxxxx//一些死代码,本来就是随机的字节,没有意义
.......
401244 jmp 40100C or call 40100C
比如401244处要求jmp或call到40100C处,但是40100C已经被我们处理成了死代码。后面执行的话cpu取的指令没有任何意义,软件就会卡死,而401244处的调用又恰好不属于被VM的指令集合里
换句话就是说,是外部代码需要跳转到虚拟机内部,但原来指令位置已经被替换成死代码了。
0x2 问题解决方案:利用编译原理-基本块划分(或控制流分析)来解决
[基本块的性质或定义:一个代码块,只可能从第一条执行到最后一条,中间不会有任何跳转,而跳转只可能出现在最后一条指令上]
既然我们已经明白了问题所在,那么解决方法也呼之欲出了,我曾为这个问题困扰了一整天。
然后上b站上翻了一下国防科技大学的编译原理 里面控制流分析学了一下,然后解决了
下面给出问题解决的算法
直接看图吧,不多bb了
图片共是两张,论坛可能有时候图片加载失败不知道啥原因,多刷新试试吧
用我自己的话说:
1.欲被划分的块的第一条指令 或 能由条件转移语句(jnz je jl.....) 或无条件转移语句(jmp)转移到的语句 或 紧跟在条件转移后面的语句
这三类指令都可以作为一个基本块的入口语句
2.对于以上每个入口语句,确定其所属的基本块,它是由该入口语句到下一入口语句(不包括该入口语句),或到一转移语句(包括该转移语句)之间的语句序列组成的
比如一条jnz 那么这个jnz的目标地址,就是一个块的开始
然后jnz指令下面的指令,就是另一个块的开始
如此划分基本块,我们的程序的控制流可以用一张有向图来表示 【不明白有向图是啥的,百度一下,一两句说不清,知识来源:离散数学 图论】
然后,我们只需要在基本块的头部开始进入虚拟机,在尾部退出虚拟机即可,中间的指令会依次顺序执行,不用再考虑跳转问题
至此,问题得到完美解决!!!!!!!!!!!!!!!
[至于在基本块尾部的跳转指令如何处理,要么原始执行,要么特殊处理【代码乱序】,【代码乱序】技术我会再开帖子讲解!!!]
下面附上代码[本来是想用易语言的,因为考虑到性能问题,直接C++写]代码一共206行 下拉鼠标可以看完整的
include<bits/stdc++.h>
#define MAX_ZL_CNT 10000007
#define MAX_VM_CNT 10007
using namespace std;
string s,b,e;//S存指令,b存要vm的地址开头,e存要vm的地址结尾
map<string,int>h; //h存放string下标
int cnt,vm_cnt;//指令总数 被v的指令总数
struct rec{
int isJmp;
int isBegin;
int jmpFrom;
int jmpTo;
int div_id;
};
rec g;//g存放指令的性质
string ItoS(int x){
string tmp;
while(x){
tmp=char(x%10+48)+tmp;
x/=10;
}
return tmp;
}
string bu0(string x){
int l=8-x.length();
for(int i=1;i<=l;i++){
x="0"+x;
}
return x;
}
string toD(string x){
for(int i=0;i<x.size();i++){
if(int(x)>=97 && int(x)<=122){
x=char(65+int(x)-97);
}
}
return x;
}
int toI(string x){
int base=1;
int ret=0;
for(int i=x.size()-1;i>=0;i--){
ret+=(int(x)-48)*base;
base*=10;
}
return ret;
}
string GetJmpType(string x){
int l=x.find("j");
int r=0;
for(int i=l;i<x.length();i++){
if(x==' '){
r=i;
break;
}
}
return x.substr(l,r-l);
}
string GetJmpAddr(string x){
int l=x.find("0x");
return x.substr(l+2,x.length()-l-2);
}
string GetZL(string x){
int ccnt=0;
int cur=0;
for(int i=1;i<x.length();i++){
if(x==' ' && x!=' ')ccnt++;
if(ccnt==2){
cur=i;
break;
}
}
return x.substr(cur,x.length()-cur);
}
string GetBinCode(string x){
int cur1=0;
int cur2=0;
for(int i=1;i<x.length();i++){
if(x==' ' && x!=' ')cur1=i;
if(x!=' ' && x==' ' && cur1!=0){
cur2=i;
break;
}
}
return x.substr(cur1,cur2-cur1);
}
void DivIt(string x,string y){
int l=h;
int r=h;
int jc=r-l+1;
int kuai_cnt=1;
int f=0;//防止块重复做的标记
//下面扫一遍统计每条指令所属块
for(int i=0;i<jc;i++){
if(g.isBegin==0 && g.isJmp==0){//既不是块开头,也不是跳转语句
g.div_id=kuai_cnt;//标记属于那个块
continue;
}
//块开头优先判断
if(g.isBegin==1){
if(f!=1){
kuai_cnt++;
}
f=0;
g.div_id=kuai_cnt;//标记属于那个块
continue;
}
//其次判断是否是跳转指令
if(g.isJmp==1){
g.div_id=kuai_cnt;//标记属于那个块
kuai_cnt++;
f=1;
continue;
}
}
int j=0;
int tmp1=g.div_id;
g.div_id=-1;
int jout_cnt=0;
int outjin_cnt=0;
string jout;//由块内部跳转到块外部的指令
string outjin;//由外部跳转到块内部的指令
cout<<"ProtectBegin"<<endl;
for(int i=l;i<=r;i++){
if(g.div_id!=g.div_id)cout<<"kuai"<<++j<<":"<<endl;
//检测是否由块外部跳转进入块
if(g.jmpFrom!=0){
if(g.jmpFrom<l || g.jmpFrom >r){//如果是来自块外部的跳转的话
outjin_cnt++;
outjin="<t><"+s.substr(0,8)+">"+"@"+ItoS(g.div_id)+"@<t>";
}
}
//如果是跳转指令,检测是跳转到块内部还是块的外部
if(g.isJmp==1){
if(g.jmpTo<l || g.jmpTo>r){//如果跳转到块的外部
jout_cnt++;
jout="<1>"+s.substr(0,8)+"<2>"+GetJmpType(s)+"<3>"+GetJmpAddr(s)+"<4>"+ItoS(g.div_id)+"<5>"+ItoS(i)+"<6>";
cout<<GetJmpType(s)+"|"+GetJmpAddr(s)+"@"<<ItoS(jout_cnt)<<"_"<<endl;
}
else cout<<"."<<GetJmpType(s)<<" kuai"<<g.jmpTo].div_id<<"_"<<endl;
}
else{
cout<<"."<<GetZL(s)<<"<"<<GetBinCode(s)<<">"<<ItoS(g.div_id)<<"_"<<endl;
}
}
g.div_id=tmp1;
cout<<"ProtectEnd"<<endl;
//输出跳转到块外部的指令
cout<<"{";
cout<<"jout__________________"<<endl;
for(int i=1;i<=jout_cnt;i++)cout<<jout<<endl;
cout<<"outjin________________"<<endl;
for(int i=1;i<=outjin_cnt;i++)cout<<outjin<<endl;
cout<<"}";
cout<<endl<<"==========================================================="<<endl;
}
int main(){
// cout<<ItoS(250);
freopen("111.txt","r",stdin);
freopen("out.txt","w",stdout);
//--------------预处理所有跳转-------------------------------
int f=0;
while(getline(cin,s[++cnt])){
g.isJmp=0;
g.isBegin=0;
h.insert(make_pair(s.substr(0,8),cnt));
}
cnt--;//由于getline多读入一次才会结束的,修正 cnt值
vm_cnt=toI(s);
for(int i=1;i<=cnt-vm_cnt-1;i++){
string a=s;
if(f==1){
g.isBegin=1;//前一句指令是跳转语句
f=0;
}
int cur=a.find("j");
if(cur!=a.npos){//找到跳转指令的 操作数
if(a.find("0x")==a.npos)continue;//如果有jxx 寄存器这种形式的指令,直接跳过
f=1;
cur=a.find("0x");
a=a.substr(cur+2,a.length()-cur-1);
a=bu0(a);//补够8位
a=toD(a);//转大写
g].isBegin=1;
g.jmpTo=h;
g.isJmp=1;
g].jmpFrom=i;
//cout<<a<<endl;
}
}
//cout<<vm_cnt<<endl;
for(int i=1;i<=vm_cnt;i++){
b=s.substr(0,8);
e=s.substr(8,8);
DivIt(b,e);
}
return 0;
}
这么好的文章,前排支持 感谢分享教程! 太强了{:6_225:} 很不错嗷{:6_225:} 感谢分享教程!
感谢分享教程! 感谢分享教程!! 插个眼 大牛更新回复一下 谢谢{:5_116:} 谢谢啦~~~~~~~