To search

w3pwnz

w3pwnz, therefore we are

Tag - crackme

Entries feed Comments feed

Saturday, October 26 2013 03:30

Hack.lu CTF 2013 - FluxArchive Part 1 & 2

You can download this challenge here.

Our target is a x64 ELF binary that archives files somehow, with some kind of encryption. Here is our target's usage:

Usage: ./archiv <command> <archiv> <password> <file>
commands:
-l <archiv> <password> - lists all files in the archiv.
-a <archiv> <password> <file> - adds a file to the archiv (when archiv does not exist create a new archiv).
-x <archiv> <password> <filename> - extracts the given filename from the archiv.
-d <archiv> <password> <filename> - delete the given filename from the archiv.

We don't know how files are encrypted and what is the password for the provided archive. In the first part we will only find what the password is, then we will go deeper into what this binary does in part 2.

Part1 - RE400: Bruteforce with LD_PRELOAD

These funny humans try to exclude us from the delicious beer of the Oktoberfest! They made up a passcode for everyone who wants to enter the Festzelt. Sadly, our human informant friend could not learn the passcode for us. But he heard a conversation between two drunken humans, that they were using the same passcode for this intercepted archive file. They claimed that the format is is absolutely secure and solves any kind of security issue. It's written by this funny hacker group named FluxFingers. Real jerks if you ask me. Anyway, it seems that the capability of drunken humans to remember things is limited. So they just used a 6 character passcode with only numbers and upper-case letters. So crack this passcode and get our ticket to their delicious german beer!

Here is the challenge: https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz

Let's see what's going on with IDA, we quickly see this code:

rev400.png

The checkHashOfPassword function is only storing the SHA-1 in the static variable hash_of_password, and we can see from the code above that encryptDecryptData and verifyArchiv both only have one argument: the archive FILE pointer.

Ok, that's enough for me. I get lazy (more than usual :o), and decide to bruteforce from within the archiv process itself. That way we don't have to reverse the remaining functions (though, that was not complicated...), and the bruteforce will be efficient enough! To do that, I only load a shared library with LD_PRELOAD, "hook" the strcmp function and launch my bruteforce there.

#include <stdio.h>
 
int strcmp(const char *s1, const char *s2)
{
	const char *charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
	char password[7] = {0};
	size_t i0, i1, i2, i3, i4, i5 = 0;
 
	void (*checkHashOfPassword)(char *) = (void*) 0x0000000000402A01;
	int (*encryptDecryptData)(FILE *) = (void*) 0x0000000000401B9A;
	int (*verifyArchiv)(FILE *) = (void*) 0x0000000000402A4E;
 
	FILE *f =  fopen("FluxArchiv.arc", "r");
 
	for (i0 = 0, password[0] = charset[i0]; password[0]; password[0] = charset[++i0]) {
		printf("passwd[0] = %c\n", password[0]);
		for (i1 = 0, password[1] = charset[0]; password[1]; password[1] = charset[++i1]) {
			printf("  passwd[1] = %c\n", password[1]);
			for (i2 = 0, password[2] = charset[0]; password[2]; password[2] = charset[++i2])
				for (i3 = 0, password[3] = charset[0]; password[3]; password[3] = charset[++i3])
					for (i4 = 0, password[4] = charset[0]; password[4]; password[4] = charset[++i4])
						for (i5 = 0, password[5] = charset[0]; password[5]; password[5] = charset[++i5]) {
 
							checkHashOfPassword(password);
							//encryptDecryptData(f); // Useless actually, only used to check the MAGIC_VALUE
 
							if (verifyArchiv(f)) {
								printf("Password found: %s\n", password);
								return 0;
							}
						}
		}
	}
 
	fclose(f);
 
	return 0;
}

Now let's compile, execute and wait for the password. It takes less than 10 minutes.

awe@awe-laptop ~/hacklu/reverse/FluxArchiv1 % gcc -O3 -shared -fPIC -o hooker.so hooker.c -ldl
awe@awe-laptop ~/hacklu/reverse/FluxArchiv1 % time LD_PRELOAD=./hooker.so ./archiv -l FluxArchiv.arc BADGER
################################################################################
FluxArchiv - solved security since 2007!
Written by sqall - leading expert in social-kernel-web-reverse-engineering.
################################################################################
 
passwd[0] = A
  passwd[1] = A
  passwd[1] = B
  passwd[1] = C
* snip *
  passwd[1] = U
  passwd[1] = V
  passwd[1] = W
Password found: PWF41L
Given password is not correct.
zsh: exit 1     LD_PRELOAD=./hooker.so ./archiv -l FluxArchiv.arc BADGER
LD_PRELOAD=./hooker.so ./archiv -l FluxArchiv.arc BADGER  537.34s user 49.71s system 99% cpu 9:47.70 total

So now we can extract the archive files, etc. However, PWF41L was actually the flag.

Part2 - RE500: Extraction of deleted entries

These sneaky humans! They do not just use one passcode, but two to enter the Festzelt. We heard that the passcode is hidden inside the archive file. It seems that the FluxFingers overrated their programming skill and had a major logical flaw in the archive file structure. Some of the drunken Oktoberfest humans found it and abused this flaw in order to transfer hidden messages. Find this passcode so we can finally drink their beer!

