Tuesday, September 22, 2015

This is an example how to use Vivisect.

Vivisect is a cool collection of tools for both static and dynamic analysis of binaries.
It supports x86, x86_64, and ARM binaries. Also runs on multiple platforms Linux, Win and osX
You are probably wondering what is the link with this blog.
It is all written in Python. Yes all of it. From the multi platform debugger through the disassembly
engine up to the emulator and GUI. It's a piece of beauty.

The code base is very nice. It uses introspection beautifiers etc. It's modular and well structured.
That actually proves the people behind it are highly skilled with the language and enjoy it.

For this presentation we will crack a middle difficulty crackme from http://crackmes.de/

The binary used for the case is Tryger's keygen_rivendel

You can also download the whole solution  from http://crackmes.de/users/tryger/keygen_rivendel/solutions/bat.serjo

SPOILER :)
DEFAULT USER: \n
DEFAULT PASS: \n

But that's not interesting :) There is no goal in this other than fun :D

This crackme is one small time, dirty, wanna be gangsta.

$ gdb -q ./breakme
Reading symbols from ./breakme...Segmentation fault

Ayee gangsta?

$ nm ./breakme
08049e8c d (null)
080484b0 t (null)
080484e0 t (null)
....

Alright then let see how you do against Vivisect (https://github.com/vivisect)

$ ./vivbin -B ./breakme
Failed to find file for 0x08049fb8 (_edata) (and filelocal == True!)
Failed to find file for 0x08049fb8 (__bss_start) (and filelocal == True!)
Failed to find file for 0x08049fb8 (__TMC_END__) (and filelocal == True!)
Loaded (0.0513 sec) ./breakme
ANALYSIS TIME: 0.267040967941
stats: {'functions': 24, 'relocations': 0}
Saving workspace: ./breakme.viv

Hmm ain't that tough after all :)


First of all I would like to say that this crack me is a perfect example
for security through obscurity. Anti debug protections combined with
decoys and recursion for the check algorithm. It's so ahead in the game
obscurity that even a successful check of the key sounds dubious :)
You got it? or not! I don't really know.

Anti debug protection number one. Obviously something is wrong with the ELF header.
Wrong enough to crash gdb. I didn't spent much time figuring out what. Since the
kernel is able to load it and execute it. Also vivisect coped well with it.

Using vivisect analyze the __entry point of the program.

.text:0x080487b6  55               push ebp
.text:0x080487b7  89e5             mov ebp,esp
.text:0x080487b9  81ecb8000000     sub esp,184
.text:0x080487bf  6a00             push 0
.text:0x080487c1  6a01             push 1
.text:0x080487c3  6a00             push 0
.text:0x080487c5  6a00             push 0
.text:0x080487c7  e894fcffff       call ptrace_08048460    ;<-- antidebug protection number two :)
.text:0x080487cc  83c410           add esp,16
.text:0x080487cf  85c0             test eax,eax
.text:0x080487d1  791a             jns loc_080487ed

Hehe old school unix anti debug tricks. Ptrace your self so that nobody else can ptrace you :D
Then there's a check for the result. So what happens if you try to debug it with something else than gdb :)

python ./crack.py ~/examples/CRACKME/rivendel/breakme
You are debugging me, I don't like it :(

Hehe really bro? We'll see about that later.
Now lets continue with analyzing the flow of the program using the FuncGraph.
We notice that if ptrace succeeds we continue with some welcoming messages etc.
Then we read the user input. With fgets.

Spoiler. If fgets fails we end up jumping to 0x08048918.THIS IS A DECOY! everything after this is a decoy!
Yes the binary is 90% bullshit :) But I'm not sure I get the reset 10% either.
If getting the password fails it will also jump there!

The password is read using getc from the function @ 0x0804877b.
It must read 3 times more data from the input than the username length!

Then the (username, password, counter, tmpVar, userLen) are fed to the function @ 0x0804856b
This is where the magic happens 0x0804856b.

Give the function a glance at the FuncGraph.
- It's recursive
- It has multiple check points
- It has multiple direct exit(-1) points
- If you survive this function you pretty much 'own' the binary :)
- It takes multiple arguments
- On every cycle it makes a check of the currently calculated theoretical hash
- If the check fails you end up in the exit(-1) case.
- Otherwise continue recursion.

Check out the function in the Viv window
.text:0x0804856b
.text:0x0804856b  FUNC: int cdecl breakme.magic( int arg0, int arg1, int arg2, int arg3, int arg4, ) [4 XREFS]
.text:0x0804856b
.text:0x0804856b  Stack Variables:
.text:0x0804856b            20: int arg4
.text:0x0804856b            16: int arg3
.text:0x0804856b            12: int arg2
.text:0x0804856b             8: int arg1
.text:0x0804856b             4: int arg0
.text:0x0804856b           -16: int local16


Here's what the arguments are - as seen @

.text:0x080488dc  83c410           add esp,16
.text:0x080488df  83ec0c           sub esp,12
.text:0x080488e2  50               push eax     ;ARG strlen_hashed_user
.text:0x080488e3  6a00             push 0     ;ARG 0
.text:0x080488e5  6a00             push 0     ;ARG 0
.text:0x080488e7  8d8557ffffff     lea eax,dword [ebp - 169]
.text:0x080488ed  50               push eax     ;ARG Password
.text:0x080488ee  8d45cf           lea eax,dword [ebp - 49]
.text:0x080488f1  50               push eax     ;ARG Hashed user
.text:0x080488f2  e874fcffff       call breakme.magic    
.text:0x080488f7  83c420           add esp,32
.text:0x080488fa  85c0             test eax,eax
.text:0x080488fc  7510             jnz loc_0804890e
.text:0x080488fe  83ec0c           sub esp,12
.text:0x08048901  688e8c0408       push str_Yout got it, or _08048c8e    ;bingo
.text:0x08048906  e8f5faffff       call puts_08048400    ;puts_08048400()
.text:0x0804890b  83c410           add esp,16
.text:0x0804890e  loc_0804890e: [1 XREFS]
.text:0x0804890e  83ec0c           sub esp,12
.text:0x08048911  6a01             push 1    ;SUCCESS !!!
.text:0x08048913  e808fbffff       call exit_08048420  

In short breakme.magic(user, pwd, cnt, unk, userLen)
Also here you can see that if you survive the magic you are done.

Using the Symbolics window in Vivisect extract the results
- Using the simplified view extract all possible paths
- Sort them in a file.
- Try to figure out what all this means.

It sounds hard but is actually not.
In the Symbolics.txt you can find a log of my struggles.
Actually I didn't expect to get anything useful out of this.
But also didn't want to start debugging yet.
The results were actually pretty good.
In short I was able to make a skeleton of the algorithm
in a pseudo language quite fast.

def reverse(user, pwd, cnt, unk=0, userLen = len(user)):
if cnt == userLen*3:
return 0

if cnt < userLen * 2:
if cnt < userLen:
if pwd[cnt] == (unk+15) ^ user[cnt]
lvar = cnt+1

if cnt + 1 == userLen:
arg3 = ((unk + 15) ^ user[cnt]) - 10
reverse(user, pwd, cnt+1, arg3, userLen)
else
arg3 = ((unk + 15) ^ user[cnt])
reverse(user, pwd, cnt+1, arg3, userLen)
else
raise Exception("missed at %s %d %s" % (user, cnt, pwd))
else:
if pwd[cnt] != (18 - unk) ^ user[ (cnt/userLen) % userLen ]:
raise Exception("missed at %s %d %s" % (user, cnt, pwd))
else
lvar = cnt + 1
if cnt + 1 == userLen*2:
arg3 = (18 - unk) ^ user[ (cnt/userLen) % userLen ] + 113
reverse(user, pwd, cnt+1, arg3, userLen)
else
arg3 = (18 - unk) ^ user[ (cnt/userLen) % userLen ]
reverse(user, pwd, cnt+1, arg3, userLen)
else:
if pwd[cnt] == ((unk - 76) * 2) ^ user[ (cnt / userLen) % userLen ]:
lvar = cnt + 1
arg3 = ((unk - 76) * 2) ^ user[ (cnt / userLen) % userLen ]
reverse(user, pwd, cnt+1, arg3, userLen)
else
raise Exception("missed at %s %d %s" % (user, cnt, pwd))


From this I made a "generator" (still not 100% working :).

Problem. As it turns out you must enter non printable characters for the password.
What the hell. Redirecting input from file or if you're a shell ninja get it done somehow.
Guess the binary is supposed to be used as a part from a bigger system and called externaly
then the exit status is used to make the judgment.

serj@debian:~/examples/CRACKME/rivendel$ python keygen.py a
6ecf89
serj@debian:~/examples/CRACKME/rivendel$ ./breakme < input.raw
Keygen me... if you can ;-)
Have fun & Good luck!
User (1 - 40 chars): Password for the user a (3 bytes):
Yout got it, or not... ;)

Problem:

