Skip to content

Commit

Permalink
add sshd/gdb infrastructure with system() command injection
Browse files Browse the repository at this point in the history
  • Loading branch information
smx-smx committed Apr 28, 2024
1 parent 47e00e4 commit 00b038b
Show file tree
Hide file tree
Showing 6 changed files with 761 additions and 38 deletions.
21 changes: 20 additions & 1 deletion common.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
define('CHACHA20_KEY_SIZE', 32);
define('CHACHA20_IV_SIZE', 16);
define('SHA256_DIGEST_SIZE', 32);
define('ED448_PUBKEY_SIZE', 57);
define('ED448_KEY_SIZE', 57);

define('OP_ENCRYPT', 0);
define('OP_DECRYPT', 1);
Expand All @@ -17,6 +17,14 @@ function path_combine(string ...$parts){
return implode(DIRECTORY_SEPARATOR, $parts);
}

function patch_data(string $data, int $offset, string $patch){
return (''
. substr($data, 0, $offset)
. $patch
. substr($data, $offset + strlen($patch))
);
}

function error(string $msg){
print("ERROR: {$msg}\n");
}
Expand Down Expand Up @@ -107,3 +115,14 @@ function align_down(int $v, int $to){
$mask = $to - 1;
return $v & ~$mask;
}

function run_cmd(string ...$args){
$hProc = proc_open($args, [], $p);
return proc_close($hProc);
}

function der2pem($der_data, $type='CERTIFICATE') {
$pem = chunk_split(base64_encode($der_data), 64, "\n");
$pem = "-----BEGIN ".$type."-----\n".$pem."-----END ".$type."-----\n";
return $pem;
}
47 changes: 47 additions & 0 deletions gdb/common.gdb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
source gdb/gdb_commands.py

define on_endless_loop
library_bounds liblzma.so.5 lzma
find_bytes post_setup, $lzma_start, $lzma_end, 4889D731C0F3AB498B4710488B4040498985A0
b *$post_setup
commands
python post_setup()
clear *$post_setup
continue
end

# unblock endless loop
set $pc += 2
continue
end


define handle_liblzma
echo ==== on_liblzma\n
library_bounds liblzma.so.5 lzma
find_bytes backdoor_entry, $lzma_start, $lzma_end, 554531C94889E55389FB4C8D45C04883
find_bytes hook_RSA_public_decrypt, $lzma_start, $lzma_end, F30F1EFA4156415541544989F4554883
find_bytes run_backdoor_commands, $lzma_start, $lzma_end, F30F1EFA4157B9AE00000031C0415641
find_bytes verify_signature, $lzma_start, $lzma_end, 415741564155415455534881ECC80000004C8944
find_bytes sshd_proxy_elevate, $lzma_start, $lzma_end, F30F1EFA550F57C0B93602000031C048
find_bytes hook_RSA_get0_key, $lzma_start, $lzma_end, F30F1EFA41564154554889F54883EC20
find_bytes is_payload_message, $lzma_start, $lzma_end, F30F1EFA4885FF0F843F0100004883FE

hbreak *$hook_RSA_public_decrypt
commands
echo === hook_RSA_public_decrypt\n
continue
end
echo === b verify_signature\n
hbreak *$verify_signature
commands
echo === verify_signature\n
printf "key type: %d\n", *(unsigned int *)$rdi
python
import gdb
[gdb.execute(cmd) for cmd in ['finish', 'p $eax', 'continue']]
end
end

info inferior
end
222 changes: 222 additions & 0 deletions gdb/gdb_commands.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
#
# Author: Stefano Moioli <[email protected]>
#

import gdb
import re
import struct
import inspect
import subprocess
import os

class FindGlobalContextCommand(gdb.Command):
def __init__(self) -> None:
super().__init__('find_global_context', gdb.COMMAND_USER)

def invoke(self, argument: str, from_tty: bool) -> None:
[code_addr, var_name] = argument.split()
result = gdb.execute(f'x/10i {code_addr}', to_string=True)
[addr] = re.findall(f'# (0x[0-9a-f]+)', result)
gdb.execute(f"set ${var_name} = {addr}")

