微信号:ikanxue

介绍:致力于移动与安全研究的开发者社区,看雪学院(kanxue.com)官方微信公众帐号.

汇编指令级混淆器的实现

2018-02-10 16:41 enoorez

之前在脱壳的时候遇到不少的花指令、指令膨胀、虚拟机之类的壳,然后想要尝试一下也弄一个这样的壳。于是便有了写一个指令混淆器的想法。起初是想写一个直接对opcode进行混淆膨胀的混淆器的,难度不是一般地小,这其中包括了膨胀之后call指令和jmp等以偏移作为操作数的指令的操作数的偏移量会被破坏。


虽然能写,但非常麻烦,需要重组整个代码段,后来就没有直接写。写了一个汇编指令级别的混淆器尝试尝试,这种汇编指令级别的混淆器之所以写得比较容易,是因为汇编指令可以使用标识符来表示偏移量,而这些标识符在汇编器开始编译汇编代码时会自行计算。


因此,在汇编指令之间插入指令完全是可行的,如果想要将指令拆分,只需将指令的opcode求出,然后拆分重组直接将opcode作为数据定义在源文件中交给编译器是完全没有问题的。

 

下面便是汇编指令混淆器的实现思路


实现思路


db伪指令不仅仅可以在数据段中定义数据,也可以在.code 段中定义数据,而这些数据如果被eip指向就会被当成代码执行。如果eip永不指向它,它虽在代码段中,但也不会被执行。那么我们完全可以这样做:

.code

    push ebp

    mov ebp , esp

    jmp _START

    db 'this is data, this is code too'

_START:

    mov eax , 1

    mov esp , ebp

    pop ebp

    ret


这段小程序执行起来之后,在执行了前两句之后,会接着jmp到_START标签之后。而那段我们在代码中定义的数据将永远不会被使用到,但有时候这种小伎俩很容易被识破,我们再观察另外的情况:

.rdata

g_str db 'hello' , 0

 

.code

push offset g_str

call ctr_cprintf  ; printf("hello")

add esp , 4


上面这段代码比较正常,调用printf函数打印hello字符串嘛,但再看以下这段:

.code

    call _PUSHSTR

    db 'hello'

_PUSHSTR:

    call ctr_cprintf

    add esp , 4


这段代码仍然还是打印字符串hello,但它的执行流程就没有上面的清晰了。例如:它运行之后,先call到了标签_PUSHSTR处。因为这是一条call条指令,执行之后是会将返回地址入栈的,而入栈的地址好巧不巧正是字符串hello在代码段中的地址。


而调用printf函数时,栈中已经保存了一个字符串的地址。因此,最后输出的正式字符串hello,而由于字符串被保存在了代码段中。当这个程序被OD反编译时,将会被当成opcode解析出来,在分析时自然就不是很好看了。


一些指令执行之后是不会产生任何作用, 例如:

    or eax,080000000h

    js J_H_Z_L_XXXXX

    db    "hello i am a string"

J_H_Z_L_XXXXX:

    and eax,0xFFFFFFFF

    JCC  J_H_Z_L_XXXXX


这段指令看似会有分支执行,但实际不会,因为or eax,080000000h执行之后,SF符号标志位必定被置1. js指令也必定会被执行,后续的代码也一样。



混淆器的设计


整个混淆器的工作流程是这样的:

  1. 从.asm文件中读取出汇编指令

  2. 将该汇编指令输入给一个混淆器对象,这个混淆器对象输出包含有这条指令的混淆指令数组(一条变多条)

  3. 将混淆指令数组写到另一个文件中

  4. 完成


因此,它看起来大概是这样的:

