Skip to content

Latest commit

 

History

History
66 lines (53 loc) · 5.33 KB

WRITEUP.md

File metadata and controls

66 lines (53 loc) · 5.33 KB

Writeup

The program allows users to create accounts and make transactions between them.

The C code is compiled with Clang Address Sanitizer and Undefined Behavior Sanitizer, thus adding tons of runtime checks to catch most undefined behaviors (i.e, bugs). The flags I used to compile the source code are available here.

The program contains lots of bugs, and most of them are caught by the sanitizers, thus making them impossible to exploit! A challenger would probably spend most of its time trying to exploit different bugs, and watch ASAN catch them, with either admiration or frustration ;)

Unfortunately, ASAN isn't perfect, and one bug can be used to exploit the program and get a shell!

Let's see a non-exhaustive list of bugs:

There is also a use-after-free: if you trigger a realloc() in add_account(), the Account* src and Account* dest pointers in transaction structures are not updated, and they become dangling pointers. Unfortunately, ASAN is able to catch it!

The only bug that ASAN does not catch correctly is line 320, the variable index is controlled by the user, and can produce a heap buffer overflow. This is because ASAN uses "red zones" to catch buffer overflows. ASAN adds a space before and after each memory block, and triggers whenever we read or write on these bytes. By choosing a specific index, we can manage to read or write in another valid memory block, thus not triggering ASAN. And this is how we bypass ASAN!

The source code is equivalent to:

fgets(account->transactions.ptr[index].comment, 30, stdin);

With basic math, you can choose index so that account->transactions.ptr[index].comment = account->transactions.ptr + index * 32 + 16 points to a valid block.

In my solution, I chose index = 10737418244 so that comment = accounts.ptr[2].transactions.ptr. I can overwrite the Transaction structure of the third account.

The Transaction structure is:

typedef struct {
    Account* src;
    Account* dest;
    char* comment;
    unsigned amount;
} Transaction;

We can easily get a read-write-everywhere from here. I chose to overwrite the comment field. To read the comment, I ask the program to show me my transactions (with option 1). To write, I ask the program to update the comment of an existing transaction (option 5).

Now, this is classic exploitation. Note that ASAN makes addresses deterministic (ie, no PIE), this is too easy.. I just leak the GOT, I replace fgets by system, and then I get a shell!