澳门新浦京8455comJVM 模板解释器之如何根据字节码生成汇编码?

澳门新浦京8455com 14

1、背景

仅针对JVM的模版解释器:

什么样依据opcode和寻址方式,将bytecode生成汇编码。

正文的言传身教中所使用的字节码和汇编码,请参见上篇博文:按值传递依然按引用?

正文详细的介绍了在Visual
Studio(以下简单的称呼VS)下促成API钩子的编制程序方法,阅读本文必要底工:有操作系统的基本知识(进度管理,内部存款和储蓄器管理),会在VS下编写制定和调治将养Win32应用程序和动态链接库(以下简单的称呼DLL)。

2.4 Instruction Format 指令格式

 

The information encoded in an 80386 instruction includes a specification
of the operation to be performed, the type of the operands to be
manipulated, and the location of these operands. If an operand is
located in memory, the instruction must also select, explicitly or
implicitly, which of the currently addressable segments contains the
operand.

80386发令的编码消息包含操作如何开展的求证,操作数类型,操作数的职责。要是七个操作数在内存中,指令必得精通或隐式地筛选当前带有操作数的可编址段。

80386 instructions are composed of various elements and have various
formats. The exact format of instructions is shown in Appendix B; the
elements of instructions are described below. Of these instruction
elements, only one, the opcode, is always present. The other elements
may or may not be present, depending on the particular operation
involved and on the location and type of the operands. The elements of
an instruction, in order of occurrence are as follows:

80386的一声令下由可形成分组成,並且有三种格式。正确的命令格式被列在附录B中;指令的因素在底下表明。在此些指令成分中,独有叁个要素,即opcode,就非得存在项。别的的因素得以有也能够未有,那信任于特定的操作和操作数类型的岗位。那么些要素,按出现的顺类别在底下:

  • 澳门新浦京8455com,Prefixes — one or more bytes preceding an instruction that modify
    the operation of the instruction. The following types of prefixes
    can be used by applications programs:

前缀 ——
三个或多个字节出今后命令中,纠正命令操作。上面列出的前缀类型可以在应用程序中央银行使:

  1. Segment override — explicitly specifies which segment register an
    instruction should use, thereby overriding the default
    segment-register selection used by the 80386 for that instruction.

段覆盖 ——
鲜明钦定该指令使用哪个段寄存器,由此会覆盖掉80386为这么些指令选拔的暗许寄放器。

  1. Address size — switches between 32-bit and 16-bit address
    generation.

地方大小 —— 三十七个人和20位地点按钮

  1. Operand size — switches between 32-bit and 16-bit operands.

操作数大小 ——
三10位和十五位操作数开关

  1. Repeat — used with a string instruction to cause the instruction to
    act on each element of the string.

重新 ——
在字符串指令中利用,提醒命令在字符串的每一个成分中运用。

  • Opcode — specifies the operation performed by the instruction. Some
    operations have several different opcodes, each specifying a
    different variant of the operation.

操作符 ——
钦赐指令的操作。一些操作有四个例外的操作符,每八个钦定分裂的可变的操作。

  • Register specifier — an instruction may specify one or two register
    operands. Register specifiers may occur either in the same byte as
    the opcode or in the same byte as the addressing-mode specifier.

贮存器区分符 ——
一条指令可钦点一个或多个存放器操作数。贮存器区分符即能够献身多少个字节中作为操作符,大概放在同二个字节中作为地方形式区分符。

  • Addressing-mode specifier — when present, specifies whether an
    operand is a register or memory location; if in memory, specifies
    whether a displacement, a base register, an index register, and
    scaling are to be used.

地点形式区分符 ——
当现身时,用来差距叁个操作数是在存放器依旧内部存款和储蓄器地点;如若在内部存款和储蓄器中,区分是八个置位符、基址存放器、索引寄放器依然比例因子。

  • SIB (scale, index, base) byte — when the addressing-mode specifier
    indicates that an index register will be used to compute the address
    of an operand, an SIB byte is included in the instruction to encode
    the base register, the index register, and a scaling factor.