(only solvable when FluxArchiv (Part 1) was solved)

Here is the challenge: https://ctf.fluxfingers.net/static/downloads/fluxarchiv/hacklu2013_archiv_challenge1.tar.gz

From the description, we quickly guess that there are issues with the archive deletion mechanism. First let's analyse the deleteArchive function. And well hmm, it doesn't behave like intended. That's because the author messed up the functions name. That can be quickly fixed by following the CFG backwards from the printed messages. We then have these functions / addresses:

Function name Segment Start
doRC4 .text 0000000000400E0C
createHashOfPassword .text 0000000000400E96
createFileEntry .text 0000000000400F05
real_addArchive .text 0000000000401043
findNextFreeChunk .text 0000000000401406
encryptDecryptData .text 0000000000401B9A
writeFileToArchiv .text 0000000000401CA7
real_searchArchive .text 0000000000401FED
real_deleteArchive .text 000000000040227D
real_extractArchive .text 00000000004025C8
checkHashOfPassword .text 0000000000402A01
verifyArchiv .text 0000000000402A4E
main .text 0000000000402B65

Let's analyse the real_deleteArchive function. It takes 2 parameters: the archive stream pointer and the offset for the entry we want to delete (which is returned from a previous call to real_searchArchive). Here is what the function does, from its CFG:

rev500_CFG.png

# PURPLE: go to the initial offset, read next_chunk (which will be used to calculate the offset for the next chunk)
f.seek(init_offset)
next_chunk = unpack("<Q", doRC4(f.read(8)))
 
# YELLOW: read max_chunks (total number of chunks)
max_chunks = unpack("<Q", doRC4(f.read(8)))
 
# DARK GREEN: overwrite next_chunk in the file with junk
next_offset = next_chunk << 4
next_offset = next_offset + (next_offset << 6) + 0x20
fseek(next_offset)
f.write(doRC4("\x00" * 8))
f.write(doRC4("\x00" * 8))
 
# RED: overwrite the MD5sum with junk
f.write(doRC4("\x00" * 16))
 
# BLUE: overwrite the file name with junk
f.write(doRC4("\x00" * 0x60))
 
# LIGHT GREEN: 
for chunk_index in range(max_chunks):
    # GREY: go to the next chunk offset, read next_chunk
    f.seek(next_offset)
    next_chunk = unpack("<Q", doRC4(f.read(8)))
 
    # ORANGE: overwrite the next_chunk in the file with junk
    f.seek(next_offset) ; f.write(doRC4("\x00" * 8))

So, they delete all metadatas associated to the file, but not the file content itself. We should be able to recover a file then! We miss some informations though:

  1. What is the size of each chunk?
  2. How many chunks do we have to read? That's overwritten, so we don't know.
  3. How are the next_chunk allocated? Is that sequential, random, ...? That's overwritten too.

We can't know the file name and we won't be able to verify the MD5 sum, but we don't care of these details. From the real_addArchive function we can get all we needed to know. It does the opposite, and shows us that for each chunk of the original file, 0x408 bytes are written, then the next_chunk value is simply incremented.

We can't know what the size of the file is, so we will assume max_chunks = 0xFF. Because can't know the initial value of next_chunk either, we will have to bruteforce, from 0 to 0xFF. We will create one file per initial next_chunk value. Because we choose a max_chunks which is overlong, we will have some garbage at the end of each file, but most file formats don't really care about that anyway.

#!/usr/bin/env python2
 
import Crypto.Cipher.ARC4 as RC4
import hashlib
 
def decryptRC4(s):
    return RC4.ARC4Cipher(hashlib.sha1("PWF41L").digest()).decrypt(s)
 
f = open("FluxArchiv2.arc", "rb")
 
for bf in xrange(0xFF):
    print '[*] Trying with offset %02x' % bf
    o = open("output/out_%02x" % bf, "w+")
 
    try:
        next_chunk = bf
        max_chunks = 0xFF
 
        for chunk_index in xrange(max_chunks):
            next_offset = next_chunk << 4
            next_offset = next_offset + (next_offset << 6) + 0x20
 
            f.seek(next_offset)
            f.read(8) # Skip overwritten next_chunk
 
            content = decryptRC4(f.read(0x408))
            o.write(content)
 
            next_chunk += 1
    except:
        pass
    finally:
        o.close()
 
f.close()

