Skip to content

Saruman crashes host program on first bootstrap instruction #1

@mgeeky

Description

@mgeeky

Hello there,

I am unable to run the Saruman correctly due to a really weird bug on my Debian 8:
Linux M 3.16.0-4-amd64 #1 SMP Debian 3.16.36-1+deb8u2 (2016-10-19) x86_64 GNU/Linux. Saruman crashes the host program right in call_fn(BOOTSTRAP_CODE, ...) at the very first instruction of program's entry point.

Host:

~/devel/infector/saruman2 $ ./launcher `pidof host` ../parasite ""

#include <stdio.h>
int main(void) {
	for(;;) {
		printf("I am a boring program\n");
		sleep(3);
	} 
}

Parasite:

#include <stdio.h>
int main(void){
	int i;
	for (i = 0; i < 10; i++)
		printf("I AM AN EVIL FUCKING PARASITE!!!\n");
}

Launching slightly modified Saruman's code:

[+] Target pid: 389
[+] map_elf_binary(ptr, ../parasite)
[+] Parasite entry point will be main(): 0x400506
[+] Found text segment
[+] Found data segment
[+] Found dynamic segment
[+] Found dynamic string table
[+] Found dynamic symbol table
[+] Found G.O.T
[+] PLT count: 72 entries
[DEBUG]-> get_sym_from_libc() addr of __libc_dl_*: 7f112c69cb00
[+] PT_ATTACHED -> 389
[+] calling bootstrap

-- [RIP - 0x00400000] Single step
rax: 0xfffffffffffffdfc (-516)
rbx: 0x7ffce59de1a0 (140724160815520)
rcx: 0xffffffffffffffff (-1)
rdx: 0x04000000 (67108864)
rsi: 0x000005c8 (1480)
rdi: 0x00c00000 (12582912)
rbp: 0xffffffff (4294967295)
rsp: 0x7ffce59de188 (140724160815496)
r8: 0x7ffce59de2a0 (140724160815776)
r9: 0x7ffce59de0e0 (140724160815328)
r10: 0x00000008 (8)
r11: 0x00000246 (582)
r12: 0x7ffce59de220 (140724160815648)
r13: 0x7ffce59de450 (140724160816208)
r14: 0x00000000 (0)
r15: 0x00000000 (0)
First 12 bytes from RIP:
55 48 89 e5 48 81 ec a0 48 81 ec a0

-- [RIP - 0x003ffffe] Single step
rax: 0x000000db (219)
rbx: 0x7ffce59de1a0 (140724160815520)
rcx: 0xffffffffffffffff (-1)
rdx: 0x04000000 (67108864)
rsi: 0x000005c8 (1480)
rdi: 0x00c00000 (12582912)
rbp: 0xffffffff (4294967295)
rsp: 0x7ffce59de188 (140724160815496)
r8: 0x7ffce59de2a0 (140724160815776)
r9: 0x7ffce59de0e0 (140724160815328)
r10: 0x00000008 (8)
r11: 0x00000246 (582)
r12: 0x7ffce59de220 (140724160815648)
r13: 0x7ffce59de450 (140724160816208)
r14: 0x00000000 (0)
r15: 0x00000000 (0)
First 12 bytes from RIP:
ff ff ff ff ff ff ff ff 89 e5 48 81

[!] Target process has been stopped, something went wrong. Signal: 11 (Segmentation fault)
call_fn(BOOTSTRAP_CODE, ...) failed: Success

One can notice that the second registers dump differs from first one in:

  • RAX being changed
  • RIP being shifted two bytes back
  • bytes at RIP being malformed with 0xff - not to mention, that following bytes: 89 e5 48 ... are shifted even further from RIP - four bytes off.

Here are all modifications made to Saruman (launcher.c):

  • added function display_regs for tracing purposes
  • slightly modified call_fn function to instead of PTRACE_CONT got into PTRACE_SINGLESTEP
  • added a do..while loop with more thorough status interpretation code
  • added failover code for run_bootstrap in main() as saruman should fail when bootstrap did not manage to run.