SIB(比例,索引,基址卡塔尔(قطر‎字节 ——
本地址形式区分符注明有叁个目录存放器被用来估测计算二个操作数的地点时,SIB字节也被含有在命令中,用来编码基址寄放器、索引贮存器和比重因子。

  • Displacement — when the addressing-mode specifier indicates that a
    displacement will be used to compute the address of an operand, the
    displacement is encoded in the instruction. A displacement is a
    signed integer of 32, 16, or eight bits. The eight-bit form is used
    in the common case when the displacement is sufficiently small. The
    processor extends an eight-bit displacement to 16 or 32 bits, taking
    into account the sign.

置位符 ——
本地址格局区分符表飞鹤个置位符被用来计量四个操作数的地点时,置位符也被编码到指令中。二个置位符是多个32、16或8位的卡尺头。8位普通用来表示置位符比较小。微机扩张二个8地方位符到16要么叁十五人,包蕴符号。

  • Immediate operand — when present, directly provides the value of an
    operand of the instruction. Immediate operands may be 8, 16, or 32
    bits wide. In cases where an eight-bit immediate operand is combined
    in some way with a 16- or 32-bit operand, the processor
    automatically extends the size of the eight-bit operand, taking into
    account the sign.

登时数 ——
当存在时,为命令直接提供一个数值。立刻数能够是8、16或33人宽度。一旦8位及时数和16人或叁十几个人操作数实行结适合时宜,微处理器自动扩大8位操作数的深浅,蕴涵符号位。

2、寻址情势

本文不允许备大力推动寻址方式的演讲,我们集中AMD的IA32-64结构的下令格式:

澳门新浦京8455com 1

简轻巧单表明下,更加多的请参见intel的手册:

– Prefixes : 用于修饰操作码Opcode,付与其lock、repeat等的语义.
– REX Prefix
—- Specify GPRs and SSE registers.
—- Specify 64-bit operand size.
—- Specify extended control registers.
Opcode:操作码,如mov、push.
Mod R/M:寻址相关,具体见手册。
SIB:和Mod Sportage/M结合起来钦点寻址。
Displacement:协作Mod 福特Explorer/M和SIB钦赐寻址。
Immediate:立即数。

对地点的Opcode、Mod 奥德赛/W、SIB、disp、imm固然不精通,看句汇编有个概念:

%mov %eax , %rax,-0x18(%rcx,%rbx,4)

要是那句汇编也不太明了,那么合营下边包车型地铁:

– Base + (Index ∗ Scale) + Displacement – Using all the addressing
components together allows efficient
indexing of a two-dimensional array when the elements of the array are
2, 4, or 8 bytes in size.

API钩子是一种高等编制程序本领,日常用来产生都部队分特意的固守,举个例子词典软件的显示器取词,游戏剧改革正软件的数码改正等。当然,此才干更加的多的是被黑客或然病毒用来抨击别的程序,截获须要的数目或转移指标程序的一举一动。本文不根究此工夫的应用,只说完结。同有的时候候希望精通此能力的人都能够合法的施用它,不要去做危急或犯罪的作业,损人不利己。

3、合法的值(陆11人)

关爱下那4个参数的合法取值:

• Displacement — An 8-bit, 16-bit, or 32-bit value.
• Base — The value in a 64-bit general-purpose register.
• Index — The value in a 64-bit general-purpose register.
• Scale factor — A value of 2, 4, or 8 that is multiplied by the index
value.

一、原理

每叁个顺序在操作系统中运转,都必须要调用操作系统提供的函数——也正是API(应用程序编制程序接口)——来兑现程序的各个功效。在Windows操作系统下,API正是那几千个系统函数。在有一些程序中并没直接调用API的代码,例如上边包车型大巴次第:

#include <iostream>
using namespace std;
int main(void)
{
    cout << "Hello World!" << endl;
    return 0;
}

实际,cout对象的此中管理函数已经替你调用API。固然你的main函数是空的,里面什么代码都不写,只要程序被操作系统运营,也会调用一些为主的API,举个例子LoadLibrary。那个函数是用来加载DLL的,也便是在进度运转的进度中,把DLL中的程序指令和数据读入当前经过并进行运转代码,大家后边会用到这一个函数。

只要能够设法用自定义函数替换宿主进程调用的靶子API函数,那么就足以收获宿主进度传入指标API的参数,并能够转移宿主进度的表现。但要想校订目的API函数必得先找找并张开宿主进程,并让自定义代码能在宿主进度中运作。因此挂API钩子分为四步:1.
查究并开辟宿主进程,2. 将注入体装入宿主进程中运转,3.
用伪装函数替换目的API,4.
实行伪装函数。整个程序也分为两片段,一部分是背负寻找并开垦宿主进度和注入代码的应用程序,另一部分是包涵校勘代码和伪装函数的注入体。

4、Mod R/M(32位寻址)

我们在后文将会用到Mod 智跑/M字节,所以将31位寻址的格式贴在这里边:

澳门新浦京8455com 2

上表的备考,当中第1条就要大家的示范中用到,所以那边在意下:

  1. The [–][–] nomenclature means a SIB follows the ModR/M
    byte.
  2. The disp32 nomenclature denotes a 32-bit displacement that follows
    the ModR/M byte (or the SIB byte if one is present) and that is
    added to the index.
  3. The disp8 nomenclature denotes an 8-bit

二、查找钦定的进度

搜寻钦定的进度有比比较多措施,上面轻松的介绍三种:

5、SIB(32位寻址)

平等,因为用到了Mod Escort/M字节,那么SIB字节也大概要用到:

澳门新浦京8455com 3

1. 找到鼠标所指窗体的进度句柄

DWORD GetProcIDFromCursor(void)
{
    //Get current mouse cursor position
    POINT ptCursor;
    if (!GetCursorPos(&ptCursor))
    {
        cout << "GetCursorPos Error: " << GetLastError() << endl;
        return 0;
    }

    //Get window handle from cursor postion
    HWND hWnd = WindowFromPoint(&ptCursor);
    if (NULL == hWnd)
    {
        cout << "No window exists at the given point!" << endl;
        return 0;
    }

    //Get the process ID belong to the window.
    DWORD dwProcId;
    GetWindowThreadProcessId(hWnd, &dwProcId);

    return dwProcId;
} 

6、示例

2. 寻找钦命文件名的经过

#include <Psapi.h>
#pragma comment(lib, "Psapi.lib")
DWORD GetProcIDFromName(LPCTSTR lpName)
{
    DWORD aProcId[1024], dwProcCnt, dwModCnt;
    HMODULE hMod;
    TCHAR szPath[MAX_PATH];

    //枚举出所有进程ID
    if (!EnumProcesses(aProcId, sizeof(aProcId), &dwProcCnt))
    {
        cout << "EnumProcesses error: " << GetLastError() << endl;
        return 0;
    }

    //遍例所有进程
    for (DWORD i = 0; i < dwProcCnt; ++i)
    {
        //打开进程,如果没有权限打开则跳过
        HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, aProcId[i]);
        if (NULL != hProc)
        {
            //打开进程的第1个Module,并检查其名称是否与目标相符
            if (EnumProcessModules(hProc, &hMod, sizeof(hMod), &dwModCnt))
            {
                GetModuleBaseName(hProc, hMod, szPath, MAX_PATH);
                if (0 == lstrcmpi(szPath, lpName))
                {
                    CloseHandle(hProc);
                    return aProcId[i];
                }
            }
            CloseHandle(hProc);
        }
    }
    return 0;
}

6.1、计划职业

来看个实在的例子。

上面包车型大巴代码是变化mov汇编码:

void Assembler::movl(Address dst, Register src) {
  InstructionMark im(this);
  prefix(dst, src);
  emit_int8((unsigned char)0x89);
  emit_operand(src, dst);
}

prefix(dst,src)不怕管理prefix和REX prefix,这里大家不尊敬。

emit_int8((unsigned char) 0x89)看名称就会想到其意义正是生成了贰个字节,那字节的内容0×89意味着怎么样啊?

先不急,还会有一句emit_operand(src,dst),那是一段十分长的代码,大家概况看下:

void Assembler::emit_operand(Register reg, Register base, Register index,
                 Address::ScaleFactor scale, int disp,
                 RelocationHolder const& rspec,
                 int rip_relative_correction) {
  relocInfo::relocType rtype = (relocInfo::relocType) rspec.type();

  // Encode the registers as needed in the fields they are used in

  int regenc = encode(reg) << 3;
  int indexenc = index->is_valid() ? encode(index) << 3 : 0;
  int baseenc = base->is_valid() ? encode(base) : 0;

  if (base->is_valid()) {
    if (index->is_valid()) {
      assert(scale != Address::no_scale, "inconsistent address");
      // [base + index*scale + disp]
      if (disp == 0 && rtype == relocInfo::none  &&
          base != rbp LP64_ONLY(&& base != r13)) {
        // [base + index*scale]
        // [00 reg 100][ss index base]

        /**************************
        * 关键点:关注这里
        **************************/

        assert(index != rsp, "illegal addressing mode");
        emit_int8(0x04 | regenc);
        emit_int8(scale << 6 | indexenc | baseenc);
      } else if (is8bit(disp) && rtype == relocInfo::none) {
        // ...
      } else {
        // [base + index*scale + disp32]
        // [10 reg 100][ss index base] disp32
        assert(index != rsp, "illegal addressing mode");
        emit_int8(0x84 | regenc);
        emit_int8(scale << 6 | indexenc | baseenc);
        emit_data(disp, rspec, disp32_operand);
      }
    } else if (base == rsp LP64_ONLY(|| base == r12)) {
      // ... 
    } else {

      // ... 
    }
  } else {
    // ... 
  }
}

上边的代码的关心点已经标明,这里大家将其收取,并将前文中的emit_int8((unsigned char) 0x89)结缘起来:

emit_int8((unsigned char) 0x89)
emit_int8(0x04 | regenc);
emit_int8(scale << 6 | indexenc | baseenc);

末段其生成了之类的汇编代码(62位机器):

mov    %eax,(%rcx,%rbx,1)

好了,难题来了:

上边那句汇编怎么得出的?

 3. 查找此外钦赐音讯的长河

透过CreateToolhelp32Snapshot枚举系统中正在运维的有所进度,并经过有关数据构造得到进度的音讯,具体用法能够参见:

6.2、总括进度

大家给个上面包车型客车值:

regenc = 0x0,scale << 6 | indexenc | baseenc = 25

开展轻巧的演算就足以获得:

emit_int8((unsigned char) 0x89) //得到0x89
emit_int8(0x04 | regenc); //得到0x04
emit_int8(scale << 6 | indexenc | baseenc); //得到0x19

合起来正是多少个字节:

0x89 0x04 0x19

1、0×89对应什么?

澳门新浦京8455com 4

从上表能够看出因为JVM工作在61个人下,所以须求匹配REX.W来“牵头”,可是在大家以此例子中,其恰恰是0。

季布一诺看这么些89/r:

MOV r/m64,r64 //64位,将寄存器中的值给到寄存器或者内存地址中

2、0×04意味怎样?

前日大家要用到上边的Mod Tiguan/M表和SIB表了。

用第二个字节0×04查Mod
Murano/M表,可以知道源操作数是存放器EAX,同不时间可以看到寻址类型是[–][–]类型,含义为:

The [–][–] nomenclature means a SIB follows the ModR/M byte.

3、0×19代表怎么样?

接轨查SIB表,对应字节0×19的是:

base = ECX
scaled index = EBX

4、汇编代码:

//32位
mov %eax,%(ecx,ebx,1)

//64位
mov %rax,%(rcx,rbx,1)

三、代码注入

上面提到过LoadLibrary能够将钦点的DLL代码注入当前进度,假若能让宿主进程来举行那一个函数,并把大家自个儿的DLL的公文名传入,那么我们的代码就足以在宿主进度中运转了。

HMODULE WINAPI LoadLibrary(
  __in          LPCTSTR lpFileName
);

再看另一个函数:CreateRemoteThread,它能够让宿主进度新开二个线程,不过新线程的管理函数(LPTHREAD_START_ROUTINE)必须是宿主进程中的函数地址或系统API。

HANDLE WINAPI CreateRemoteThread(
  __in          HANDLE hProcess,
  __in          LPSECURITY_ATTRIBUTES lpThreadAttributes,
  __in          SIZE_T dwStackSize,
  __in          LPTHREAD_START_ROUTINE lpStartAddress,
  __in          LPVOID lpParameter,
  __in          DWORD dwCreationFlags,
  __out         LPDWORD lpThreadId
);

//其中LPTHREAD_START_ROUTINE的定义如下
typedef DWORD (WINAPI *PTHREAD_START_ROUTINE)(
    LPVOID lpThreadParameter
    );

假若得以用让宿主进度新开一个线程,奉行LoadLibrary函数,而参数是流入体DLL的文件名,就瓜熟蒂落了。但是要产生这个操作,大家先来深入分析一下倾向。

大家知晓,全数系统API函数的调用方式都是__stdcall,即参数采纳从右到左的压栈格局,本身在退出时清空货仓。这一类函数的实际调用进度如下:在调用前先由调用者将全部参数以地点或数值的花样从右向左压入栈中,然后用call指令调用该函数;步入函数后,先从栈中抽出那个参数再扩充览演出算,并在函数再次回到前将事情发生前压入的栈数据总体弹出以保全栈平衡,最终用eax寄放款和储蓄传递再次回到值(地址或数值)给调用者。那也便是说在指令层面上讲,API函数的骨干调用格局都一点差距也未有,不过调用者必得在栈中压入显明数量的参数,若压入的参数数量不相同盟,函数内的取栈和弹栈操作将会使得栈数据错乱,最后诱致程序崩溃。

通过观望开采LoadLibrary的参数数量适逢其时与LPTHREAD_START_ROUTINE都唯有一个参数,那么一旦可以获得LoadLibrary函数在宿主进度中的地址,作为lpStartAddress传入CreateRemoteThread,并将大家的注入体DLL的文件名作为lpParameter传入,那么就足以让宿主进程试行注入体代码了。为了将DLL的公文名传入宿主进程,大家还需求以下五个API:VirtualAllocEx和VirtualFreeEx能够在宿主进度中分配和自由一段内部存款和储蓄器空间;ReadProcessMemory和WriteProcessMemory能够在宿主进度中的内定内部存款和储蓄器地址读出或写入数据。

在注入的代码推行达成后,还要做到清监护人业。首先是卸载刚刚载入的DLL,须要利用另八个系统API:FreeLibrary。进度与地方的代码注入同样,使用CreateRemoteThread,将FreeLibrary的地点作为lpStartAddress参数字传送入。注意到FreeLibrary的参数是二个HMODULE,该句柄其实是一个Module的全局ID,平日由LoadLibrary的重返值给出。因而能够调用GetExitCodeThread获取后面推行的LoadLibrary线程的再次来到值,再作为CreateRemoteThread的lpParameter参数字传送入,那样就到位了DLL的卸载。还要记得用VirtualFreeEx释放VirtualAllocEx申请到的内部存款和储蓄器,并关闭张开的有所句柄,达成末段的清总管业。

近期流入代码的步调就比较清晰了:

  1. 调用OpenProcess获取宿主进度句柄;
  2. 调用GetProcAddress查找LoadLibrary函数在宿主进度中的地址;
  3. 调用VirtualAllocEx和WriteProcessMemory将DLL文件名字符串写入宿主进度的内部存款和储蓄器;
  4. 调用CreateRemoteThread实施LoadLibrary在宿主进程中运作DLL;
  5. 调用VirtualFreeEx释放刚申请的内部存款和储蓄器;
  6. 调用WaitForSingleObject等待注入线程甘休;
  7. 调用GetExitCodeThread获取前边加载的DLL的句柄;
  8. 调用CreateRemoveThead执行FreeLibrary卸载DLL;
  9. 调用CloseHandle关闭打开的全数句柄。

代码注入的兼具代码整理如下。(注意:这一个顺序要求在win32调控台形式下编写翻译生成叁个exe文件。在支配台下运维时索要多少个参数:第四个参数为宿主进度的镜第一名称,能够在职责微电脑中查阅;第4个参数为注入体DLL的完全路线文件名。程序运转后就能够将内定的DLL装入内定名称的宿主进度)

#include <tchar.h>
#include <Windows.h>
#include <atlstr.h>
#include <Psapi.h>
#pragma comment(lib, "Psapi.lib") 

#include <iostream>
#include <string>
using namespace std;

DWORD FindProc(LPCSTR lpName)
{
    DWORD aProcId[1024], dwProcCnt, dwModCnt;
    char szPath[MAX_PATH];
    HMODULE hMod;

    //枚举出所有进程ID
    if (!EnumProcesses(aProcId, sizeof(aProcId), &dwProcCnt))
    {
        //cout << "EnumProcesses error: " << GetLastError() << endl;
        return 0;
    }

    //遍例所有进程
    for (DWORD i = 0; i < dwProcCnt; ++i)
    {
        //打开进程,如果没有权限打开则跳过
        HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, aProcId[i]);
        if (NULL != hProc)
        {
            //打开进程的第1个Module,并检查其名称是否与目标相符
            if (EnumProcessModules(hProc, &hMod, sizeof(hMod), &dwModCnt))
            {
                GetModuleBaseNameA(hProc, hMod, szPath, MAX_PATH);
                if (0 == _stricmp(szPath, lpName))
                {
                    CloseHandle(hProc);
                    return aProcId[i];
                }
            }
            CloseHandle(hProc);
        }
    }
    return 0;
}

//第一个参数为宿主进程的映象名称,可以任务管理器中查看
//第二个参数为需要注入的DLL的完整文件名
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cout << "Invalid parameters!" << endl;
        return -1;
    }
    //查找目标进程,并打开句柄
    DWORD dwProcID = FindProc(argv[1]);
    if (dwProcID == 0)
    {
        cout << "Target process not found!" << endl;
        return -1;
    }
    HANDLE hTarget = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcID);
    if (NULL == hTarget)
    {
        cout << "Can't Open target process!" << endl;
        return -1;
    }

    //获取LoadLibraryW和FreeLibrary在宿主进程中的入口点地址
    HMODULE hKernel32 = GetModuleHandle(_T("Kernel32"));
    LPTHREAD_START_ROUTINE pLoadLib = (LPTHREAD_START_ROUTINE)
        GetProcAddress(hKernel32, "LoadLibraryW");
    LPTHREAD_START_ROUTINE pFreeLib = (LPTHREAD_START_ROUTINE)
        GetProcAddress(hKernel32, "FreeLibrary");
    if (NULL == pLoadLib || NULL == pFreeLib)
    {
        cout << "Library procedure not found: " << GetLastError() << endl;
        CloseHandle(hTarget);
        return -1;
    }

    WCHAR szPath[MAX_PATH];
    MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, argv[2], -1,
        szPath, sizeof(szPath) / sizeof(szPath[0]));

    //在宿主进程中为LoadLibraryW的参数分配空间,并将参数值写入
    LPVOID lpMem = VirtualAllocEx(hTarget, NULL, sizeof(szPath),
        MEM_COMMIT, PAGE_READWRITE);
    if (NULL == lpMem)
    {
        cout << "Can't alloc memory block: " << GetLastError() << endl;
        CloseHandle(hTarget);
        return -1;
    }

    // 参数即为要注入的DLL的文件路径
    if (!WriteProcessMemory(hTarget, lpMem, (void*)szPath, sizeof(szPath), NULL))
    {
        cout << "Can't write parameter to memory: " << GetLastError() << endl;
        VirtualFreeEx(hTarget, lpMem, sizeof(szPath), MEM_RELEASE);
        CloseHandle(hTarget);
        return -1;
    }

    //创建信号量,DLL代码可以通过ReleaseSemaphore来通知主程序清理
    HANDLE hSema = CreateSemaphore(NULL, 0, 1, _T("Global\InjHack"));

    //将DLL注入宿主进程
    HANDLE hThread = CreateRemoteThread(hTarget, NULL, 0, pLoadLib, lpMem, 0, NULL);

    //释放宿主进程内的参数内存
    VirtualFreeEx(hTarget, lpMem, sizeof(szPath), MEM_RELEASE);

    if (NULL == hThread)
    {
        cout << "Can't create remote thread: " << GetLastError() << endl;
        CloseHandle(hTarget);
        return -1;
    }

    //等待DLL信号量或宿主进程退出
    WaitForSingleObject(hThread, INFINITE);
    HANDLE hObj[2] = {hTarget, hSema};
    if (WAIT_OBJECT_0 == WaitForMultipleObjects(2, hObj, FALSE, INFINITE))
    {
        cout << "Target process exit." << endl;
        CloseHandle(hTarget);
        return 0;
    }
    CloseHandle(hSema);

    //根据线程退出代码获取DLL的Module ID
    DWORD dwLibMod;
    if (!GetExitCodeThread(hThread, &dwLibMod))
    {
        cout << "Can't get return code of LoadLibrary: " << GetLastError() << endl;
        CloseHandle(hThread);
        CloseHandle(hTarget);
        return -1;
    }

    //关闭线程句柄
    CloseHandle(hThread);

    //再次注入FreeLibrary代码以释放宿主进程加载的注入体DLL
    hThread = CreateRemoteThread(hTarget, NULL, 0, pFreeLib, (void*)dwLibMod, 0, NULL);
    if (NULL == hThread)
    {
        cout << "Can't call FreeLibrary: " << GetLastError() << endl;
        CloseHandle(hTarget);
        return -1;
    }
    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);

    CloseHandle(hTarget);
    return 0;
}

