如何让病毒调用系统的API函数
如何获取API的入口地址
- 找到提供这个API函数的DLL的加载基址
- 从DLL的导出表中拿到API函数地址
DLL导出表机制:
在这个表中根据函数名找到函数入口地址
一个简单方法:
原理:一个系统中,所有进程加载的同一个DLL的加载基址是相同的
病毒代码的处理:
在病毒的数据区添加3个字段,分别存储MessageBox的地址(4字节)、函数参数“test”(5字节,留一个字节给结尾‘\0’字节)和“hello”(6字节)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18;MessageBox Entry 4 bytes,偏移为13字节(之前数据区13字节)
nop;
nop;
nop;
nop;
;hello串,要留一个字节给结尾0字节,共6字节,偏移为17字节
nop;
nop;
nop;
nop;
nop;
nop;
;test串,要留一个字节给结尾0字节,偏移为23字节
nop
nop
nop
nop
nop获取MessageBox的地址,将函数地址和函数参数填入数据区
1
2
3
4
5
6
7//填写MessageBox的入口地址
//该数据在数据区偏移13字节处
*(void **)(virusData + 13) = MessageBox;
//填写参数,hello字符串
strcpy(virusData + 17, "hello");
//填写参数,test字符串
strcpy(virusData + 23, "test");在真正寄生的病毒代码中调用MessageBox函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16call yy;
数据区;
yy: pop eax; //eax为数据区的实际起始内存地址
……
//调用MessageBox
push 0; //传递最后一个参数
mov ebx, eax; //ebx指向数据区起始内存地址
add ebx, 23; //ebx指向test串
push ebx; //压栈,传参test
sub ebx, 6; //hello串在test串前6个字节
push ebx; //压栈,传参hello串
push 0; //传递最后一个参数
add eax, 13; //MessageBox地址在数据区偏移13个字节处
mov eax, [eax]; //获取MessageBox的地址
call eax; //调用MessageBox函数
……
如何判断是否是控制台程序
在PE文件可选映像头中,字段subsystem描述了该属性
Windows GUI和Windows CUI (G——Graphic,C——Console)
病毒真正获取API函数地址的方法
获取DLL基址
- FS寄存器在偏移0x30处保存了一个指针,指向PEB结构
- PEB结构的偏移0x0c处保存了另外一个指针,指向PEB_LDR_DATA结构
- PEB_LDR_DATA偏移0c处是加载模块链表的头指针,8个字节,头4个字节指向LDR_MODULE结构体(代表一个模块,每个模块都对应一个这样的结构体),后4个字节指向下一个结构体组成链表,该链表是循环链表
- LDR_MODULE偏移0x2c有个成员BaseDllName,8个字节,后4个字节为地址,指向纯模块名的unicode串
代码:
1 |
|
获取DLL中的函数地址
DLL对外暴露自己的函数的方式:
- 函数名
- 序号
注:函数名和序号并非一一对应
序号查找
好处:快、高效
不足:不够直观,不够稳定
做法:用一个数组存放函数的入口地址,这样存放和读取都方便,从n开始就做一个减法
函数名查找
好处:直观,具体
做法:让函数名表和函数地址表的索引一一对应
不足:有的函数名不暴露出来,会导致函数名表和函数地址表的索引不一致
解决方法:增加一个表描述对应关系,如图
导出表的关键信息汇总
- 序号查找需要知道序号的最小值n,利用他可以直接计算函数地址表的索引
- 函数名表:函数名的字串表,每个名字以\0结尾
- 函数名表的元素个数X
- 函数地址索引表:元素个数就是函数名表的个数X,函数名表的第x项对应函数地址索引表的x项,其中存储的是该函数在函数地址表中的索引值
- 由函数名查找的方法是:找到函数名在函数名表的索引x,然后读函数地址索引表的第x项,假设该项的值为y,那么就在函数地址表的第y项拿到函数地址
- 函数地址表:存储的是函数的入口地址,不论用序号还是名字导出的函数,相应的函数地址都存在其中,函数地址表是按序号增序排列。序号和函数地址表索引的对应关系是:i = 序号 - n(最小序号)
PE格式中导出表的信息
在模块可选头中有个数据目录表,其中每项包括导出表、导入表、重定位表等;在导出表头中有如下信息:
- Address Table RVA就是函数地址表的RVA
- Ordinal Table RVA就是函数地址索引表的RVA,PE格式叫它序号表
- Ordinal Base就是最小的序号
- Number of names就是函数名表的条数
- Name Pointer Table RVA是函数名指针表,每项4个字节,存放一个RVA,指向一个函数名的字符串
导出函数查找算法总结
从DLL加载的实际基址获取可选头,从其中数据目录表的第一项找到导出表入口RVA
从导出表的表头获取Number of names,即查找的最大循环次数
循环遍历函数名指针表,比对每项RVA指向的字串是否为要找的函数名
如果找到,记下此时函数名指针表项的索引,设为 i
根据索引 i,在序号表中找到对应项,获取其内容为n
以n为索引在函数地址表中找到函数入口的RVA,加上DLL的实际基址即为函数的实际入口地址
注:以上算法中,所有访问实际地址的地方,就用DLL的实际加载基址+RVA即可
程序设计——查找DLL中的函数地址
代码:
1 | //为各个所需的偏移量定义宏 |
将finddll和getproc函数放入寄生的病毒代码
数据区改进:
增加的代码是:
1 | //填写user32.dll的unicode串 |
程序设计图:
例题
- 关于导出表的说法,以下正确的是( )
A. 函数地址表中存放的是函数入口VA(RVA)
B. 导入表中具有函数名表,通过函数名表的索引直接就可以定位到函数地址表中相应的项
C. DLL中所有的函数都有函数名和序号
D. 序号表中存储的是函数的序号
E. 以上都不正确
参考答案:E
解析:
A. 函数地址表中存的是函数的入口地址;
B. 通过函数名表索引定位函数地址索引表索引,再根据函数地址索引表内容定位函数地址表的索引;
C. 有的函数名不暴露;
D. 序号表存的是函数名表和函数地址表的对应关系