信息网站开发网络公司/百度客服平台
https://blog.csdn.net/whklhhhh/article/details/79275932
1625-5 王子昂 总结《2018年2月3日》 【连续第491天总结】
A. PinTools编写、win32程序的适配
B.
之前编译好了ManualExamples中的inscount,但是0、1、2的计算都很不准确,高达几百万且波动极大,所以没法通过例程的指令计数来爆破
于是只好自己参照API来修改
感觉很久没有从零开始查文档写程序啦~
官方的例程真的很充分,文档也特别详细
基础介绍
PinTools文档:
https://software.intel.com/sites/landingpage/pintool/docs/81205/Pin/html/index.html
Linux
Pin tool由int main(int argc, char * argv[])函数开始,由NMAKE编译选项编译成特定的动态链接库,如果要编译自己的动态链接库,在Nmakefile文件中把要编译的动态链接库名字加到COMMON_TOOLS=后面,使用..\nmake.bat TARGET=ia32 xxx.dll命令进行编译。
Windows
在makefile.rules中找到
TEST_TOOL_ROOTS := inscount0_win_stdout
来修改,添加目标
另外obj-ia32中的库只能对32位的程序(无论是elf还是exe使用),对于64位的程序则需要编译出obj-intel64的版本
make TARGET=ia32/intel64 可以指定目标版本
如果在程序中要使用符号,要调用PIN_InitSymbols();
来初始化PIN_Init(argc, argv)
PIN tool分四种插装粒度
指令级插桩(instruction instrumentatio),通过函数INS_AddInstrumentFunctio实现。
轨迹级插装(trace instrumentation),通过函数TRACE_AddInstrumentFunction实现。(貌似就是基本块插装)
镜像级插装(image instrumentation),使用IMG_AddInstrumentFunction函数,由于其依赖于符号信息去确定函数边界,因此必须在调用PIN_Init之前调用PIN_InitSymbols。
函数级的插装(routine instrumentation),使用RTN_AddInstrumentFunction函数。函数级插装比镜像级插装更有效,因为只有镜像中的一小部分函数被执行。
其中,IMG_AddInstrumentFunction和RTN_AddInstrumentFunction需要先调用PIN_InitSymbols(),来分析出符号。在无符号的程序中,IMG_AddInstrumentFunction和RTN_AddInstrumentFunction无法分析出相应的需要插装的块。
在各种粒度的插装函数调用时,可以添加自己的处理函数在代码中,程序被加载后,在被插装的代码运行时,自己添加的函数会被调用。
INS_AddInstrumentFunctio、TRACE_AddInstrumentFunction、IMG_AddInstrumentFunction、RTN_AddInstrumentFunction指定的回调函数只有在相应的代码被分析到时才会被调用,即分析到一次只被调用一次,但程序运行过程中一般不再被调用,但INS_InsertCall之类的程序添加的函数,是在相应的代码位置添加函数,根据程序运行的情况,会被多次调用。
在INS_AddInstrumentFunctio指令级插装的代码中,只有在INS_AddInstrumentFunctio指定的函数被调用时INS指令才有效,在INS_InsertCall函数中,INS无效。
从API中可以看出
Img->Sec->Rtn->Ins,相邻的级别是可以相互转换的
例如在Img中可以通过IMG_SecHead(img)来得到首个section,再通过section遍历其中的rtn即可得到期望的主函数模块了
编写心得
image对应模块,即文件(exe/dll)
routine对应的是可识别的函数,对于无符号的exe中的用户模块,似乎通常会识别出unnamedImageEntryPoint
和.text
两个函数,其中前者为start函数,后者为真正的main函数
instruction对应的是汇编指令,没什么好说的
trace不明白是啥来的,之后有空再试用一下
如果直接由IMG_Entry的地址和RTN_FindByAddress的越级方式获取,仅能得到一个RTN,而不是所有的RTN链表
刚开始因为没有sec的级别所以没注意,在这上卡了不少时间
start()所在的RTN中运行代码跟输入几乎无关,似乎仅在输入输出流会有几十个指令的区别
编写思路
proccount的例程中可以看出 ,用户模块的代码仅有几万,大量代码都是各种系统库的调用
那么目标是排除系统库,仅保留用户模块、即主程序的指令计数
为了尽量加快速度,我选择了在IMG级别插第一个桩,刚开始是校验地址是否为0x401000,或者模块名是否为程序名,但是一方面编写比较复杂,另一方面无法保证是否存在地址重定向,或是ASLR之类修改地址
在查找API的时候偶然发现有IMG_IsMainExecutable
检查img是否为主程序,检查为我们的需求量身定做
通过它来判断截下的img是否为需要的主程序,如果不是就直接放过,是的话则进行指令插桩计数
刚开始直接通过地址取rtn,发现每次拿到的都是start,并且链接不到后一个rtn
现在想来应该是FindByAddress这个API本来拿到的就是单独一个rtn
后来发现IMG中有通过SEC来转RTN的API,这样就能取到整个RTN链了
得到RTN 以后遍历INS,插入计数的桩最后输出即可
代码
#include <fstream>
#include <iomanip>
#include <iostream>
#include <string.h>
#include "pin.H"static UINT64 icount = 0;
static string name;
ofstream outFile;VOID docount()
{icount++;
}VOID Routine(IMG img, VOID *v)
{if(IMG_IsMainExecutable(img)){SEC sec = IMG_SecHead(img);RTN rtn = (SEC_RtnHead(sec));{RTN_Open(rtn);name = RTN_Name(rtn);for (INS ins = RTN_InsHead(rtn); INS_Valid(ins); ins = INS_Next(ins)){INS_InsertCall(ins, IPOINT_BEFORE, (AFUNPTR)docount, IARG_END);}RTN_Close(rtn);}}
}VOID Fini(INT32 code, VOID *v)
{outFile << name << endl << icount << endl;
}INT32 Usage()
{cout<<"Please input the win32 exe";return -1;
}int main(int argc, char * argv[])
{PIN_InitSymbols();outFile.open("inscount1_win.out", );if (PIN_Init(argc, argv)) return Usage();IMG_AddInstrumentFunction(Routine, 0);PIN_AddFiniFunction(Fini, 0
);PIN_StartProgram();return 0;
}
- 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
虽然其实相比iscount来说改动其实很小,不过对于PinTools的API理解总算是入了门 。经过修改后的程序仅仅会取main所在的一个rtn,还有一些遍历rtn、sec的代码因为在大部分程序中应该都无用所以略去了
爆破用的python2代码:
#coding=utf-8
import popen2
import stringINFILE = "test"
CMD = r"E:\ctf\pin\pin.exe -t E:\ctf\pin\source\tools\ManualExamples\obj-ia32\inscount1_win.dll -- F:\ctf\Whale\CrackMe\CrackMe.exe <" + INFILE
choices = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!#$%&'()*+,-./:;<=>?@[\]^_`{|}~"
# 重复放置0是为了保证如果0正确,有9->0这样错误到正确的变化,能正确捕获def execlCommand(command):global ffin,fout = popen2.popen2(command)fin.readline()# fin.readline()result1 = fin.readline()#获取程序自带打印信息,wrong或者correctprint(result1)if('Failed' not in result1):#输出Correct时终止循环f = 0result2 = fin.readline()#等待子进程结束,结果输出完成fin.close()def writefile(data):fi = open(INFILE,'w')fi.write(data)fi.close()def pad(data, n, padding):return data + padding * (n - len(data))flag = ''
# flag = '#7Ff@(24'
f = 1
while(f):l = 0#初始化计数器for i in choices:key = flag + i#测试字符串print(">",key)writefile(key)execlCommand(CMD)fi = open('./inscount1_win.out', 'r')# 管道写入较慢,读不到内容时继续尝试读while(1):try:# n = int(fi.read().split(' ')[1], 10)fi.readline()n = int(fi.readline())breakexcept IndexError:continuefi.close()print(n)if(n-l > 0 and l):#如果两次运行指令差别过大,说明字符正确flag += ibreakelse:l = nprint(flag)
- 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
后记
虽然总算是能跑起来了,但是由于Windows程序加载库过于多的原因,PinTools注入的速度特别慢
整个程序大概保持在每个字符花费2s的速度,对于实验程序CrackMe的47位flag来说,得到flag的每一位字符平均需要一分钟以上 总耗时还是有点夸张的
思考了一下,耗时过久有两个因素
- 插桩参考了proccount例程,使用的是INS级的插桩,相比BBL代码块级的指令计数会慢许多。但是Img->Sec->Rtn->Ins的链中不知道该怎么引入Trace->Bbl,之后尝试一下Trace级别的插桩
- Python逐字符+管道+PinTools三者都是比较耗时的程序,可以考虑多线程来批量运行PinTools。不过由于PinTools的原理是注入Dll,所以有点担心多线程的dll可能会彼此影响,从而得到不正确的结果。说多无益,明天跑一遍看看吧
C. 明日计划
PinTools多线程爆破尝试
Trace级理解