二进制插桩

二进制插桩

什么是二进制插桩?

答:二进制插桩是指在二进制程序中的任意位置插入新代码,用来观察或修改二进制程序的行为。添加新代码位置为插桩点,添加的代码为插桩代码。

插桩平台主要分为:静态插桩(SBI)和动态插桩(DBI),采用不同的方法解决插入和重定位代码的问题

  • 静态插桩使用二进制重写方法永久修改磁盘上的二进制文件
  • 动态插桩不会修改磁盘上的二进制程序,而是监视二进制程序的执行状态,在程序运行时将新指令插入指令流中。DBI缺点是运行时插桩计算成本高,导致其速度相较于SBI更慢。

比如对二进制程序中的所有call指令进行插桩,统计二进制程序中调用最频繁的函数;在所有间接的控制转移指令处插入代码,来检查控制流转移目标是否属于预期目标集合,若不属于则中断程序执行并发出警告,从而提高二进制程序抵御流劫持攻击的能力。

静态二进制插桩

静态插桩对二进制程序进行反汇编,然后按需添加插桩代码并将更新的二进制程序存入磁盘。SBI平台包括PEBIL和Dyninst。目前有两种流行的解决方法:

  • int3方法
  • 跳板方法

int3方法

int3指令用于调试器实现软件断点。静态二进制插桩,一般使用jmp指令覆盖插桩点处的指令,将控制流转移到插桩代码,使其访问插桩代码

但是问题在于jmp指令回占用多字节,通常需要一个5字节的长度,当对短指令插桩时,指向插桩代码的jmp指令可能比它替换的指令长,会覆盖并破坏下一条指令。

基于以上问题,需要用到int3指令,int3指令可以用来对不适用于多字节跳转的简短指令进行插桩。在x86架构中,int3指令会生成一个软中断,用户空间的程序能够通过操作系统提供的SIGTRAP信号捕获中断。int3的关键在于它的长度只有1字节,可以覆盖任何指令。从SBI的角度,使用int3对指令进行插桩,用0xcc覆盖该指令的第一字节。当SIGTRAP信号产生时,可以用linux系统的ptrace API找出中断发生的地址,从而获取插桩点地址,然后根据插桩点位置调用相应的插桩代码。

缺点:int3软中断速度很慢,导致插桩后的应用程序的运行开销过大。

跳板方法

跳板方法不会直接对原始代码进行插桩,相反,它创建一个原始代码的副本并只对这个副本进行插桩。

  • 不对原始代码进行插桩,而是对原始代码副本进行插桩。调用f1时跳转到f1_copy
  • 在f1_copy中每个可能的插桩点插入几个nop指令,茶庄引擎回修补所有相对jmp指令的偏移
  • 重写直接调用的时候,调用f2函数使其指向原始代码的副本函数f2_copy
  • 间接函数调用,跳板方法使得间接控制流转移指令把在副本上的控制流转移到原始的插桩代码中,如上图从插桩代码中调用原始代码中的rax
  • 间接函数跳转,使用放置在原始代码中的跳板拦截控制流,将其从原始代码定位回插桩后的代码

动态二进制插桩

动态二进制插桩引擎通过监视和控制所有的指令来动态地插桩进程。通过公开API接口,允许用户编写自定义的DBI工具,指定应该插桩哪些代码以及如何插桩。

此外,大多数动态二进制插桩框架都有三种执行模式:解释模式( Interpretation mode)、探测模式(probe mode)和JIT模式(just-in-time mode)。JIT模式是最常见的实现方式,也是最常用的模式,即使动态二进制插桩系统支持多种执行模式。在JIT模式下,原始二进制文件或可执行文件实际上从未被修改或执行过。因为,此时二进制文件被视为数据,修改后的二进制文件副本将在新的内存区域中生成(但只针对二进制文件的执行部分,而不是整个二进制文件),此时执行的就是这个修改后的文件副本。而在解释模式中,二进制文件也被视为数据,每条指令都被用作具有相应功能的替代指令的查找表(由用户实现) 。在探测模式中,二进制文件实际上是通过使用新指令来覆盖旧的指令,来达到修改目的的,不过这会导致运行开销增大,但在某些体系结构(如x86)中,该方式却很好用。

Pin分析

Pin 是 Intel 公司研发的一个动态二进制插桩框架,可以在二进制程序运行过程中插入各种函数,以监控程序每一步的执行。官网(目前有 2.x 和 3.x 两个版本,2.x 不能在 Linux 内核 4.x 及以上版本上运行,这里我们选择 3.x)

Pin 具有以下优点:

  • 易用
    • 使用动态插桩,不需要源代码、不需要重新编译和链接。
  • 可扩展
    • 提供了丰富的 API,可以使用 C/C++ 编写插桩工具(被叫做 Pintools)
  • 多平台
    • 支持 x86、x86-64、Itanium、Xscale
    • Windows、Linux、OSX、Android
  • 鲁棒性
    • 支持插桩现实世界中的应用:数据库、浏览器等
    • 支持插桩多线程应用
    • 支持信号量
  • 高效
    • 在指令代码层面实现编译优化

基于结构和原理

Pin 是一个闭源的框架,由 Pin 和 Pintool 组成。Pin 内部提供 API,用户使用 API 编写可以由 Pin 调用的动态链接库形式的插件,称为 Pintool。

由图可看出,Pin由虚拟机、代码缓存和检测API组成。当 Pin 将待插桩程序加载并获得控制权之后,在调度器的协调下,JIT 编译器负责对二进制文件中的指令进行插桩,动态编译后的代码即包含用户定义的插桩代码。编译后的代码保存在代码缓存中,经调度后交付运行。

通常插桩需要的两个组件都在 Pintool 中:

  • 插桩代码(Instrumentation code)
    • 在什么位置插入插桩代码
  • 分析代码(Analysis code)
    • 在选定的位置要执行的代码

Pintool 采用向 Pin 注册插桩回调函数的方式,对每一个被插桩的代码段,Pin 调用相应的插桩回调函数,观察需要产生的代码,检查它的静态属性,并决定是否需要以及插入分析函数的位置。分析函数会得到插桩函数传入的寄存器状态、内存读写地址、指令对象、指令类型等参数。

  • Instrumentation routines:仅当事件第一次发生时被调用
  • Analysis routines:某对象每次被访问时都调用
  • Callbacks:无论何时当特定事件发生时都调用

示例分析

下面分析用户手册中的指令计数工具,在source/tools/ManualExamples/inscount0.cpp,代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
/*
* Copyright (C) 2004-2021 Intel Corporation.
* SPDX-License-Identifier: MIT
*/

#include <iostream>
#include <fstream>
#include "pin.H"
using std::cerr;
using std::endl;
using std::ios;
using std::ofstream;
using std::string;

ofstream OutFile;

// The running count of instructions is kept here
// make it static to help the compiler optimize docount
static UINT64 icount = 0;

// This function is called before every instruction is executed
VOID docount() { icount++; }

// Pin calls this function every time a new instruction is encountered
VOID Instruction(INS ins, VOID* v)
{
// Insert a call to docount before every instruction, no arguments are passed
INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);
}

KNOB< string > KnobOutputFile(KNOB_MODE_WRITEONCE, "pintool", "o", "inscount.out", "specify output file name");

// This function is called when the application exits
VOID Fini(INT32 code, VOID* v)
{
// Write to a file since cout and cerr maybe closed by the application
OutFile.setf(ios::showbase);
OutFile << "Count " << icount << endl;
OutFile.close();
}

/* ===================================================================== */
/* Print Help Message */
/* ===================================================================== */

INT32 Usage()
{
cerr << "This tool counts the number of dynamic instructions executed" << endl;
cerr << endl << KNOB_BASE::StringKnobSummary() << endl;
return -1;
}

/* ===================================================================== */
/* Main */
/* ===================================================================== */
/* argc, argv are the entire command line: pin -t <toolname> -- ... */
/* ===================================================================== */

int main(int argc, char* argv[])
{
// Initialize pin
if (PIN_Init(argc, argv)) return Usage();

OutFile.open(KnobOutputFile.Value().c_str());

// Register Instruction to be called to instrument instructions
INS_AddInstrumentFunction(Instruction, 0);

// Register Fini to be called when the application exits
PIN_AddFiniFunction(Fini, 0);

// Start the program, never returns
PIN_StartProgram();

return 0;
}

执行流程如下:

  1. main()中首先调用**PIN_Init()初始化,注册指令粒度的回调函数 INS_AddInstrumentFunction(Instruction, 0),被注册插桩函数为instruction,使用PIN_StartProgram()**启动Pin执行
  2. 在每条指令之前(IPOINT_BEFORE)执行分析函数**docount()**,功能是对全局变量递增计数
  3. 执行**Fini()**,输出计数结果到文件

运行程序如下

1
../../../pin -ifeellucky -t obj-intel64/inscount0.so -o inscount0.log -- /bin/ls

我们查看输出文件,统计执行的指令数量为800151

应用

由于程序具有循环、分支等结构,每次运行时执行的指令数量不一定相同,于是我们可是使用 Pin 来统计执行指令的数量,从而对程序进行分析。特别是对一些使用特殊指令集和虚拟机,或者运用了反调试等技术的程序来说,相对于静态分析去死磕,动态插桩技术是一个比较好的选择。

源码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<stdio.h>
#include<string.h>
void main() {
char pwd[] = "abc123";
char str[128];
int flag = 1;
scanf("%s", str);
for (int i=0; i<=strlen(pwd); i++) {
if (pwd[i]!=str[i] || str[i]=='\0'&&pwd[i]!='\0' || str[i]!='\0'&&pwd[i]=='\0') {
flag = 0;
}
}
if (flag==0) {
printf("Bad!\n");
} else {
printf("Good!\n");
}
}

将上述代码编译后,使用inscount0.so对其进行指令计数,如下图所示先来测试下密码长度,可以看出密码位数从3到6计数值都在增大,而当密码位数为7时则下降,可以判断处密码位数为6

接下来测试密码第一位,如下可判断处第一位为a

以此类推,暴力破解可得密码

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2024 John Doe
  • 访问人数: | 浏览次数:

让我给大家分享喜悦吧!

微信