2

I am trying to analyze the CryptGenRandom algorithm on my Windows 10 laptop.

According to Niels Ferguson's Whitepaper called 'The Windows 10 random number generation infrastructure', the CryptGenRandom algorithm uses a buffer for small requests:

All PRNGs in the system are SP800-90 AES_CTR_DRBG with 256-bit security strength using the df() function for seeding and re-seeding (see SP 800-90 for details). (…) The Basic PRNGs are not used directly, but rather through a wrapping layer that adds several features.

  • A small buffer of random bytes to improve performance for small requests.
  • A lock to support multi-threading.
  • A seed version.

(…) The buffering is straightforward. There is a small buffer (currently 128 bytes). If a request for random bytes is 128 bytes or larger, it is generated directly from AES_CTR_DRGB. If it is smaller than 128 bytes it is taken from the buffer. The buffer is re-filled from the AES_CTR_DRBG whenever it runs empty. So, if the buffer contains 4 bytes and the request is for 8 bytes, the 4 bytes are taken from the buffer, the buffer is refilled with 128 bytes, and the first 4 bytes of the refilled buffer are used to complete the request, leaving 124 bytes in the buffer.

I would like to know if it is possible to locate and access this buffer.

To achieve this, I located the Advapi32.dll file in my windows/system32 folder. The CryptGenRandom algorithm is defined in this file. I decompiled this file with Ghidra. However, I am very unexperienced with Ghidra and did not manage to find the buffer. Below you can see the entry function with renamed parameters:

    /* WARNING: Function: _guard_dispatch_icall replaced with injection: guard_dispatch_icall */
    /* WARNING: Globals starting with '_' overlap smaller symbols at the same address */
    
    ulonglong entry(undefined8 hProv,int dwLen,longlong pbBuffer)
    
    {
      byte bVar1;
      int iVar2;
      undefined4 extraout_var;
      uint *puVar3;
      undefined1 *puVar4;
      uint uVar5;
      uint *puVar6;
      uint uVar7;
      
      if (dwLen == 1) {
        FUN_18001d464(hProv,1);
      }
      uVar7 = 0;
      if (dwLen == 1) {
        uVar5 = 0;
        puVar4 = &DAT_1800a1008;
        do {
          iVar2 = RtlInitializeCriticalSection(*(undefined8 *)(puVar4 + -8));
          if (iVar2 < 0) {
            iVar2 = FUN_180015850();
            return CONCAT44(extraout_var,iVar2) & 0xffffffffffffff00;
          }
          *puVar4 = 1;
          uVar5 = uVar5 + 1;
          puVar4 = puVar4 + 0x10;
        } while (uVar5 < 4);
        _DAT_1800a44d4 = 1;
      }
      uVar5 = 1 << ((byte)dwLen & 0x1f);
      puVar6 = &DAT_1800a43c8;
      bVar1 = 1;
      puVar3 = &DAT_18006a738;
      do {
        if (((*puVar3 & uVar5) != 0) &&
           ((dwLen != 0 || (((*puVar6 & 2) != 0 && ((pbBuffer == 0 || ((int)*puVar3 < 0)))))))) {
          bVar1 = (**(code **)(puVar3 + -2))(hProv,dwLen,pbBuffer);
          if (bVar1 == 0) goto LAB_18002d198;
          (&DAT_1800a43c8)[(int)uVar7] = *puVar6 | uVar5;
        }
        uVar7 = uVar7 + 1;
        puVar3 = puVar3 + 4;
        puVar6 = puVar6 + 1;
      } while (uVar7 < 8);
      if (bVar1 == 0) {
    LAB_18002d198:
        if (dwLen != 1) goto LAB_18001577c;
      }
      else {
    LAB_18001577c:
        if ((dwLen != 0) || (pbBuffer != 0)) goto LAB_180015785;
      }
      FUN_180015850();
    LAB_180015785:
      return (ulonglong)bVar1;
    }

Since dwLen is the number of requested bytes, I would expect there to be a statement that checks whether this number is at most the number of available random bytes in the buffer. But I see no such statement. Also, pbBuffer is a different buffer than the one I am looking for.

I hope that someone can point me in the right direction!

2 Answers 2

1
+50

This is not a ghidra answer but done using windbg Assuming You Have Code as below

#include <windows.h>
#include <stdio.h>
#pragma comment(lib, "Advapi32.lib")
void hexdump(byte *inbuf, DWORD count)
{
    DWORD j = 0;
    while (j < count)
    {
        for (DWORD i = j; i < j + 16; i++)
        {
            printf("%02x ", inbuf[i]);
        }
        printf("\n");
        j = j + 16;
    }
}
int main(void)
{
    HCRYPTPROV hCryptProv = NULL;
    byte buff[0x200] = {0};
    DWORD count = 0x30;
    BOOL res = CryptAcquireContextA(&hCryptProv, "MyKeyContainer", NULL, PROV_RSA_FULL, 0);
    if (res)
    {
        res = CryptGenRandom(hCryptProv, count, buff);
        if (res)
        {
            hexdump(buff, count);
        }
    }
    CryptReleaseContext(hCryptProv, 0);
}

you can employ memory Breakpoint to find who is writing to your buffer and just corelate with your ghidra/ida disassembly.

the buffers are Actually Created in Heap and the pointers to them are Written in bcryptprimitives!g_AesStatesTable

0:000> lsa .
    21:     DWORD count = 0x30;
    22:     BOOL res = CryptAcquireContextA(&hCryptProv, "MyKeyContainer", NULL, PROV_RSA_FULL, 0);
    23:     if (res)
    24:     {
>   25:         res = CryptGenRandom(hCryptProv, count, buff); <--------------
    26:         if (res)
    27:         {
    28:             hexdump(buff, count);
    29:         }
    30:     }
0:000> ba w1 buff
0:000> g
Breakpoint 0 hit
ntdll!memcpy+0x144:
00007ffe`988d4044 0f100411        movups  xmm0,xmmword ptr [rcx+rdx] ds:0000027d`a4dbc500=25fc4b9c5d3ddad46e5610a21eff9e1f
0:000> k
 # Child-SP          RetAddr           Call Site
00 000000d0`1b6ff648 00007ffe`96815400 ntdll!memcpy+0x144
01 000000d0`1b6ff650 00007ffe`9681513c bcryptprimitives!AesRNGState_generate+0x190
02 000000d0`1b6ff700 00007ffe`9586101d bcryptprimitives!ProcessPrng+0x12c
03 000000d0`1b6ff7b0 00007ffe`94f054f7 CRYPTBASE!SystemFunction036+0xd
04 000000d0`1b6ff7e0 00007ffe`958416b2 rsaenh!CPGenRandom+0x27
05 000000d0`1b6ff810 00007ff6`f72a110f CRYPTSP!CryptGenRandom+0x42
06 000000d0`1b6ff840 00007ff6`f72a14d4 crypt!main+0x7f [crypt.cpp @ 25] <-----------
07 (Inline Function) --------`-------- crypt!invoke_main+0x22 
08 000000d0`1b6ffaa0 00007ffe`968a7614 crypt!__scrt_common_main_seh+0x10c 
09 000000d0`1b6ffae0 00007ffe`988826f1 KERNEL32!BaseThreadInitThunk+0x14
0a 000000d0`1b6ffb10 00000000`00000000 ntdll!RtlUserThreadStart+0x21
0:000> ub . 
ntdll!memcpy+0x126:
00007ffe`988d4026 0f2959e0        movaps  xmmword ptr [rcx-20h],xmm3
00007ffe`988d402a 0f28c4          movaps  xmm0,xmm4
00007ffe`988d402d 75d1            jne     ntdll!memcpy+0x100 (00007ffe`988d4000)
00007ffe`988d402f 4d8bc8          mov     r9,r8
00007ffe`988d4032 49c1e904        shr     r9,4
00007ffe`988d4036 7419            je      ntdll!memcpy+0x151 (00007ffe`988d4051)
00007ffe`988d4038 0f1f840000000000 nop     dword ptr [rax+rax]
00007ffe`988d4040 0f2941f0        movaps  xmmword ptr [rcx-10h],xmm0 <------------- 
this triggered the write breakpoint
0:000> r rcx
rcx=000000d01b6ff890
0:000> ? xmm0
Evaluate expression: 5380964661682152702 = 4aad06d9`a87ea4fe <----------- 
this is being copied from api buffer to codes buff

0:000> db bcryptprimitives!g_AesStatesTable l2
00007ffe`9687b8e0  60 c0 db a4 7d 02 00 00-00 00 00 00 00 00 00 00  `...}...........

0:000> db poi(bcryptprimitives!g_AesStatesTable) l4
0000027d`a4dbc060  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000027d`a4dbc070  f0 c3 db a4 7d 02 00 00-90 c2 db a4 7d 02 00 00  ....}.......}...

0:000> db poi(poi(bcryptprimitives!g_AesStatesTable)+10) l120
0000027d`a4dbc3f0  30 01 00 00 00 00 00 00-00 00 00 00 00 00 00 00  0...............
0000027d`a4dbc400  dd 1e e5 f2 c9 ec e9 b5-70 2e 3b c6 da e1 f9 59  ........p.;....Y
0000027d`a4dbc410  f7 7d 2c 07 2d 57 ae e0-c8 5c 31 05 08 9a 10 9f  .},.-W...\1.....
0000027d`a4dbc420  bc 83 0c 3b 8f 11 ee 03-b0 8a 9a 7e 0e f5 c5 cd  ...;.......~....
0000027d`a4dbc430  da 4f 77 48 92 54 98 57-12 17 ed 2b af 97 1e 88  .OwH.T.W...+.... <-------
0000027d`a4dbc440  03 00 00 00 00 00 00 00-01 00 00 00 00 00 00 00  ................
0000027d`a4dbc450  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000027d`a4dbc460  80 00 00 00 00 00 00 00-39 00 00 00 00 00 00 00  ........9.......
0000027d`a4dbc470  ff ff ff ff ff ff ff ff-fe ff ff ff 01 00 00 00  ................
0000027d`a4dbc480  c8 2d 00 00 00 00 00 00-00 00 00 00 00 00 00 00  .-..............
0000027d`a4dbc490  d0 07 00 02 00 00 00 00-00 00 00 00 00 00 00 00  ................
0000027d`a4dbc4a0  01 20 b5 34 61 da 46 2a-20 ab 63 2a 90 28 cb 36  . .4a.F* .c*.(.6
0000027d`a4dbc4b0  51 4a dc 28 50 b5 a4 97-78 b8 13 33 e3 3c 5a 24  QJ.(P...x..3.<Z$
0000027d`a4dbc4c0  e4 6c 8d 70 7a 19 5d 5e-71 15 78 95 4b 9b 61 f2  .l.pz.]^q.x.K.a.
0000027d`a4dbc4d0  0f 8e 0d 32 5b 39 1a 53-6b 2a 0e 47 ae e1 2a 47  ...2[9.Sk*.G..*G
0000027d`a4dbc4e0  37 d4 d7 78 01 de 34 d8-f4 27 fe bf fb 2b 07 7a  7..x..4..'...+.z
0000027d`a4dbc4f0  fe a4 7e a8 d9 06 ad 4a-4a 70 8a c3 57 53 54 e9  ..~....JJp..WST. <-------
0000027d`a4dbc500  1f 9e ff 1e a2 10 56 6e-d4 da 3d 5d 9c 4b fc 25  ......Vn..=].K.% <--------

you need to catch this buffer before it is wiped off after memcpy using SymCryptWipeAsm

0:000> db buff
000000d0`1b6ff880  fe a4 7e a8 d9 06 ad 4a-4a 70 8a c3 57 53 54 e9  ..~....JJp..WST.
000000d0`1b6ff890  1f 9e ff 1e a2 10 56 6e-d4 da 3d 5d 9c 4b fc 25  ......Vn..=].K.%
000000d0`1b6ff8a0  da 4f 77 48 92 54 98 57-12 17 ed 2b af 97 1e 88  .OwH.T.W...+....
000000d0`1b6ff8b0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000000d0`1b6ff8c0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000000d0`1b6ff8d0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000000d0`1b6ff8e0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................
000000d0`1b6ff8f0  00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00  ................

the pertinent disassembly / decompilation in ghidra is as follows

