调用未知DLL中的导出函数

不知道诸位看官是否有过这样的经历:在不经意之间发现一个DLL文件,它里边有不少有趣的导出函数——但是由于你不知道如何调用这些函数,所以只能大发感慨而又无能为力焉。固然有些知名的DLL可以直接通过搜索引擎来找到它的使用方式(比如本文中的例子ipsearcher.dll),不过我们诚然不能希望自己总能交到这样的好运。所以在本文中,李马希望通过自己文理不甚通达的讲解能够给大家以授人以渔的效果。

先决条件

阅读本文,你需要具备以下先决条件:

  • 初步了解汇编语言,虽然你并不一定需要去读懂DLL中导出函数的汇编代码,但是你至少应该了解诸如push、mov这些常用的汇编指令。
  • 一个能够查看DLL中导出函数的工具,Visual Studio中自带的Dependency Walker就足够胜任了,当然你也可以选择eXeScope。
  • 一个调试器。理论上讲VC也可以完成调试的工作,但它毕竟是更加针对于源代码一级调试的工具,所以你最好选择一个专用的汇编调试器。在本文中我用的是OllyDbg——我不会介绍有关这个调试工具的任何东西,而只是简要介绍我的调试过程。

准备好了吗?那么我们做一个热身运动吧先。

热身——函数调用约定

这里要详细介绍的是有关函数调用约定的内容,如果你已经了解了这方面的内容,可以跳过本节。

你可能在学习Windows程序设计的时候早已接触过“函数调用约定”这个词汇了,那个时候你所了解的内容可能是一个笼统的概念,内容大抵是说函数调用约定就是指的函数参数进栈顺序以及堆栈修正方式。譬如cdecl调用约定是函数参数自右而左进栈,由调用者修复堆栈;stdcall调用约定亦是函数参数自右而左进栈,但是由被调用者修复堆栈……噢不,这太晦涩了——在源代码上我们是无法看到这些东西的!

那么我们别无选择,只有深入到汇编一层了。考虑以下C++代码:

#include <stdio.h>

int __cdecl max1( int a, int b )
{
    return a > b ? a : b;
}

int __stdcall max2( int a, int b )
{
    return a > b ? a : b;
}

int main()
{
    printf( "max( 1, 2 ) of cdecl version: %d\n", max1( 1, 2 ) );
    printf( "max( 1, 2 ) of stdcall version: %d\n", max2( 1, 2 ) );
    return 0;
}

对应的汇编代码为:

; int __cdecl max1( int a, int b )
00401000 MOV EAX,DWORD PTR SS:[ESP+4]
00401004 MOV ECX,DWORD PTR SS:[ESP+8]
00401008 CMP EAX,ECX
0040100A JG SHORT CppTest.0040100E
0040100C MOV EAX,ECX
0040100E RETN

; int __stdcall max2( int a, int b )
00401010 MOV EAX,DWORD PTR SS:[ESP+4]
00401014 MOV ECX,DWORD PTR SS:[ESP+8]
00401018 CMP EAX,ECX
0040101A JG SHORT CppTest.0040101E
0040101C MOV EAX,ECX
0040101E RETN 8 ; 被调用者的堆栈修正

; max1( 1, 2 )
00401030 PUSH 2
00401032 PUSH 1
00401034 CALL CppTest.00401000
00401039 ADD ESP,8 ; 调用者的堆栈修正

; max2( 1, 2 )
0040104A PUSH 2
0040104C PUSH 1
0040104E CALL CppTest.00401010

好了,我来简要介绍一下。函数参数传入函数体是借由堆栈段完成的,也就是将各个参数依某种次序推入SS中——在cdecl与stdcall约定中,这个次序都是自右而左的。另外,由于将参数推入了堆栈致使堆栈指针ESP发生了变化,所以要在函数结束的时候重新修正ESP。从上边的汇编代码中你也可以很清楚地看到,cdecl约定是在调用max1之后修正的ESP,而stdcall约定则是在max2返回时借由RETN 8完成了这个修正工作。

另外,从上边的汇编代码中还可以看到,函数的返回值是由EAX带回的。

庖丁解牛

在了解了以上的知识后,我们就可以使用调试器来调试那个未知的DLL了。可以说,这整个的调试过程充满了惊险和刺激,而且我们还需要一定的技巧——如果你像我一样不喜欢阅读汇编代码的话。

在本文中,我所选择的调试示例是FTerm中附带的ipsearcher.dll,它提供了对纯真IP数据库的查询接口。下图是用Dependency Walker对其分析的结果:

dllexport1.gif

