Space Force - Binary Exploitation

Solved by Day - gg for the writeup :smile:

Flag found at the end

Given the name "space FORCE" this was probably meant to be house of force but I felt like that would be more complicated than what I ended up doing so I... didn't do that.

Instead, I went along the path of a more complex tcache poisoning attack.

Let's digest the program first before we look at how we are to attack it.

We have 5 functions:

  • register account

  • print account by UID

  • print all accounts

  • delete last registered account

  • launch rocket.

I'm not gonna talk about launch rocket, since it seems rather useless and I didn't use it at all.

Register account

We choose a first name, last name, and can set the expiry if we want. Note the expiry, unlike the comment, is malloc-ed no matter what, it's just not initialised if we don't set the expiry. We can also set the comment, for this we get to specify the size and it gets malloced. Here are some representations of how accounts are stored:

struct account {
  unsigned long uid;
  struct expiry * expiration;
  char firstname[32];
  char lastname[32];
  char* comment;
  long comment_size;
}
struct expiry {
  int day;
  int year;
  char month[16];
}

UID

Simply represents the index of the account in the account array, which by the way is stored on the stack and has no cap.

Specify UID(which is used as an index to grab the account) and the UID, first name and last name will be printed. Note that there's no checks at all on the UID and deleted accounts aren't removed from the array, the account counter is just decremented(read after free) but I ended up not using this, as the UID is only printed as an integer and thus not a lot of information can be reaped.

Note that the program keeps an account counter, incrementing when an account is made, decrementing when the last account is deleted.(This is also how it keeps track of the last account). All accounts up to this counter are printed: UID, last name, first name But this time so is the expiry - day, year and month.

Ok, we've covered all the important functions. Let's dissect our two main vulns.

VULN 1

Malloc does NOT initialise data. Info leak! Left over pointers from a free chunk newly allocated remain. This allows us to get a heap leak by smartly handling expiries, which I'll explain below.

VULN 2

There is overflow when we make an expiry. 32 bytes are read into the month when only 16 should be read. This gives overflow of 15 bytes after the end of the expiry chunk(1 byte is reserved for null as fgets is used)

Let's dissect a little more into exactly how our data is stored on the heap.

So, we've already covered the account and expiry structs.

  • Accounts are 0x70 size chunks(0x60 allocation)

  • Expiries are 0x20 size chunks(0x10 allocation)

  • Comments are malloc-ed to their exact size

  • Note we can't read to comments, only write.

So, how do we get this info leak?

Let's start off with two accounts, no comment. Heap is as follows:

0x70 chunk
0x20 chunk
0x70 chunk
0x20 chunk

Let's free them both, and take a look at the tcache

  • 0x20 bin -> account 0 expiry -> account 1 expiry

  • 0x70 bin -> account 0 -> account 1

Let's make another account, it'll be served pretty much on top of the old count 0. Let's again not set an expiry.

Now, remember how I said it just doesnt initialise when we dont set an expiry? This means in place of the day and year fields, will set a heap pointer! We can leak this pointer!:

  • Printing all accs

  • Extracting the day and year pointers

  • Doing twos complement (as they may be displayed negative)

  • Reconstructing the ints to get the original heap address

This gives us the heap base

> Ok.. ok, so we know the whereabouts of the heap. What about libc?

In hindsight this could've been done SOOO much simpler by forcing an unsorted bin chunk then doing the same sort of protocol as the 0x20 would be served by the unsorted bin but.. here we are. The 15 bytes of overflow past the expiry chunk lets us overwrite the size field of the next chunk (I didnt do anything here, just set it to what it already was) and then overwrite the 8 bytes afterwards. This can be used nicely for tcache poisoning, overwriting the next pointer of a tcache chunk.

What I did was:

  • allocate an account with a 0xe0 sized comment

  • freeed it

  • allocated an account with no comment.

This account would be in around the same place, just below the old, freed, tcache comment chunk.

account chunk
expiry chunk
0xf0 size tcache chunk

With the expiry overflow, we can overwrite the next pointer to execute tcache poisoning. Given my knowledge of the whereabouts of the heap, I used this to:

  • go back

  • overwrite the expiry pointer of chunk 0 to an existing unsorted bin chunk

  • (as that would have libc pointers).

So, we can set the next pointer to the address of the expiry pointer of chunk 0. Make a 0xe0 comment allocation, make one again - this will be on top of chunk 0's expiry pointer! Let's overwrite the expiry pointer with that of the unsorted bin chunk.

> Now, by printing all accounts, we also print the libc pointer hidden in the expiry(it'll be right at the month field).

> Note the 0xf0 tcache is utterly fried after this so we wont be making any 0xe0 allocations again.

Now, we've got heap AND libc!!! We just need the final piece - an arb write, preferable to write to free hook.

We can repeat this process again, but things get more complicated. Going back to getting an unsorted chunk, this is only possible through:

  • filling up the tcache (I used 0xf0 tcache)

  • freeing another one

Problem with this is...

  1. It leaves an unwanted unsorted/small bin chunk we'll need to completely serve out to stop allocations earlier on in the heap

  2. It wastes a lot of space on the heap as all these old tcache chunks get disconnected from tcache. We'll need to refill these phantom freed account blocks before we move on

  3. We can't have too many accounts, it'll overwrite important things on the stack and maybe even the return address to account addresses(since NX is on this isn't at all helpful)

I went smart about this, and in my filling up of the heap so that we can get a nice "clean slate" ready for the second poisoning. I used comment sizes that imitated account creations.

Eventually, through some trial and error and thinking:

  • I was able to do a good set of allocations

    • that efficiently used the list to fill up the entirety of the previous section of the heap

    • such that all new allocations are top chunk allocations.

Finally, we can:

  • allocate a chunk with 0xf0 size

    • such that the comment chunk will be of size 0x100

    • it won't be in the previously destroyed tcache.

  • Then, we free it

  • Allocate an account with no comment

  • tcache poison via overflow

  • Set the next pointer to free hook-8

    • Allocate an account

    • 0xf0 comment size

  • Do that again

    • The second one's comment will be at free hook-8

  • write /bin/sh + system

  • free the previous account

  • shell popped!

Explanation

Free hook is called on the pointer that is about to be freed before it is freed. Overwriting free hook with system means the chunk about to be freed (in this case /bin/sh as we wrote that to free hook -8) will have system called on it Allocating at free hook-8 to get free hook overwrite and also extra space for /bin/sh for system is a nice little trick i learned from a fizzbuzz writeup, which he learned from NotDeGhost's writeups.

Summarisation

  1. Abuse lack of pointer clearing to leak heap pointers using expiries by allocating two, freeing two then allocation one(all without expiry initialisation)

  2. Fill 0xf0 tcache, get an unsorted bin chunk this way(we can allocate chunks of arbitrary sizes via allocating accounts with specific sizes of comments)

  3. Tcache poison to get a chunk at the expiry pointer of account 0

  4. Overwrite expiry pointer to point at unsorted bin chunk(which will have libc pointer)

  5. Leak libc by printing all accounts

  6. Fill up previous section of the heap through a series of specifically comment-sized registrations

  7. Tcache poison again this time in the 0x100 tcache to get a chunk at free hook-8

  8. Write /bin/sh\x00 + system

  9. Free last account

  10. Shell popped

Exploit script can be found here

Flag

flag{michael_scott_for_president}

Last updated