Now we go to output/, do a file *, find the documents we extracted previously from the part 1, but no new image file with the flag as I expected :( So, it is probably simply a text file...

head -n1 *|less
# Output: *snip*
# ==> out_9d <==
# <B7>9<AF>       <B0><FB><91>0<D8>fB^Sȑ
# 
# ==> out_9e <==
# Another one got caught today, it's all over the papers.  "Teenager
# 
# ==> out_9f <==
# els threatened by me...
# 
# ==> out_a0 <==
# e electron and the switch, the
# *snip*
less out_9e
# Output:
# Another one got caught today, it's all over the papers.  "Teenager
# Arrested in Computer Crime Scandal", "Hacker Arrested after Bank Tampering"...
# Damn kids.  They're all alike.
# *snip*
# 
# +++The Mentor+++
# 
# Flag: D3letinG-1nd3x_F4iL
# * snip*

The flag is D3letinG-1nd3x_F4iL.

Sunday, March 25 2012 23:33

NDH2k12 Prequals - New email from our contact - strange binary file #2

From: Jessica <jessica@megacortek.com>
To: w3pwnz <w3pwnz@megacortek.com>
Subject: New email from our contact
Attachments : executable2.ndh
Thank you again for your help, our technical staff has a pretty good overview
of the new device designed by Sciteek. Your account will be credited with $500.

You did work hard enough to impress me, your help is still more than welcome,
you will get nice rewards. Our anonymous guy managed to get access to another
bunch of files. Here is one of his emails:

---
Hi there, see attached file for more information. It was found on
http://sci.nuitduhack.com/EgZ8sv12.
---

Maybe you can get further than him by exploiting this website.
We also need to get
as much information as possible about the file itself. If you succeed, you will
be rewarded with $2500 for the ndh file and $1000 for the website. Please use
"Sciteek shortener" and "strange binary file #2" titles.

Regards,
Jessica.


This challenge was another crackme. I called it "VMception", and it was a little harder than the other one =)

First we noticed the string "password is wrong:" (0x8737), and another a little stranger "Unhandled exception occured during execution. Exiting" (0x874b) and search for their references :

0x85f6: movl r0, #0x874b		; <-- unhandled exception
0x85fb: call 0xfb7a
0x85ff: end
------
0x8672: movl r0, #0x8737		; <-- password is wrong
0x8677: call 0xfafe
0x867b: end

The entry point of the crackme is a "jmpl 0x0624", corresponding to the following routine :

0x8627: movl r0, #0x867c		; <-- source = 0x867c
0x862c: movl r1, #0x86e8
0x8631: sub r1, r0			; <-- 0x86e8 - 0x867c = 0x6C = 108
0x8635: call 0xfc64			; --> 0x829d (memcpy_to_0x000a)
0x8639: movl r0, #0x0000	
0x863e: movl r1, #0x0035
0x8643: movl r2, #0x0000
0x8648: movl r3, #0x0000
0x864d: call 0xfc67
0x8651: call 0xffab			; --> 0x8600 (ask password)
0x8655: call 0xfed8			; --> 0x8531 (VMception)
0x8659: movl r0, #0x0000		; <-- 0x0000 = buffer password
0x865e: call 0xfca9	
0x8662: test r0, r0
0x8665: jz 0x000a			; --> goto password_is_wrong if r0 = 0
0x8668: movl r0, #0x872a
0x866d: call 0xfb90			; --> pwned \o/ 
0x8671: end

The first action is to copy 0x6c bytes of data from 0x867c to 0x000a :

niklos@box:~/ndh/jessica4# vmndh -file executable2.ndh -debug
[Console]#> x/x 0x867c:0x6c
0x867c: 0a 06 06 00 00 0b 02 07 4d 06 00 07 02 07 78 07
0x868c: 00 07 09 02 02 07 61 06 01 07 02 07 02 07 01 07
0x869c: 09 02 02 07 72 06 02 07 02 07 43 07 02 07 09 02
0x86ac: 02 07 31 06 03 07 02 07 45 07 03 07 09 02 02 07
0x86bc: 30 06 04 07 02 07 03 07 04 07 09 02 02 07 4c 06
0x86cc: 05 07 02 07 7f 07 05 07 09 02 02 07 64 06 06 07
0x86dc: 02 07 0f 07 06 07 09 02 02 00 01 0b

So far, we've got no idea of what the hell it could be, except that's probably vicious... We need to go deeper :)
Then, a 8bytes password is read, and stored in 0x0000 (which is quite surprising when you forget that you're in a very simple virtual machine). Next is a quite big routine, but quite easily understandable :

0x8531: push r2
0x8534: call 0xfdf9			; --> 0x8331 (code = get_next_ element)
0x8538: cmpb r0, #11		; --> code == 0xb
0x853c: jnz 0x0003
0x853f: jmpl 0x008f 			; --> goto 0x85d1 (BREAK)
0x8542: cmpb r0, #01		; --> code == 0x01		
0x8546: jnz 0x0007
0x8549: call 0xfeec
0x854d: jmpl 0xffe4
0x8550: cmpb r0, #02		; --> code == 0x2
0x8554: jnz 0x0007
0x8557: call 0xff0e
0x855b: jmpl 0xffd6			; --> loop
 
[...]
 
0x85c0: cmpb r0, #0a			; --> code == 0xa
0x85c4: jnz 0x0007
0x85c7: call 0xff5d
0x85cb: jmpl 0xff66			; --> loop
0x85ce: jmpl 0x0025			; --> 0x85f6 (unhandled_exception)
 
.label BREAK:
[...]
0x85f5: ret

First, this routine get an opcode from the buffer we previously copied in 0x000a, by calling 0x8331. Then, there's a switch/case test on this code, with a registered action for each from 0x00 to 0x0a, and 0x0b is the code signifying the end of the routine.

We now have a better understanding of the crackme : VMception : a VM inside a VM, and the buffer in 0x000a contains our program (we already see that most bytes are in [0x0 - 0xa] and it ends by 0xb.

Now comes the fun part when you trace the program opcode by opcode to understand the corresponding subroutines...
Here is what you can say then :

  • The password is actually 7bytes long, since the 8th byte is overitten (by a value used to check a byte of the password btw)
  • The password is checked one char at the time, and a single check use several opcodes
  • Opcodes used to do a single check are : 0x2 (routine 0x8469) 0x6 (routine 0x83a4), 0x7 (routine 0x8495), and 0x9 (routine 0x84fe)
  • The program-counter is stored at 0x9 (just before the program itself)

Let's check the subroutines of opcodes 0x02, 0x6, 0x7 and 0x9, starting with 0x2 :

[...]
0x8472: call 0xfebb		; --> 0x8331  (get_next_element = always 7)
0x8476: mov r1, r0	
0x847a:  call 0xfeb3		; --> 0x8331 (get_next_element = X, a value outside [0,a], like 0x4d, 0x78, ...)
0x847e: mov r2, r0
0x8482: mov r0, r1
0x8486: mov r1, r2
0x848a: call 0xfe90		; --> 0x831e (write_r0_at_r1 : writes the )
[...]
0x8494: ret

So this routine retrieves a value that is not an opcode, and stores it at 0x7.
Now 0x6 :

[...]
0x83b0: call 0xff7d		; --> 0x8331 (get_next_element = index i in the password of the char to be tested)
0x83b4: mov r3, r0
0x83b8: call 0xff75		; --> 0x8331 (get_next_ element = always 7)
0x83bc: call 0xff4b		; --> 0x830b (return *R0 == get [0x07])
0x83c0: mov r2, r0
0x83c4: mov r0, r3
0x83c8: call 0xff3f		; --> 0x830b (return *R0 = get password[i], the value of the char to be tested)
0x83cc: xor r0, r2		; --> XOR
0x83d0: mov r1, r0		
0x83d4: mov r0, r3		
0x83d8: call 0xff42		; --> 0x831e (write_r1_at_r0 = write the xored value at password[i])
[...]
0x83e4: ret

This one takes the previous value at 0x7 and XOR the current byte of the password with it.
Now 0x7 :

[...]
0x849e: call 0xfe8f		; --> 0x8331 (get_next_element = index i in the password of the char to be tested)
0x84a2: call 0xfe65		; --> 0x830b (return *R0 = get password[i], the value of the char to be tested)
0x84a6: mov r1, r0		
0x84aa: call 0xfe83		; --> 0x8331 (get_next_ element = always 7)
0x84ae: call 0xfe59		; --> 0x830b (return *R0 == get [0x07])
0x84b2: mov r2, r0	
0x84b6: cmp r1, r2		; --> CMP password[i], [0x7]
0x84ba: movl r1, #0x00	; --> R1 = 0
0x84bf: jnz 0x0002
0x84c2: inc r1			; --> R1 = 1
0x84c4: movl r0, #0x08	; --> R0 = 8
0x84c9: call 0xfe51		; --> 0x831e (write_r0_at_r1 = writes the result of CMP at 0x8)
[...]
0x84d3: ret

This routine compares the current byte of the password with some value stored in 0x7.
And finally 0x9 :

[...]
0x8504: call 0xfe29		; --> 0x8331 (get_next_element = always 0x2)
0x8508: mov r1, r0		
0x850c: movl r0, #0x0008	; --> R0 = 8
0x8511: call 0xfdf6		; --> 0x830b (return *R0 = get [0x08])
0x8515: test r0, r0
0x8518: jnz 0x0008
0x851b: mov r0, r1
0x851f: call 0xfe30		; --> IF [0x08] == 0 THEN : 0x8353 (change_program_counter by 0x2)
[...]
0x8527: ret

This one changes the program counter by 0x2 if the previous comparison fails.

We now know how one byte of the password is checked :

  • 0x02 : store next element at 0x7
  • 0x06 : XOR password[i] and [0x7]
  • 0x02 : store next element at 0x7
  • 0x07 : compare xored value and [0x7] and write the result at 0x8
  • 0x09 : f*ck up program counter if [0x8] is 0

So with 3 breakpoints in routines of opcodes 0x6, 0x7 and 0x9, we can get the xor keys and the xored values :

niklos@box:~/ndh/jessica4# vmndh -file executable2.ndh -debug
[Console]#> bp 0x83c0
Breakpoint set in 0x83c0
[Console]#> bp 0x84b6
Breakpoint set in 0x84b6
[Console]#> bp 0x8515
Breakpoint set in 0x8515
[Console]#> run
[...]
[SYSCALL output]: Please enter Sciteek admin password:
AAAAAAA
[...]
[BreakPoint 1 - 0x83c0]
0x83c0 > mov r2, r0
[Console]#> info reg
[r0]: 004d		; --> first xor key is 0x4d
[...]
[Console]#> run
[BreakPoint 2 - 0x84b6]
0x84b6 > cmp r1, r2
[Console]#> info reg
[r0]: 0078		; --> first xored value is 0x78
[Console]#> run
[...]
[BreakPoint 3 - 0x8515]
0x8515 > test r0, r0
[Console]#> set r1=1	; --> set the result of the comparison to 1
r1 = 0x0001
[Console]#> run

[...]

And so on for the 7 bytes of the password :)

Now we xor all this and get the password :

>>> a = [0x4d, 0x61, 0x72, 0x31, 0x30, 0x4c, 0x64]
>>> b = [0x78, 0x02, 0x43, 0x45, 0x03, 0x7f, 0x0f]
>>> print ''.join([ chr(a[i] ^ b[i]) for i in range(len(a)) ])
5c1t33k

Bazinga \o/

NDH2k12 Prequals - unknown binary, need your help - Strange binary file

From: Jessica <jessica@megacortek.com>
To: w3pwnz <w3pwnz@megacortek.com>
Subject: unknown binary, need your help
Attachments : executable1.ndh
Hello again,

Thank you very much for your help. It is amazing that our technical staff and
experts did not manage to recover any of it: the password sounds pretty weak.
I will notify our head of technical staff.

Anyway, I forwarded them the file for further investigation. Meanwhile, we got
fresh news from our mystery guy. He came along with an intersting binary file.
It just looks like an executable, but it is not ELF nor anything our experts
would happen to know or recognize. Some of them we quite impressed by your skills
and do think you may be able to succeed here. I attached the file, if you discover
anything, please send me an email entitled "Strange binary file".

This will be rewarded, as usual. By the way, your account has just been credited
with $100.

Regards,
Jessica.


We first noticed the two strings "Good password" and "Bad password" at the end of the file. An easy way to attack a crackme is to search for string references in the code. The disassembly from vmndh tells us that the "Bad password" string is loaded in 0x8480, and referenced from

0x82d4:
0x82d4: movl r0, #0x8480
0x82d9: call 0xffdd
0x82dd: ret


This is the "bad boy" case, and whatever "call 0xffdd" is, it must be the impression routine. There were two methods to get the actual adresses of the calls: check them in the debugger, or patch the disassembled output to translate relative calls into absolute ones. This is what patch.py does.

With it, we can see that the address 0x82d4 is called 9 times between 0x82e8 and 0x83e1, just after "jz" instructions.

A first test is made, that checks the length of the input:

0x82e8: mov r7, r0
0x82ec: movl r6, #0x840d
0x82f1: call 0x8003
0x82f5: cmpb r0, #09        ; 9 bytes (8 without "\x0a")
0x82f9: jz 0x0005
0x82fc: call 0x82d4         ; -> bad boy
0x8300: end


After that, each time the bytes pointed by r7 and r6 are xored together and compared to a hardcoded value. Then r7 and r6 are incremented:

0x8301: mov r0, [r7]
0x8305: mov r1, [r6]
0x8309: xor r0, r1
0x830d: cmpb r0, #78
0x8311: jz 0x0005
0x8314: call 0x82d4
0x8318: end
0x8319: inc r7
0x831b: inc r6


Let's load the program in the debugger and put a breakpoint at 0x8301, to see what these registers point to:

[BreakPoint 1 - 0x8301]
0x8301 > mov r0, [r7]
[Console]#> info reg
[r0]: 0061	[r4]: 0000
[r1]: 0000	[r5]: 0000
[r2]: 7fda	[r6]: 840d
[r3]: 001f	[r7]: 7fda
 
[bp]: 7ffa	[zf]: 0001
[sp]: 7fd8	[af]: 0000
[pc]: 8305	[bf]: 0000
[Console]#> x/x 7fda:8
0x7fda: 61 62 63 64 65 66 67 68     <- our input
[Console]#> x/x 840d:8
0x840d: 02 05 03 07 08 06 01 09     <- the key


According to the cmpb instructions, the result must be 78 44 73 6b 61 3e 6e 5e. The correct input is therefore

>>> format(0x7844736b613e6e5e ^ 0x0205030708060109,"x").decode("hex")
'zApli8oW'

Let’s use it:

~/ndh2012$ nc sciteek.nuitduhack.com 4001
Sciteek protected storage #1
Enter your password: zApli8oW
<PSP version="1.99">
<MOTD>
<![CDATA[
Welcome on SciPad Protected Storage.

The most secure storage designed by Sciteek. This storage protocol
allows our users to share files in the cloud, in a dual way.

This daemon has been optimized for SciPad v1, running SciOS 16bits
with our brand new processor.
]]>
</MOTD>
<FLAG>
ea1670464251ea3b65afd624d9b17cd7
</FLAG>
<ERROR>
An unexpected error occured: PSP-UNK-ERR-001> application closed.
</ERROR>
</PSP>

Thursday, October 20 2011 17:34

Unpack FSG2 - Reconstruction manuelle de l'IAT

La cible :


Je vous propose aujourd’hui mon premier tutoriel concernant l’unpack d’un programme packé avec FSG2. Et j’en profiterai pour vous montrer comment on peut reconstruire l’IAT manuellement.

Tout d’abord notre cible =>UnPackMe_FSG2.0.rar, on confirme qu'il est bien packé en l'ouvrant dans PEid qui nous détecte "FSG 2.0 -> bart/xt"

PEiD_FSG

Dump du programme :


La première étape est de retrouver l'OEP (Original Entry Point) du programme et de faire un dump du programme. Ouvrons donc le programme dans Olly, il nous averti que l'OEP est en dehors du code et que le programme est sûrement packé.

Olly_Detect_Packer
Pour trouver l'OEP, faire F9 dans Olly jusqu'à tomber sur la section de code 0040XXXX où l'on peux tomber sur le code suivant correspondant au début de l'unpack du code :

00400154 > 8725 D03E4100    XCHG DWORD PTR DS:[413ED0],ESP        // Déplace ESP à l'adresse 413ED0
0040015A   61               POPAD                                                               // Récupération des registres 
0040015B   94               XCHG EAX,ESP                                                    // Déplace ESP dans EAX
0040015C   55               PUSH EBP                                                           // Push EBP = 401000

On peut voir en regardant la mémoire à l'adresse 401000 (Ctrl+G dans la zone en bas à droite correspondant à la mémoire) et en débugguant pas à pas que le programme est en train de s'unpacker en mémoire à partir de cette adresse.

En parcourant la suite du code, on tombe sur une succession de sauts conditionnels suivi d'un jmp vers l'OEP, typique au FSG :

004001CD  ^78 F3            JS SHORT UnPackMe.004001C2
004001CF   75 03            JNZ SHORT UnPackMe.004001D4
004001D1   FF63 0C          JMP DWORD PTR DS:[EBX+C]

Il suffit de poser un BP sur le dernier JMP pour voir vers quelle adresse le programme va sauter et nous avons alors notre OEP => 401000. Maintenant que nous avons notre OEP en main, il va falloir faire un dump du programme. Pour cela nous allons utiliser un plugin Olly nommé Ollydump. Faire F8 pour basculer sur notre OEP et faire un click droit Analysis / Remove Analysis from module pour voir le code du programme.
Olly_Remove_Analysis
Pour faire un dump du programme, il suffit maintenant de faire un click droit - Dump Debugged Process Une nouvelle fenêtre va s'ouvrir où l'on va pouvoir spécifier notre nouvel EP et lancer notre dump :
OllyDump

Attention : Pensez à décocher Rebuild Import qui permet de reconstruire les imports depuis Ollydump car ce n'est pas sa spécialité et le but ici est de reconstruire l'IAT manuellement.

Reconstruction de l'IAT :


Avant de commencer, pour les plus novices vous vous demandez sûrement qu'est ce que l'IAT ( Import Address Table). Il faut savoir que la plupart des programmes font appel à de nombreuses APIs disponibles dans des fichiers dll. On peux prendre par exemple l'API MessageBoxA disponible dans la dll user32.dll permettant d'afficher une boîte de dialogue. Toutes ces APIs sont donc disponibles au travers d'appels aux fonctions contenus dans les dll mais les API selon l'architecture et le système d'exploitation ne sont pas au même emplacement mémoire. Chaque programme comporte donc une table permettant au programme de retrouver l'adresse des fonctions qu'il va utiliser. Cette table va se remplir automatiquement lors de son lancement avec les bonnes adresses. Si vous souhaitez des explications plus détaillées et plus techniques, je vous envoie vers un article de mysterie

Après le dump d'un programme, il faut savoir que l'IAT n'est pas correct. Il faut donc le reconstruire.

La méthode "classique" :


Après le dump du programme fait avec Ollydump, on conserve Ollydbg ouvert et on lance ImportRec. On sélectionne dans le champ du haut le process à attacher, dans notre cas unpackme_fsg2.0.exe.

ImpRec

1) On modifie l'OEP par celui que l'on a trouvé précédemment : 404000, on indique donc dans le champs OEP la valeur RVA : 4000

