Jump to content
Sign in to follow this  
Tommi Prami

Getting RDSEED with Delphi

Recommended Posts

I never really done anything with ASM, and it is more than less magic to me,  did ton of googling and landed this far... This seems to work, but I have no idea is it even remotely correct.

function RDSEED64: UInt64;
asm
  DB $48, $0F, $C7, $F8  // RDSEED 64bit value (if correct magic numbers)
  // The 64-bit result is already in RAX
end;

I think it would need as per Intel documentation some short, maybe constant loop, Apparently RDSEED returns 0 if it fails (Too many calls in short period of time). Also possibility checking the instruction availability would be nice.

I've got even less idea how to port this to 64bit compiler.

This is purely "academic" (Read hobby stuff) for me if seems too much work don't bother, but if someone really needs it and someone could help, I think there could be appreciation of some sorts.

 

-Tee-

Edited by Tommi Prami
clarification

Share this post


Link to post

RDSEED and RDRAND are available since (https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html) :

 

RDRAND = 3rd Generation Intel Core processors, Intel Xeon processor E3-1200 v2 product family, Intel Xeon processor E5 v2 and E7 v2 families, Intel Atom processor based on Silvermont microarchitecture;

 

RDSEED = Intel Core M processor family, 5th Generation Intel Core processor family, Intel Atom processor based on Goldmont microarchitecture;

 

Like you told, the functions may fail and you must check the carry flag (CF=1) and loop until that.

 

I don't check the opcode but i think you are able to use mnemonic notation to write asm code like this:

 

{$IFDEF WIN64}
function GetRandom: UInt64; register;
asm
  @here:
  rdseed, RAX       //RAX return value 64 bit (WIN64)
  jnc @here
end;
{$ELSE}
function GetRandom: UInt32; register;
asm
  @here:
  rdseed, EAX       //EAX return value 32 bit (WIN32)
  jnc @here
end;
{$ENDIF}

You can use also UInt64 with full random ( 😉 ) on 32 bit program like this:

//ONLY ON WIN32
function GetRandom: UInt64; register;
asm
  @here1:
  rdseed, EAX       //EAX return value low 32 bit (WIN32)
  jnc @here1
  @here2:
  rdseed, EDX       //EDX return value high 32 bit (WIN32)
  jnc @here2
  // UInt64 value on 32bit = EDX:EAX
end;

Bye

Edited by DelphiUdIT
  • Like 1

Share this post


Link to post
1 hour ago, Tommi Prami said:

Apparently RDSEED returns 0 if it fails

Small correction here, when RDSEED or RDRAND fail they will flag the carry flag CF, but Intel documentation itself check for 0 value in loop for failure, strange.

 

https://www.intel.com/content/dam/develop/external/us/en/documents/drng-software-implementation-guide-2-1-185467.pdf

 

This SO answer explains the difference between RDSEED and RDRAND

 

 

1 hour ago, Tommi Prami said:

I've got even less idea how to port this to 64bit compiler.

You already did, and it should work for x64 and only for X64, unless you are talking about the value checking and looping, in that case here a fix:

 

function RDSEED64(aRetryCount: UInt64 = 0): UInt64;
asm
        .noframe
        mov     RDX, aRetryCount
        inc     RDX
@LOOP:
        dec     RDX
        js      @Exit
        DB      $48, $0F, $C7, $F8  // RDSEED RAX
        jnc     @LOOP
@EXIT:
end;

function RDRAND64(aRetryCount: UInt64 = 0): UInt64;
asm
        .noframe
        mov     RDX, aRetryCount
        inc     RDX
@LOOP:
        dec     RDX
        js      @Exit
        DB      $48, $0F, $C7, $F0  // RDRAND RAX
        jnc     @LOOP
@EXIT:
end;

I see DelphiUdIT already added valuable information but am posting anyway.

Share this post


Link to post

And the x86 is even shorter

function RDSEED32(aRetryCount: UInt32 = 0): UInt32;
asm
        inc edx
