hello_world in shellcode
get syscalls
系统调用号:
- 32位:
/usr/include/x86_64-linux-gnu/asm/unistd_32.h
- 64位:
/usr/include/x86_64-linux-gnu/asm/unistd_64.h
以下实验均以64位实验环境作为实验基础
register transmit arguments
arch |
syscall NR |
return |
arg0 |
arg1 |
arg2 |
arg3 |
arg4 |
arg5 |
arm |
r7 |
r0 |
r0 |
r1 |
r2 |
r3 |
r4 |
r5 |
arm64 |
x8 |
x0 |
x0 |
x1 |
x2 |
x3 |
x4 |
x5 |
x86 |
eax |
eax |
ebx |
ecx |
edx |
esi |
edi |
ebp |
x86_64 |
rax |
rax |
rdi |
rsi |
rdx |
r10 |
r8 |
r9 |
x86_64系统调用寄存器为rax。
first stage clear!
- 使用系统函数
write
输出到屏幕,ssize_t write(int fd, const void *buf, size_t count);
- fd: 文件标识符。当fd为1时,输出到屏幕。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| global _start section .text _start: mov rax, 0x1 ; write syscalls mov rdi, 0x1 ; fd mov rsi, hello_world ; buf mov rdx, length ; count syscall
mov rax, 60 ; exit syscalls mov rdi, 0 ; status syscall
section .data hello_world: db 'hello world', 0xa ; str and newline length: equ $-hello_world
|
- 汇编成为适应系统的可重定位二进制:
nasm -felf64 hello_world.asm -o hello_world.o
- 链接为可执行二进制:
ld hello_world.o -o hello_world
fetch shellcode from binary
1 2
| objdump -M intel -D hello_world | grep '[0-9a-f]:' | grep -v 'file' | cut -f2 -d: | cut -f1-7 -d' ' | tr -s ' ' | tr '\t' ' ' | sed 's/ $//g' | sed 's/ /\\\x/g' | paste -d '' -s \xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a
|
use shellcode in C
1 2 3 4 5 6 7 8 9
| #include <stdio.h> #include <string.h> unsigned char code[] = "\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60\x00\x00\x00\x00\x00\xba\x0c\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64\x0a";
int main() { printf("shellcode length: %d\n", (int)strlen(code)); int (*ret) () = (int(*) ()) code; ret(); }
|
1 2 3 4
| gcc -z execstack -o test 1.c ./test shellcode length:2 t
|
运行,唉怎么只有一个字母呢?我的”哈喽我叠”呢?
hex |
str |
function |
00 |
\0 |
null |
0A |
\n |
换行 |
FF |
\f |
换页 |
0D |
\r |
回车 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| hello_world3: file format elf64-x86-64
Disassembly of section .text:
00000000004000b0 <_start>: 4000b0: b8 01 00 00 00 mov eax,0x1 4000b5: bf 01 00 00 00 mov edi,0x1 4000ba: 48 be d8 00 60 00 00 movabs rsi,0x6000d8 4000c1: 00 00 00 4000c4: ba 0c 00 00 00 mov edx,0xc 4000c9: 0f 05 syscall 4000cb: b8 3c 00 00 00 mov eax,0x3c 4000d0: bf 00 00 00 00 mov edi,0x0 4000d5: 0f 05 syscall
|
mov eax, 0x1
会出现\0
,al
表示eax
寄存器的低位。
- 先通过
xor
使寄存器置零
- 再通过
add
增加寄存器到指定值
1 2 3 4
| 00000000004000b0 <_start>: 4000b0: b0 01 mov al,0x1 4000b2: 48 31 ff xor rdi,rdi 4000b5: 48 83 c7 01 add rdi,0x1
|
但是字符串地址仍然有0。
relative address technique
通过rel可以找到相对rip偏移的变量地址,执行到存储buf的寄存器rsi取地址时,rip减去偏移的地址就是字符串地址,将该地址加载到rsi中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| global _start
_start: jmp code hello_world:db 'hello world',0xa
code: mov al, 1 xor rdi, rdi add rdi, 1 lea rsi, [rel hello_world] xor rdx, rdx add rdx, 0xc syscall
mov al, 60 xor rdi, rdi syscall
|
看一下dump出来的shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| hello_world1: file format elf64-x86-64
Disassembly of section .text:
0000000000400080 <_start>: 400080: eb 0c jmp 40008e <code>
0000000000400082 <hello_world>: 400082: 68 65 6c 6c 6f push 0x6f6c6c65 400087: 20 77 6f and BYTE PTR [rdi+0x6f],dh 40008a: 72 6c jb 4000f8 <code+0x6a> 40008c: 64 fs 40008d: 0a .byte 0xa
000000000040008e <code>: 40008e: b0 01 mov al,0x1 400090: 48 31 ff xor rdi,rdi 400093: 48 83 c7 01 add rdi,0x1 400097: 48 8d 35 e4 ff ff ff lea rsi,[rip+0xffffffffffffffe4] # 400082 <hello_world> 40009e: 48 31 d2 xor rdx,rdx 4000a1: 48 83 c2 0c add rdx,0xc 4000a5: 0f 05 syscall 4000a7: b0 3c mov al,0x3c 4000a9: 48 31 ff xor rdi,rdi 4000ac: 0f 05 syscall
|
已经没有bad char了。
jump-call technique
写一个简单的asm教学程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| global _start section .text
_start: jmp string
code: pop rsi pop rdi
mov al, 0x3c ; exit syscalls xor rdi, rdi ; status: 0 syscall
string: call code hello_world:db 'hello world', 0xa bin_sh:db '/bin/sh', 0xa sh:db 'sh', 0xa
|
汇编和链接后使用gdb调试,可以发现在call string
后,stack的信息
1 2 3 4 5
| ───────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────── 00:0000│ rsp 0x7fffffffe0a8 —▸ 0x400090 (hello_world) ◂— push 0x6f6c6c65 /* 'hello world\n/bin/sh\nsh\n' */ 01:0008│ 0x7fffffffe0b0 ◂— 0x1 02:0010│ 0x7fffffffe0b8 —▸ 0x7fffffffe387 ◂— '/home/codeman/binary_research/shellcode/system_call/tech1' 03:0018│ 0x7fffffffe0c0 ◂— 0x0
|
pop rsi后rsi寄存器的信息
1
| *RSI 0x400090 (hello_world) ◂— push 0x6f6c6c65 /* 'hello world\n/bin/sh\nsh\n' */
|
该字符串就跳到rsi寄存器上,所以我们可以使用这个方式给rsi赋值。
最终的asm程序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| global _start section .text
_start: jmp string
code: pop rsi mov al, 1 xor rdi, rdi add rdi, 1 xor rdx, rdx add rdx, 0xc syscall
mov al, 0x3c xor rdi, rdi syscall
string: call code hello_world:db 'hello world', 0xa
|
stack technique
字符串逆序插入
1 2 3 4 5 6 7
| import base64 a = b'hello world\n' b = base64.b16encode(a[::-1]).lower() print(b)
print(len(b))
|
放入栈中,然后push进去,push只能放入4个字节,该字符串可以按12个字节来算,4个字节push,然后剩下8个字节先放入一个通用寄存器,然后在push进去。