[...]
void display_regs(pid_t pid, struct user_regs_struct *pt_reg ) {

    long word[3] = {0};
    char *buf = (char*)word;

    word[0] = ptrace(PTRACE_PEEKTEXT, pid, pt_reg->rip, NULL);
    word[1] = ptrace(PTRACE_PEEKTEXT, pid, pt_reg->rip+4, NULL);
    word[2] = ptrace(PTRACE_PEEKTEXT, pid, pt_reg->rip+8, NULL);
    
    printf("\n-- [RIP - 0x%08lx] Single step \n"
            "rax: 0x%08lx (%ld)\nrbx: 0x%08lx (%ld)\nrcx: 0x%08lx (%ld)\n"
            "rdx: 0x%08lx (%ld)\nrsi: 0x%08lx (%ld)\nrdi: 0x%08lx (%ld)\n"
            "rbp: 0x%08lx (%ld)\nrsp: 0x%08lx (%ld)\nr8: 0x%08lx (%ld)\n"
            "r9: 0x%08lx (%ld)\nr10: 0x%08lx (%ld)\nr11: 0x%08lx (%ld)\n"
            "r12: 0x%08lx (%ld)\nr13: 0x%08lx (%ld)\nr14: 0x%08lx (%ld)\n"
            "r15: 0x%08lx (%ld)\n"
            "First 12 bytes from RIP:\n"
            "%02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x\n",
            pt_reg->rip,
            pt_reg->rax, pt_reg->rax, pt_reg->rbx, pt_reg->rbx, pt_reg->rcx, pt_reg->rcx,
            pt_reg->rdx, pt_reg->rdx, pt_reg->rsi, pt_reg->rsi, pt_reg->rdi, pt_reg->rdi, 
            pt_reg->rbp, pt_reg->rbp, pt_reg->rsp, pt_reg->rsp, pt_reg->r8, pt_reg->r8,
            pt_reg->r9, pt_reg->r9, pt_reg->r10, pt_reg->r10, pt_reg->r11, pt_reg->r11,
            pt_reg->r12, pt_reg->r12, pt_reg->r13, pt_reg->r13, pt_reg->r14, pt_reg->r14,
            pt_reg->r15, pt_reg->r15, 
            buf[0] & 0xff, buf[1] & 0xff, buf[2] & 0xff, buf[3] & 0xff, buf[4] & 0xff, 
            buf[5] & 0xff, buf[6] & 0xff, buf[7] & 0xff, buf[8] & 0xff, buf[9] & 0xff, buf[10] & 0xff, buf[11] & 0xff);
}

/*
 * call_fn() allows one to inject a function
 * (select by functionPayloads_t) into the remote
 * process, and execute it. The return value for
 * the function is stored in payloads.function[func].retval
 */
#define SLACK_SIZE 32
int call_fn(functionPayloads_t func, handle_t *h, uint64_t ip)
{

[...]

case 6:
            pt_reg->rdi = (uintptr_t)h->payloads.function[func].args[0];
                        pt_reg->rsi = (uintptr_t)h->payloads.function[func].args[1];
                        pt_reg->rdx = (uintptr_t)h->payloads.function[func].args[2];
                        pt_reg->rcx = (uintptr_t)h->payloads.function[func].args[3];
                        pt_reg->r8 =  (uintptr_t)h->payloads.function[func].args[4];
            pt_reg->r9 =  (uintptr_t)h->payloads.function[func].args[5];
            break;
    }

    display_regs(h->tasks.pid, pt_reg);
    
    if (ptrace(PTRACE_SETREGS, h->tasks.pid, NULL, pt_reg) < 0)
        return -1;
    
    //if (ptrace(PTRACE_CONT, h->tasks.pid, NULL, NULL) < 0)
    //    return -1;

    do {
        ptrace(PTRACE_SINGLESTEP, h->tasks.pid, 0, 0);
		if (waitpid(h->tasks.pid, &status, 0) == -1) {
			perror("waitpid");
            return -1;
		}

        if (ptrace(PTRACE_GETREGS, h->tasks.pid, NULL, pt_reg) < 0) {
            perror("PTRACE_GETREGS");
            return -1;
        }
        
        display_regs(h->tasks.pid, pt_reg);

		if (WIFEXITED(status)) {
			fprintf(stderr, "\n[!] Target process has exited, something went wrong. Status: %d\n", WEXITSTATUS(status));
			return -1;
		} else if (WIFSIGNALED(status)) {
			fprintf(stderr, "\n[!] Target process has been killed, something went wrong. Signal: %d (%s)\n", WTERMSIG(status), strsignal(WTERMSIG(status)));
			if (WCOREDUMP(status)) {
				fprintf(stderr, "\tChild process has produced a core dump. Damn it.\n");
			}
			return -1;
		} else if (WIFSTOPPED(status)) {
			fprintf(stderr, "\n[!] Target process has been stopped, something went wrong. Signal: %d (%s)\n", WSTOPSIG(status), strsignal(WSTOPSIG(status)));
			return -1;
		} else if (WIFCONTINUED(status)) {
		}

	} while (!WIFEXITED(status) && !WIFSIGNALED(status));
    
    /* Get return value */
    if (ptrace(PTRACE_GETREGS, h->tasks.pid, NULL, pt_reg) < 0) {
        perror("PTRACE_GETREGS");
        return -1;
    }

    h->payloads.function[func].retval = (pt_reg_t)pt_reg->rax;
    
    return 0;

}

[...]

int main(int argc, char **argv)
{
[...]
if(run_bootstrap(&parasite) < 0) {
        goto done;
    }

As can be seen, there is a really strange behaviour that I don't understand going on in host's body:

  1. First, host get's saruman's bootstrap code injected, which gets injected fine - as it can be seen from registers (RIP, and so on) also from first 12 bytes dumped from RIP:
55 push $rsp
48 89 mov $rsp, $rbp
...
  1. Then, after a single step, where push $rsp should occur, out of the sudden we land at 2 bytes before entry point (0x3ffffe), RAX modified, and bytes at RIP malformed.

  2. Host gets it's SIGSEGV and it's life is over.

I don't understand that course of action, neither how it can be traced or fixed.
Any ideas?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions