Skip to content

Commit bcbe288

Browse files
committed
Add functionality to block HTTP/3 by default
1 parent 33c9feb commit bcbe288

File tree

3 files changed

+50
-28
lines changed

3 files changed

+50
-28
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ The scripts can automatically handle:
1313
* Patching many (all?) known certificate pinning and certificate transparency tools, to allow interception by your CA certificate even when this is actively blocked.
1414
* 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).
1515
* Disabling many common root & jailbreak detections.
16+
* 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.
1617

1718
## Android Getting Started Guide
1819

@@ -93,14 +94,15 @@ Each script includes detailed documentation on what it does and how it works in
9394
* `PROXY_HOST` - the IP address (IPv4) of the proxy server to use (not required if you're only unpinning)
9495
* `PROXY_PORT` - the port of the proxy server to use (not required if you're only unpinning)
9596
* `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.
97+
* `BLOCK_HTTP3` - defaults to `true`, which blocks HTTP/3 by dropping all UDP connections to port 443.
9698

9799
This should be listed on the command line before any other scripts.
98100

99101
* `native-connect-hook.js`
100102

101103
Captures all network traffic directly, routing all connections to the configured proxy host & port.
102104

103-
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.
105+
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.
104106

105107
This hook applies to libc, and works for Android, Linux, iOS, and many other related environments.
106108

config.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ const DEBUG_MODE = false;
3232
// sent via the proxy and intercepted despite this setting.
3333
const IGNORED_NON_HTTP_PORTS = [];
3434

35+
// As HTTP/3 is often not well supported by MitM proxies, by default it
36+
// is blocked entirely, so all outgoing UDP connections to port 443
37+
// will fail. If this is set to false, they will instead be redirected
38+
// to the same proxy port & address as TCP connections.
39+
const BLOCK_HTTP3 = true;
3540

3641
// ----------------------------------------------------------------------------
3742
// You don't need to modify any of the below, it just checks and applies some

native-connect-hook.js

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,16 @@ if (!connectFn) { // Should always be set, but just in case
4343
const addrLen = args[2].toInt32();
4444
const addrData = addrPtr.readByteArray(addrLen);
4545

46-
if (sockType === 'tcp' || sockType === 'tcp6') {
46+
const isTCP = sockType === 'tcp' || sockType === 'tcp6';
47+
const isUDP = sockType === 'udp' || sockType === 'udp6';
48+
const isIPv6 = sockType === 'tcp6' || sockType === 'udp6';
49+
50+
if (isTCP || isUDP) {
4751
const portAddrBytes = new DataView(addrData.slice(2, 4));
4852
const port = portAddrBytes.getUint16(0, false); // Big endian!
4953

50-
const shouldBeIntercepted = !IGNORED_NON_HTTP_PORTS.includes(port);
51-
52-
const isIPv6 = sockType === 'tcp6';
54+
const shouldBeIgnored = IGNORED_NON_HTTP_PORTS.includes(port);
55+
const shouldBeBlocked = BLOCK_HTTP3 && !shouldBeIgnored && isUDP && port === 443;
5356

5457
const hostBytes = isIPv6
5558
// 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
6568

6669
if (isIntercepted) return;
6770

68-
if (!shouldBeIntercepted) {
69-
// Not intercecpted, sent to unrecognized port - probably not HTTP(S)
71+
if (shouldBeBlocked) {
72+
if (isIPv6) {
73+
// Skip 8 bytes: 2 family, 2 port, 4 flowinfo, then write :: (all 0s)
74+
for (let i = 0; i < 16; i++) {
75+
addrPtr.add(8 + i).writeU8(0);
76+
}
77+
} else {
78+
// Skip 4 bytes: 2 family, 2 port, then write 0.0.0.0
79+
addrPtr.add(4).writeU32(0);
80+
}
81+
this.state = 'Blocked';
82+
} else if (!shouldBeIgnored) {
83+
// Otherwise, it's an unintercepted connection that should be captured:
84+
85+
console.log(`Manually intercepting connection to ${getReadableAddress(hostBytes, isIPv6)}:${port}`);
86+
87+
// Overwrite the port with the proxy port:
88+
portAddrBytes.setUint16(0, PROXY_PORT, false); // Big endian
89+
addrPtr.add(2).writeByteArray(portAddrBytes.buffer);
90+
91+
// Overwrite the address with the proxy address:
92+
if (isIPv6) {
93+
// Skip 8 bytes: 2 family, 2 port, 4 flowinfo
94+
addrPtr.add(8).writeByteArray(PROXY_HOST_IPv6_BYTES);
95+
} else {
96+
// Skip 4 bytes: 2 family, 2 port
97+
addrPtr.add(4).writeByteArray(PROXY_HOST_IPv4_BYTES);
98+
}
99+
this.state = 'Intercepted';
100+
} else {
101+
// Explicitly being left alone
70102
if (DEBUG_MODE) {
71103
console.debug(`Allowing unintercepted connection to port ${port}`);
72104
}
73-
return;
74-
}
75-
76-
// Otherwise, it's an unintercepted connection that should be captured:
77-
78-
console.log(`Manually intercepting connection to ${getReadableAddress(hostBytes, isIPv6)}:${port}`);
79-
80-
// Overwrite the port with the proxy port:
81-
portAddrBytes.setUint16(0, PROXY_PORT, false); // Big endian
82-
addrPtr.add(2).writeByteArray(portAddrBytes.buffer);
83-
84-
// Overwrite the address with the proxy address:
85-
if (isIPv6) {
86-
// Skip 8 bytes: 2 family, 2 port, 4 flowinfo
87-
addrPtr.add(8).writeByteArray(PROXY_HOST_IPv6_BYTES);
88-
} else {
89-
// Skip 4 bytes: 2 family, 2 port
90-
addrPtr.add(4).writeByteArray(PROXY_HOST_IPv4_BYTES);
105+
this.state = 'ignored';
91106
}
92107
} else if (DEBUG_MODE) {
93108
console.log(`Ignoring ${sockType} connection`);
94-
this.ignored = true;
109+
this.state = 'ignored';
95110
}
96111

97112
// N.b. we ignore all non-TCP connections: both UDP and Unix streams
98113
},
99114
onLeave: function (result) {
100-
if (!DEBUG_MODE || this.ignored) return;
115+
if (!DEBUG_MODE || this.state === 'ignored') return;
101116

102117
const fd = this.sockFd;
103118
const sockType = Socket.type(fd);
104119
const address = Socket.peerAddress(fd);
105120
console.debug(
106-
`Connected ${sockType} fd ${fd} to ${JSON.stringify(address)} (${result.toInt32()})`
121+
`${this.state} ${sockType} fd ${fd} to ${JSON.stringify(address)} (${result.toInt32()})`
107122
);
108123
}
109124
});

0 commit comments

Comments
 (0)