首页
论坛
课程
招聘
[原创]16位汇编学习笔记 自写反汇编引擎和机器码指令分析
2021-9-20 19:24 17844

[原创]16位汇编学习笔记 自写反汇编引擎和机器码指令分析

2021-9-20 19:24
17844

武汉科锐逆向学习笔记

开心进入三阶段学习 今日学习汇编第二课

笔记的由来 能上图的咱不废


进入正题

初次学习16汇编语言(DOS汇编) 第二课硬编码(机器码)

相关知识点

C语言入门水平

16位命令行调试器

计算机硬件的工作原理

汇编语言基础知识

十六进制转换二进制

计算机硬件的工作原理

看一下百度百科即可(不用深入了解)

https://baike.baidu.com/item/%E8%AE%A1%E7%AE%97%E6%9C%BA%E5%B7%A5%E4%BD%9C%E5%8E%9F%E7%90%86/7681289?fr=aladdin


问答环节

什么是汇编语言以及它和机器码的关系

汇编语言 即第二代计算机语言(第一代就是机器码)


由于机器码都是01难以理解和记忆,所以后来我们使用一些容易理解和记忆的字母,单词来代替一个特定的指令,比如:ADD代表数字逻辑上的加减,MOV代表数据传递等等,通过这种方法,我们很容易去阅读已经完成的程序或理解程序正在执行的功能,对现有程序的BUG修复以及维护变得更加简单,但是CPU不认识字母符号,这时候就需要一个专门的程序把这些字符变成计算机能够认识的指令(机器码)

什么是汇编什么是反汇编

把字符变成计算机能够认识的指令,这个过程就是汇编,反之把机器码变成字符的过程叫做反汇编


灵魂三问

反汇编引擎是什么

可以理解为一个功能库

写反汇编引擎有什么用

反汇编引擎是很多工具的基础 :调试器 自动分析工具 自动HOOK( Detours) 等等

最关键的是怎么写

首先我们先要了解以下知识并且分析机器码


由于8086真机我已经买不到了 所以我们只能在XP虚拟机中使用模拟的调试器 命令行中输入Debug 即可打开模拟的调试器


我们先看一下反汇编代码和机器码在调试器中的什么位置

输入U命令 查看反汇编

蓝色的部分是地址 红色的部分是机器码 粉红色的部分是反汇编代码


反汇编代码解析
操作码部分 目的操作数部分 源操作数部分

