SHOW:
|
|
- or go back to the newest paste.
| 1 | // | |
| 2 | // dumpster_ordinals.cpp | |
| 3 | // https://pastebin.com/C3qcNMbh | |
| 4 | - | // https://x.com/sixtyvividtails/status/1795410561775095816 |
| 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 |