7、结语

正文简要商讨了:

什么依照opcode和寻址格局,将bytecode生成汇编码。

四、挂钩

上边的顺序已经得以将自编代码注入到宿主进程中了,下边将要尤其研究哪边来编排注入体(动态链接库)以促成对指标API举办阻拦。这一片段的内容比上面要深一些,供给或多或少汇编底工知识。

1. 在VS中开展汇编级调节和测验

VS为客商提供了格外常有力的调弄收拾功用,能够方便的查看注入代码与宿主代码的运维情况。今后亟待另创造叁个品类作为宿主进度,MFC轻巧对话框程序是三个不利的选用。下边就以GetTickCount作为对象API进行讲明。先响应对话框的鼠标左键按下事件,并增添GetTickCount代码:

void CMyTargetDlg::OnLButtonDown(UINT nFlags, CPoint point)
{
    GetTickCount();
    CDialog::OnLButtonDown(nFlags, point);
}

 在GetTickCount前安设断点,运行程序后点左键让程序停在那,然后展开反汇编(调节和测量检验菜单->窗口),拜会到上面包车型地铁反汇编代码:

 澳门新浦京8455com 5

 上海体育场地中有4行汇编指令,第1列是指令所在的内部存款和储蓄器地址,第2列是汇编指令,第3列是操作数。在差别的机械上编写翻译结果也不一致,所以内存地址会不均等,但背后的指令和操作数都差不多。按一下F10(逐进度),运维到0063E615这一行,再按下F11(逐语句)就能够步向到GetTickCount的代码中去,见下图:

 澳门新浦京8455com 6

 接下来要推行的吩咐是:

mov edx, 7FFE0000h

专心这一句代码所在的地址是7C80934A,下一句代码是7C80934F,表明这一行mov指令的长短为5。今后展开内部存款和储蓄器查看窗口(调节和测量检验->窗口->内存),并在地方里输入0x7C80934A,展现如下:

 澳门新浦京8455com 7

可见那条mov指令对应的机器码正是:ba 00 00 fe
7f。那时候开辟寄放器窗口(调节和测验->窗口->寄存器),能够看出眼下各存放器的值。按下F10举行单步,仍可以看看各寄放器的生成(变化的值用革命标记),如下图:

 澳门新浦京8455com 8

2. 限令的格式

为了世襲要询问x86布局下汇编码和机器码的照望关系,必要参照他事他说加以考察一部特别重要的文献“英特尔®
64 and IA-32 Architectures Software Developer’s
Manual”(以下简单的称呼IA32SDM),那是AMD公司免费提要求开荒者的,能够在上面包车型大巴网站找到3卷合订本:

在IA32SDM的Vol. 2B – 4.2(总第1257页)能够找到各类mov指令的表达:

 澳门新浦京8455com 9

上表中每一行表示了一种mov指令,第一列是这种指令的操作码格式,第二列是指令格式,最终一列是描述消息。格令格式一列中r*是指位长为*的寄存
器,r/m*是指位长为*的内部存款和储蓄器地址,Imm*是指位长为*的当下数。上文中的mov指令“mov
edx,
7FFE0000h”的操作数有八个:第二个是三十六人存放器(r32)edx,第叁个是一个三十一位及时数(Imm32)7FFE0000h。查上表可以看到该
mov指令便是用红框划出的那一种:“MOV r32,
Imm32”,它对应的Opcode(操作码)是“B8+rd”,机器码编码格式为OI。在1258页能够见到各类mov指令的编码格式,第一列与上表中的第三列对应,前边四列是七个操作数。

