Author:SungLin @ Knownsec 404 Team
Chinese Version:

0x00 Channel Creation, Connection and Release

The channel's data packet is defined in the MCS Connect Inittial PDU with GCC Conference Create Request. The RDP connection process is shown in the following figure:

The format of the packet is as follows:

In MCS Connect Initial, which belongs to the Client Network Data data segment, MS_T120 will create a structure with a virtual channel id of 0x1f and size 0x18 by the function termdd!_IcaRegisterVcBin at the beginning of the connection. Then it will call termdd!IcaCreateChannel to start creating a channel structure with size in 0x8c, and it will be bound to the virtual channel (id 0x1f).

The definition field of the channel is mainly its name plus the configuration, and the configuration includes the priority, etc.

In the response packet sent by server to MCS Connect Inittial, the id of the corresponding virtual channel will be given in turn:

The values registered in the RDP kernel should be 0, 1, 2, and 3, and the MS_T120 channel will be bound again by the value we’ve sent (its id is 3). First, we found the registration just started by termdd!_IcaFindVcBind. The virtual channel id is 0x1f as follows:

But in termdd!_IcaBindChannel, we have our custom id value of 3 and the channel structure body once again bound, this channel structure is MS_T120

At the same time, our own user id will overwrite 0x1f.

We send data to the channel MS_T120 to actively release its allocated structure, and its incoming virtual channel id value is 3. The function returns the corresponding channel structure in the channeltable by the function termdd!IcaFindChannel:

The following figure shows the returned MS_T120 channel structure, where 0xf77b4300 is an array of function pointers that can be called for this channel:

In this function pointer array, there are mainly three functions: termdd!IcaCloseChannel, termdd!IcaReadChannel, termdd!IcaWriteChannel

The data we have released to release the MS_T120 channel is as follows, the byte size is 0x12, and the main data corresponds to 0x02.

After that, I will enter the nt! IofCompleteRequest function. After apc injection, it will respond to the data request through nt! IopCompleteRequest and nt!IopAbortRequest, and finally complete the request for sending data in termdd!IcaDispatch. `_BYTE v2 is the data we send, so the data 0x02 we sent will eventually call the IcaClose function to enter the IcaCloseChannel function, and finally release the MS_T120 channel structure.

0x01 Data Occupancy Through the RDPDR Channel

Let's first get to know about the RDPDR channel. First, the RDPDR channel is File System Virtual Channel Extension, which runs over a static virtual channel with the name RDPDR. Its purpose is to redirect access from the server to the client file system.

Here we just use the data of the Client Name Request to make the memory in the pool.

After the connection is fully established, the structure of the RDPDR channel will be created.

In Windows 7, after receiving the RDPDR request from the server after the establishment is completed, by sending the client name response data, it will call the non-paged pool memory in the termdd! IcaChannelInputInternal, and the length is we can control. Meet the needs of UAF utilization:

However, in windowsxp, directly sending the client name request will cause the memory allocation to fail. It will directly go into the termdd! _IcaCopyDataToUserBuffer, Tao Yan and Jin Chen [1] also mentioned that by sending the client name request after triggering certain conditions, we will bypass the termdd!_IcaCopyDataToUserBuffer and enter ExAllocatePoolWithTag to allocate the non-paged memory we want. Here is how to break this condition:

Let's first look at the creation of the initial channel structure. We can see that when the channel structure is created from the beginning, two flags will appear, and the two flags are arranged in the order of addresses. And as long as the address of channelstruct +0x108 is stored in the same address, the loop will be broken.

We send a normal RDPDR name request packet with the header identifiers 0x7244 and 0x4e43

After termdd!_IcaCopyDataToUserBuffer, it will enter nt!IofCompleteRequest and enter rdpdr!DrSession::ReadCompletion after responding to the request. The function of this function is as follows. It will traverse a linked list and take the corresponding array of vftable functions from the linked list.

First traverse takes out the first array of functions.

After passing in the data, we call rdpdr!DrSession::RecognizePacket to read it through the function array.

Determine if the header is (RDPDR_CTYP_CORE) 0x7244.

Then the second address of the function vftable will be read and forwarded.

You can see the packet processing logic of RDPDR as below.

After a series of data packet processing, RDPDR finally enters the place we care about, and will pass the channelstruct to handle the flag bit by calling termdd! _IcaQueueReadChannelRequest.

The initial RDPDR channelstruct flag is as follows

After being processed by function termdd! _IcaQueueReadChannelRequest, the flag becomes as the following, and the next data will still enter termdd!_IcaCopyDataToUserBuffer, causing pool injection failure.

Going back to the RDPDR header handler rdpdr!DrSession::RecognizePacket, we find that after the linked list traversal fails, we will jump, and finally we will enter the read failure handler `rdpdr!DrSession::ChannelIoFailed, and then directly return

We send a packet with a wrong header. We set its header flag to be 0x7240, which will cause false in rdpdr!DrSession::RecognizePacket . After that, we will continue to traverse the linked list and then take out two function arrays.

The last two function arrays call rdpdr!DrExchangeManager::RecognizePacket and rdpdr!DrDeviceManager::RecognizePacket in turn, which will determine the wrong header flag 0x7240, and it will result in the wrong jumping after traversing the linked list. This will bypass the termdd! _IcaQueueReadChannelRequest 's modificatoin towards the flag and break the loop.

Finally, we will construct multiple error packets and then enter ExAllocatePoolWithTag and assign it to the non-paged memory we need!

0x02 Win7 EXP Brief Analysis of Pool Injection

The size of the frst released MS_T120 pool is 0x170, and the flag of the pool is TSic.

By analyzing Win7 exp, we can know the data occupancy is the RDPSND channel, the author does not use the RDPDR channel, it should also be related to the stability of the injection, RDPSND injection is completed after the initialization of RDPDR is established, before the free MS_T120 structure, send 1044 packets to apply for 0x170 size pool memory, this can be said to prevent the memory that was later freed from being used by other programs, improve the survival chance of memory occupied by us after free.

The actual data size of the placeholder being freed is 0x128, and the transit address used is 0xfffffa80ec000948.

After starting the pool injection, the payload is injected to the place where you can call [rax] == 0xfffffa80ec000948. The size of the injected payload is basically 0x400, and the total data size of the injection is 200mb. Let us first look at the total amount of memory occupied by the TSic before the injection. Its size is around 58kib.

After the injection, the memory size of the TSic logo pool is about 201mb. The pool memory injection is successful. My win7 is sp1. The total memory size is 1GB. There is no other interference during the injection process.

In this picture, we can find the payload of the pool injection has become quite stable. the higher the address of the memory is, the more stable it is.

At the end of the disconnection, the free memory has been occupied by the 0x128 size data we injected.

After executing the call instruction, it jumps to our payload successfully!



Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址: