Avoiding the JMP in the JMP CALL POP technique for shellcode NASM?
Even though both the programs abide by relative addressing instructions required for shellcode execution, and both print the desired message on execution, the 2nd Sample fails when used as a shellcode. Can anyone explain the behavior? Strangely a 3rd sample identical to the 1st one fails as well.
Output: Sample 1 Hello World
Other Samples(2&3) print garbage values
Sample 1
global _start section .text _start: jmp widen pHworld: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi add rdx,11 syscall ;Exit xor rax,rax mov rax,60 xor rdi,rdi syscall widen: call pHworld Hworld db "Hello World",0xa
Sample 2
global _start section .text _start: call pHworld Hworld db "Hello World",0xa pHworld: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi add rdx,11 syscall ;Exit xor rax,rax mov rax,60 xor rdi,rdi syscall
Sample 3
global _start section .text _start: jmp label1 label1: call pHworld Hworld db "Hello World",0xa pHworld: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi add rdx,11 syscall ;Exit xor rax,rax mov rax,60 xor rdi,rdi syscall
Unable to rest my curiosity , I tried yet another variation , and this fails (prints garbage values) even though I the objdump does not have any 0x00. Sample 4
global _start section .text pHworld: pop rsi xor rax,rax mov al,1 mov rdi,rax mov rdx,rdi add rdx,11 syscall xor rax,rax xor rdi,rdi mov al,60 syscall l1: call pHworld Hworld db "Hello World", 0xa _start: jmp l1 enter code here
Objdump of sample4
./hworld2.s: file format elf64-x86-64 Disassembly of section .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 add rdx,0xb 400090: 0f 05 syscall 400092: 48 31 c0 xor rax,rax 400095: 48 31 ff xor rdi,rdi 400098: b0 3c mov al,0x3c 40009a: 0f 05 syscall 000000000040009c : 40009c: e8 df ff ff ff call 400080 00000000004000a1 : 4000a1: 48 rex.W 4000a2: 65 gs 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 and BYTE PTR [rdi+0x6f],dl 4000a9: 72 6c jb 400117 4000ab: 64 fs 4000ac: 0a eb or ch,bl 00000000004000ad : 4000ad: eb ed jmp 40009c
TL;DR : With shellcode you want to avoid encoding 0x00 bytes, otherwise when the code is used as a string for an exploit they will truncated at the first 0x00. This effectively will cut your code short.
The extra 0x00 bytes don't cause an issue when running outside an exploit because they aren't being converted to strings. The instructions are executed as is like any normal executable.
The reason for the JMP instruction in the JMP/CALL/POP method is to eliminate the insertion of unwanted 0x00 bytes in the generated code. JMP in 64-bit code has a rel8
and rel32
encoding. In 64-bit code CALL only has a rel32
encoding. This means that if you use CALL in 64-bit code to make a small transfer forward in memory it will be encoded as a 32-bit zero extended target. That zero extension would cause unwanted 0x00 values to be placed in the shell code. In 64-bit code this CALL instruction:
call next
nop
next:
Would be encoded as:
e8 01 00 00 00
Since the JMP instruction supports rel8
(relative byte displacement) NASM can generate a JMP instruction forward in memory if the target is no more than 127 bytes away (signed byte is -128 to +127). This JMP instruction:
jmp next
nop
next:
Would be encoded as:
eb 01
So no extra zeroes. You may ask why the CALL instruction in the JMP/CALL/POP method works. The reasoning is that negative values have their sign extended across the upper bytes. Filling the upper bits with 1 will not produce extra 0x00 bytes and so it works. This CALL instruction:
prev:
call prev
Would be encoded as:
e8 fb ff ff ff
Notice that the extra bytes are not 0. This is why calling to a spot earlier in memory can avoid generating zeroes.
Sample 2
If we keep the above in mind we only need to examine the generated code for Sample 2 to see where things go wrong. objdump -D ./sample2 -Mintel
generates:
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
So we see the issue with the CALL being encoded with extra zeroes which is why you need a traditional JMP/CALL/POP. There is a second issue with mov eax, 60
since it encodes extra bytes. I think you meant to use mov al, 60
Sample 3
objdump -D ./sample3 -Mintel
generates:
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
Same type of issues as Sample 2.
Sample 1
objdump -D ./sample1 -Mintel
generates:
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
Although you say sample 1
works, it still has a problem that needs to be corrected. mov rax, 60
needs to be mov al, 60
.