@LOOP:
        dec     edx
        js      @Exit
        DB      $0F, $C7, $F8   // RDSEED EAX
        jnc     @LOOP
@EXIT:
end;

function RDRAND32(aRetryCount: UInt32 = 0): UInt32;
asm
        inc edx
@LOOP:
        dec     edx
        js      @Exit
        DB      $48, $0F, $C7, $F0  // RDRAND EAX
        jnc     @LOOP
@EXIT:
end;

My CPU doesn't support these instructions, so i wrote them on XDBG64 then pasted them on the IDE, any way this code should be checked for support on CPU side.

Edited by Kas Ob.
  • Like 1

Share this post


Link to post
16 minutes ago, Kas Ob. said:

 

You already did, and it should work for x64 and only for X64, unless you are talking about the value checking and looping, in that case here a fix:

 

What I can't understand how it worked on 32bit app, or at least it did not fail or die...

Because I need 64bit random seed.

So 32bit version should do some bit fiddling to make two calls and make 64bit value from two 32bit value?



Something like:

 

{$IF NOT Defined(WIN64)}
function RDSEED64(const ARetryCount: UInt32 = 10): UInt64;
var
  LValue1: UInt32;
  LValue2: UInt32;
begin
  LValue1 := RDSEED32(ARetryCount);
  LValue2 := RDSEED32(ARetryCount);

  Result := UInt64(LValue1) shl 32 or LValue2;
end;
{$ENDIF}

-Tee-

Edited by Tommi Prami

Share this post


Link to post
5 minutes ago, Tommi Prami said:

What I can't understand how it worked on 32bit app, or at least it did not fail or die...

This is really long story !

 

But to short it a lot, like a lot ..

Please see https://www.felixcloutier.com/x86/rdseed

Quote

In 64-bit mode, the instruction's default operation size is 32 bits. Using a REX prefix in the form of REX.B permits access to additional registers (R8-R15). Using a REX prefix in the form of REX.W promotes operation to 64 bit operands. See the summary chart at the beginning of this section for encoding data and limits.

Now to more short explain, the only difference between the x64 and x86 assembly instruction is the prefix NFx, look at my code above, and NFx is somehow similar to REX, further explanation need more manual when NFx should fail aka ( trigger hardware illegal instructions)

Share this post


Link to post
51 minutes ago, Tommi Prami said:

What I can't understand how it worked on 32bit app, or at least it did not fail or die...

Because I need 64bit random seed.

So 32bit version should do some bit fiddling to make two calls and make 64bit value from two 32bit value?



Something like:

 


{$IF NOT Defined(WIN64)}
function RDSEED64(const ARetryCount: UInt32 = 10): UInt64;
var
  LValue1: UInt32;
  LValue2: UInt32;
begin
  LValue1 := RDSEED32(ARetryCount);
  LValue2 := RDSEED32(ARetryCount);

  Result := UInt64(LValue1) shl 32 or LValue2;
end;
{$ENDIF}

-Tee-

You can also do like i show you in my example: in the asm function add a second loop that use the EDX register .... and you have a 64 bit random number for 32 bit application.

 

  • Like 1

Share this post


Link to post
1 hour ago, Kas Ob. said:

Small correction here, when RDSEED or RDRAND fail they will flag the carry flag CF, but Intel documentation itself check for 0 value in loop for failure, strange.

 

https://www.intel.com/content/dam/develop/external/us/en/documents/drng-software-implementation-guide-2-1-185467.pdf

 

This SO answer explains the difference between RDSEED and RDRAND

 

 

You already did, and it should work for x64 and only for X64, unless you are talking about the value checking and looping, in that case here a fix:

 


function RDSEED64(aRetryCount: UInt64 = 0): UInt64;
asm
        .noframe
        mov     RDX, aRetryCount
        inc     RDX
@LOOP:
        dec     RDX
        js      @Exit
        DB      $48, $0F, $C7, $F8  // RDSEED RAX
        jnc     @LOOP
@EXIT:
end;

