SHOW:
|
|
- or go back to the newest paste.
1 | // | |
2 | // dumpster_ordinals.cpp | |
3 | // https://pastebin.com/C3qcNMbh | |
4 | // | |
5 | // See Homies Twee t relating to cli obfus in Windows | |
6 | /* | |
7 | https://x.com/sixtyvividtails/status/1795410561775095816 | |
8 | https://x.com/sixtyvividtails/status/1797667842185372084 | |
9 | https://x.com/sixtyvividtails/status/1825951974153662711 | |
10 | https://x.com/malmoeb/status/1825888362265182578 | |
11 | ||
12 | */ | |
13 | // Compile via msvc: | |
14 | // cl /O2 /GS- /Brepro dumpster_ordinals.cpp /link /dll /nocoffgrpinfo /noentry /out:dumpster_ordinals.dll | |
15 | // | |
16 | // Self-test via rundll32: | |
17 | // rundll32.exe %__CD__%\dumpster_ordinals.dll,test | |
18 | // | |
19 | ||
20 | #define _NO_CRT_STDIO_INLINE | |
21 | ||
22 | #include <stdio.h> | |
23 | #include <Windows.h> | |
24 | ||
25 | ||
26 | #pragma comment(lib, "ntdllp") // you might need wdk for this | |
27 | #pragma comment(lib, "user32") | |
28 | #pragma comment(lib, "kernel32") | |
29 | ||
30 | #pragma execution_character_set("utf-8") | |
31 | ||
32 | ||
33 | // round 1 | |
34 | #pragma comment(linker, "/export:A=advapi32.dll.#1212,@1") // with dll extension (and we enforce our ordinal) | |
35 | #pragma comment(linker, "/export:B=advapi32.#+4294968508!") // overflows uint32 into 1212 | |
36 | // "advapi32.#▲ ♫1212*1337", linker directive quoted due to " "; RtlCharToInteger skips leading chars with ords <= 0x20 | |
37 | #pragma comment(linker, "\"/export:C=advapi32.#\x1E \x0E" "1212*1337\"") | |
38 | #pragma comment(linker, "/export:D=advapi32.#-0x44FFFFFB44") // overflows and then gets negated | |
39 | ||
40 | // round 2 | |
41 | #pragma comment(linker, "/export:E=advapi32.dll::$DATA.#1212") // use ads streams (here unnamed default) | |
42 | #pragma comment(linker, "/export:F=C:\\windows\\system32\\advapi32.#1212") // full path | |
43 | #pragma comment(linker, "/export:G=\\windows\\system32\\advapi32.#1212") // ok only when current drive is C: | |
44 | #pragma comment(linker, "/export:H=C:advapi32.#1212") // 👎 doesn't work even if cwd is %system32% | |
45 | ||
46 | // round 2 with editing own export (only coz msvc linker doesn't allow to build such file statically) | |
47 | #pragma comment(linker, "/export:J=dumpster_ordinals.#") // 👎 means ordinal is 0; but 0 ordinal doesn't work | |
48 | #pragma comment(linker, "/export:K=dumpster_ordinals.#0x13370001") // not an overflow(!): ok when ordinalBase 0x13370001 | |
49 | ||
50 | // for rundll32.exe test | |
51 | #pragma comment(linker, "/export:test") | |
52 | #pragma comment(linker, "/alternatename:test=_test@16") // needed for x32 only | |
53 | ||
54 | ||
55 | extern "C" | |
56 | IMAGE_DOS_HEADER __ImageBase; // linker-defined | |
57 | ||
58 | ||
59 | static void test_round2() | |
60 | { | |
61 | HMODULE ownImage = (HMODULE)&__ImageBase; | |
62 | ||
63 | char funcName[2]{"E"}; | |
64 | void* funcs[6]; | |
65 | for (int i = 0; i < _countof(funcs); ++i, ++funcName[0]) | |
66 | funcs[i] = GetProcAddress(ownImage, funcName); | |
67 | ||
68 | // prepare to edit our own export table, to change ordinal base first to 0, and then to 0x13370001; | |
69 | // it doesn't have to be edited dynamically, it's just msvc linker doesn't allow us to build it statically | |
70 | auto* peHeader = (const IMAGE_NT_HEADERS*)(PBYTE(ownImage) + __ImageBase.e_lfanew); | |
71 | UINT exportRva = peHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; | |
72 | auto* exports = (IMAGE_EXPORT_DIRECTORY*)(PBYTE(ownImage) + exportRva); | |
73 | ULONG dummyOldProtect; | |
74 | VirtualProtect(exports, sizeof(*exports), PAGE_READWRITE, &dummyOldProtect); | |
75 | UINT origOrdinalBase = exports->Base; | |
76 | ||
77 | void* funcsX[2]; | |
78 | exports->Base = 0; // 'J' still won't resolve, but only because LdrpGetProcedureAddress bans ordinal 0 | |
79 | funcsX[0] = GetProcAddress(ownImage, "J"); | |
80 | exports->Base = 0x13370001; // 'K' should resolve now | |
81 | funcsX[1] = GetProcAddress(ownImage, "K"); | |
82 | // note if module has OrdinalBase larger than 0xFFFF in its export table, you won't be able to resolve any of its | |
83 | // funcs by ordinals via GetProcAddress; however, you can use native ntdll!LdrGetProcedureAddressForCaller for that | |
84 | exports->Base = origOrdinalBase; | |
85 | ||
86 | wchar_t buf[0x740]; | |
87 | swprintf_s(buf, _countof(buf), | |
88 | LR"(Secret round2 discovered! | |
89 | ||
90 | ||
91 | %p "E" > advapi32.dll::$DATA.#1212 | |
92 | %p "F" | |
93 | > C:\windows\system32\advapi32.#1212 | |
94 | ||
95 | %p "G" | |
96 | > \windows\system32\advapi32.#1212 | |
97 | should work iff "C:" is current drive | |
98 | ||
99 | %p "H" > C:advapi32.#1212 | |
100 | does NOT work even if %%system32%% is cwd | |
101 | ||
102 | %p "J" > dumpster_ordinals.# | |
103 | %p "J" > dumpster_ordinals.# | |
104 | ^ OrdinalBase is 0 - still does NOT work | |
105 | ||
106 | %p "K" > dumpster_ordinals.#0x13370001 | |
107 | %p "K" > dumpster_ordinals.#0x13370001 | |
108 | ^ OrdinalBase is 0x13370001 - should work okay)", | |
109 | funcs[0], funcs[1], funcs[2], funcs[3], | |
110 | funcs[4], funcsX[0], | |
111 | funcs[5], funcsX[1]); | |
112 | ||
113 | MessageBoxW({}, buf, L"dumpster ordinals", MB_OK|MB_ICONINFORMATION|MB_SETFOREGROUND); | |
114 | } | |
115 | ||
116 | ||
117 | extern "C" | |
118 | void __stdcall test(HWND, HINSTANCE, LPSTR, int) | |
119 | { | |
120 | void* funcs[4]; | |
121 | char funcName[2]{"A"}; | |
122 | unsigned exportsResolved = 0; | |
123 | for (int i = 0; i < _countof(funcs); exportsResolved += !!funcs[i], ++i, ++funcName[0]) | |
124 | funcs[i] = GetProcAddress((HMODULE)&__ImageBase, funcName); | |
125 | ||
126 | // may only retrieve image base like that once we've resolved forwarded exports | |
127 | HMODULE advapi = GetModuleHandleA("advapi32"); | |
128 | void* testFuncs[3]{}; | |
129 | if (advapi) | |
130 | { | |
131 | testFuncs[0] = GetProcAddress(advapi, "CryptGenRandom"); | |
132 | testFuncs[1] = GetProcAddress(advapi, (LPCSTR)1212); | |
133 | testFuncs[2] = GetProcAddress(advapi, (LPCSTR)1213); | |
134 | } | |
135 | ||
136 | wchar_t buf[0x400]; | |
137 | swprintf_s(buf, _countof(buf), | |
138 | LR"(%p advapi32.dll | |
139 | %p dumpster_ordinals.dll | |
140 | ||
141 | ------------------------------------------------ | |
142 | Sanity test; exports resolved via | |
143 | GetProcAddress(advapi32): | |
144 | ||
145 | %p "CryptGenRandom" | |
146 | %p #1212 | |
147 | %p #1213 | |
148 | ------------------------------------------------ | |
149 | ||
150 | ||
151 | ------------------------------------------------ | |
152 | Actual test; exports resolved via | |
153 | GetProcAddress(dumpster_ordinals): | |
154 | ||
155 | %p "A" > advapi32.dll.#1212 | |
156 | %p "B" > advapi32.#+4294968508! | |
157 | %p "C" > advapi32.#▲ ♫1212*1337 | |
158 | %p "D" > advapi32.#-0x44FFFFFB44 | |
159 | ------------------------------------------------ | |
160 | ||
161 | ||
162 | RESULT: %u/4 weird exports resolved.)", | |
163 | advapi, &__ImageBase, | |
164 | testFuncs[0], testFuncs[1], testFuncs[2], | |
165 | funcs[0], funcs[1], funcs[2], funcs[3], exportsResolved); | |
166 | ||
167 | // Note we cheat a bit with visual representation of variant C - we use higher Unicode chars. That doesn't affect | |
168 | // our export table, sequence "\x1E\x20\x0E" there still consists of just 3 bytes. !▲ ♫! << Unicode | |
169 | // This comment line contains actual lower ansi chars just to test your environment. ! ! << Ansi | |
170 | int rez = MessageBoxW({}, buf, L"dumpster ordinals", | |
171 | MB_CANCELTRYCONTINUE|MB_DEFBUTTON2|MB_ICONINFORMATION|MB_SETFOREGROUND); | |
172 | if (rez == IDTRYAGAIN) | |
173 | test_round2(); | |
174 | } | |
175 |