Monday, May 18, 2015

Defcon 2015 : Coding Challenge

This was an interesting challenge where you connect to a remote machine and you're given a set of x64 registers(no rsp, rbp), some bytes and are expected to send back a reply in the "same format". We can assume that the bytes correspond to some shellcode that we are supposed to emulate and send the results of.
I first tried using pyasmjit but for some reason had trouble getting it up and running. Later on, I ended up writing a solution that involves a python script to interact with the server, pwntools to disassemble the bytes received, nasm to assemble a binary, and a pintool to instrument the binary and print out the register state at the end.

The python script can be found here and the pintool can be found here. The pintool emulates the instructions till the "ret" instruction, prints out the registers then exits.

Monday, February 23, 2015

A simple PIN detection mechanism(and its circumvention)

PIN, the pintool and the application being instrumented share the address space. However, no libraries are shared-- there typically are 3 versions of glibc in the address space to avoid any unwanted interaction[1]. As a result of this ptrace based antidebugging checks are not detected even though PIN relies on ptrace.

A simple PIN detecting mechanism would be to scan /proc/self/maps and look for a page corresponding to the pinbin binary[2]. This can be circumvented by doing something as simple as renaming pinbin to something else and creating a symbolic link to it by the name of pinbin.


[1] http://www.cs.virginia.edu/kim/courses/cs851/papers/luk05pin.pdf
[2] https://gist.github.com/eQu1NoX/529e7dc69b8f4b3bb5e4

Wednesday, September 24, 2014

Monday, September 22, 2014

CSAW 2014 Exploitation 400 - Saturn

[Participated with Segfault]
You have stolen the checking program for the CSAW Challenge-Response-Authentication-Protocol system. Unfortunately you forgot to grab the challenge-response keygen algorithm (libchallengeresponse.so). Can you still manage to bypass the secure system and read the flag?
The binary is an ELF 32-bit binary that accepted socket accepted input from stdin(and was running using xinetd or something similar). Looking at the binary we can see that there is a call to _fillChallengeResponse from the missing shared object file, that is probably setting two global variables(renamed as g1 and g2). 

The program then enters the following loop, where it reads a byte from the user, ands it against 0xF0, compares it against 0xA0, 0xE0 and 0x80 -- and performs actions accordingly.
Case 160:
We can see that our input char is and'd with 0x0F, left shifted twice(multiply by 4), added to the base address of g2 and 4 bytes are read back. Essentially, given an index, 4 bytes can be dereferenced starting at an offset from g2.
Case 128:
This function is interesting as it computes the computes the product of the contents of a global integer array and prints out the flag if the product is 1. Needless to say, each element of the global array is initialised to 0.
Case 224:
The input character is and'd with 0xF, left shifted by 2 and used as an index to deref addresses with g1 as the base. It then reads 4 bytes from the user and checks its contents against the deref address; if they match they set the globalarray[pos] = 1 where pos is calculated as inputchar & 0xF.
Armed with this information, we can try and write a python script that supplies values to trigger case 160, leaks values, triggers case 224, uses the leaked value to set a position in the global array to 1. Finally, we can trigger case 128 to read the key.
One could create a libchallengeresponse.so file with a _fillChallengeResponse function that could set g1 and g2, so that we could try debugging the saturn binary against our exploit.
The exploit can be found here.

Friday, July 11, 2014

Hopper scripting bug : Document.getCurrentDocument().getSegmentAtAddress(addr)

I like using Hopper; however, there do seem to be a few bugs when scripting that require working around. One such bug I encountered recently is the instance where the function `Document.getCurrentDocument().getSegmentAtAddress(addr)` returns None for a perfectly valid address. This behaviour occurs when you have a loop and you call the function repeatedly from within it(yeah I probably shouldn't do that but well...).

The workaround was to do something as follows :-

curseg = Document.getCurrentDocument().getSegmentAtAddress(addr)
while curseg is None:
time.sleep(2)
curseg = Document.getCurrentDocument().getSegmentAtAddress(addr)


Ugly but works!

Monday, July 7, 2014

pwnium 2014 rev300 (using Hopper scripting)

[Played with Team Segfault]
I've noted down a failed approach and a solution in the form of a Hopper script that I came up with after the CTF.
teammate who had solved the challenge in time has written a writeup on the same here(using PIN). Other writeups for the challenge can be found here(static analysis), and here(using gdb tracing). I strongly urge you to read those interesting writeups before proceeding.

-===========================]
Failed attempt description
During the CTF, I was trying to solve the problem in a bottom up fashion rather than top down. I realised that there were 6 possible end points that moved "1" into rax before returning from the function, something that was required to reach the winning state of the crackme. An instance is shown below. It is clear that one way of reaching that instruction would be if "al" evaluated to "0x6f".
As there is an unconditional jmp at 403574, the only way to get to 403580(the cmp instruction) would be via 403579(mov rax, qword [ss:rbp-0x0+var_m8]). So lets examine XREFS at this point,
Visiting the XREF we have,

Hence, to get to this point, eax needs to have a value of 0x90305228. Scrolling up we can see that eax gets the value here :-
Now we could examine the XREFS for the dword at 0x606b18 and see where its set to 0x90305228.

So far so good. We can continue tracing XREFS in this manner, however, this strategy fails to work for two reasons :-

  1. There are self loops.
  2. At certain points you see that the state value is set at multiple locations as shown in the image below.

-===========================]
Working approach

A hopper script that automates a top down approach can be found here. It uses the initial state value and follows checks as the occur, keeping track of state values. The code is simple, for API reference I used HopperScripts.

Tuesday, May 27, 2014

Playing around with getcwd, readpath and readlink

Recently, I had to write some code that dealt with the functions getcwd, readpath and readlink and I noticed a few peculiarities in them. I performed a few small tests and below are my observations(this post is mostly so that I can document for later reference if needed).

A few notes based on what the man pages say.

-=========================]
char *getcwd(char *buf, size_t size);
  1. Return a null terminated string containing an absolute pathname as the cwd
  2. Size should be the length of buf
  3. If length of the cwd > size of buf, NULL is returned, errno is set to ERANGE. An application should check for this error and allocate larger buffers if necessary.
  4. On success, it returns a pointer to buf. 
-=========================]
char *realpath(const char *path, char *resolved_path);
  1. Return a null terminated string, upto a maximum of PATH_MAX bytes in `resolved_path`
  2. if `resolved_path` is NULL, realpath allocates a buffer of upto PATH_MAX bytes and returns a pointer
  3. If function fails to resolve the path, NULL is returned. 
-=========================]
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
  1. Contents of the symbolic link are placed in `buf`, of size `bufsize`.
  2. A null byte is not appended into buf
  3. If the buffer is too small to hold the contents, the results are truncated to bufsize.
-=========================]
A few silly tests

Lets try and create a few 1000 nested directories such that the length of the cwd exceeds 4096. I used this bash script. Interestingly the `pwd` at the end of the script works and prints out a path of length 7022.

We can't cd or ls that directory from home -- we get an error File name too long.

In order to cd into the directory we could use something as :-
for i in `seq 1 1000`; do cd tester; done

Lets try using readlink to resolve /proc/self/cwd using this. Running the code resulted in a "File name too long" error.

Trying to use getcwd to print the current working directory using this, surprisingly works.

Using realpath to resolve the full path of a file in the current directory as shown here, I get an error message Numerical result out of range.