function RDRAND64(aRetryCount: UInt64 = 0): UInt64;
asm
        .noframe
        mov     RDX, aRetryCount
        inc     RDX
@LOOP:
        dec     RDX
        js      @Exit
        DB      $48, $0F, $C7, $F0  // RDRAND RAX
        jnc     @LOOP
@EXIT:
end;

I see DelphiUdIT already added valuable information but am posting anyway.

Hello @Kas Ob., if you really think that you need a "retry count" then you must add a parameter in the function that return also the fails ... otherwise the output value of the function could be constant and this would violate its functionality.

 

Bye

Edited by DelphiUdIT

Share this post


Link to post
3 minutes ago, DelphiUdIT said:

Hello @Kas Ob., if you really think that you need a "retry count" then you must add a parameter in the function that return also the fails ... otherwise the output value of the function could be constant and this would violate its functionality.

Hi, and you are right, only the fail value is 0 as result in this case following Intel own code.

Share this post


Link to post
3 minutes ago, Kas Ob. said:

Hi, and you are right, only the fail value is 0 as result in this case following Intel own code.

Uhmm, yes and not ..... the manual says (for rdseed too):

Quote

Runtime failures in the random number generator circuitry or statistically
anomalous data occurring by chance will be detected by the self test hardware and flag the resulting data as being
bad. In such extremely rare cases, the RDRAND instruction will return no data instead of bad data. The RDRAND
instruction indicates the occurrence of this rare situation by clearing the CF flag.

That means that the RAX (or EAX or ...) are not changed by the rdrand or rdseed, and the value of these registers is likely to be non-zero.

It could instead happen that these registers, in the event of failures up to the end of the count, report repetitive data linked to the flow of the program... if the function is inserted in a single block it is very likely that the registers are always the same between the various calls.

BUT ... I believe it is unlikely that there will be continuous "defects" which is why I have not included counts in the examples given, and it is equally likely (of course) that your cycle counts will never reach zero.

Repeated failures of these functions are not documented by Intel or other sources.

 

Bye

Share this post


Link to post
9 minutes ago, DelphiUdIT said:

Uhmm, yes and not ..... the manual says (for rdseed too):

Quote

Runtime failures in the random number generator circuitry or statistically
anomalous data occurring by chance will be detected by the self test hardware and flag the resulting data as being
bad. In such extremely rare cases, the RDRAND instruction will return no data instead of bad data. The RDRAND
instruction indicates the occurrence of this rare situation by clearing the CF flag.

 

Being called bad doesn't mean it is not 0, also it will return no data in this case is 0, but the documentation doesn't point this by words. 

 

For deep explain of the opcode steps for both instruction please refer to 

https://www.felixcloutier.com/x86/rdrand

https://www.felixcloutier.com/x86/rdseed

 

For both the operation goes like this

IF HW_NRND_GEN.ready = 1
    THEN
        CASE of
            osize is 64: DEST[63:0] := HW_NRND_GEN.data;
            osize is 32: DEST[31:0] := HW_NRND_GEN.data;
            osize is 16: DEST[15:0] := HW_NRND_GEN.data;
        ESAC;
        CF := 1;
    ELSE
        CASE of
            osize is 64: DEST[63:0] := 0;
            osize is 32: DEST[31:0] := 0;
            osize is 16: DEST[15:0] := 0;
        ESAC;
        CF := 0;
FI;
OF, SF, ZF, AF, PF := 0;

meaning not only CF is flagged but the the destination also zeroed.

Share this post


Link to post

Also want to point to OP, about this retries recommendation

5.2.1 Retry Recommendations 
It is recommended that applications attempt 10 retries in a tight loop in the unlikely event 
that the RDRAND instruction does not return a random number. This number is based on 
a binomial probability argument: given the design margins of the DRNG, the odds of ten 
failures in a row are astronomically small and would in fact be an indication of a larger 
CPU issue. 

So 10 should be enough.

  • Like 2

Share this post


Link to post
3 minutes ago, Kas Ob. said:

Also want to point to OP, about this retries recommendation


5.2.1 Retry Recommendations 
It is recommended that applications attempt 10 retries in a tight loop in the unlikely event 
that the RDRAND instruction does not return a random number. This number is based on 
a binomial probability argument: given the design margins of the DRNG, the odds of ten 
failures in a row are astronomically small and would in fact be an indication of a larger 
CPU issue. 

So 10 should be enough.

Yes, so count is not necessary. But i will try to put the hardware in fails mode and see whats occurs... (... "This will lead to the RDSEED instruction returning no data transitorily" .... is a little be confusing from hardware Intel reference).

 

Stay tuned ...:classic_biggrin:

Share this post


Link to post
8 minutes ago, DelphiUdIT said:

(... "This will lead to the RDSEED instruction returning no data transitorily" .... is a little be confusing from hardware Intel reference).

And for this exactly, i always refer to https://www.felixcloutier.com/x86/ and see the operation section, warning, faults(exception)..etc, it way better and clearer documentation.

Share this post


Link to post

I try to force the fault of the instructions (really simple for one, no fault in the other):

 

the rdseed instruction set the register to zero and the CF to zero, after two (2) consecutive executions :classic_biggrin:;

 

for the rdrand instruction, I was not able to make it in fault state :classic_blink:

 

image.thumb.png.505f076f59e81cc91049580356b4d07e.png

 

so, at least for the rdseed instruction the register is set to zero (like you told @Kas Ob.)
The rdrand instruction appears to be less sensitive to errors than the rdseed.

  • Like 2

Share this post


Link to post

On side note: 

Here a sample of the $48 prefixes on x64 mode

procedure Test;
asm
  inc EDX
  inc RDX
  db $48, $FF, $C2
  db $48, $48, $FF, $C2
  db $48, $48, $48, $FF, $C2
  db $48, $48, $48, $48, $FF, $C2
  db $48, $48, $48, $48, $48, $FF, $C2
  db $48, $48, $48, $48, $48, $48, $FF, $C2
  db $48, $48, $48, $48, $48, $48, $48, $FF, $C2
  db $48, $48, $48, $48, $48, $48, $48, $48, $FF, $C2
end;

begin
  Writeln('Start');
  Test;
  Readln;
end.

Here how Delphi CPU handle wrongly disassemble them

image.thumb.png.1df376369b0514f4ce510c075d78086c.png

The right disassembly

image.png.e3fe52e42c5b2f0975bbebbaac77e712.png

 

Why the same instruction executed without a fault in x86 build, most likely the CPU handled $48 as its own instruction which will touch eax the one being overwritten by following RDxxx. 

 

just off-topic notes.

image.png

  • Like 1

Share this post


Link to post
5 hours ago, Kas Ob. said:

Why the same instruction executed without a fault in x86 build, most likely the CPU handled $48 as its own instruction which will touch eax the one being overwritten by following RDxxx. 

 

I don't understand what you mean.

 

The debugger in the disassembly windows doesn't execute the instruction that you see (wrong or right) but the real instruction. And so the results may be unexpected. But they are the right ones.

This is because the mnemonic / symbolic view is only ... a view.

 

Bye

Edited by DelphiUdIT

Share this post


Link to post
1 hour ago, DelphiUdIT said:

The debugger in the disassembly windows doesn't execute the instruction that you see (wrong or right) but the real instruction. And so the results may be unexpected. But they are the right ones.

If you commented the "inc RDX" in the above code, it will compile for win32 and run, and here the view

image.thumb.png.ad90802f34a0401aeaa062b89fba7eb7.png

 

This behavior expected and understandable for such instructions, but i have doubts when combining this with with special instructions like RDRAND, can't be sure though, see RDRAND can't be found on CPU doesn't support x64, but what is the behavior can be with emulators like QEmu ?!!

I have seen many codes that detect virtualization with such instructions specific combination, by depending either on wrong behavior or triggered faults.

Share this post