class ForkFollowCommand(gdb.Command):
def __init__(self) -> None:
super().__init__('fork_follow', gdb.COMMAND_USER)

def invoke(self, argument: str, from_tty: bool) -> None:
[pid_var] = argument.split()
result = gdb.execute('finish', to_string=True)
[pid] = re.findall(r'\d+', result)
print(f"now set {pid_var} to {pid}")
gdb.execute(f"set ${pid_var} = {pid}")

class GetLibraryBoundsCommand(gdb.Command):
def __init__(self) -> None:
super(GetLibraryBoundsCommand, self).__init__('library_bounds', gdb.COMMAND_USER)

def invoke(self, argument: str, from_tty: bool) -> None:
args = argument.split()
[lib_name, var_prefix] = args
lib_start, lib_end = find_library(lib_name)
gdb.execute(f"set ${var_prefix}_start = {lib_start}")
gdb.execute(f"set ${var_prefix}_end = {lib_end}")

def split_hex_string(hex_string):
hex_string = hex_string.lstrip("0x")
# split the string into pairs of characters
pairs = [hex_string[i:i+2] for i in range(0, len(hex_string), 2)]
formatted_string = ", ".join([f"0x{pair}" for pair in pairs])
return formatted_string

def find_library(lib_name):
result = gdb.execute(f'info sharedlib {lib_name}', to_string=True)
[lib_start, lib_end] = re.findall(r'0x[0-9a-fA-F]+', result)
return (lib_start, lib_end)

def find_bytes(start, end, pattern):
hex_bytes = split_hex_string(pattern)
find_output = gdb.execute("find/b {}, {}, {}".format(start, end, hex_bytes), to_string=True)
[find_addr] = re.findall(r'0x[0-9a-fA-F]+', find_output)
return find_addr

class FindBytesCommand(gdb.Command):
def __init__(self) -> None:
super(FindBytesCommand, self).__init__('find_bytes', gdb.COMMAND_USER)

def invoke(self, argument: str, from_tty: bool) -> None:
args = argument.split(', ')
[var, start, end, pattern] = [args[0], args[1], args[2], args[3]]
start = gdb.parse_and_eval(start)
end = gdb.parse_and_eval(end)
find_addr = find_bytes(start, end, pattern)
gdb.execute(f"set ${var} = {find_addr}")

GetLibraryBoundsCommand()
FindBytesCommand()
ForkFollowCommand()
FindGlobalContextCommand()

def find_global_context():
(lzma_start, lzma_end) = find_library('liblzma.so.5')
hook_RSA_get0_key = find_bytes(lzma_start, lzma_end, 'F30F1EFA41564154554889F54883EC20')
result = gdb.execute(f'x/10i {hook_RSA_get0_key}', to_string=True)
[addr] = re.findall(f'# (0x[0-9a-f]+)', result)
return addr

def get_base(exe: str, pid: int):
out = subprocess.check_output(f"grep -E 'r--p 00000000.*{exe}' /proc/{pid}/maps | cut -d '-' -f1", shell=True)
return int(out.decode('utf-8').rstrip(), 16)

def write_pid(name:str, pid:int):
with open(f'gdb/pid_{name}', 'w') as f:
f.write(str(pid))

def wait_endless_loop(on_endless_loop:str, name:str = 'main', forced:bool = False):
wait_loop = os.environ.get('GDB_WAIT_LOOP')
if not forced and (wait_loop is None or int(wait_loop) == 0):
gdb.execute('continue')
return

pid = gdb.inferiors()[0].pid
while True:
dasm = gdb.execute('x/i $pc', to_string=True)
print(dasm)
addrs = re.findall(r'0x[0-9a-fA-F]+', dasm)
is_endless_loop = (len(addrs) == 2\
and 'jmp' in dasm\
and addrs[0] == addrs[1])

if is_endless_loop:
if len(on_endless_loop) > 0:
gdb.execute(on_endless_loop)
break
else:
write_pid(name, pid)
gdb.execute('continue')

