Add functionality to block HTTP/3 by default

This commit is contained in:
Tim Perry
2025-06-24 17:43:09 +02:00
parent 33c9febf1b
commit bcbe288aa2
3 changed files with 50 additions and 28 deletions
+3 -1
View File
@@ -13,6 +13,7 @@ The scripts can automatically handle:
* Patching many (all?) known certificate pinning and certificate transparency tools, to allow interception by your CA certificate even when this is actively blocked.
* On Android, as a fallback: auto-detection of remaining pinning failures, to attempt auto-patching of obfuscated certificate pinning (in fully obfuscated apps, the first request may fail, but this will trigger additional patching so that all subsequent requests work correctly).
* Disabling many common root & jailbreak detections.
* Blocking most HTTP/3 connections (all UDP to port 443), which may be inconvenient to intercept, ensuring apps fall back to HTTP/2 or HTTP/1.
## Android Getting Started Guide
@@ -93,6 +94,7 @@ Each script includes detailed documentation on what it does and how it works in
* `PROXY_HOST` - the IP address (IPv4) of the proxy server to use (not required if you're only unpinning)
* `PROXY_PORT` - the port of the proxy server to use (not required if you're only unpinning)
* `DEBUG_MODE` - defaults to `false`, but switching this to `true` will enable lots of extra output that can be useful for debugging and reverse engineering any issues.
* `BLOCK_HTTP3` - defaults to `true`, which blocks HTTP/3 by dropping all UDP connections to port 443.
This should be listed on the command line before any other scripts.
@@ -100,7 +102,7 @@ Each script includes detailed documentation on what it does and how it works in
Captures all network traffic directly, routing all connections to the configured proxy host & port.
This is a low-level hook that applies to _all_ network connections. This ensures that all connections are forcibly redirected to the target proxy server, even those which ignore proxy settings or make other raw socket connections.
This is a low-level hook that applies to _all_ network connections. This ensures that all connections are forcibly redirected to the target proxy server, even those which ignore proxy settings or make other raw socket connections, and also blocks HTTP/3 connections if enabled.
This hook applies to libc, and works for Android, Linux, iOS, and many other related environments.
+5
View File
@@ -32,6 +32,11 @@ const DEBUG_MODE = false;
// sent via the proxy and intercepted despite this setting.
const IGNORED_NON_HTTP_PORTS = [];
// As HTTP/3 is often not well supported by MitM proxies, by default it
// is blocked entirely, so all outgoing UDP connections to port 443
// will fail. If this is set to false, they will instead be redirected
// to the same proxy port & address as TCP connections.
const BLOCK_HTTP3 = true;
// ----------------------------------------------------------------------------
// You don't need to modify any of the below, it just checks and applies some
+42 -27
View File
@@ -43,13 +43,16 @@ if (!connectFn) { // Should always be set, but just in case
const addrLen = args[2].toInt32();
const addrData = addrPtr.readByteArray(addrLen);
if (sockType === 'tcp' || sockType === 'tcp6') {
const isTCP = sockType === 'tcp' || sockType === 'tcp6';
const isUDP = sockType === 'udp' || sockType === 'udp6';
const isIPv6 = sockType === 'tcp6' || sockType === 'udp6';
if (isTCP || isUDP) {
const portAddrBytes = new DataView(addrData.slice(2, 4));
const port = portAddrBytes.getUint16(0, false); // Big endian!
const shouldBeIntercepted = !IGNORED_NON_HTTP_PORTS.includes(port);
const isIPv6 = sockType === 'tcp6';
const shouldBeIgnored = IGNORED_NON_HTTP_PORTS.includes(port);
const shouldBeBlocked = BLOCK_HTTP3 && !shouldBeIgnored && isUDP && port === 443;
const hostBytes = isIPv6
// 16 bytes offset by 8 (2 for family, 2 for port, 4 for flowinfo):
@@ -65,45 +68,57 @@ if (!connectFn) { // Should always be set, but just in case
if (isIntercepted) return;
if (!shouldBeIntercepted) {
// Not intercecpted, sent to unrecognized port - probably not HTTP(S)
if (shouldBeBlocked) {
if (isIPv6) {
// Skip 8 bytes: 2 family, 2 port, 4 flowinfo, then write :: (all 0s)
for (let i = 0; i < 16; i++) {
addrPtr.add(8 + i).writeU8(0);
}
} else {
// Skip 4 bytes: 2 family, 2 port, then write 0.0.0.0
addrPtr.add(4).writeU32(0);
}
this.state = 'Blocked';
} else if (!shouldBeIgnored) {
// Otherwise, it's an unintercepted connection that should be captured:
console.log(`Manually intercepting connection to ${getReadableAddress(hostBytes, isIPv6)}:${port}`);
// Overwrite the port with the proxy port:
portAddrBytes.setUint16(0, PROXY_PORT, false); // Big endian
addrPtr.add(2).writeByteArray(portAddrBytes.buffer);
// Overwrite the address with the proxy address:
if (isIPv6) {
// Skip 8 bytes: 2 family, 2 port, 4 flowinfo
addrPtr.add(8).writeByteArray(PROXY_HOST_IPv6_BYTES);
} else {
// Skip 4 bytes: 2 family, 2 port
addrPtr.add(4).writeByteArray(PROXY_HOST_IPv4_BYTES);
}
this.state = 'Intercepted';
} else {
// Explicitly being left alone
if (DEBUG_MODE) {
console.debug(`Allowing unintercepted connection to port ${port}`);
}
return;
}
// Otherwise, it's an unintercepted connection that should be captured:
console.log(`Manually intercepting connection to ${getReadableAddress(hostBytes, isIPv6)}:${port}`);
// Overwrite the port with the proxy port:
portAddrBytes.setUint16(0, PROXY_PORT, false); // Big endian
addrPtr.add(2).writeByteArray(portAddrBytes.buffer);
// Overwrite the address with the proxy address:
if (isIPv6) {
// Skip 8 bytes: 2 family, 2 port, 4 flowinfo
addrPtr.add(8).writeByteArray(PROXY_HOST_IPv6_BYTES);
} else {
// Skip 4 bytes: 2 family, 2 port
addrPtr.add(4).writeByteArray(PROXY_HOST_IPv4_BYTES);
this.state = 'ignored';
}
} else if (DEBUG_MODE) {
console.log(`Ignoring ${sockType} connection`);
this.ignored = true;
this.state = 'ignored';
}
// N.b. we ignore all non-TCP connections: both UDP and Unix streams
},
onLeave: function (result) {
if (!DEBUG_MODE || this.ignored) return;
if (!DEBUG_MODE || this.state === 'ignored') return;
const fd = this.sockFd;
const sockType = Socket.type(fd);
const address = Socket.peerAddress(fd);
console.debug(
`Connected ${sockType} fd ${fd} to ${JSON.stringify(address)} (${result.toInt32()})`
`${this.state} ${sockType} fd ${fd} to ${JSON.stringify(address)} (${result.toInt32()})`
);
}
});