Link to post
1 hour ago, Kas Ob. said:

If you commented the "inc RDX" in the above code, it will compile for win32 and run, and here the view

.....

This behavior expected and understandable for such instructions, but i have doubts when combining this with with special instructions like RDRAND, can't be sure though, see RDRAND can't be found on CPU doesn't support x64, but what is the behavior can be with emulators like QEmu ?!!

I have seen many codes that detect virtualization with such instructions specific combination, by depending either on wrong behavior or triggered faults. 

It is natural that if the instruction is not supported by the processor / virtualization environment something will be generated (crash / AV / fault of code / .... ) but this depends on what the virtualization environment is "able to do ".

 

image.thumb.png.a91b279388e304c90b122dac8a1f50c6.pngimage.thumb.png.a70d48dfc537cfe025773bf1f29e3ae9.png

 

RDRAND itself is expected to throw an exception in a virtualized environment (and depends by settings) :

 

image.thumb.png.8d07e146562ea4446a09b135a003efae.png

 

 

Edited by DelphiUdIT

Share this post


Link to post
2 hours ago, DelphiUdIT said:

RDRAND itself is expected to throw an exception in a virtualized environment (and depends by settings) :

 

image.thumb.png.8d07e146562ea4446a09b135a003efae.png

If CPUID faulted then may be more than 90% of all software will not work in VM , right ?!

 

That part is cut from different context completely, i will assume its about either it is about VM mode not the virtual mode we are using browser in.

 

These are the default mode mentioned in the Intel doc. above 

image.thumb.png.d57784ce838b3a00d466ce2238fc1df4.png

 

While that paragraph 27.2.5 from different one and most likely talking about Intel VT-x instructions https://docs.hyperdbg.org/tips-and-tricks/considerations/basic-concepts-in-intel-vt-x not form Intel but way more clearer than Intel.

 

Now back to this

  

2 hours ago, DelphiUdIT said:

RDRAND itself is expected to throw an exception in a virtualized environment

RDRAND will not fault in a host code nor in guest code, BUT (big one) will fault in the virtualizing code layer between these two, this to protect the host and the guest, the virtual machine should be in oblivious realm like a black hole to guarantee security and integrity of the process, the VM will act like a kernel at its own, closed and isolated from the host kernel as much as it should, hence it should be limited with instructions, this limitation will be left when execution handed to wither the host or the guest.

 

About this

image.thumb.png.2e2e65c32e55572634d7c0cec54df807.png

These columns will show what will fail in which mode, but not discussing any virtualization.

 

Share this post


Link to post

Intel's documentation specifically talks about that chapter regarding NON-ROOT virtualized code.

But this does not mean that the software CRASHES, the virtual machine managers (aka supervisor, aka hypervisor) such as VirtualBox or others implement their own ROOT system alongside that of Intel and which outclasses the functioning and runs its own monitor on the entire virtual environment.
From what I know, the VirtualBox hypervisor for example runs as VMX ROOT and the virtual machine runs in ring 1 (OS) and ring 3 (user space). VirtualBox performs all checks and handling of all exceptions of both the guest operating system (OS in ring 1) and USER (in ring 3) ... including CPUIDs and all others if they occur.
All this for classic 32-bit VMs, while for 64-bit it's all more complex.

 

I would also like to remind you that for any instruction that can generate an exception there is a specific test that can be used and if the test cannot be performed or the test itself fails then the instruction(s) MUST NOT BE USED... including the CPUID. Example to test if the CPUID is available.

 

image.thumb.png.b7225d113f8e14d15208381cd7d4d3d7.pngimage.thumb.png.17f4646c909967d033b164255b2fc421.png

 

So, no software should crash if CPUID is used ONLY when it is available, and this is true also for RDRAND and RDSEED of course:

 

image.thumb.png.3d8b0bdab32e5fbf2d34aeb9cd9bbc71.png

image.thumb.png.3bf218e7955877cd3150cbf9afd16814.png

 

Bye

 

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
Sign in to follow this  

×