char line[200];
    vector<Instruction> vecIns;

    // 获取每一行汇编源文件的代码
    while( iFile.getline(line,200) ) {

        // 将这行汇编指令交给混淆器对象出来, 混淆器将多行混淆后的指令输出到vector中.
         if( mixer.mix( line , &vecIns ) ) {
            // 混淆成功,则将混淆后的内容输出到文件
            for( auto &i : vecIns ) {
                oFile << i << endl;
            }
        }
        else {
            // 混淆失败则将原指令输出到文件。
            oFile << line << endl;
        }
    }


至于混淆器对象 , 它是这样做的:

virtual bool mix( const Instruction& pInsObj , vector<Instruction>* vecMixer ) = 0;


没错,这是个纯虚函数,因为我把指令分成了不同种类,有数据传输的、有算术运算的、有位运算的,不同的指令有不同的混淆。而我不想将所有的代码都放在此函数中实现,因此,我派生了几个混淆不同种类指令的混淆器类来,它们分别有:

  1. MixEngine_tran  : 数据传输指令

  2. MixEngine_bit  : 位运算指令

  3. MixEngine_jcc : 条件转移指令

  4. MixEngine_call  : 调用指令


例如, 对数据传输指令的操作是这样的:

// 对数据传输指令进行简单的混淆
bool MixEngine_tran::mix(const Instruction& pInsObj, vector<Instruction>* vecMixer)
{
    static int ___ = srand(time(nullptr));

    if (pInsObj.type() != e_tran)
    {
        return false;
    }

    if (vecMixer == nullptr)
        return false;

    vecMixer->clear();

    /**
    * 混淆方式:
    * 将预定义的指令加入到原指令的周围.
    */
    char* ins[10] =
    {
        "add esp,2;add esp,2;sub esp,4",
        "ADD EAX,ECX; NEG ECX; ADD ECX,EAX;SUB EAX,ECX;XCHG EAX,ECX",
    };


    int count = m_hardness > _countof(ins) ? _countof(ins) : m_hardness;

    int pos = rand() % count;
    for (int i = 0; i<count; ++i)
    {
        InstructionGroup insGrp(ins[i]);
        for (auto&item : insGrp)
        {
            if( i == pos)
                vecMixer->push_back(item);
        }
    }
    vecMixer->push_back(pInsObj);
    return true;
}


这其中,涉及到了一个叫做Instruction的类,这个类没什么逻辑,它主要提供了操作一条婚变指令的功能。

  const char* mnemonic( )const; // 得到指令的助记符
    const char* operator1()const; // 得到指令的操作数1
    const char* operator2( )const;// 得到指令的操作数2
    const char* operator3( )const;// 得到指令的操作数3

    void        setMnemonic( const char* mnemonic ); // 设置指令的助记符
    void        setOperator1(const char* pOperator ); // 设置指令的操作数1
    void        setOperator2( const char* pOperator );// 设置指令的操作数2
    void        setOperator3( const char* pOperator );// 设置指令的操作数3


这个类一般接收一条字符串,例如:mov eax , 1 ,然后通过这个指令的几个成员函数,就可以得到一条汇编指令的操作数,或者替换指令的操作数了。

 

还有一个 InstructionGroup 类,它负责保存一组指令,实际上就是一个vector<Instruction>。

 

这毕竟只是花了一个下午写出来的。因此,只能当汇编中的高级知识来学学啦,真心没有啥用。

 

代码:https://github.com/enoorez/win32-sources-mixer




本文由看雪论坛 enoorez 原创

转载请注明来自看雪社区


热门阅读


点击阅读原文/read,

更多干货等着你~

 
看雪学院 更多文章 旅行的青蛙Unity游戏逆向修改Android&amp;iOS 登录抓包逆向分析学习笔记 图像隐写之使用PHP隐藏图像中的文本 LLVM代码混淆分析及逻辑还原 加固 C\/C++ 程序
猜您喜欢 Raft算法赏析 腾讯SNG技术开放日:关注前端分论坛,IMWeb讲师带你听 RPI打造天气显示屏 Gartner:分析、基础架构与云计算仍是政府CIO三大技术重点 Java入门|怎样才能自学好Java?