你可以看到,这里边有两个导出函数:LookupAddress和_GetAddress,那么我们可以按照返回值、调用约定、函数名、参数列表的顺序将它们声明如下:

? ? LookupAddress( ? );
? ? _GetAddress( ? );

是的,有太多的未知,下面李马将要逐一地破解这些问号。

调试器不可能孤立地对DLL进行调试,我们所需要的应该是一个合适的EXE,这样有助于我们的探究工作。在这里我选择的EXE是我编写的ipsearcher.exe,当然这可能会让你认为我这篇文章的组织顺序有问题——毕竟是我已经知道了这两个导出函数之后(编写了ipsearcher.exe)还要假装成不知道的样子来对ipsearcher.dll来进行探究,所以我决定在下文中不对ipsearcher.exe的代码进行任何关注,而是直接进入到ipsearcher.dll的领空。

dllexport2.gif

打开调试器,载入ipsearcher.exe。当ipsearcher.dll被装载后,会引发一个访问异常,可以忽略这个异常继续调试。根据Dependency Walker的分析结果,在ipsearcher.dll的0x00001BB0和0x00001C40处各下一个断点。现在在“IP地址”中输入一个IP地址(这里以寒泉BBS的IP为例),点击“查询”,会发现指令跳入0x00001C40中(也就是_GetAddress),它的代码如下:

10001C40 MOV EAX,DWORD PTR SS:[ESP+4] ; 一个参数
10001C44 PUSH ipsear_1.10009BE8
10001C49 PUSH EAX
10001C4A CALL ipsear_1.LookupAddress ; 两个参数
10001C4F ADD ESP,8 ; LookupAddress是cdecl调用约定
10001C52 MOV EAX,ipsear_1.10009BE8
10001C57 RETN ; _GetAddress这厮也是cdecl调用约定

很短的几行代码,不过它已经可以提供这些信息了:

  • 从SS的使用来看,_GetAddress只带有一个参数。
  • _GetAddress中调用了LookupAddress,后者带有两个参数。
  • 调用LookupAddress之后进行了堆栈修正,所以LookupAddress是cdecl调用约定。
  • _GetAddress返回时并未进行堆栈修正,所以_GetAddress也是cdecl调用约定。

于是,我们可以替换一下刚才的问号了:

? CDECL LookupAddress( ?, ? );
? CDECL _GetAddress( ? );

下面可以进行单步调试了,当代码步至10001C44时,你会发现寄存器窗口发生了如下的变化:

dllexport3.gif

“202.207.177.9”终于出现了,这样一来我们可以继续对问号进行替换了:

? CDECL LookupAddress( PCSTR, ? );
? CDECL _GetAddress( PCSTR );

现在继续对代码进行跟踪,是进入LookupAddress的时候了。我们可以从先前_GetAddress的代码中可以发现,这两个导出函数一直在围绕10009BE8这个地址做文章,那么我们就要在单步调试LookupAddress的同时关注这个地址的数据改变。几步跟踪之后,你会发现10009BE8开头的8字节(两个DWORD)数据发生了改变,变成了10009AB4和10009B1C。那么我们再转向这两个地址,会发现:

dllexport4.gif

这样一来就很清楚了,10009BE8是一个字符串指针的数组,它有两个元素。也就是说,我们的函数声明可以换成这样:

? CDECL LookupAddress( PCSTR, PSTR* );
PSTR* CDECL _GetAddress( PCSTR );

接下来需要确定的就是LookupAddress的返回值了。纵观LookupAddress的返回代码,你会发现这样的片断:

; 片断1
10001C0B XOR EAX,EAX
10001C0D POP ESI
10001C0E RETN
; 片断2
10001C2B MOV EAX,1
10001C30 POP ESI
10001C31 RETN

也就是说,这个函数有两个返回值:0或1。那么最后的真相终于大白于天下——

BOOL CDECL LookupAddress( PCSTR, PSTR* );
PSTR* CDECL _GetAddress( PCSTR );

GetProcAddress?

到此为止,这两个函数的声明终于让我们找出来了。也许你会觉得这就够了——接下来就是用typedef定义函数指针,然后使用LoadLibrary、GetProcAddress调用这些函数的事情了。

如果你真的这么认为的话,那我认为我有必要向你介绍这另外的一种方式。

首先请你建立一个名为ipsearcher.def的文件,然后在其中写入如下内容:

LIBRARY "ipsearcher"

EXPORTS
LookupAddress @1
_GetAddress   @2

将文件保存后,进入到命令行模式下,输入以下命令(前提是你拥有Visual Studio的附带工具lib.exe并有正确的路径指向。以Visual Studio 6.0为例,这个工具通常位于Microsoft Visual Studio\VC98\Bin下):