2) On clique sur IAT AutoSearch, il va alors chercher pour nous les imports de fonctions à effectuer, il va nous afficher à partir de quelle adresse (RVA) il récupère les fonctions et quelle taille il doit récupérer. On peux alors vérifier sous Olly qu'il n'en a pas oublié. Pour cela sélectionner la zone en bas à gauche d'Olly qui liste les octets en mémoire et faire un CTRL+G pour aller à l'adresse indiquée : 004011E8.

olly_mem

On voit donc bien les différents appels de fonctions que l'on peux repérer par leur adresses commençant par 75XXXXXX, mais en remontant un peu dans la zone mémoire, on se rend compte que d'autres adresses vers des fonctions sont stockées à partir de l'adresse 00401198. On va donc modifier dans ImportRec le champ RVA par l'adresse que l'on a trouvé RVA : 11998 et on va définir une taille suffisamment grande pour ne rien oublier Size : 100

3) On clique sur Get Imports, il va alors nous afficher les dlls qu'il a trouvé

ImpRecImportFonction

On voit que certains thunks sont invalides. On va donc cliquer sur Show Invalid pour voir ce qui cloche. On voit alors deux imports incorrects. Il suffit de cliquer sur chacun d'eux et faire Cut Thunks.

Nous n'avons plus qu'à ajouter ces imports au dump que l'on a effectué précédemment, pour cela :

