ssjjdns 发表于 2021-8-27 22:28

芒果教你写壳之代码虚拟化(二)

本帖最后由 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;
}



alei666 发表于 2021-8-27 23:17

这么好的文章,前排支持

小小沫涵 发表于 2021-8-27 23:31

感谢分享教程!

橄榄绿 发表于 2021-8-28 10:10

太强了{:6_225:}

拧紧 发表于 2021-8-28 10:47

很不错嗷{:6_225:}

blood1116 发表于 2021-9-9 14:07

感谢分享教程!

逆光尘埃 发表于 2021-11-29 10:36

感谢分享教程!

beamstyle 发表于 2021-12-7 10:41

感谢分享教程!!

保安 发表于 2021-12-9 12:37

插个眼 大牛更新回复一下 谢谢{:5_116:}

mengzhisuoliu1 发表于 2021-12-9 14:24

谢谢啦~~~~~~~
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 芒果教你写壳之代码虚拟化(二)