lib /def:ipsearcher.def

执行的结果有一个警告,不必理会。这时候我们会发现,lib为我们生成了一个ipsearcher.lib。

然后,我们继续编写ipsearcher.h文件,如下:

#ifndef IPSEARCHER_H
#define IPSEARCHER_H

#include <windows.h>

#pragma commentlib, "ipsearcher.lib" )

extern "C"
{

BOOL CDECL LookupAddress( PCSTR, PSTR* );

PSTR* CDECL _GetAddress( PCSTR );

};

#endif // IPSEARCHER_H

大功告成!这样我们就为这个光秃秃的ipsearcher.dll做了一份SDK开发包,而不必再使用动态加载的方法了。

总结一下再

其实,探究一个DLL并非像我这里所讲述的这么简单。这项工作很可能需要阅读大量的汇编代码,了解DLL函数体的流程才能使真相大白于天下。另外,还不能排除有的DLL被加密、加壳、反跟踪……也就是说对于ipsearcher.dll,那简直就是我捡了个便宜来借花献佛了。

点这里下载ipsearcher SDK

转载于:https://www.cnblogs.com/flying_bat/archive/2006/09/27/516416.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/412057.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

构建之法与博客首秀

问题一&#xff1a;软件的安全功能是指什么呢&#xff1f; 我看了《构建之法》第七页对飞机安全功能的的描述&#xff0c;想知道在软件中是否有类似飞机的安全功能&#xff1f;如果有&#xff0c;具体是指什么功能呢&#xff1f;我在查阅资料后只找到对软件安全的的总结(软件安…

java接口import_深入理解Java架构师在组件注册@Import导入容器(ImportSelector接口)的神操作!...

ImportSelector&#xff1a;返回需要导入的组件的全类名数组创建一个实现了ImportSelector接口的类&#xff0c;然后让入Import中。ImportSelector的实现类包含了所有需要导入到容器中的组件。public class StuConfigurationImportSerlect implements ImportSelector {Override…

java builder pool_每周10道Java面试题:String, String Pool, StringBuilder

每周10道 Java 面试题由 ImportNew 整理编译自网络。1. 写出下面代码的运行结果。int src 65536;Integer dst new Integer(65536);System.out.println(src dst);System.out.println(dst.equals(src));答案&#xff1a; true true考点&#xff1a; Integer 的 equals 实现。查…

将字符串中的字符按Z字形排列,按行输出

示例1&#xff1a; Input: s "PAYPALISHIRING", numRows 3 Output: "PAHNAPLSIIGYIR" 示例2&#xff1a; Python解决方案&#xff1a; def convert(self, s, numRows):""":type s: str:type numRows: int:rtype: str"""…

大型网络架构变迁和知识图谱

——仅供个人学习使用&#xff0c;如有侵犯版权&#xff0c;请作者联系我&#xff0c;立马处理。 【前端页面缓存】 sessionStorage,localStorage,userData,cookie, [sessionStorage] h5后出现的新技术&#xff0c;这个生命周期短&#xff0c;当页面关闭后&#xff0c;存储资源…

H265摄像头如何实现网页直播

介绍 目前安防监控行业&#xff0c;基本所有的摄像头都支持H264编码&#xff0c;但是已经有部分摄像头开始支持H265&#xff0c;并且支持H265的摄像机已经越来越多。H265相比H264有着很多优势&#xff0c;是压缩更高&#xff0c;网络传输消耗的带宽更小&#xff0c;相同码率下H…

【Python selenium自动化环境配置】4步搞定ChromeDriver版本选择

很多刚做自动化的小伙伴&#xff0c;会在ChromeDriver版本选择时犯难&#xff0c;看来大家都被坑过&#xff0c;真正掌握独门绝技&#xff0c;都不是难事儿。 看好了主要步骤就4部 1、确定谷歌浏览器版本 2、找到谷歌浏览器版本与ChromeDriver版本对应关系 3、下载ChromeDriver…

[贴图]TVB香港大部分演员照片、姓名(值得收藏)

转载于:https://www.cnblogs.com/chinhr/archive/2006/11/15/561101.html

我的世界java笔刷指令_《我的世界》基本笔刷教程

《我的世界》是一款3D的第一人称沙盘游戏&#xff0c;所呈现的世界并不是华丽的画面与特效&#xff0c;而是注重在游戏性上面。玩家在游戏中做着建设与破坏两件事&#xff0c;但是透过像乐高一样的积木来组合与拼凑&#xff0c;轻而易举的就能制作出小木屋、城堡甚至城市&#…