4) On clique sur FixDump et on sélectionne notre dump que l'on a sauvegardé précédemment. On peux vérifier qu'il fonctionne bien en l’exécutant et on peux également l'ouvrir avec PEiD qui ne détecte plus de packer.

La méthode "manuelle" :


Et « just for fun » comme on dit, voici comment faire manuellement ce que fait ImportRec.

La première étape est de noter l'ensemble des API utilisées par le programme. Pour cela on retourne dans Olly juste après avoir fait le dump et on fait un click droit Search for / All intermodular call et il nous affiche alors l'ensemble des API utilisées.
olly_intermodular_call

On va maintenant ouvrir le dump que l'on a effectué tout à l'heure et aller rajouter manuellement l'ensemble des fonctions utilisées dans notre IAT. Pour cela on utilise LordPe, on clique sur PE Editor on sélectionne notre dump. Une nouvelle fenêtre va s'ouvrir avec l'ensemble des informations du header de notre EXE. On va cliquer sur Directories puis sur les 3 petits points à coté du champ Import Table

LordPe_IAT

LordPe va alors nous afficher l'IAT. Dans notre cas il nous affichera un peu n'importe quoi vu que l'IAT est corrompu. On va donc rajouter nous même l'ensemble des imports dont nous avons besoin, pour cela faire un click dans la partie haute de la fenêtre sur l'import en erreur et sélectionner Add Import

LordPe_AddImport

Une nouvelle fenêtre va alors s'afficher où l'on va pouvoir ajouter l'ensemble de nos appels de fonctions. Pour cela rien de plus facile, il suffit de renseigner le nom de la dll et les API dont le programme a besoin :

LordPe_adddll

Il faudra effectuer cette opération pour chacune des dlls que le programme utilise. Lorsque vous avez ajouté tout ce qu'il vous faut, vous pouvez supprimer l'import en error. Pour cela faire un click droit sur l'import et sélectionner KillImageImportDescriptor. Vous pouvez alors fermer cette fenêtre et cliquer sur save pour sauvegarder tout le travail que l'on vient de faire.

Nous avons donc maintenant un dump du programme non packé avec une table d'import correct, une dernière étape reste à faire : modifier les différents appels à ces fonctions dans le programme. On va donc ouvrir notre dump dans OllyDbg et faire un click droit Search For / All Intermodulars Calls où l'on va retrouver la liste des fonctions que l'on avait noté à la première étape et pour chacun des ces calls nous allons modifier l'adresse par celle qui est spécifiée dans notre table d'import.

olly_fixcall

Par exemple pour la fonction GetModuleHandleA il faudra modifier le CALL DWORD PTR DS:4011F4 par CALL DWORD PTR DS:4140C1. Pour cela sous Olly il suffit de double cliquer sur la fonction que l'on veux modifier, OllyDBG va nous placer sur l'endroit du code où l'appel est passé et il faudra de nouveau double-cliquer dessus pour modifier le code.

Après avoir effectué cette opération pour l'ensemble des CALL, il faudra sauvegarder les modifications. Pour cela sélectionner l'ensemble du code en faisant click droit Copy / Select All puis click droit Copy to executable / Selection et enfin dans la fenêtre qui s'affiche faire un click droit puis Save File.

Vous aurez alors un programme complètement unpacké avec sa table d'import reconstruite manuellement, enfin presque. Vous pouvez si vous le souhaitez aller encore plus loin et reconstruire l'IAT en modifiant manuellement le PE header du fichier sans passer par LordPE...

Pour terminer, comme vous avez pu le constater la méthode manuelle est tout de même très fastidieuse, surtout si votre programme fait appel à de nombreuses API mais je pense qu'il est toujours intéressant de faire à la main au moins une fois cette opération pour bien comprendre son fonctionnement.

Thursday, April 7 2011 20:50

NDH2k11 Prequals - Rce100

A priori, c'est un binaire win32 normal, apparemment packé mais PeiD ne trouve pas de signature.

On commence par l'ouvrir sous OllyDbg v2, la version 1 ayant du mal à l'analyser. On tombe directement sur l'instruction PUSHAD, qui fait penser à un packer UPX-like. Une recherche de la commande POPAD confirme cette intuition, et il suffit de mettre un breakpoint à l'adresse 00466F1B puis F9 pour unpacker l’exécutable.

rce100_pic1

Une analyse de la table des appels inter-modulaires ne nous révèle pas d'appels intéressants concernant une éventuelle comparaison de serial, on va donc breaker sur les appels à SendMessageA (sauf celui avec WM_MOUSELEAVE) pour intercepter les évènements de clicks et autres sur la fenêtre.

rce100_pic2

On run avec F9, et on entre un serial bidon (abcdef). On est interrompu après avoir entré le premier caractère car Olly a breaké sur un appel à SendMessageA. Après être entré dans le CALL suivant l'appel et quelques F8 plus tard on tombe sur une boucle qui semble compter le nombre de caractères entrés dans la textbox (en 4042F5)

rce100_pic4