操作码(必要

目的操作数(可有可无)源操作数(可有可无)
MOVAHCL
MOVAXCX


接下来我们开始分析机器码

由于机器指令太多 无法进行全部分析 本文就以Mov的第一种为例子来进行分析

MOV指令的第一种是 从寄存器到寄存器或者从寄存器到内存或者从内存到寄存器


如何分析:首先我们在Debug通过A命令进行输入汇编代码 然后我们输入MOV指令的第一种的汇编代码 先输入几个从寄存器到寄存器的指令 每个指令都要改变源操作数 输入完成后复制到文本编辑工具里面进行十六进制转换二进制后进行对比 看每一条指令和上一条指令都有什么变化 变化的部分肯定就是源操作数的部分 下面笔者会进行演示


先在Debug里面使用A命令 输入指令MOV目的操作数不变,我们改变源操作数(这样就可以看出源操作数)


然后把圈出来的部分复制出来(拖白点击右键可以复制)


把16进制的机器码转换为2进制

大眼一看我们发现只有这一部分不一样 是不是可以说明这一部分是源操作数

 

然后我们仔细观察发现 后面6位的前3位表示操作数 后3位表示目的操作数

 

因此我们可以得知 AX为000  CX为001  DX为010  BX为011 SP 为100  BP为101  SI为110   DI为111

它们的顺序是 AX CX DX BX SP BP SI DI


我们来验证一下我们分析的对不对

预测MOV SI,DI 的机器码为 1000 1001 1111 1110 十六进制89FE

进行上机验证89FE MOV SI,DI 我们分析的是正确的

 

接着我们继续分析 这一次我们看一下寄存器8位的和16位的有什么区别

继续输入指令MOV目的操作数不变,我们改变源操作数 把16位寄存器换成8位寄存器 看下图即可

 

输入完成后 复制黏贴到文件编辑工具上 进行十六到二的转换 

转换完成发现8位和16位的操作数部分没有什么区别 还是后面6位的前3位表示目的操作数 后3位表示源操作数

 

因此我们可以得知 AL为000  AH为001  CL为010  CH为011 DL为100  DH为101  BL为110  BH为111

它们的顺序是 AX CX DX BX SP BP SI DIAL AH CL CH DL DH BL BH


然后我们在看一下其他位置 发现粉红色的地址不一样 所以这个地方应该就是区分8位和16位的

我们接着按照上次验证方法进行验证 验证结果正确(省略图 读者可以和笔者一样验证一下)

因此我们可以得知 粉色圈内如果为1就是16位的操作数 0就是8 位的操作数


然后我们在看一下内存到寄存器和寄存器到内存是怎么来区分的

继续输入有序的指令 看下图即可


还是一样我们把十六进制转换为二进制 接着继续分析

我们看下图发现红色圈内为1的都是内存到寄存器的 为0的都是寄存器到内存的

然后发现粉碎圈内00的EA中没有常量 01的EA中有1字节常量 10的EA中有2字节常量


但是我们上面说过后面6位的前3位表示源操作数 后3位表示目的操作数 这个好像不是这样的

我们知道AX和AL都是000 那么我们通过已经知道来当作参考继续分析 发现内存都在后面 而前面的是寄存器

因此我们可以得知 BX+SI为000  BX+DI001 BP+SI010  BP+DI011 SI100  DI101  BP110  BX111

它们的顺序是 BX+SI BX+DI  BP+SI BP+DI SI DI BP BX


我们接着按照上次验证方法进行验证 

预测MOV DI,[BX+SI+10]的机器码为1000 1011 0111 1000 0001 0000 十六进制8B7810

进行上机验证8B7810  MOV DI,[BX+SI+10] 我们分析的是正确的

根据我们上面的分析 得到下图

前面6字节表示机器码 接着下一个字节表示方向 紧接下一个字节表示长度 后两个字节表示模式 然后就是寄存器 寄存器或内存

我们来详细讲解一下MOV指令 第一种

前面6位是操作码

D表示 数据方向 0表示寄存器到内存 1表示内存到寄存器

W表示 数据长度 1=16位 0=8

MOD表示 模式 00 EA中没有常量 01 EA中常量1字节 10 EA中常量2字节 11 寄存器操作

REG表示 寄存器

R/M表示 寄存器或者内存


需要注意的是:操作数部分如果没有内存的情况就是 后面6位的前3位表示源操作数 后3位表示目的操作数 如果有内存的情况就是 后面6位的前3位寄存器 后3位表示内存


请读者自己分析MOV指令的其他情况

MOV指令的第二种 从立即数到寄存器或者内存

MOV指令的第三种 从立即数到寄存器

MOV指令的第四种 从内存到累加器

MOV指令的第五种 从累加器到内存

MOV指令的第六种 从寄存器或者内存到段寄存器

MOV指令的第七种 从段寄存器到寄存器或者内存



以上是分析方法 那么我们写反汇编引擎总不要这么一个一个的分析吧 是不是很麻烦

其实上面只是告诉大家 在没有文档的情况下怎么来进行分析

由于写编译器的作者和写反汇编引擎的作者会用到 所以CPU厂商就把指令对应的机器码写了一个文档 


在英特尔Intel8086手册上16位的详细介绍(看下图即可)

                                    

在英特尔白皮书上有32位和64位的详细介绍 在第二卷里面(看下图即可)


                              

   

                 

代码部分 就不解释了 学过C语言的都可以看的懂 tagAsm1Mov1 tagAsm2Mov1  是位段

#include <iostream>

struct tagAsm1Mov1
{
    unsigned char wide : 1; //数据长度
    unsigned char dis  : 1; //数据方向
    unsigned char op  : 6; //指令
};

struct tagAsm2Mov1
{
    unsigned char dst  : 3; //目的操作数
    unsigned char src  : 3; //源操作数
    unsigned char mod  : 2; //模式
};

//机器码
unsigned char data[45] = 
{
    0x89,0xC3,0x89,0xCB,0x89,0xD3,0x89,0xDB,0x89,0xE3,0x89,0xEB,0x89,0xF3,0x89,0xFB,
    0x88,0xC7,0x88,0xE7,0x88,0xCF,0x88,0xD7,0x88,0xDF,0x8B,0x07,0x8B,0x05,0x8B,0x03,
    0x8B,0x00,0x8B,0x42,0x12,0x8B,0x82,0x56,0x34,0x89,0x81,0x78,0x56
};

const char* Register16[8] = { "AX","CX" ,"DX" ,"BX" ,"SP" ,"BP" ,"SI" ,"DI"};
const char* Register8[8]  = { "AL","CL" ,"DL" ,"BL" ,"AH" ,"CH" ,"DH" ,"BH"};
const char* RegisterEa[8] = { "BX + SI","BX + DI","BP + SI","BP + DI","SI","DI","BP","BX"};

int main()
{
    for (int i = 0;i < 45;i++)
    {
        tagAsm1Mov1* Test;
        Test = (tagAsm1Mov1*)&data[i];
        switch (Test->op)
        {
        case 0x22://MOV指令 第一种
        {
            printf("MOV ");
            if(Test->dis == 0)           //寄存器到内存
            {
                if(Test->wide == 1)       //数据长度是16位的
                {
                    i++;
                    tagAsm2Mov1* Test1;
                    Test1 = (tagAsm2Mov1*)& data[i];
                    switch (Test1->mod)
                    {
                    case 0x00://模式 EA中没有常量
                        printf("[%s],%s",RegisterEa[Test1->dst],Register16[Test1->src]);
                        break;
                    case 0x01://模式 EA中常量1字节
                        printf("[%s + %x],%s",RegisterEa[Test1->dst],data[i + 1],
                                                Register16[Test1->src]);
                        i = i + 1;
                        break;
                    case 0x02://模式 EA中常量2字节
                        printf("[%s + %x%x],%s",RegisterEa[Test1->dst],data[i + 2],data[i + 1],
                                      Register16[Test1->src]);
                        i = i + 2;
                        break;
                    case 0x03://模式 寄存器操作
                        printf("%s,%s",Register16[Test1->dst],Register16[Test1->src]);
                        break;
                    }
                }
                else if (Test->wide == 0) //数据长度是8位的
                {
                    i++;
                    tagAsm2Mov1* Test1;
                    Test1 = (tagAsm2Mov1*)&data[i];
                    switch (Test1->mod)
                    {
                    case 0x00://模式 EA中没有常量
                        printf("[%s],%s", RegisterEa[Test1->dst],Register8[Test1->src]);
                        break;
                    case 0x01://模式 EA中常量1字节
                        printf("[%s + %x],%s", RegisterEa[Test1->dst],data[i + 1],
                                                  Register8[Test1->src]);
                        i = i + 1;
                        break;
                    case 0x02://模式 EA中常量2字节
                        printf("[%s + %x%x],%s",RegisterEa[Test1->dst],data[i + 2],data[i + 1],
                                      Register8[Test1->src]);
                        i = i + 2;
                        break;
                    case 0x03://模式 寄存器操作
                        printf("%s,%s",Register8[Test1->dst],Register8[Test1->src]);
                        break;
                    }
                }
            }
            else if(Test->dis == 1)       //内存到寄存器
            {
                if(Test->wide == 1)     //数据长度是16位的
                {
                    i++;
                    tagAsm2Mov1* Test1;
                    Test1 = (tagAsm2Mov1*)&data[i];
                    switch (Test1->mod)
                    {
                    case 0x00://模式 EA中没有常量
                        printf("%s,[%s]", Register16[Test1->src],RegisterEa[Test1->dst]);
                        break;
                    case 0x01://模式 EA中常量1字节
                        printf("%s,[%s + %x]",Register16[Test1->src],RegisterEa[Test1->dst],
                                     data[i + 1]);
                        i = i + 1;
                        break;
                    case 0x02://模式 EA中常量2字节
                        printf("%s,[%s + %x%x]", Register16[Test1->src],RegisterEa[Test1->dst],
                                       data[i + 2],data[i + 1]);
                        i = i + 2;
                        break;
                    case 0x03://模式 寄存器操作
                        printf("%s,%s",Register16[Test1->dst],RegisterEa[Test1->src]);
                        break;
                    }
                }
                else if (Test->wide == 0) //数据长度是8位的
                {
                    i++;
                    tagAsm2Mov1* Test1;
                    Test1 = (tagAsm2Mov1*)&data[i];
                    switch (Test1->mod)
                    {
                    case 0x00://模式 EA中没有常量
                        printf("%s,[%s]",Register16[Test1->src],RegisterEa[Test1->dst]);
                        break;
                    case 0x01://模式 EA中常量1字节
                        printf("%s,[%s + %x]",Register16[Test1->src],RegisterEa[Test1->dst],
                                                data[i + 1]);
                        i = i + 1;
                        break;
                    case 0x02://模式 EA中常量2字节
                        printf("%s,[%s + %x%x]",Register16[Test1->src],RegisterEa[Test1->dst],
                                     data[i + 2],data[i + 1]);
                        i = i + 2;
                        break;
                    case 0x03://模式 寄存器操作
                        printf("%s,%s",Register8[Test1->dst],Register8[Test1->src]);
                        break;
                    }
                }
            }
        }
        }
        printf("\r\n");
    }
    return 0;
}
总结

反汇编引擎 听名字是不是很高大上 其实写起来就是体力活 由于代码量庞大笔者只能写MOV的一小部分解析 其实我们自己如果没有特殊情况还是没有必要去自己写反汇编引擎的,能写出部分指令的解析就以及能证明我们可以完成这个工作就可以啦,剩下的都是体力活 因为有开源的反汇编引擎 我会上传到附件


推荐文章
机器码指令格式解析 https://www.shuzhiduo.com/A/gVdngx1DzW/
X86汇编之指令格式解析 https://bbs.pediy.com/thread-191802.htm

笔者是一个刚刚学习汇编不久的小白 如果本文中哪里有错误 请各位前辈指导 目前正在武汉科锐学习 第三阶段 第二课

[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

最后于 2021-9-27 00:37 被旺仔_小可爱编辑 ,原因:
上传的附件: