Ghost in the Shellcode_ TI-1337 (Pwnable 100) » SkullSecurity

download Ghost in the Shellcode_ TI-1337 (Pwnable 100) » SkullSecurity

of 2

Transcript of Ghost in the Shellcode_ TI-1337 (Pwnable 100) » SkullSecurity

  • 8/13/2019 Ghost in the Shellcode_ TI-1337 (Pwnable 100) SkullSecurity

    1/2

    st in the Shellcode: TI-1337 (Pwnable 100) SkullSecurity

    2014/01/28 17:13ra:353

    Ghost in the Shellcode: TI-1337 (Pwnable 100) 2 Replies Hey everybody, This past weekend was Shmoocon, and you know what that meansG

    n the Shellcode! Most years I go to Shmoocon, but this year I couldn't attend, so I did the next best thing: competed in Ghost in the Shellcode! Th

    ear, our rag-tag band of misfitsthat is, the team who purposely decided not to ever decide on a team name, mainly to avoid getting competitive

    managed to get 20th place out of at least 300 scoring teams! I personally solved three levels: TI-1337, gitsmsg, and fuzzy. This is the first of three

    writeups, for the easiest of the three: TI-1337solved by 44 teams. You can download the binary, as well as the exploit, the IDA Pro files, and

    verything else worth keeping that I generated, from my Github repository. Getting started Unlike some of my teammates, I like to dive head-first

    ssembly, and try not to drown. So I fired up IDA Pro to see what was going on, and I immediately noticed is that it's a 64-bit Linux binary, and

    doesn't have a ton of code. Having never in my life written a 64-bit exploit, this would be an adventure! Small aside: Fork this! I'd like to take a qu

    moment to show you a trick I use to solve just about every Pwn-style CTF level: getting past that pesky fork(). Have you ever been trying to debug

    uln in a forking program? You attach a debugger, it forks, it crashes, and you never know. So you go back, you set affinity to 'child', you debug,

    debugger follows the child, catches the crash, and the socket doesn't get cleaned up properly? It's awful! There is probably a much better way to dhis, but this is what I do. First, I load the binary into IDA and look for the fork() call: .text:00400F65 good_connection: ; CODE XREF: do_

    onnection_stuff+39j .text:00400F65 E8 06 FD FF FF call _fork .text:00400F6A 89 45 F4 mov [rbp+child_pid], eax .text:00400F6D 83 7D F4 F

    mp [rbp+child_pid], 0FFFFFFFFh .text:00400F71 75 02 jnz short fork_successful You'll note that opcode bytes are turned on, so I can see the h

    ncoded machine code along with the instruction. The call to fork() has the corresponding code e8 06 fd ff ff. That's what I want to get rid of. So,

    pen the binary in a hex editor, such as 'xvi32.exe', search for that sequence of bytes (and perhaps some surrounding bytes, if it's ambiguous), and

    eplace it with 31 c0 90 90 90. The first two bytes31 c0is "xor eax, eax" (ie, clear eax), and 90 90 90 is "nop / nop / nop". So basically, the

    unction does nothing and returns 0 (ie, behaves as if it's the child process). You may want to kill the call to alarm(), as well, which will kill the

    rocess if you spend more than 30 seconds looking at it. You can replace that call with 90 90 90 90 90it doesn't matter what it returns. I did thi

    ll three levels, and I renamed the new executable "-fixed". You'll find them in the Github repository. I'm not going to go over that again

    he next two posts, but I'll be referring back to this instead. The program Since this is a post on exploitation, not reverse engineering, I'm not goin

    o super in-depth into the code. Instead, I'll describe it at a higher level and let you delve in more deeply if you're interested. The main handle_

    onnection() function can be found at offset 0x00401567. It immediately jumps to the bottom, which is a common optimization for a 'for' or 'whil

    oop, where it calls the code responsible for receiving datathe function at 0x00401395. After receiving data, it jumps back to the top of handleonnection() function, just after the jump to the bottom, where it goes through a big if/else list, looking for a bunch of symbols (like '+', '-', '/' and

    ook familiar?) After the if/else list, it goes back to the receive function, then to the top of the loop, and so on. Receive, parse, receive, parse, etc.

    Let's look at those two pieces separately, then we'll explore the vulnerability and see the exploit. Receive As I mentioned above, the receive func

    tarts at 0x00401395. This function starts by reading up to 0x100 (256) bytes from the socket, ending at a newline (0x0a) if it finds one. This is d

    sing a simple receive-loop function located at 0x0040130E that is worthwhile going through, if you're new to this, but that doesn't add much to th

    xploit. After reading the input, it's passed to sscanf(buffer, "%lg", ...). The format string "%lg" tells sscanf() to parse the input as a "double" var

    a 64-bit floating point. Great: a x64 process handling floating point values; that's two things I don't know! If the sscanf() failsthat is, the rece

    data isn't a valid-looking floating point valuethe received data is copied wholesale into the buffer. A flag at the start of the buffer is set indicat

    whether or not the double was parsed. Then the function returns. Quite simple! Processing the data I mentioned earlier that this binary looks for

    mathematical symbols'+', '-', '*', '/' in the received data. I didn't actually notice that right away, nor did the name "TI-1337" (or the fact that it us

    ort "31415"... think about it) lead me to believe this might be a calculator. I'm not the sharpest pencil sometimes, but I try hard! Anyway, back to

    main parsing code (near the top of the function at 0x00401567 again)! The parsing code is actually divided into two parts: a short piece of code th

    uns if a valid double was received (ie, the sscanf() worked), and a longer one that runs if it wasn't a double. The short piece of code simply calls

    unction (spoiler alert: the function pushes it onto a global stack object they use, not to be confused with the runtime stack). The longer one perforbunch of string comparisons and does soemthing based on those. I think at this point I'll give away the trick: whole application is a stack-based

    alculator. It allocates a large chunk of memory as a global variable, and implements a stack (a length followed by a series of 64-bit values). If yo

    nter a double, it's pushed onto the stack and the length is incremented. If you enter one of a few symbols, it pops one or more values (without

    hecking if we're at the beginning!), updates the length, and performs the calculation. The new value is then pushed back on top of the stack. He

    n example session: (sent) 10 (sent) 20 (sent) + (sent) . (received) 30 And a list of all possible symbols: + :: pops the top two elements off the stack

    dds them, pushes the result - :: same as '+', except it subtracts * :: likewise, multiplication / :: and, to round it out, division ^ :: exponents ! :: I nev

    eally figured this one out, might be a bitwise negation (or might not, it uses some heavy floating point opcodes that I didn't research :) ) . :: display

    he current value b :: display the current value, and pop it q :: quit the program c :: clear the stack And, quite honestly, that's about it! That's how

    works, let's see how to break it! The vulnerability As I alluded to earlier, the program fails to check where on the stack it currently is when it pop

    alue. That means, if you pop a value when there's nothing on the stack, you wind up with a buffer underflow. Oops! That means that if we pop a

    unch of times then push, it's going to overwrite something before the beginning of the stack. So where is the stack? If you look at the code in ID

    ou'll find that the stack starts at 0x00603140the .bss section. If you scroll up, before long you'll find this: .got.plt:00603018 off_603018 dq off

    ree ; DATA XREF: _freer .got.plt:00603020 off_603020 dq offset recv ; DATA XREF: _recvr .got.plt:00603028 off_603028 dq offset strncpy DATA XREF: _strncpyr .got.plt:00603030 off_603030 dq offset setsockopt ; DATA XREF: _setsockoptr ... The global offset table! And it's

    eadable/writeable! If we pop a couple dozen times, then push a value of our choice, we can overwrite any entryor all entrieswith any value w

    want! That just leaves one last step: where to put the shellcode? Aside: floating point One gotcha that's probably uninteresting, but is also the reas

    hat this level took me significantly longer than it should havethe only thing you can push/pop on the application's stack is 64-bit double values

    They're read using "%lg", but if I print stuff out using printf("%lg", address), it would truncate the numbers! Boo! After some googling, I discove

    hat you had to raise printf's precision a whole bunch to reproduce the full 64-bit value as a decimal number. I decided that 127 decimal places wa

    more than enough (probably like 5x too much, but I don't even care) to get a good result, so I used this to convert a series of 8 bytes to a unique

    double: sprintf(buf, "%.127lg\n", d); I incorporated that into my push() function: /* This pushes an 8-byte value onto the server's stack. */ void do

    ush(int s, char *value) { char buf[1024]; double d; /* Convert the value to a double */ memcpy(&d, value, 8); /* Turn the double into a string */

    printf(buf, "%.127lg\n", d); printf("Pushing %s", buf); /* Send it */ if(send(s, buf, strlen(buf), 0) != strlen(buf)) perror("send error!"); } And it

    worked perfectly! The exploit Well, we have a stack (one again, not to be confused with the program's stack) where we can put shellcode. It has a

    tatic memory address and is user-controllable. We also have a way to encode the shellcode (and addresses) so we wind up with fully controlled

    alues on the stack. Let's write an exploit! Here's the bulk of the exploit: int main(int argc, const char *argv[]) { char buf[1024]; int i; int s = get_

  • 8/13/2019 Ghost in the Shellcode_ TI-1337 (Pwnable 100) SkullSecurity

    2/2

    st in the Shellcode: TI-1337 (Pwnable 100) SkullSecurity

    2014/01/28 17:13ra:353

    ocket(); /* Load the shellcode */ for(i = 0; i < strlen(shellcode); i += 8) do_push(s, shellcode + i); /* Pop the shellcode (in retrospect, this could be

    eplaced with a single 'c') */ for(i = 0; i < strlen(shellcode); i += 8) do_pop(s); /* Pop until we're at the recv() call */ for(i = 0; i < 38; i++) do_pop(

    do_push(s, TARGET); /* Send a '.' just so I can catch it */ sprintf(buf, ".\n"); send(s, buf, strlen(buf), 0); sleep(100); return 0; } You can find the fu

    xploit here! Conclusion And that's all there is to it! Just push the shellcode on the stack, pop our way back to the .got.plt section, and push the

    ddress of the stack. Bam! Execution! That's all for now, stay tuned for the much more difficult levels: gitsmsg and fuzzy!