编写dll文件的目的是在dll文件中实现一些函数功能,以供其他程序使用。而导出表的作用就是用来告诉操作系统所到处的函数的信息,好让其他程序可以方便的调用导出函数。
导出表保存的地址在数据目录的第一项保存着,在文档中有如下的定义
Name字段保存的是文件名的字符串的RVA,通过它可以找到dll文件的文件名,那么就可以使用LoadLibrary来将dll文件加载进内存。
最关键的三个成员就是最后三个成员,它们分别保存了指向特定的三个数组的RVA,如下图所示
AddressOfFunctions指向的是整型数组,数组的个数由NumberOfFunctions来确定,如果数组元素的内容非0,那它就是一个导出函数的地址。
AddressOfNames指向的也是一个整型数组,数组里面保存的是相应的函数名称的RVA,数组个数由NumberOfNames确定。
AddressOfNameOrdinals指向的是短整型数组,数组里面保存的是序号
上面根据Name字段找到dll的文件名以后,使用LoadLibrary将dll文件加载进内存,而为了找到相应的函数地址,还需要使用GetProcAddress函数来得到函数地址,而GetProcAddress函数可以使用字符串和序号的方式来获得函数地址。·
对于字符串的方式,首先是去AddressOfNames得到各个字符串的地址,然后在字符串的地址中找到相应的字符串,和GetProcAddress函数传入的字符串做比较,如果相等,则记录下这个相等的数组的下标,根据这个下标去AddressOfNameOrdinals中查找对应的下标中的序号值,得到的值就是作为数组AddressOfFunctions的索引来找到相应的函数地址。
另外一种是根据导出序号来查找函数,这个时候,GetProcAddress函数会首先将序号减去Base的值,得到AddressOfFunctions数组的索引,在根据这个索引来获得对应的函数的地址。
相应的导出表解析的代码如下
运行结果如下图
所谓的ShellCode就是一段可以在任何情况下都能正常运行的机器码。那么为了这段机器码在任何时候都能运行就需要遵循下面的三个原则
不能使用全局变量,因为这些变量只在本进程有,其他进程空间中没有
不能嵌套调用其他函数,原因如上
不能调用系统函数,不同程序导入表内容是不一样
要解决上面的问题
对于字符串赋值要用char szTest = { 't', 'e', 's', 't', 0 }代替szTest = {"test"}
从遍历模块找到KERNEL32中,从它的导出表的GetProcAddress的地址,以此来导入需要的函数来调用
据此可以写出下列的ShellCode函数
提取出相应的ShellCode如下
这里通过新增一个节来把ShellCode写入这个新增节,在修改程序的入口来在程序执行前先执行ShellCode。
新增节的步骤如下
检测节头的间隙是否够容纳下一个节头
申请足够的空间容纳原文件和ShellCode及跳转指令
填充要跳转回去的地址到Jmp
将原文件内容和ShellCode以及跳转指令复制到申请的空间中
将新增节的属性加入节表
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-10-20 20:05
被1900编辑
,原因: