Finding uses of cryptographic functions and the data encrypted by an application


Jan 26, 2021
by Louis
Categories: Tutorial -
Tags: Use cases - REVEN - Reverse Engineering - Analysis API - Cryptography -




How to find cryptography implemented by an application in a REVEN trace? Let’s explore two ways of doing so!

Looking for known symbol calls

REVEN provides several features related to symbol calls:

  • The symbol search feature allows you to look throughout the entire trace to find calls to a specific binary’s symbol. binary.
  • The ltrace script displays all the calls to symbols from external binaries.

For example, Windows proposes the Cryptography API: Next Generation (CNG) for cryptography. We could look for calls to the bcrypt.dll!BCryptGenerateKeyPair function, that creates an empty public/private key pair for asymetric cryptography.

Using the Axion GUI, we looked for this function in a scenario where we recorded an HTTPS connection to Duck Duck Go, and sure enough:


Going to the first call, we can see that we are in the middle of what appears to be a system call. Let’s go back in our backtrace to retrace that system call.


Sure enough, we can go back to the TLS Handshake: see the call to the ProcessHandshakeCommon method.

Running the REVEN ltrace script on the ncryptprov.dll binary:

python -m reven2_ltrace -p 42797 "c:/windows/system32/ncryptprov.dll" | grep BCrypt

we can get the following (filtered for brevity) interesting lines of output:

#223512674 NTSTATUS bcrypt!BCryptGenerateKeyPair(BCRYPT_ALG_HANDLE hAlgorithm=0x2245e4e4d00, BCRYPT_KEY_HANDLE * phKey=0x2245ec6c030, ULONG dwLength=0, ULONG dwFlags=0) = 0 at #223513919
#224035526 NTSTATUS bcrypt!BCryptSetProperty(BCRYPT_HANDLE hObject=0x2245ebaf4c0, LPCWSTR pszProperty='ECCParameters' 0x7ff85c0c6e60, PUCHAR pbInput=2355231875984, ULONG cbInput=221, ULONG dwFlags=140703128616960) = 0 at #224091165
#224091186 NTSTATUS bcrypt!BCryptFinalizeKeyPair(BCRYPT_KEY_HANDLE hKey=0x2245ebaf4c0, ULONG dwFlags=0) = 0 at #224937546
#224937575 NTSTATUS bcrypt!BCryptExportKey(BCRYPT_KEY_HANDLE hKey=0x2245ebaf4c0, BCRYPT_KEY_HANDLE hExportKey=0x0, LPCWSTR pszBlobType='ECCFULLPRIVATEBLOB' 0x7ff85c0c7148, PUCHAR pbOutput=0, ULONG cbOutput=140703128616960, ULONG * pcbResult=411686131508, ULONG dwFlags=0) = 0 at #224938242
#224938360 NTSTATUS bcrypt!BCryptExportKey(BCRYPT_KEY_HANDLE hKey=0x2245ebaf4c0, BCRYPT_KEY_HANDLE hExportKey=0x0, LPCWSTR pszBlobType='ECCFULLPRIVATEBLOB' 0x7ff85c0c7148, PUCHAR pbOutput=411686131176, ULONG cbOutput=2353642078529, ULONG * pcbResult=411686131508, ULONG dwFlags=408021893120) = 0 at #226522856
#227422893 NTSTATUS bcrypt!BCryptSecretAgreement(BCRYPT_KEY_HANDLE hPrivKey=0x2245ebaf4c0, BCRYPT_KEY_HANDLE hPubKey=0x2245ebaf960, BCRYPT_SECRET_HANDLE * phSecret=0x5fda67d908, ULONG dwFlags=0) = 0 at #228604923
#228751171 NTSTATUS bcrypt!BCryptDestroyKey(BCRYPT_KEY_HANDLE hKey=0x2245ebaf4c0) = 0 at #228754391

In this sequence of calls, we can follow what happens to the key with handle 0x2245ebaf4c0, from its allocation at #223512674 in BCryptGenerateKeyPair to its destruction at #228751171 in BCryptDestroyKey (we made sure that we had the correct BCryptGenerateKeyPair by looking at the memory pointed by phKey at the end of that call, using the GUI).

With both of the symbol search or the ltrace methods, if you already know the symbols corresponding to some cryptography implementation, then you can look for them in the trace.

But what about the cases where you don’t know the function names for crypto? This can happen while looking at unknown software, for instance.

In such cases, you can look for cryptographic constants in the trace!

Looking for cryptographic constants

A means of detecting the use of cryptography in a REVEN trace is to search for well-known cryptographic constants.

For example, most symetric crypto algorithms use S-boxes to perform substitutions, and these are generally part of the algorithms, meaning that we should find trace of these giant constants in memory.

For example, AES uses the Rijdael S-box, which starts with the following bytes: “63 7c 77 7b f2 6b 6f c5 …”

As REVEN allows to search for any pattern in memory in the whole trace, let’s turn to the REVEN Python API and look for S-boxes in our recording of the BlueKeep vulnerability:

transitions = []
for event in trace.search.memory(b"\x63\x7c\x77\x7b").events():
    if isinstance(event, reven2.search_in_memory.FirstSeenEvent):
        display(event.transition)
        transitions.append(event.transition)

Running this script on the 1,484,521,602 transitions of our execution trace returns the following relevant transition numbers in around 10 minutes:

#8770742
#26845898
#26853286
#35629491
#85653126
#1172460273
#1172546713

Let’s display the backtrace and current process for each of the detected transitions:

for transition_id in transition_list:
    transition = trace.transition(transition_id)
    ctx = trace.context_before(transition_id)
    print("In process {}, backtrace before {}".format(ctx.ossi.process(), transition))
    print(ctx.stack.backtrace)
    print()
In process lsass.exe (424), backtrace before #8770742 movzx eax, byte ptr [rax + r14 + 0x37300]
[0] #8770518 - bcryptprimitives!AesExpandKey
[1] #8770351 - bcryptprimitives!MSCryptGenRandom
[2] #8770264 - schannel!BCryptGenRandom
[3] #8738142 - schannel!private: unsigned long int __cdecl CSsl3TlsServerContext::GenerateClientHelloResponse(struct SPBuffer * __ptr64) __ptr64
[4] #8738122 - schannel!private virtual: unsigned long int __cdecl CSsl3TlsServerContext::GenerateResponse(struct SPBuffer * __ptr64) __ptr64
[5] #8725891 - schannel!private: unsigned long int __cdecl CSsl3TlsContext::TlsProtocolHandlerWorker(struct SPBuffer * __ptr64, struct SPBuffer * __ptr64) __ptr64
[6] #8725885 - schannel!private virtual: unsigned long int __cdecl CSsl3TlsContext::SslProtocolHandler(struct SPBuffer * __ptr64, struct SPBuffer * __ptr64) __ptr64
...
[21] ??? - ntdll!TppWorkerThread+0x24b

In process svchost.exe (1004), backtrace before #26845898 mov dl, byte ptr [rax + r14 + 0x5400]
[0] #26845465 - cryptbase!AesExpandKey
[1] #26845392 - cryptbase!AesCtrRng_Generate
[2] #26845332 - cryptbase!RandomFillBuffer
[3] #26845304 - rsaenh!SystemFunction036
[4] #26844906 - rsaenh!AesCtrWithFipsChecks
...
[19] #4857830 - termsrv!public virtual: long int __cdecl CConnectionEx::Accept(void) __ptr64
[20] #4857782 - termsrv!private: static unsigned long int __cdecl CListenerEx::staticTransferWorkItem(void * __ptr64)
[21] #4857777 - kernel32!BaseThreadInitThunk
[22] ??? - ntdll!LdrInitializeThunk

In process svchost.exe (1004), backtrace before #26853286 mov r9b, byte ptr [rax + r14 + 0x31600]
[0] #26853211 - rsaenh!AesExpandKey
[1] #26853190 - rsaenh!AesCtrRng_Update
[2] #26853007 - rsaenh!AesCtrRng_Instantiate
[3] #26844906 - rsaenh!AesCtrWithFipsChecks
...
[17] #5942073 - rdpcorekmts!public virtual: long int __cdecl CKMRDPConnection::AcceptConnection(void) __ptr64
[18] #4857830 - termsrv!public virtual: long int __cdecl CConnectionEx::Accept(void) __ptr64
[19] #4857782 - termsrv!private: static unsigned long int __cdecl CListenerEx::staticTransferWorkItem(void * __ptr64)
[20] #4857777 - kernel32!BaseThreadInitThunk
[21] ??? - ntdll!LdrInitializeThunk

In process lsm.exe (432), backtrace before #35629491 mov r11b, byte ptr [rax + r14 + 0x31600]
[0] #35629141 - rsaenh!AesExpandKey
[1] #35629120 - rsaenh!AesCtrRng_Update
[2] #35629015 - rsaenh!AesCtrRng_Generate
[3] #35628898 - rsaenh!AesCtrWithFipsChecks
[4] #35628885 - rsaenh!CPGenRandom
[5] #35628854 - cryptsp!CryptGenRandom
[6] #35628832 - ole32!public: long int __cdecl CRandomNumberGenerator::GenerateRandomNumber(unsigned char * __ptr64, unsigned long int) __ptr64
[7] #35628810 - ole32!?AddIPIDEntry@CStdMarshal@@AEAAJPEAVOXIDEntry@@PEAU_GUID@@AEBU3@PEAVCCtxComChnl@@PEAUIUnknown@@PEAXPEAPEAUtagIPIDEntry@@@Z
[8] #35628775 - ole32!private: long int __cdecl CStdMarshal::MarshalServerIPID(struct _GUID const & __ptr64, unsigned long int, unsigned long int, struct tagIPIDEntry * __ptr64 * __ptr64, struct IUnknown * __ptr64) __ptr64
AppInvoke@@YAJPEAVCMessageCall@@PEAVCRpcChannelBuffer@@PEAUIRpcStubBuffer@@PEAX3PEAUtagIPIDEntry@@PEAULocalThis@@@Z
...
[36] ??? - ntdll!TppWorkerThread+0x24b

In process svchost.exe (1716), backtrace before #85653126 mov r8b, byte ptr [rax + r14 + 0x31600]
[0] #85652512 - rsaenh!AesExpandKey
[1] #85648071 - rsaenh!AesCtrRng_Generate
[2] #85647954 - rsaenh!AesCtrWithFipsChecks
[3] #85647941 - rsaenh!CPGenRandom
[4] #85647908 - advapi32!CryptGenRandomStub
[5] #85647839 - mpclient!void __cdecl MpUtilsExports::MpUtilsUninitializeImpl(void)+0x70
[6] #85647832 - mpclient!long int __cdecl MpUtilsExports::MpGenRandBufferImpl(unsigned long long(unsigned __int64), void * __ptr64)
...
[16] #85311420 - mpclient!?WorkCallback@CMpThreadPoolProviderVista@CommonUtil@@CAXPEAU_TP_CALLBACK_INSTANCE@@PEAXPEAU_TP_WORK@@@Z
[17] #85311262 - ntdll!TppWorkpExecuteCallback
[18] ??? - ntdll!TppWorkerThread+0x24b

In process csrss.exe (2292), backtrace before #1172460273 movaps xmm0, xmmword ptr [rax + rdx]
[0] #1172460010 - ntoskrnl!KeCopyPage
[1] #1172459667 - ntoskrnl!MiCopyOnWrite
[2] #1172459538 - ntoskrnl!MmAccessFault
[3] #1172459482 - ntoskrnl!KiPageFault
[4] ??? - ntoskrnl!KiSystemCall64+0x45f

In process csrss.exe (2292), backtrace before #1172546713 mov r9b, byte ptr [rax + r14 + 0x52400]
[0] #1172546373 - cng!AesExpandKey
[1] #1172546297 - cng!AesCtrRng_Generate
[2] #1172546183 - cng!SystemPrng
[3] #1172546104 - cng!CngDeviceControl
[4] #1172546084 - ksecdd!KsecDeviceControl
[5] #1172546042 - ntoskrnl!IofCallDriver
[6] #1172544962 - ntoskrnl!IopXxxControlFile
[7] #1172544947 - ntoskrnl!NtDeviceIoControlFile
[8] ??? - ntoskrnl!KiSystemCall64+0x45f

(output shortened for brevity)

We can find that most of these are calls to the key expansion routine of AES (the match at #1172460273 is a page handler that accesses the S-Box).

Closing words

What to do after finding evidence of cryptography in a trace? Well, depending on the context, just knowing some program performs cryptography and which algorithms are used can be invaluable information. From these points of interest, you can use the other features of REVEN such as memory history or data tainting to find out what message was encrypted or decrypted, where it came from or went to (network, file, …), or look for some encryption keys.

For an example of extracting all the decrypted messages sent by a malware, you can take a look at the “Tracing network data back to encryption” article, that does that on PocoDown malware (in particular look around the 3:30 timestamp in the video).

Bonus

While preparing for this article, I had actually forgotten which of my scenarios had BCryptGenerateKeyPair calls in it, so I used the reven2.preview.project_manager API in REVEN Enterprise edition to look for such calls in all of my scenarios. The quickly written code below allowed me to find the correct scenario.

from reven2.preview.project_manager import ProjectManager
from reven2.preview.project_manager.project_manager import ResponseError

pm = ProjectManager("http://localhost:8880")

for scenario in pm.get_scenarios_list(limit=900)['results']:
    try:
        with pm.connect(scenario['name']) as server:
            bcrypt = list(server.ossi.executed_binaries("bcrypt.dll"))[0]
            generate_key_pair = list(bcrypt.symbols("BCryptGenerateKeyPair"))[0]
            calls = len(list(server.trace.search.symbol(generate_key_pair)))
            print("[{}]{} calls".format(scenario['name'], calls))
    except ResponseError as e:
        print("[{}]Scenario cannot be analyzed".format(scenario['name']))
    except IndexError as e:
        print("[{}]Binary or symbol missing from scenario".format(scenario['name']))
    except RuntimeError as e:
        print("[{}]ERROR ({})".format(scenario['name'], e))

Here’s a sample output:

[HTTP_request_noSSL]0 calls
[named_pipe_example]0 calls
[CVE-2018-8653_x64_light_oleaut32]Binary or symbol missing from scenario
[bksod]0 calls
[tokio_chat]Binary or symbol missing from scenario
[backdoor.win32.agent.vx0]Binary or symbol missing from scenario
[CVE-2019-1347_Nocrash_BYTE_3_again]ERROR (Binary index is unavailable.)
[CVE-2019-1347_peparsercrash_minimal]0 calls
[ddg_query]18 calls
[notepad]0 calls
[quick-player-1.3-windows-7-crash-vbox]Binary or symbol missing from scenario
[CVE-2020-16898]Binary or symbol missing from scenario
[div_by_zero_amd64_0]0 calls

The scenario of interest was called ddg_query.

Next post: Detecting Buffer-Overflow vulnerabilities using REVEN
Previous post: HITBCyberWeek 2020 REVEN Lab replay