在 shellcode NASM 的 JMP CALL POP 技术中避免 JMP?
Avoiding the JMP in the JMP CALL POP technique for shellcode NASM?
即使这两个程序都遵守 shellcode 执行所需的相对寻址指令,并且都在执行时打印所需的消息,但 2nd Sample 在用作 shellcode 时失败了。谁能解释这种行为?奇怪的是,与第一个相同的 第三个样本 也失败了。
输出:
示例 1
你好世界
其他示例(2&3) 打印垃圾值
示例 1
全局_start
节.text
_开始:
跳转加宽
酸碱度世界:
pop rsi
xor rax,rax
mov al,1
mov rdi,rax
mov rdx,rdi
添加 rdx,11
系统调用
;出口
xor rax,rax
mov rax,60
xor rdi,rdi
系统调用
扩大:
致电 pHworld
Hworld 数据库 "Hello World",0xa
示例 2
全局_start
节.text
_开始:
致电 pHworld
Hworld 数据库 "Hello World",0xa
酸碱度世界:
pop rsi
xor rax,rax
mov al,1
mov rdi,rax
mov rdx,rdi
添加 rdx,11
系统调用
;出口
xor rax,rax
mov rax,60
xor rdi,rdi
系统调用
示例 3
全局_start
节.text
_开始:
jmp标签1
标签 1:
致电 pHworld
Hworld 数据库 "Hello World",0xa
酸碱度世界:
pop rsi
xor rax,rax
mov al,1
mov rdi,rax
mov rdx,rdi
添加 rdx,11
系统调用
;出口
xor rax,rax
mov rax,60
xor rdi,rdi
系统调用
无法满足我的好奇心,我尝试了另一种变体,但失败了(打印垃圾值),即使我的 objdump 没有任何 0x00。
示例 4
全局_start
节.text
酸碱度世界:
pop rsi
xor rax,rax
mov al,1
mov rdi,rax
mov rdx,rdi
添加 rdx,11
系统调用
xor rax,rax
xor rdi,rdi
移动 al,60
系统调用
l1:
致电 pHworld
Hworld 数据库 "Hello World", 0xa
_开始:
跳转 l1
在此处输入代码
样本4的Objdump
./hworld2.s: 文件格式 elf64-x86-64
.text 部分的反汇编:
0000000000400080 :
400080:5e pop rsi
400081: 48 31 c0 xor rax,rax
400084: b0 01 mov al,0x1
400086: 48 89 c7 mov rdi,rax
400089: 48 89 fa mov rdx,rdi
40008c: 48 83 c2 0b 添加 rdx,0xb
400090: 0f 05 系统调用
400092: 48 31 c0 xor rax,rax
400095: 48 31 ff xor rdi,rdi
400098: b0 3c mov al,0x3c
40009a: 0f 05 系统调用
000000000040009c:
40009c: e8 df ff ff ff 呼叫 400080
00000000004000a1 :
4000a1: 48 rex.W
4000a2:65 克
4000a3: 6c ins BYTE PTR es:[rdi],dx
4000a4: 6c ins BYTE PTR es:[rdi],dx
4000a5: 6f outs dx,DWORD PTR ds:[rsi]
4000a6: 20 57 6f 和 BYTE PTR [rdi+0x6f],dl
4000a9: 72 6c jb 400117
4000ab:64飞秒
4000ac: 0a eb 或 ch,bl
00000000004000ad :
4000ad: eb ed jmp 40009c
TL;DR :对于 shell 代码,您希望避免编码 0x00 字节,否则当代码用作漏洞利用的字符串时,它们将被截断第一个 0x00。这将有效地缩短您的代码。
当 运行 在漏洞利用之外时,额外的 0x00 字节不会导致问题,因为它们不会被转换为字符串。这些指令像任何普通可执行文件一样执行。
之所以在JMP/CALL/POP方法中使用JMP指令是为了消除插入不需要的0x00字节生成的代码。 JMP in 64-bit code has a rel8
and rel32
encoding. In 64-bit code CALL 只有 rel32
编码。这意味着如果您在 64 位代码中使用 CALL 在内存中进行小的前向传输,它将被编码为 32 位零扩展目标。该零扩展将导致不需要的 0x00 值被放置在 shell 代码中。在 64 位代码中,此 CALL 指令:
call next
nop
next:
将被编码为:
e8 01 00 00 00
由于JMP指令支持rel8
(相对字节位移)NASM可以生成一条JMP指令如果目标距离不超过 127 个字节(有符号字节为 -128 到 +127),则在内存中转发。这条 JMP 指令:
jmp next
nop
next:
将被编码为:
eb 01
所以没有额外的零。你可能会问为什么 CALL 方法中的 JMP/CALL/POP 指令有效。原因是负值的符号扩展到高位字节。用 1 填充高位不会产生额外的 0x00 字节,因此可以正常工作。此 CALL 指令:
prev:
call prev
将被编码为:
e8 fb ff ff ff
注意额外的字节不是 0。这就是为什么调用内存中较早的位置可以避免生成零。
示例 2
如果我们牢记以上几点,我们只需要检查示例 2 的生成代码,看看哪里出了问题。 objdump -D ./sample2 -Mintel
生成:
0000000000000000 <_start>:
0: e8 0c 00 00 00 call 11 <pHworld> <---------- Extra zeros
0000000000000005 <Hworld>:
5: 48 rex.W
6: 65 6c gs ins BYTE PTR es:[rdi],dx
8: 6c ins BYTE PTR es:[rdi],dx
9: 6f outs dx,DWORD PTR ds:[rsi]
a: 20 57 6f and BYTE PTR [rdi+0x6f],dl
d: 72 6c jb 7b <pHworld+0x6a>
f: 64 0a 5e 48 or bl,BYTE PTR fs:[rsi+0x48]
0000000000000011 <pHworld>:
11: 5e pop rsi
12: 48 31 c0 xor rax,rax
15: b0 01 mov al,0x1
17: 48 89 c7 mov rdi,rax
1a: 48 89 fa mov rdx,rdi
1d: 48 83 c2 0b add rdx,0xb
21: 0f 05 syscall
23: 48 31 c0 xor rax,rax
26: b8 3c 00 00 00 mov eax,0x3c <---------- Extra zeros
2b: 48 31 ff xor rdi,rdi
2e: 0f 05 syscall
所以我们看到 CALL 的问题是用额外的零编码的,这就是为什么你需要传统的 JMP/CALL/POP . mov eax, 60
还有第二个问题,因为它编码了额外的字节。我认为你打算使用 mov al, 60
示例 3
objdump -D ./sample3 -Mintel
生成:
0000000000000000 <_start>:
0: eb 00 jmp 2 <label1> <---------- Extra zeros
0000000000000002 <label1>:
2: e8 0c 00 00 00 call 13 <pHworld> <---------- Extra zeros
0000000000000007 <Hworld>:
7: 48 rex.W
8: 65 6c gs ins BYTE PTR es:[rdi],dx
a: 6c ins BYTE PTR es:[rdi],dx
b: 6f outs dx,DWORD PTR ds:[rsi]
c: 20 57 6f and BYTE PTR [rdi+0x6f],dl
f: 72 6c jb 7d <pHworld+0x6a>
11: 64 0a 5e 48 or bl,BYTE PTR fs:[rsi+0x48]
0000000000000013 <pHworld>:
13: 5e pop rsi
14: 48 31 c0 xor rax,rax
17: b0 01 mov al,0x1
19: 48 89 c7 mov rdi,rax
1c: 48 89 fa mov rdx,rdi
1f: 48 83 c2 0b add rdx,0xb
23: 0f 05 syscall
25: 48 31 c0 xor rax,rax
28: b8 3c 00 00 00 mov eax,0x3c <---------- Extra zeros
2d: 48 31 ff xor rdi,rdi
30: 0f 05 syscall
与样本 2 相同类型的问题。
示例 1
objdump -D ./sample1 -Mintel
生成:
0000000000000000 <_start>:
0: eb 1f jmp 21 <widen>
0000000000000002 <pHworld>:
2: 5e pop rsi
3: 48 31 c0 xor rax,rax
6: b0 01 mov al,0x1
8: 48 89 c7 mov rdi,rax
b: 48 89 fa mov rdx,rdi
e: 48 83 c2 0b add rdx,0xb
12: 0f 05 syscall
14: 48 31 c0 xor rax,rax
17: b8 3c 00 00 00 mov eax,0x3c <---------- Extra zeros
1c: 48 31 ff xor rdi,rdi
1f: 0f 05 syscall
0000000000000021 <widen>:
21: e8 dc ff ff ff call 2 <pHworld>
0000000000000026 <Hworld>:
26: 48 rex.W
27: 65 6c gs ins BYTE PTR es:[rdi],dx
29: 6c ins BYTE PTR es:[rdi],dx
2a: 6f outs dx,DWORD PTR ds:[rsi]
2b: 20 57 6f and BYTE PTR [rdi+0x6f],dl
2e: 72 6c jb 9c <Hworld+0x76>
30: 64 fs
31: 0a .byte 0xa
虽然你说sample 1
有效,但还是有问题需要改正。 mov rax, 60
需要 mov al, 60
.
即使这两个程序都遵守 shellcode 执行所需的相对寻址指令,并且都在执行时打印所需的消息,但 2nd Sample 在用作 shellcode 时失败了。谁能解释这种行为?奇怪的是,与第一个相同的 第三个样本 也失败了。
输出: 示例 1 你好世界
其他示例(2&3) 打印垃圾值
示例 1
全局_start 节.text _开始: 跳转加宽 酸碱度世界: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi 添加 rdx,11 系统调用 ;出口 xor rax,rax mov rax,60 xor rdi,rdi 系统调用 扩大: 致电 pHworld Hworld 数据库 "Hello World",0xa
示例 2
全局_start 节.text _开始: 致电 pHworld Hworld 数据库 "Hello World",0xa 酸碱度世界: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi 添加 rdx,11 系统调用 ;出口 xor rax,rax mov rax,60 xor rdi,rdi 系统调用
示例 3
全局_start 节.text _开始: jmp标签1 标签 1: 致电 pHworld Hworld 数据库 "Hello World",0xa 酸碱度世界: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi 添加 rdx,11 系统调用 ;出口 xor rax,rax mov rax,60 xor rdi,rdi 系统调用
无法满足我的好奇心,我尝试了另一种变体,但失败了(打印垃圾值),即使我的 objdump 没有任何 0x00。 示例 4
全局_start 节.text 酸碱度世界: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi 添加 rdx,11 系统调用 xor rax,rax xor rdi,rdi 移动 al,60 系统调用 l1: 致电 pHworld Hworld 数据库 "Hello World", 0xa _开始: 跳转 l1 在此处输入代码
样本4的Objdump
./hworld2.s: 文件格式 elf64-x86-64 .text 部分的反汇编: 0000000000400080 : 400080:5e pop rsi 400081: 48 31 c0 xor rax,rax 400084: b0 01 mov al,0x1 400086: 48 89 c7 mov rdi,rax 400089: 48 89 fa mov rdx,rdi 40008c: 48 83 c2 0b 添加 rdx,0xb 400090: 0f 05 系统调用 400092: 48 31 c0 xor rax,rax 400095: 48 31 ff xor rdi,rdi 400098: b0 3c mov al,0x3c 40009a: 0f 05 系统调用 000000000040009c: 40009c: e8 df ff ff ff 呼叫 400080 00000000004000a1 : 4000a1: 48 rex.W 4000a2:65 克 4000a3: 6c ins BYTE PTR es:[rdi],dx 4000a4: 6c ins BYTE PTR es:[rdi],dx 4000a5: 6f outs dx,DWORD PTR ds:[rsi] 4000a6: 20 57 6f 和 BYTE PTR [rdi+0x6f],dl 4000a9: 72 6c jb 400117 4000ab:64飞秒 4000ac: 0a eb 或 ch,bl 00000000004000ad : 4000ad: eb ed jmp 40009c
TL;DR :对于 shell 代码,您希望避免编码 0x00 字节,否则当代码用作漏洞利用的字符串时,它们将被截断第一个 0x00。这将有效地缩短您的代码。
当 运行 在漏洞利用之外时,额外的 0x00 字节不会导致问题,因为它们不会被转换为字符串。这些指令像任何普通可执行文件一样执行。
之所以在JMP/CALL/POP方法中使用JMP指令是为了消除插入不需要的0x00字节生成的代码。 JMP in 64-bit code has a rel8
and rel32
encoding. In 64-bit code CALL 只有 rel32
编码。这意味着如果您在 64 位代码中使用 CALL 在内存中进行小的前向传输,它将被编码为 32 位零扩展目标。该零扩展将导致不需要的 0x00 值被放置在 shell 代码中。在 64 位代码中,此 CALL 指令:
call next
nop
next:
将被编码为:
e8 01 00 00 00
由于JMP指令支持rel8
(相对字节位移)NASM可以生成一条JMP指令如果目标距离不超过 127 个字节(有符号字节为 -128 到 +127),则在内存中转发。这条 JMP 指令:
jmp next
nop
next:
将被编码为:
eb 01
所以没有额外的零。你可能会问为什么 CALL 方法中的 JMP/CALL/POP 指令有效。原因是负值的符号扩展到高位字节。用 1 填充高位不会产生额外的 0x00 字节,因此可以正常工作。此 CALL 指令:
prev:
call prev
将被编码为:
e8 fb ff ff ff
注意额外的字节不是 0。这就是为什么调用内存中较早的位置可以避免生成零。
示例 2
如果我们牢记以上几点,我们只需要检查示例 2 的生成代码,看看哪里出了问题。 objdump -D ./sample2 -Mintel
生成:
0000000000000000 <_start>:
0: e8 0c 00 00 00 call 11 <pHworld> <---------- Extra zeros
0000000000000005 <Hworld>:
5: 48 rex.W
6: 65 6c gs ins BYTE PTR es:[rdi],dx
8: 6c ins BYTE PTR es:[rdi],dx
9: 6f outs dx,DWORD PTR ds:[rsi]
a: 20 57 6f and BYTE PTR [rdi+0x6f],dl
d: 72 6c jb 7b <pHworld+0x6a>
f: 64 0a 5e 48 or bl,BYTE PTR fs:[rsi+0x48]
0000000000000011 <pHworld>:
11: 5e pop rsi
12: 48 31 c0 xor rax,rax
15: b0 01 mov al,0x1
17: 48 89 c7 mov rdi,rax
1a: 48 89 fa mov rdx,rdi
1d: 48 83 c2 0b add rdx,0xb
21: 0f 05 syscall
23: 48 31 c0 xor rax,rax
26: b8 3c 00 00 00 mov eax,0x3c <---------- Extra zeros
2b: 48 31 ff xor rdi,rdi
2e: 0f 05 syscall
所以我们看到 CALL 的问题是用额外的零编码的,这就是为什么你需要传统的 JMP/CALL/POP . mov eax, 60
还有第二个问题,因为它编码了额外的字节。我认为你打算使用 mov al, 60
示例 3
objdump -D ./sample3 -Mintel
生成:
0000000000000000 <_start>:
0: eb 00 jmp 2 <label1> <---------- Extra zeros
0000000000000002 <label1>:
2: e8 0c 00 00 00 call 13 <pHworld> <---------- Extra zeros
0000000000000007 <Hworld>:
7: 48 rex.W
8: 65 6c gs ins BYTE PTR es:[rdi],dx
a: 6c ins BYTE PTR es:[rdi],dx
b: 6f outs dx,DWORD PTR ds:[rsi]
c: 20 57 6f and BYTE PTR [rdi+0x6f],dl
f: 72 6c jb 7d <pHworld+0x6a>
11: 64 0a 5e 48 or bl,BYTE PTR fs:[rsi+0x48]
0000000000000013 <pHworld>:
13: 5e pop rsi
14: 48 31 c0 xor rax,rax
17: b0 01 mov al,0x1
19: 48 89 c7 mov rdi,rax
1c: 48 89 fa mov rdx,rdi
1f: 48 83 c2 0b add rdx,0xb
23: 0f 05 syscall
25: 48 31 c0 xor rax,rax
28: b8 3c 00 00 00 mov eax,0x3c <---------- Extra zeros
2d: 48 31 ff xor rdi,rdi
30: 0f 05 syscall
与样本 2 相同类型的问题。
示例 1
objdump -D ./sample1 -Mintel
生成:
0000000000000000 <_start>:
0: eb 1f jmp 21 <widen>
0000000000000002 <pHworld>:
2: 5e pop rsi
3: 48 31 c0 xor rax,rax
6: b0 01 mov al,0x1
8: 48 89 c7 mov rdi,rax
b: 48 89 fa mov rdx,rdi
e: 48 83 c2 0b add rdx,0xb
12: 0f 05 syscall
14: 48 31 c0 xor rax,rax
17: b8 3c 00 00 00 mov eax,0x3c <---------- Extra zeros
1c: 48 31 ff xor rdi,rdi
1f: 0f 05 syscall
0000000000000021 <widen>:
21: e8 dc ff ff ff call 2 <pHworld>
0000000000000026 <Hworld>:
26: 48 rex.W
27: 65 6c gs ins BYTE PTR es:[rdi],dx
29: 6c ins BYTE PTR es:[rdi],dx
2a: 6f outs dx,DWORD PTR ds:[rsi]
2b: 20 57 6f and BYTE PTR [rdi+0x6f],dl
2e: 72 6c jb 9c <Hworld+0x76>
30: 64 fs
31: 0a .byte 0xa
虽然你说sample 1
有效,但还是有问题需要改正。 mov rax, 60
需要 mov al, 60
.