serj@debian:~/examples/CRACKME/rivendel$ python keygen.py plp
7fe281f777f7488808
serj@debian:~/examples/CRACKME/rivendel$ ./breakme < input.raw
Keygen me... if you can ;-)
Have fun & Good luck!
User (1 - 40 chars): Password for the user plp (9 bytes): WRONG SERIAL :-(

As mentioned before not 100% accurate.

Lets debug this little fucker. vtrace is powerful tool. Maybe a tid bit slow.
But powerful. Lets write a script that will runtime patch the binary to spit out the password.

Using vtrace:
- Patch the ptrace call so that execution proceeds as normal.
- Add instrumentation to print the calculated value for the password
- Hook the unwanted branch of every check manipulate it and jump to the wanted branch as if nothing happened.


I'm usint breakpoints for all this.
For example a breakpoint on the ptrace call instruction can manipulate the eip register and simply continue.
This effectively bypasses the ptrace protection.

class PtracePatch ( Breakpoint ):
def notify( self, event, trace ):
rctx = trace.getRegisterContext()
curaddr = rctx.getProgramCounter()
jumpaddr = curaddr + 5 #bytes: e894fcffff
rctx.setProgramCounter(jumpaddr)


Get it right :) Something similar for the branch and printing and the crack.py was ready.

Put the crack.py inside the vivisect folder in order to be able to resolve needed modules etc.

serj@debian:~/netstock/vivisect$ cp ~/examples/CRACKME/rivendel/crack.py ./
serj@debian:~/netstock/vivisect$ python ./crack.py ~/examples/CRACKME/rivendel/breakme
Keygen me... if you can ;-)
Have fun & Good luck!
User (1 - 40 chars): plp
Password for the user plp (9 bytes): aaaaaaaaa

Yout got it, or not... ;)
7f  e2  81  eb  4b  b7  c8  94  e0

serj@debian:~/examples/CRACKME/rivendel$ python ./keygen.py -use plp 7f  e2  81  eb  4b  b7  c8  94  e0
serj@debian:~/examples/CRACKME/rivendel$ ./breakme  < input.raw
Keygen me... if you can ;-)
Have fun & Good luck!
User (1 - 40 chars): Password for the user plp (9 bytes):
Yout got it, or not... ;)

Problem solved :)
So you got it? or not. I still have no idea. But at least I have a keygen :D

And here is the script that does the job

'''
Created on Apr 24, 2015

@author: serj
'''
import sys
import errno
import struct

import envi
import envi.archs.i386 as e_i386

import vtrace
from vtrace.breakpoints import Breakpoint


class PtracePatch ( Breakpoint ):
def notify( self, event, trace ):
rctx = trace.getRegisterContext()
#self.resolveAddress( trace )
curaddr = rctx.getProgramCounter()
jumpaddr = curaddr + 5 #bytes: e894fcffff
#trace.setRegister( e_i386.REG_EIP, jumpaddr )
rctx.setProgramCounter(jumpaddr)

class BranchPatch ( Breakpoint ):
def setRedirectAddr(self, desiredAddr):
self.desiredAddr = desiredAddr

def notify( self, event, trace ):
rctx = trace.getRegisterContext()
rctx.setProgramCounter( self.desiredAddr )

#1 part
#0x080485b7
#2 part
#0x080485ec
#3 part
#0x08048622

class PrintAL ( Breakpoint ):
def notify( self, event, trace ):
rctx = trace.getRegisterContext()

al  = rctx.getRegister(e_i386.REG_AL)
print '%.2x ' % al,


if __name__ == '__main__':
trace = vtrace.getTrace()
trace.execute('%s' % sys.argv[1])
trace.requireAttached()

#maps = trace.getMemoryMaps()
#for m in maps:
#    print m

_ppach1 = PtracePatch(0x80487c7)
trace.addBreakpoint( _ppach1 )

_bb1 = BranchPatch(0x080485ca)
_bb1.setRedirectAddr(0x0804863c)
trace.addBreakpoint( _bb1 )

_bb2 = BranchPatch(0x080485ff)
_bb2.setRedirectAddr(0x0804866c)
trace.addBreakpoint( _bb2 )

_bb3 = BranchPatch(0x08048635)
_bb3.setRedirectAddr(0x080486a0)
trace.addBreakpoint( _bb3 )


_pal1 = PrintAL(0x080485b7)
trace.addBreakpoint( _pal1 )

_pal2 = PrintAL(0x080485ec)
trace.addBreakpoint( _pal2 )

_pal3 = PrintAL(0x08048622)
trace.addBreakpoint( _pal3 )

trace.setMode("RunForever", True)
trace.run()