def fork_capture_child_pid():
gdb.execute('finish')
pid = gdb.parse_and_eval('$rax')
print(f'child: {pid}')
write_pid('child', int(pid))

def fork_attach_child_pid():
gdb.execute('finish')
pid = gdb.parse_and_eval('$rax')
print(f'-- attaching to {pid}')
gdb.execute(f'attach {pid}')
gdb.execute('x/i $pc')


def post_setup():
print('-- post_setup')
tgt = gdb.inferiors()[0]

global_ctx_ptr_loc = int(find_global_context(), 16)
global_ctx_ptr = struct.unpack('<Q', tgt.read_memory(global_ctx_ptr_loc, 8))[0]

fmt_ctx = '<QQQQQQQQQQLLQQQQQQQQQ64sQQQQLL57s31sQ'
ctx_data = struct.unpack(fmt_ctx,
tgt.read_memory(global_ctx_ptr, struct.calcsize(fmt_ctx)))

sshd_base = get_base('/usr/sbin/sshd', tgt.pid)
lzma_base = get_base('liblzma.so.5', tgt.pid)

print(inspect.cleandoc(f'''
- uses_endbr64: {hex(ctx_data[0])}
- imported_funcs: {hex(ctx_data[1])}
- libc_imports: {hex(ctx_data[2])}
- disable_backdoor: {ctx_data[3]}
- sshd_ctx: {hex(ctx_data[4])}
- sshd_sensitive_data: {hex(ctx_data[5])}, r {hex(ctx_data[5] - sshd_base)}
- sshd_log_ctx: {hex(ctx_data[6])}
- STR_ssh_rsa_cert_v01_openssh_com: {hex(ctx_data[7])}, r {hex(ctx_data[7] - sshd_base)}
- STR_rsa_sha2_256: {hex(ctx_data[8])}, r {hex(ctx_data[8] - sshd_base)}
- struct_monitor_ptr_address: {hex(ctx_data[9])}, r {hex(ctx_data[9] - sshd_base)}
- exit_flag: {hex(ctx_data[10])}
- sshd_offsets: {hex(ctx_data[11])}
- sshd_code_start: {hex(ctx_data[12])}, r {hex(ctx_data[12] - sshd_base)}
- sshd_code_end: {hex(ctx_data[13])}, r {hex(ctx_data[13] - sshd_base)}
- sshd_data_start: {hex(ctx_data[14])}, r {hex(ctx_data[14] - sshd_base)}
- sshd_data_end: {hex(ctx_data[15])}, r {hex(ctx_data[15] - sshd_base)}
- sshd_main: {hex(ctx_data[16])}, r {hex(ctx_data[16] - sshd_base)}
- lzma_code_start: {hex(ctx_data[17])}
- lzma_code_end: {hex(ctx_data[18])}
- uid: {hex(ctx_data[19])}
- sock_read_buf_size: {hex(ctx_data[20])}
- sock_read_buf: {ctx_data[21].hex()}
- payload_data_size: {hex(ctx_data[22])}
- digest_offset: {hex(ctx_data[23])}
- payload_data: {hex(ctx_data[24])}
- sshd_payload_ctx: {hex(ctx_data[25])}
- sshd_host_pubkey_idx: {hex(ctx_data[26])}
- payload_state: {hex(ctx_data[27])}
- secret_data: {ctx_data[28].hex()}
- shift_operations: {ctx_data[29].hex()}
- num_shifted_bits: {hex(ctx_data[30])}
------------------------------------------------
'''))

payload_data = tgt.read_memory(ctx_data[24], ctx_data[22])
print(f'- payload_data: {payload_data.hex()}')

fmt_sshd_ctx = '<LLLLQQQQQQQQQQQQQQLHHQQQQQQLLQQQQ'
sshd_ctx = struct.unpack(fmt_sshd_ctx, tgt.read_memory(ctx_data[4], struct.calcsize(fmt_sshd_ctx)))

print(inspect.cleandoc(f'''
- have_mm_answer_keyallowed: {hex(sshd_ctx[0])}
- have_mm_answer_authpassword: {hex(sshd_ctx[1])}
- have_mm_answer_keyverify: {hex(sshd_ctx[2])}
- have_unk: {hex(sshd_ctx[3])}
- monitor_req_fn: {hex(sshd_ctx[4])}
- unk: {hex(sshd_ctx[5])}
- unk: {hex(sshd_ctx[6])}
- mm_answer_authpassword_start: {hex(sshd_ctx[7])} r {hex(sshd_ctx[7] - sshd_base)}
- mm_answer_authpassword_end: {hex(sshd_ctx[8])} r {hex(sshd_ctx[8] - sshd_base)}
- mm_answer_authpassword_ptr: {hex(sshd_ctx[9])} r {hex(sshd_ctx[9] - sshd_base)}
- unk: {hex(sshd_ctx[10])}
- mm_answer_keyallowed_start: {hex(sshd_ctx[11])} r {hex(sshd_ctx[11] - sshd_base)}
- mm_answer_keyallowed_end: {hex(sshd_ctx[12])} r {hex(sshd_ctx[12] - sshd_base)}
- mm_answer_keyallowed_ptr: {hex(sshd_ctx[13])} r {hex(sshd_ctx[13] - sshd_base)}
- unk: {hex(sshd_ctx[14])}
- mm_answer_keyverify_start: {hex(sshd_ctx[15])} r {hex(sshd_ctx[15] - sshd_base)}
- mm_answer_keyverify_end: {hex(sshd_ctx[16])} r {hex(sshd_ctx[16] - sshd_base)}
- mm_answer_keyverify_ptr: {hex(sshd_ctx[17])} r {hex(sshd_ctx[17] - sshd_base)}
- unk: {hex(sshd_ctx[18])}
- writebuf_size: {hex(sshd_ctx[19])}
- unk: {hex(sshd_ctx[20])}
- writebuf: {hex(sshd_ctx[21])}
- unk: {hex(sshd_ctx[22])}
- unk: {hex(sshd_ctx[23])}
- unk: {hex(sshd_ctx[24])} r {hex(sshd_ctx[24] - sshd_base)}
- mm_request_send_start: {hex(sshd_ctx[25])} r {hex(sshd_ctx[25] - sshd_base)}
- mm_request_send_end: {hex(sshd_ctx[26])} r {hex(sshd_ctx[26] - sshd_base)}
- unk: {hex(sshd_ctx[27])}
- unk: {hex(sshd_ctx[28])}
- use_pam_ptr: {hex(sshd_ctx[29])} r {hex(sshd_ctx[29] - sshd_base)}
- permit_root_login_ptr: {hex(sshd_ctx[30])} r {hex(sshd_ctx[30] - sshd_base)}
- STR_without_password: {hex(sshd_ctx[31])} r {hex(sshd_ctx[31] - sshd_base)}
- STR_publickey: {hex(sshd_ctx[32])} r {hex(sshd_ctx[32] - sshd_base)}
------------------------------------------------
'''))

59 changes: 59 additions & 0 deletions gdb/run_sshd.gdb
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
set confirm off
set pagination off
set breakpoint pending on
set startup-with-shell off
set disassembly-flavor intel

source gdb_commands.py
source common.gdb

define set_term
python
import os
os.environ['TERM'] = 'xterm-256color'
end
end

unset env
set env LANG=C
set env LD_LIBRARY_PATH=/tmp/xzre

file /usr/sbin/sshd
set args -f /tmp/sshd_config

define gdb_detach_p2
python
pid = gdb.parse_and_eval('$rax')
with open('gdb/pid', 'w') as f:
f.write(str(pid))
end
detach
quit
end

define gdb_detach
catch fork
commands
delete
python
import gdb
[gdb.execute(cmd) for cmd in ['finish', 'gdb_detach_p2']]
end
end
end

python
import os
detach = os.environ.get('GDB_DETACH')
if detach is not None:
gdb.execute('gdb_detach')

end



set_term
starti

python wait_endless_loop('on_endless_loop')

Loading

0 comments on commit 00b038b

Please sign in to comment.