idea创建springcloud主工程和springboot子项目

创建主工程&#xff0c;选择file-new-project&#xff0c;选择maven,直接next 填写GroupId包名,ArtifactId项目名&#xff0c;next-finish 创建子项目springboot&#xff0c;项目右击-new-module-Spring Initializr-->next 填写项目相关信息&#xff0c;next 选择Clou…

小程序实现无限瀑布流

实现瀑布流 实现效果 有好几种方案 1.用column-count属性把页面元素分为俩列或多列来实现 2.用display flex 分列来展示页面 3.比如说用js实现 我今天介绍的就是第三种&#xff0c;因为前两种都会有局限&#xff0c;实现的效果我们希望是左右左右&#xff0c;而不是分成两列去显…

python canny检测_【数字图像分析】基于Python实现 Canny Edge Detection(Canny 边缘检测算法)...

Canny 边缘检测算法Steps:高斯滤波平滑计算梯度大小和方向非极大值抑制双阈值检测和连接代码结构&#xff1a;Canny Edge Detection|Gaussian_Smoothing||convolution.py|||convolution()||gaussion_smoothing.py|||dnorm()|||gaussian_kernel()|||gaussian_blur()|Sobel_Filte…

java 拖放文字_myeclipse2014如何实现jsp中的html代码的文字拖放

本帖最后由 liyihongcug 于 2015-3-5 16:53 编辑把 jsp打开 (visual jsp editor)之后上班区 单击右键 show --- pallette就可以了感叹myeclipse确实强大的 标签技术强于.net mvc强。vs2013能实现iis无需安装的情况下 动态指定 某个页面为首选 启动 --------------------希望2…

因缺思厅的绕过

看一下页面源码&#xff0c;看到source.txt。所以进入同目录下的source.txt 代码审计下&#xff0c;并且百度了一些函数。过滤了很多关键字&#xff0c;因此常规的SQL注入没有头绪。想了挺久&#xff0c;因为要满足三个条件。1&#xff1a;不能输入过滤的关键字2&#xff1a;只…

Python自制微信机器人:群发消息、自动接收好友

运营公众号也有半年了&#xff0c;今年5月份开始的&#xff0c;之前一直用一款windows工具来运营自动接受好友请求、群发文章、自动回复等操作&#xff0c;但颇有不便。 举几个场景&#xff1a; 突然在外面看到一篇文章很好&#xff0c;临时写了一篇&#xff0c;想群发一下。好…

记一次webpack4+react+antd项目优化打包文件体积的过程

背景 最近自己整了一个基于webpack4和react开发的博客demo项目&#xff0c;一路整下来磕磕碰碰但也实现了功能&#xff0c;就准备发到阿里云上面去看看&#xff0c;借用了同事的阿里云小水管服务器&#xff0c;配置完成之后首页加载花了十几秒&#xff0c;打开控制台network查看…

[19/03/16-星期六] 常用类_Date时间类DateFormat类

一、Date时间类 计算机中 以1970 年 1 月 1 日 00:00:00定为基准时间&#xff0c;每个度量单位是毫秒(1秒的千分之一) 用ong类型的变量来表示时间&#xff0c;如当前时刻数值&#xff1a;long now new System.currentTimeMillis(); 【常用方法】 1. Date() 分配一个Date对象&a…

实验一作业

Part1&#xff1a;实验总结体会 1.实验过程对程序编码的使用不熟练&#xff0c;经常在书写代码过程中有停顿与疑惑&#xff0c;但熟能生巧&#xff0c;经过训练还是有不少收获&#xff1b; 2.实验内容不够严谨&#xff0c;容易犯小错误&#xff0c;还是需要勤加练习&#xff1b…

php7引用计数,PHP7 引用计数 读书笔记

PHP7 引用计数 读书笔记每个 PHP 变量存在一个加 "zval" 的变量容器中. 一个 zval 变量容器, 除了包含变量的类型和值, 还包括两个字节的额外信息.1.is_ref : 是一个 bool 值, 用来标识这个变量是否属于引用集合. 通过这个字节, PHP 引擎才能把普通变量和引用变量区分…

焦呼?焦矣!

焦了。做了个香蕉蛋糕。总觉得它还没熟&#xff0c;就让它没完没了地烤着。于是焦了。可里面居然还是湿润的。是蛋糕的问题&#xff0c;还是6寸模的问题&#xff0c;还只是人的问题&#xff1f;烤一半时&#xff0c;爹临出门时&#xff0c;过来往上头按了一大拇指。很好&#x…