in bool AesRNGState_generate(byte *param_1,byte *buff,dword dwLen)

 if (uVar10 < 0x80) {
    uVar2 = *(ulonglong *)(param_1 + 0x70);
    puVar1 = (ulonglong *)(param_1 + 0x70);
    uVar9 = uVar10;
    if (uVar2 != 0) {
      if (uVar2 <= uVar10) {
        uVar9 = uVar2;
      }
      memcpy(local_80,param_1 + (uVar2 - uVar9) + 0xb0,uVar9);
      SymCryptWipeAsm(param_1 + ((*puVar1 + 0xb0) - uVar9),(dword)uVar9);
      *puVar1 = *puVar1 - uVar9;
      local_80 = pbVar8 + uVar9;
      uVar9 = uVar10 - uVar9;
    }
    if (uVar9 != 0) {
      SymCryptRngAesGenerate(param_1 + 0x10,param_1 + 0xb0,0x80);
      *puVar1 = 0x80;
      memcpy(local_80,param_1 + (0x130 - uVar9),uVar9);
      SymCryptWipeAsm(param_1 + ((*puVar1 + 0xb0) - uVar9),(dword)uVar9);
      *puVar1 = ~(ulonglong)((uint)((uVar10 - 1 ^ uVar10) >> 1) & 0xf) & *puVar1 - uVar9;
    }
  }
  else {
    SymCryptRngAesGenerate(param_1 + 0x10,buff,dwLen);
  }
3
  • In my Ghidra decompiler, the function AesRNGState_generate is not defined and probably is one of the FUN_1800 functions. How can I change this and find out which one is the actual function?
    – Riemann
    Commented Aug 17, 2023 at 11:59
  • And I can not open Bcryptprimitives.dll in Ida at all, but firstly I could… I don’t know what changed
    – Riemann
    Commented Aug 17, 2023 at 12:02
  • However, that doesn't matter if I can access the buffer with windbg as you specified
    – Riemann
    Commented Aug 17, 2023 at 14:08
1

This seems to be a simple exercise in following the stubs/imports in different DLLs. This is the flow that I see

CRYPTSP!CryptGenRandom -> rsaenh!CPGenRandom -> CRYPTBASE!SystemFunction036(on my machine atleast) -> bcryptPrimitives!ProcessPrng 

bcryptPrimitives!ProcessPrng has the most code. On opening this with any disassembler of choice we can see that AesRNGState_generate is called and additionally involves g_AesStatesTable/g_rootAesRngState and further eventually bcryptPrimitives!SymCryptRngAesReseed is called which looks like.

__int64 __fastcall SymCryptRngAesReseed(__int64 a1, void *a2, size_t a3)
{
  __int64 result; // rax
  __int64 v5[6]; // [rsp+20h] [rbp-48h] BYREF

  if ( a3 - 32 > 0x20 )
    return 209715368i64;
  SymCryptRngAesDf(a2, a3);
  SymCryptRngAesUpdate(a1, v5, 0i64);
  result = 0i64;
  *(_QWORD *)(a1 + 64) = 1i64;
  memset(v5, 0, sizeof(v5));
  return result;
}

This is IDA output - I assume that Ghidra's would be similar. SymCryptRngAesDf is the df function that you mention probably.

4
  • Thank you for your answer! I could not even find the first import in Ghidra, because in the Entry function of advapi32.dll it says bVar1 = (**(code **)(puVar3 + -2))(param_1,param_2,param_3); and I had no idea which function this referred to.
    – Riemann
    Commented Aug 16, 2023 at 12:57
  • Wouldn't reseed only be called when the PRNG is reseeded? That should happen not nearly as often as calling the buffer. So then the buffer might be at a different place than inside SymCryptRngAesReseed.
    – Riemann
    Commented Aug 16, 2023 at 15:06
  • So basically, the call to the buffer could be anywhere along the flow that you specified…
    – Riemann
    Commented Aug 16, 2023 at 15:53
  • Some version of SymCrypt is open source at github.com/microsoft/SymCrypt. Please read the documentation. Also as I mentioned g_AesStatesTable/g_rootAesRngState are responsible to maintain state - you should look into those.
    – sudhackar
    Commented Aug 17, 2023 at 7:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.