澳门新浦京8455com 10

表中编码格式OI蕴含五个操作数,先是Opcode加存放器代码,前面紧跟了三个立马数。而Opcode的编辑格式参见IA32SDM的Vol.
2A – 3.1.1.1(总第606页),摘录如下:

The “Opcode” column in the table above shows the object code produced
for each form of the instruction. When possible, codes are given as
hexadecimal bytes in the same order in which they appear in memory.
Definitions of entries other than hexadecimal bytes are as follows:

  • REX.W — Indicates the use of a REX prefix that affects operand
    size or instruction semantics. The ordering of the REX prefix and
    other optional/mandatory instruction prefixes are discussed
    Chapter 2. Note that REX prefixes that promote legacy instructions
    to 64-bit behavior are not listed explicitly in the opcode column.
  • /digit — A digit between 0 and 7
    indicates that the ModLAND/M byte of the instruction uses only the
    r/m (register or memory卡塔尔国 operand. The reg 田野 contains the digit
    that provides an extension to the instruction’s
    opcode.(翻译:那是贰个0到7的数字,表示指令的ModOdyssey/M字节只使用r/m操作数。ModWrangler/M的reg位正是该数,作为操作码的八个附加码)
  • /r — Indicates that the ModR/M byte of the instruction contains a
    register operand and an r/m operand.
  • cb, cw, cd, cp, co, ct — A 1-byte (cb), 2-byte (cw), 4-byte (cd),
    6-byte (cp), 8-byte (co) or 10-byte (ct) value following the
    opcode. This value is used to specify a code offset and possibly a
    new value for the code segment register.
  • ib, iw, id, io — A 1-byte (ib), 2-byte (iw), 4-byte (id) or 8-byte
    (io) immediate operand to the instruction that follows the opcode,
    ModR/M bytes or scaleindexing bytes. The opcode determines if the
    operand is a signed value. All words, doublewords and quadwords
    are given with the low-order byte first.
  • +rb, +rw, +rd, +ro — A register
    code, from 0 through 7, added to the hexadecimal byte given at the
    left of the plus sign to form a single opcode byte. See Table 3-1
    for the codes style=”color: #ff0000;”> style=”color: #ff0000;”>(翻译:那是一个贮存器代码,范围由0到7。与+号左侧的16进制数代数相加构成两个完好的操作码字节。具体代码参见Table
    3-1).  style=”color: #000000;”>The +ro columns in the table are
    applicable only in 64-bit mode.

  • +i — A number used in floating-point instructions when one of the
    operands is ST(i) from the FPU register stack. The number i (which
    can range from 0 to 7) is added to the hexadecimal byte given at
    the left of the plus sign to form a single opcode byte.

 依照标识为革命的陈述可以看到Opcode“B8+rd”中的B8是根基码值0xB8,rd表示三19个人存放器EDX的代号。贮存器的代码表可参见IA32SDM的Vol.
2A – Table 3-1(总第607页),如下图:

 澳门新浦京8455com 11

 从上表中栗色线框标出的一对中能够看看,EDX对应的附加码为2,因而那条mov指令的Opcode正是0xB8

  • 0x02 =
    0xBA。跟据编码格式OI,前面紧跟二个叁14人的及时数0x7FFE0000,由于AMD的CPU连串是Little
    Ending,所以字节序为逆序,故在内部存款和储蓄器查看器中及时数字显示示为“00 00 fe
    7f”。由此可知,该mov指令的一体化学工业机械器码为:“ba 00 00 fe
    7f”,与内部存款和储蓄器查看器的结果相符。

2. 准备JMP

地点简介了在VS进行汇编级调试的主导方法,并以mov指令为模范疏解了什么剖判机器码。领会了那个工具和素材,就足以清楚地打听我们下边要到位的代码在系统内部实施的内部原因。从上节亦可,GetTickCount那几个API实施的首先条指令是mov,如若能把mov的Opcode改为jmp,那就足以跳转到自定义的函数地址实行任意代码了。从IA32SDM(Vol.
2A – 3.2)中获悉jmp指令的机器码:

 澳门新浦京8455com 12

鉴于自定义的函数地点随机,且在win32操作系统的有限支撑下,每一种进度的段地址都以原则性的,程序能够透过CS寄存器访谈,但不可见转移。由此咱们有三种采用,一是用JMP
r/m32下令推行段内相对跳转,二是用JMP
rel32指令施行段内相对跳转。先讲授怎样利用JMP
r/m32实施相对跳转。机器码的格式参见IA32SDM的Vol. 2A – 2.1,如下图:

 澳门新浦京8455com 13

从上海体育场所可以知道,机器码由6超过十分之五结缘,而JMP r/m32发令对应的机器码为“FF
/4”(当中/4的意义参见上文中Opcode表明里用黄褐标记的文字),用到了里面3个部分:1个字节的Opcode(即0xFF)、1个字节的Mod牧马人/M和4个字节的Displacement操作数。个中的ModTiguan/M钦命了CPU的寻址格局以致Opcode的附加码,它又分为三段:Mod、Reg/Opcode和GL450/M,具体构成可参见IA32SDM的Vol.
2A – 2.1.3和后边的Table 2-2,如下图:

 澳门新浦京8455com 14

先看一下表头最左侧一格,第6行“/digit (Opcode卡塔尔”正是机器码“FF
/4”中的4,所以看红框标识的那一列(4的二进制为100)就能够了。“Effective
Address”钦命了寻指方式,为了幸免对贮存器进行操作,用1条指令就形成跳转,大家选用最轻松易行的“disp32”这一行,它象征仅用命令机器码中的第3片段Displacement表示跳转的靶子地址。那样就规定了动用的Mod位为00,Reg/Opcode位为100,Lacrosse/M位为101。总括可得Mod福特Explorer/M字节为00
100 101(二进制) = 0x25。

Displacement指向一段4字节的内部存款和储蓄器,这段内部存款和储蓄器里贮存的是终极的靶子地址。由此供给先用VirtualAllocEx申请4个字节的半空中,将自定义函数之处存入,然后再将报名的地点填入Displacement。总之,完整的机器码应该是FF
25 XX XX XX XX,最后边的4个字节是三个存有目的函数入口地址的内部存款和储蓄器地址。

用JMP
r/m32下令完结跳转是相比较复杂的,不止供给报名和自由内部存款和储蓄器,且一切机器指令有6个字节。更简便的章程正是应用JMP
rel32下令实行相对跳转,而机器码唯有5个字节。JMP rel32应和的机器码是E9
cd,此中cd正是相对地址,计算方式为:目的地点 – 当前下令地址 –
5。在备选好JMP指令的机器码后,就能够将其替换成目标API的入口地址处,诈欺宿主进程履行伪装函数。

3. 校正入口点

看完上边的介绍,相信您曾经急不可待的想要尝试什么对目的API挂钩了。尽管还大概有不菲难点远非缓和,譬喻怎么样再次来到,如何实行原API成效,怎么着全身而退等等,但那么些难题得以先放一放,先来拜访是不是利用方面包车型地铁议程成功联系。

第一须求建设构造一个DLL项目以改动注入体,自定义三个DllMain函数,如下:

#include <windows.h>

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hInstDll);
        InstallMonitor();
        break;

    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

 然后编写挂钩函数InstallMonitor:

void InstallMonitor(void)
{
    HANDLE hProc = GetCurrentProcess();
    BYTE aOpcode[5] = {0xE9}; //JMP Procudure
    *(DWORD*)(&g_aOpcode[1]) = DWORD(MonFunc) - DWORD(GetTickCount) - 5;
    WriteProcessMemory(hProc, LPVOID(GetTickCount), LPVOID(aOpcode), 5, NULL);
    CloseHandle(hProc);
}

上面的代码很好精通,aOpcode就是基于前文介绍的不二秘籍组织的jmp指令,钦赐跳转到自定义的伪装函数MonFunc,然后用WriteProcessMemory将jmp指令填写入GetTickCount的代码处。伪装函数MonFunc函数很好写:

void WINAPI MonFunc(void)
{
    MessageBox(NULL, _T("注入代码"), _T("示例"), 0);
}

 至此,您就足以按上面包车型大巴代码编写翻译叁个注入体DLL了,然后接受本文第三部分的注入程序就足以将此DLL注入到宿主进程试行。

五、完美棍骗

假若您按上文所述的法子试行出成功的结果,那么您很或然会发觉在对话框分明后宿主进度崩溃了。原因有下边几条:

  1. 伪装函数未有科学的涵养栈的平衡,以致再次来到时宿主清栈出错;
  2. 伪装函数未有按API执行办法实施出结果,宿主必须要奇怪的调用系统API招致错误;
  3. 伪装函数不是线程安全的,引致宿主在产出调用时出错;
  4. 宿主有安全防止章程,检查到攻击后自动复苏或自个儿灭亡。

正文只谈谈前3条原因的施工方案,不思量第4条原因。上边逐一解释。

1. 维持栈的平衡

好多API都以有参数的,而参数是由宿主在call指令实施前压入货仓。Win32API的调用约定是__stdcall,表示由API肩负栈的清理,那么只要伪装函数在回届期未尝合适的清栈一定会将招致出错。因而,伪装函数的参数表一定要与原API相近,才具担保编写翻译器生成的代码能够正确重临到宿主代码。

2. 执行原API的功能

为了能够施行原API的机能,必得在调用它以前恢复生机它原先的代码,不然就能够沦为死循环。当然,应该在改写机器码时保留原本的机器码,那样就能够应用WriteProcessMemory将其恢复生机原状。ReadProcessMemory这一个API函数与WriteProcessMemory的功用相反,能够读取钦定地方的机器码。还要记得,在原API调用截至后还要匡正它的入口点,不然下一次就不能够诈骗了。整个伪装函数的构造如下:

//Monitor Function
DWORD WINAPI MonFunc()
{
    //Restore the original API before calling it
    ReleaseBase();

    //Calling the original API
    DWORD dw = GetTickCount();

    //Monitor the original API again
    MonitorBase();

    //You can do anything here

    return dw;
}

3. 线程安全

用EnterCriticalSection和LeaveCriticalSection是保障线程安全的一级选项,将伪装函数用那对函数包起来就足以扑灭出现访谈的标题。未来的代码应该看起来是这么:

//Monitor Function
DWORD WINAPI MonFunc()
{
    //Thread safety
    EnterCriticalSection(&g_cs);

    //Restore the original API before calling it
    ReleaseBase();
    DWORD dw = GetTickCount();
    MonitorBase();

    //You can do anything here

    //Thread safety
    LeaveCriticalSection(&g_cs);
    return dw;
}

4. 平安无事示例

上面贴出注入体DLL的完全代码,供您参谋。那么些DLL对GetTickCount挂了钩子,您可以在伪装函数MonFunc中加上任性的自定义代码,并在脱离的时候调用UninstallMonitor截止钩子程序。

#include <tchar.h>
#include <Windows.h>

//Handle of current process
HANDLE g_hProc;

//Backup of orignal code of target api
BYTE g_aBackup[6];
BYTE g_aOpcode[6];

//Critical section, prevent concurrency of calling the monitor
CRITICAL_SECTION g_cs;

//Base address of target API in DWORD
DWORD g_dwApiFunc = (DWORD)GetTickCount;

//Hook the target API
__inline BOOL MonitorBase(void)
{
    // Modify the heading 6 bytes opcode in target API to jmp instruction,
    // the jmp instruction will lead the EIP to our fake function
  ReadProcessMemory(g_hProc, LPVOID(g_dwApiFunc), LPVOID(g_aBackup),
    sizeof(g_aBackup)/ sizeof(g_aBackup[0]), NULL);
 return WriteProcessMemory(g_hProc, LPVOID(g_dwApiFunc),
        LPVOID(g_aOpcode), sizeof(g_aOpcode) / sizeof(g_aOpcode[0]), NULL);
}

//Unhook the target API
__inline BOOL ReleaseBase(void)
{
    // Restore the heading 6 bytes opcode of target API.
    return WriteProcessMemory(g_hProc, LPVOID(g_dwApiFunc),
        LPVOID(g_aBackup), sizeof(g_aOpcode) / sizeof(g_aOpcode[0]), NULL);
}

//Pre-declare
BOOL UninstallMonitor(void);

//Monitor Function
DWORD WINAPI MonFunc()
{
    //Thread safety
    EnterCriticalSection(&g_cs);

    //Restore the original API before calling it
    ReleaseBase();
    DWORD dw = GetTickCount();
    MonitorBase();

    //You can do anything here, and you can call the UninstallMonitor
    //when you want to leave.

    //Thread safety
    LeaveCriticalSection(&g_cs);
    return dw;
}

//Install Monitor
BOOL InstallMonitor(void)
{
    //Get handle of current process
    g_hProc = GetCurrentProcess();

    g_aOpcode[0] = 0xE9; //JMP Procudure
    *(DWORD*)(&g_aOpcode[1]) = (DWORD)MonFunc - g_dwApiFunc - 5;

    InitializeCriticalSection(&g_cs);

    //Start monitor
    return MonitorBase();
}

BOOL UninstallMonitor(void)
{
    //Release monitor
    if (!ReleaseBase())
        return FALSE;

    DeleteCriticalSection(&g_cs);

    CloseHandle(g_hProc);

    //Synchronize to main application, release semaphore to free injector
    HANDLE hSema = OpenSemaphore(EVENT_ALL_ACCESS, FALSE, _T("Global\InjHack"));
    if (hSema == NULL)
        return FALSE;
    return ReleaseSemaphore(hSema, 1, (LPLONG)g_hProc);
}

BOOL WINAPI DllMain(HINSTANCE hInstDll, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
    case DLL_PROCESS_ATTACH:
        DisableThreadLibraryCalls(hInstDll);

        InstallMonitor();
        break;

    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

 

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图