Enfait la fonction dans laquelle on se trouve est chargée de dessiner la textbox avec les caractères qu'on a entrés, donc useless pour ce qu'on veut faire. Néanmoins, elle permet de récupérer l'adresse mémoire de l'emplacement de la chaîne, contenue dans EDX à ce moment là (soit 9866E8 ici).

On note cette adresse précieusement, elle servira plus tard :)

Maintenant on enlève le breakpoint en 404657 sinon on va être interrompu après chaque nouveau caractère entré dans la textbox, et on tape notre serial bidon : abcdefg, puis on clique sur Login. Olly break une fois de plus sur un appel à SendMessageA. Pour trouver la routine de vérification, on place un breakpoint mémoire à l'adresse du serial 9866E8, puis F9 pour relancer le programme. Olly break en 403F76.

rce100_pic5

Quelques F8 plus loin, on tombe sur cette fonction :

rce100_pic6

On remarques quelques lignes plus haut le CALL 0041EC50, puis la comparaison entre EAX et un une constante hexadécimale. Regardons cette fonction de plus près :

rce100_pic7

On voit ici que cette fonction génère un hash avec les caractères du serial, il reste donc plus qu'à recoder la fonction en C et lancer un bruteforce jusqu'à obtenir le bon checksum. Pour viser large on prend comme charset la plage ascii 48;122.

#include <stdio.h>
#include <stdlib.h>
 
#define MIN 48
#define MAX 122
 
void bf(char* input,int lenght)
{
	int i;
	int eax;
	int esi = 0xdeadbeef;
	for(i=0;i<lenght;i++)
	{
		eax = input[i];
		esi = esi*0x38271606;
		eax = eax*0x5b86affe;
		esi = eax - esi;
	}
 
	if(esi==0xc4b1801c)
	{
		printf("Password : %s",input);
		exit(0);
	}
}
 
void construct(char* input,int i,int j)
{
	if(i==j)
	{
		bf(input,j);
	}
	else
	{
		int k;
 
		for(k=MIN;k<=MAX;k++)
		{
			input[i]=k;
			construct(input,i+1,j);
		}
	}
}
 
int main()
{
 
	char pass[1024];
 
	int i;
 
	for(i=1;i<20;i++)
	{
		construct(pass,0,i);
		printf("Lenght : %d\n",i);
	}
 
	return 0;
}

Après quelques secondes, on trouve pWn3D.

Tuesday, April 5 2011 00:12

NDH2k11 Prequals - Rce300

L'épreuve qui nous a donné le plus de mal, est sans doute la RCE300. C'était la dernière épreuve qu'il nous restait, et toute la team s'y est attaqué afin de finir ces préquals :p

On nous fournissais un fichier crackme.nds, avec pour seule indication qu'il s'agissait d'une application pour nintendo DS !

L'intéressant ici était de se documenter sur le format, et d'arriver à se faire un environnement de débug + reverse :)

Au final, nous avons utilisé les outils suivants :

Avec ça on est paré, on peut donc maintenant lancer DeSmuME en mode dev avec la commande :

DesMume_dev.exe --arm9gdb=5555 crackme.nds

Pour y attacher ensuite un gdb, avec la commande :

target remote localhost:5555

rce300_gdb_desmusme.png

On utilise ensuite IDA, avec le loader permettant de charger le *.nds :

rce300_ida_nds.png

Au passage il est bon de noter que la DS utilise un processeur ARM, il va donc falloir se documenter un peu sur l'ARM, ici, ici, ici et ici.

En cherchant la chaîne password, on tombe sur une première routine intéressante : sub_20002B8. Dans laquelle on voit clairement l'affichage du prompt et la demande de serial (on devine que sub_2003888 est un printf() et que sub_2003ACC est un scanf()) : rce300_ida_printf_scanf.png

On voit ensuite que si on entre un serial, le chemin emprunté passe par 2 boucles qui ont la même structure :

  • Multiplication de R0 par une constante
  • Appel de sub_2003530 (qui prend en paramètre R0, et le caractère courant du serial entré dans R1, et qui renvoie R0 modifié)
  • Xor de R0 par une constante
  • En fin de boucle : comparaison de R0 à une checksum : granted si les 2 checksums matchent, denied sinon !

gdb_ida_checksums.png

On voit que :

  • La première checksum est 0x33E0D2F1
  • La deuxième checksum est 0xBCFA8D3F

Et enfin, on analyse la fonction sub_3530, qui était assez impréssionnante : rce300_ida_sub3530.png

Plusieurs hints avaient été données sur l'IRC #ndhprequals, notamment qu'il fallait faire un brute-force, et qu'il fallait se limiter au charset alphanumérique et à une longueur de 6 caractères. En refaisant l'alogrithme en C (le plugin hex-rays pour IDA peut grandement aider...) on est alors capable de faire un rapide bruteforce, qui au bout de quelques dizaine de minutes nous a sorti le serial valide "DsLrox" ! \o/

Voilà donc une épreuve vraiment sympathique, dont le plus intéressant pour moi a été de découvrir comment mettre en place un environnement de débug comme celui ci. Et ce fut également un bel exemple de travail en équipe ;)