wtf

WTF is white tight feet.

  1. 1. hello_world in shellcode
    1. 1.1. get syscalls
    2. 1.2. register transmit arguments
    3. 1.3. first stage clear!
    4. 1.4. fetch shellcode from binary
    5. 1.5. use shellcode in C
    6. 1.6. relative address technique
    7. 1.7. jump-call technique
    8. 1.8. stack technique

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时,输出到屏幕。
Integer value Name <unistd.h> symbolic constant <stdio.h> file stream
0 Standard input STDIN_FILENO stdin
1 Standard output STDOUT_FILENO stdout
2 Standard error STDERR_FILENO stderr
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

运行,唉怎么只有一个字母呢?我的”哈喽我叠”呢?

  • 原因:shellcode中有\0,他被称为”bad character”,”bad character”会被字符串截断。

  • bad character

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会出现\0al表示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)
# b'0a646c726f77206f6c6c6568'
print(len(b))
# 24

放入栈中,然后push进去,push只能放入4个字节,该字符串可以按12个字节来算,4个字节push,然后剩下8个字节先放入一个通用寄存器,然后在push进去。

本文作者 : wtfff
本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议(CC BY-NC-SA 4.0)进行许可。This blog is under a CC BY-NC-SA 4.0 Unported License
本文链接 : http://im0use.github.io/2022/05/14/shellcode/

本文最后更新于 天前,文中所描述的信息可能已发生改变