ACIDVEGAS most dangerous motherfuck x come get some
x
blog
x
2026-11-04
An IPv4 address is just a 32-bit number, and glibc will accept it in forms you would never recognize. â°ðâ´5ï¼ð¯°ï¼âãâµââð²ð¯¶ is one of them. Anything that judges an address by how it looks can be walked around.
That is a real ping, to a real box, and it got a real reply. The thing after ping -c1 is a mix of LCD segmented digits, superscripts, subscripts, circled digits, a double-struck X and math-alphabet digits, with nine invisible characters that my terminal drew as white boxes because it had no idea what else to do with them. Paste it into a terminal and it looks like line noise. Hand it to getaddrinfo() and it is 69.30.206.130.
Every example in this post points at the same server, 69.30.206.130. It answers ping, and it serves exactly one page, /poc.html, on its bare IP so you can prove the HTTP side works too. Every screenshot is real output, captured on Ubuntu 24.04 with glibc 2.39, iputils 20240117 and curl 8.5.0.
An Address Is Just a Number
Start with the boring part, which is already weirder than most people expect. An IPv4 address is a 32-bit unsigned integer. The dotted quad is just how we have agreed to write it down. The parser that turns text into that integer is inet_aton(), and its rules date back to 4.2BSD. In glibc it lives in resolv/inet_addr.c, and each part is read like this:
/* Collect number up to ``.''. Values are specified as for C: 0x=hex, 0=octal, isdigit=decimal. */ ... unsigned long ul = __strtoul_internal (cp, &endp, 0, 0);
Base 0 means strtoul() picks the base from the prefix: 0x is hex, a leading 0 is octal, anything else is decimal. Each dotted part gets its own prefix, so you can mix bases inside one address. Then there is the part count:
/* Internet format:
a.b.c.d
a.b.c (with c treated as 16 bits)
a.b (with b treated as 24 bits). */
One part is the whole 32-bit number. Two parts are an 8-bit network and a 24-bit host. Three parts are 8, 8 and 16 bits. These were meant for the old class A/B/C networks, and nobody ever took them out. Leading zeros are unlimited too, as long as the value still fits.
| Form | 69.30.206.130 as |
|---|---|
| Decimal integer | 1159646850 |
| Hex integer | 0x451ece82 |
| Octal integer | 010507547202 |
| Dotted hex | 0x45.0x1e.0xce.0x82 |
| Dotted octal | 0105.036.0316.0202 |
| 3 parts, last one 16 bits | 69.30.52866 |
| 2 parts, last one 24 bits | 69.2018946 or 69.0x1ece82 |
| Mixed bases | 0x45.036.206.0202 |
| Over-padded hex | 0x00000000000000451ece82 |
Enter IDNA
Everything above is well known. It is the reason SSRF filters are supposed to parse addresses instead of regexing them. The fun starts with a flag most people never look at: AI_IDN.
AI_IDN is a glibc extension that tells getaddrinfo() the name might be an internationalized domain name, so it should be converted to ASCII before lookup. ping sets it, because iputils is built with IDN support. Here is what glibc does with it, in sysdeps/posix/getaddrinfo.c:
if (req->ai_flags & AI_IDN)
{
char *out;
result = __idna_to_dns_encoding (name, &out);
if (result != 0)
return -result;
name = out;
malloc_name = true;
}
if ((result = text_to_binary_address (name, req, &res)) != 0)
goto free_and_return;
Look at the order. The IDNA conversion runs first, and the numeric address parser only sees the output. __idna_to_dns_encoding() hands the string to libidn2, which applies UTS #46 mapping. UTS #46 exists so that a user typing a domain in a different width or style still lands on the same name. To do that it folds a huge range of compatibility characters down to plain ASCII. That table does not care that the result is going to be read as a number.
2473 ; mapped ; 0032 0030 # CIRCLED NUMBER TWENTY â³ -> 20 325A ; mapped ; 0033 0030 # CIRCLED NUMBER THIRTY ã -> 30 24EA ; mapped ; 0030 # CIRCLED DIGIT ZERO ⪠-> 0 1FBF6 ; mapped ; 0036 # SEGMENTED DIGIT SIX 𯶠-> 6 1D7CE ; mapped ; 0030 # MATHEMATICAL BOLD DIGIT ZERO ð -> 0 1D501 ; mapped ; 0078 # MATHEMATICAL BOLD SCRIPT SMALL X ð -> x 216D ; mapped ; 0063 # ROMAN NUMERAL ONE HUNDRED â -> c 1D49 ; mapped ; 0065 # MODIFIER LETTER SMALL E áµ -> e 3002 ; mapped ; 002E # IDEOGRAPHIC FULL STOP ã -> .
Digits are covered, and so are the dots. The x in a hex prefix and the a–f hex letters are covered too, which means every one of the classic forms above can be rebuilt out of Unicode lookalikes:
Circled two-digit numbers are a nice touch: â¥â¨.ã.â³â¥.â¬âª is four characters shorter than the plain address it decodes to. Hex works the same way, with a bold script x for the prefix, a Roman numeral one hundred standing in for c, and modifier letters for e:
Notice that ping echoes back 0x451ece82 in its PING line. ping also asks for AI_CANONIDN, so what it prints is the name after IDNA mapping. That is the clearest view you will get of what the resolver actually parsed.
Full Chaos
Nothing says a single address has to stick to one style. The mapping is done one character at a time, so every digit, dot and hex letter can come from a different block. I wrote a small script that reads IdnaMappingTable.txt, collects every code point that maps to 0–9, a–f, x or ., and rebuilds each classic form by picking a random lookalike for every character: fullwidth, superscript, subscript, circled, math bold, sans-serif, monospace, double-struck, script, fraktur, Roman numerals, and the LCD segmented digits from 𯰠to ð¯¹. Every result resolved:
Here are a few more, all 69.30.206.130, ready to paste:
ping -c1 ð¼ðµï¼ð¯³ð¶ãððð¯¶ï¼ðð¥âª # 69.30.206.130 ping -c1 ð¯°ðâ´ð。â°ððð²ï½¡ï¼âð¢ï½ 。âªðð𯲠# 0x45.0x1e.0xce.0x82 ping -c1 ð¢ð§â£ð±âáµâ¸ðð¯¸ð¯² # 0x451ece82 ping -c1 ï¼ï¼âªâ 。ðð¯³ðï¼âï¼ððãð¶ð¤âð® # 0105.036.0316.0202 ping -c1 ððãðð¢ï½¡ð¯µð¤âð¼ï¼ # 69.30.52866 ping -c1 ð¨â¨ï¼ð¬ð©ðï¼¥ðáµð¯¸ð¸ # 69.0x1ece82 ping -c1 â°ðâ´5ï¼ð¯°ï¼âãâµââð²ð¯¶ # 0x45.036.52866 ping -c1 ð£ð¯±ð»â¨6ðâ¥ð¯¸ðð # 1159646850 ping -c1 ð¬ï½ð¯´ðï¼ð¹ð¬ï¼â3ð£ï¼ï½¡ðâ ©ð¯¸ð # 0x45.30.0316.0x82
Invisible Ink
UTS #46 does not only map characters. It also has a category called ignored, and an ignored code point is simply deleted from the input:
00AD ; ignored # SOFT HYPHEN 200B ; ignored # ZERO WIDTH SPACE 2064 ; ignored # INVISIBLE PLUS E0100..E01EF ; ignored # VARIATION SELECTOR-17..VARIATION SELECTOR-256 1BCA0..1BCA3 ; ignored # SHORTHAND FORMAT LETTER OVERLAP..UP STEP
So you can put invisible characters before, inside and after the address, and they are gone before inet_aton ever sees the string. I verified soft hyphen, zero width space, word joiner, invisible plus, combining grapheme joiner, Mongolian free variation selector, variation selectors 1, 16 and 17, the BOM, and the shorthand format controls. Most terminals render them as nothing. Mine renders them as the white <fe0f> boxes in the cover image, which is how I noticed the copy-pasted address was not what it looked like.
The cover combines all of it: a zero-padded hex first part, a zero-padded octal second part, and a 16-bit decimal tail, with every character swapped for a random lookalike and nine ignored code points scattered through it. After mapping it is 0x00000045.00036.52866, which inet_aton reads as 0x45, 036 (octal 30) and 52866 (206 × 256 + 130).
Who Falls For It
None of this is magic in getaddrinfo() itself. It only happens when the caller passes AI_IDN. Without the flag, glibc goes straight to the numeric parser, sees a circled zero, and falls through to a DNS lookup that fails. Python makes the difference easy to see:
>>> socket.getaddrinfo('âªðâ£â¤â ðð¸ââ§â¡'.encode(), None, socket.AF_INET, 0, 0, 0)
socket.gaierror: [Errno -2] Name or service not known
>>> socket.getaddrinfo('âªðâ£â¤â ðð¸ââ§â¡'.encode(), None, socket.AF_INET, 0, 0, 0x40) # AI_IDN
[(<AddressFamily.AF_INET: 2>, ..., ('69.30.206.130', 0))]
Tools that do their own IDN conversion work as well. curl and wget both link libidn2 and run the hostname through it before resolving. OpenBSD netcat does not, and fails:
The last command there is a different failure. IPv4-mapped IPv6 such as ::ffff:69.30.206.130 works in curl, because the kernel turns a connect on an IPv6 TCP socket to a mapped address into IPv4. ping instead opens an ICMPv6 socket and sends a real IPv6 packet to ::ffff:0:0/96, which no router will carry.
curl has its own wrinkle. Its URL parser has an ipv4_normalize() step that rewrites 0x451ece82 or 69.2018946 to 69.30.206.130, but that runs before IDN conversion. A Unicode host skips normalization, gets converted to ASCII afterwards, and goes out on the wire like that. The server receives a Host: header of 0x451ece82:
curl also percent-decodes the host part of a URL, so http://%36%39.30.206.130/, http://69%2E30%2E206%2E130/, and the fully percent-encoded UTF-8 of the circled hex address all connect as well.
Why Bother
Because a lot of code decides whether something is an IP address by looking at it. Blocklists, SSRF guards that regex for 169.254. or 127., log searches, chat filters, and a human reading a config file all compare strings. If the thing doing the check does not run the same IDNA mapping and the same inet_aton rules as the thing doing the connect, the check and the connection are looking at two different strings. âªðâ¦ðâªâªâªâªâªâ is 127.0.0.1, and so is â â¡â¦ãâ .
â°ðâ´5ï¼ð¯°ï¼âãâµââð²ð¯¶ is a valid IP address. It resolves to 69.30.206.130, and that is fucking weird.
It is also more than a party trick. Malware has used the plainer versions of this for years: Emotet spam in 2022 wrote its download URLs with hex and octal IP addresses, likely to dodge pattern matching. The same idea applies to any filter that only reads the input. If something refuses 127.0.0.1, you could hand it â°ðð³ð¿ï¼â instead, and ping lands on loopback all the same.
The defence is the boring one: resolve first, then check the address you actually got back, and connect to that exact address. Never check the string.
Perception Deception
The address is still just a number, so the trick is not in getaddrinfo() â it is in the address bar. Drop a fully percent-encoded Unicode host into a URL with a made-up user:pass@ prefix and a /? query, and the host stops being the part a human reads.
http://images.google.com:static-uploads@%E2%93%AA%F0%9D%94%81%E2%85%AD%E2%85%AEb97a5a/?picture_of_you.png
âââ user âââ âââ pass âââ âââââââââââ host âââââââââââ âââ path ââââ
(circled + math-bold hex, the integer 0xcdb97a5a)
Glance at it and the eye lands on the trusted-looking parts: images.google.com as the user, static-uploads as the password, ?picture_of_you.png as a path to a photo. But the host is the hex integer 0xcdb97a5a, and inet_aton() turns it into 205.185.122.90. curl connects there, sends Host: 0xcdb97a5a, and ignores the credentials and the path entirely â they are decoration. A single one is easy to pass; a list of them is how a phishing link looks like a Google image.
The same trick works with the plain hex form, which is the cleanest because nothing has to be mapped:
curl images.google.com:static-uploads@0xcdb97a5a/?picture_of_you.png
âââ user âââ âââ pass âââ âââ host = 205.185.122.90 âââ âââ path ââ
Two things make it stick. First, humans read a URL as one token and rarely isolate the host field; the user:pass@ prefix and the /?file suffix both read as authoritative and pull attention away from the middle. Second, the decoy and the real target disagree about what the URL means. The browser or the reader sees a path to an image; getaddrinfo() sees a number and connects to it. The decoy does not have to be wrong â 0xcdb97a5a really is 205.185.122.90 â it only has to be something the reader does not expand.
It is the same gap as the blocklist check, aimed at the eye instead of the regex. A guard that regexes 127. or 169.254. on the string still misses 0x7f000001; a person who reads 0xcdb97a5a as "some hex" misses it too. The defence is the same: expand the host to the number it really is, and connect to that.
One Number, Two URLs
curl is not the only tool that reads a number where you do not. wget takes the URL as its first positional argument and treats everything after it as an option. Drop a bare integer in place of the URL and a URL-looking value in an option, and the eye lands on the option â but the number is what actually gets fetched.
wget -q 1159646850 -A=http://images.google.com/static/pic_of_you.png
âââ host âââ âââââââââââââ decoy ââââââââââââââ
69.30.206.130
1159646850 is just 69.30.206.130 in decimal, and it is the URL â wget connects there and sends Host: 1159646850. -A is --user-agent, but most people read it as just a flag and take the images.google.com link as the target, thinking the number belongs to -q. The number is the host; the link is the decoy, and it does not have to be wrong â the same gap, a tool that reads the option as the target instead of the argument. -A works better than --comment for the trick: short, nearly invisible, and nobody assumes a user-agent is a URL.
Full Results
Every form I tested, against 69.30.206.130. ping is iputils ping with one packet. curl fetched http://<address>/poc.html and got a 200. Every address is the real string, so you can copy it straight into a terminal. The invisible characters are in there even though you cannot see them.
| Form | Address | ping | curl |
|---|---|---|---|
| inet_aton forms | |||
| Dotted decimal | 69.30.206.130 | yes | yes |
| Decimal integer | 1159646850 | yes | yes |
| Hex integer | 0x451ece82 | yes | yes |
| Octal integer | 010507547202 | yes | yes |
| Dotted hex | 0x45.0x1e.0xce.0x82 | yes | yes |
| Dotted octal | 0105.036.0316.0202 | yes | yes |
| 3-part (last 16 bits) | 69.30.52866 | yes | yes |
| 2-part (last 24 bits) | 69.2018946 | yes | yes |
| 2-part, hex tail | 69.0x1ece82 | yes | yes |
| Mixed hex/oct/dec | 0x45.036.206.0202 | yes | yes |
| Uppercase hex prefix | 0X451ECE82 | yes | yes |
| Over-padded hex | 0x00000000000000451ece82 | yes | yes |
| Over-padded octal | 000000000000010507547202 | yes | yes |
| IPv4-mapped IPv6 | |||
| IPv4-mapped IPv6 | ::ffff:69.30.206.130 | no | yes |
| IPv4-mapped IPv6, hex | ::ffff:451e:ce82 | no | yes |
| Expanded mapped | 0:0:0:0:0:ffff:451e:ce82 | no | yes |
| Unicode lookalikes (IDNA / UTS #46) | |||
| Fullwidth digits and dots | ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ï¼ | yes | yes |
| Ideographic full stop | 69ã30ã206ã130 | yes | yes |
| Halfwidth ideographic stop | 69。30。206。130 | yes | yes |
| Fullwidth hex integer | ï¼ï½ï¼ï¼ï¼ï½
ï½ï½
ï¼ï¼ | yes | yes |
| Mathematical bold digits | ðð.ðð.ððð.ððð | yes | yes |
| Mathematical monospace digits | ð¼ð¿.ð¹ð¶.ð¸ð¶ð¼.ð·ð¹ð¶ | yes | yes |
| Double-struck digits | ðð¡.ðð.ððð.ððð | yes | yes |
| Superscript digits | â¶â¹.³â°.²â°â¶.¹³Ⱐ| yes | yes |
| Subscript digits | ââ.ââ.âââ.âââ | yes | yes |
| Circled digits | â¥â¨.â¢âª.â¡âªâ¥.â â¢âª | yes | yes |
| Circled two-digit numbers | â¥â¨.ã.â³â¥.â¬âª | yes | yes |
| Segmented (LCD) digits | ð¯¶ð¯¹.ð¯³ð¯°.ð¯²ð¯°ð¯¶.ð¯±ð¯³ð¯° | yes | yes |
| Roman numeral X and C | 0â
©451eâ
½e82 | yes | yes |
| Modifier letter hex | 0x451áµá¶áµ82 | yes | yes |
| Mixed-script hex integer | âªðâ£â¤â ðð¸ââ§â¡ | yes | yes |
| Circled 2-part, ideographic | â¥â¨ãâ¡âªâ â§â¨â£â¥ | yes | yes |
| Mixed-script 3-part | âªâ
©â£â¤.âªâ¢â¥.â¤â¡â§â¥â¥ | yes | yes |
| Invisible characters (UTS #46 ignored) | |||
| Soft hyphen + ZWSP inside | 6Ââ9.30.206.130 | yes | yes |
| Word joiner + VS16 inside | 69.3â ï¸0.206.130 | yes | yes |
| Shorthand format controls | 69.30.2ð² â¤06.130 | yes | yes |
| Kitchen sink | ó âªðï¸âªâªâªâªâªâªâ£â¤ï¸.Íâªâªâªâ¢â¥á .ââ¤â¡â§â¥â¥ï¸ó | yes | yes |
| Full chaos (random mix of every lookalike, generated from the UTS #46 table) | |||
| Chaos mix of 69.30.206.130 | ð¼ðµï¼ð¯³ð¶ãððð¯¶ï¼ðð¥âª | yes | yes |
| Chaos mix of 0x45.0x1e.0xce.0x82 | ð¯°ðâ´ð。â°ððð²ï½¡ï¼âð¢ï½
。âªðð𯲠| yes | yes |
| Chaos mix of 0x451ece82 | ð¢ð§â£ð±âáµâ¸ðð¯¸ð¯² | yes | yes |
| Chaos mix of 0105.036.0316.0202 | ï¼ï¼âªâ
。ðð¯³ðï¼âï¼ððãð¶ð¤âð® | yes | yes |
| Chaos mix of 69.30.52866 | ððãðð¢ï½¡ð¯µð¤âð¼ï¼ | yes | yes |
| Chaos mix of 69.0x1ece82 | ð¨â¨ï¼ð¬ð©ðï¼¥ðáµð¯¸ð¸ | yes | yes |
| Chaos mix of 0x45.036.52866 | â°ðâ´5ï¼ð¯°ï¼âãâµââð²ð¯¶ | yes | yes |
| Chaos mix of 1159646850 | ð£ð¯±ð»â¨6ðâ¥ð¯¸ðð | yes | yes |
| Chaos mix of 0x45.30.0316.0x82 | ð¬ï½ð¯´ðï¼ð¹ð¬ï¼â3ð£ï¼ï½¡ðâ
©ð¯¸ð | yes | yes |
| Invisible characters (UTS #46 ignored) | |||
| Chaos kitchen sink (cover) | ó Íââ¤ðð¬á ð¶ð¶â°ð¯°âªð¯´â¤ð»ï¼ï¼â°ââ°ð¯ð¯¶ï½¡ð² âµðâ¸Âðâï¸ | yes | yes |
| URL only (percent-encoded host) | |||
| Percent-encoded digits | %36%39.30.206.130 | n/a | yes |
| Percent-encoded dots | 69%2E30%2E206%2E130 | n/a | yes |
| Percent-encoded Unicode | %E2%93%AA%F0%9D%94%81%E2%91%A3%E2%91%A4%E2%91%A0%F0%9D%95%96%F0%9D%92%B8%E2%93%94%E2%91%A7%E2%91%A1 | n/a | yes |
Sources & References:
- sysdeps/posix/getaddrinfo.c, resolv/inet_addr.c, glibc 2.39
- getaddrinfo(3) and inet_aton(3), Linux man-pages
- UTS #46: Unicode IDNA Compatibility Processing and its IdnaMappingTable.txt, Unicode Consortium
- libidn2, GNU
- ping/ping.c, iputils 20240117
- lib/urlapi.c and lib/idn.c, curl 8.5.0