-1

I am working on to use a C library in C#. Library is the globalplatform by kaoh, here is the github link.

What I have done is compiled the C library into a shared library (DLL) by following these steps written in the readme.

cd \path\to\globalplatform
cmake -G "NMake Makefiles" -DZLIB_ROOT="C:\Users\OFFICE\globalplatform\zlib-1.2.8\win32-build" -DCMOCKA_ROOT="C:\Users\OFFICE\Desktop\globalplatform\cmocka-cmocka-1.1.5\build-w32"
nmake

I used dllimport in my C# code and successfully called some of the library functions.

But then I hit a problem when trying to call a certain function that have a specific struct as its parameter. The problem is, its members value in the C side is differs from what it have been assigned in C# side.

I decided to do some testing by replicating the same struct then printing its members value. Here I created two structs which is identical to the struct that causing the problem:

typedef struct {
    BYTE securityLevel;
    BYTE secureChannelProtocol; 
    BYTE secureChannelProtocolImpl; 
    BYTE keySetVersion; 
    BYTE keyIndex; 
    DWORD invokingAidLength;
    LONG sessionEncryptionCounter; 
    DWORD keyLength; 
    BYTE C_MACSessionKey[32]; 
    BYTE R_MACSessionKey[32]; 
    BYTE encryptionSessionKey[32]; 
    BYTE dataEncryptionSessionKey[32]; 
    BYTE lastC_MAC[16];
    BYTE invokingAid[16];   
    BYTE lastR_MAC[8]; 
}ZEN;

typedef struct{
    BYTE securityLevel;
    BYTE secureChannelProtocol;
    BYTE secureChannelProtocolImpl;
    BYTE keySetVersion; 
    BYTE keyIndex; 
    DWORD invokingAidLength;
    LONG sessionEncryptionCounter;
    DWORD keyLength; 
    BYTE C_MACSessionKey[32]; 
    BYTE R_MACSessionKey[32]; 
    BYTE encryptionSessionKey[32]; 
    BYTE dataEncryptionSessionKey[32];
    BYTE lastC_MAC[16];
    BYTE invokingAid[16];
    BYTE lastR_MAC[8];
}MYARRAYSTRUCT;

For printing its member value, I add these functions in the C library:

void test_zen(ZEN * pZen);
void test_myarraystruct(MYARRAYSTRUCT * pStruct);
#define _T(arg) arg
void test_myarraystruct(MYARRAYSTRUCT * pStruct){
    _tprintf(_T("MY->securityLevel: %x\n"), pStruct->securityLevel);
    _tprintf(_T("MY->secureChannelProtocol: %x\n"), pStruct->secureChannelProtocol);
    _tprintf(_T("MY->secureChannelProtocolImpl: %x\n"), pStruct->secureChannelProtocolImpl);
    unsigned int i;
    _tprintf(_T("MY->C_MACSessionKey\n"));
    for(i = 0; i < 32; i++){
        _tprintf(_T("%x "),pStruct->C_MACSessionKey[i]);
    }
    _tprintf(_T("\n"));
    _tprintf(_T("MY->R_MACSessionKey\n"));
    for(i = 0; i < 32; i++){
        _tprintf(_T("%x "),pStruct->R_MACSessionKey[i]);
    }
    _tprintf(_T("\n"));
    _tprintf(_T("MY->encryptionSessionKey\n"));
    for(i = 0; i < 32; i++){
        _tprintf(_T("%x "),pStruct->encryptionSessionKey[i]);
    }
    _tprintf(_T("\n"));
    _tprintf(_T("MY->dataEncryptionSessionKey\n"));
    for(i = 0; i < 32; i++){
        _tprintf(_T("%x "),pStruct->dataEncryptionSessionKey[i]);
    }
    _tprintf(_T("\n"));
    _tprintf(_T("MY->lastC_MAC\n"));
    for(i = 0; i < 16; i++){
        _tprintf(_T("%x "),pStruct->lastC_MAC[i]);
    }
    _tprintf(_T("\n"));
    _tprintf(_T("MY->lastR_MAC\n"));
    for(i = 0; i < 8; i++){
        _tprintf(_T("%x "),pStruct->lastR_MAC[i]);
    }
    _tprintf(_T("\n"));
    _tprintf(_T("MY->keySetVersion: %x\n"), pStruct->keySetVersion);
    _tprintf(_T("MY->keyIndex: %x\n"), pStruct->keyIndex);
    _tprintf(_T("MY->invokingAid\n"));
    for(i = 0; i < 16; i++){
        _tprintf(_T("%x "),pStruct->invokingAid[i]);
    }
    _tprintf(_T("\n"));
    _tprintf(_T("MY->invokingAidLength: %d\n"), pStruct->invokingAidLength);
    _tprintf(_T("MY->sessionEncryptionCounter: %ld\n"), pStruct->sessionEncryptionCounter);
    _tprintf(_T("MY->keyLength: %d\n"), pStruct->keyLength);
}

I used pass by pointer to pass the struct, replicating the function that causing the problem.

Now, for the C#. First, I created a C# library project and did the prototyping/signature/marshalling as follow :

[StructLayout(LayoutKind.Sequential)]
public struct MYARRAYSTRUCT
{
    public byte securityLevel;
    public byte secureChannelProtocol;
    public byte secureChannelProtocolImpl;
    public byte keySetVersion;
    public byte keyIndex;        
    public int invokingAidLength;
    public int sessionEncryptionCounter;
    public int keyLength;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] C_MACSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] R_MACSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] encryptionSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] dataEncryptionSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] lastC_MAC;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] invokingAid;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]  public byte[] lastR_MAC;
}

[StructLayout(LayoutKind.Sequential)]
public class ZEN
{
    public byte securityLevel;
    public byte secureChannelProtocol;
    public byte secureChannelProtocolImpl;
    public byte keySetVersion;
    public byte keyIndex;
    public int invokingAidLength;
    public int sessionEncryptionCounter;
    public int keyLength;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] C_MACSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] R_MACSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] encryptionSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 32)] public byte[] dataEncryptionSessionKey;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] lastC_MAC;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 16)] public byte[] invokingAid;
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 8)]  public byte[] lastR_MAC;
}

[DllImport("globalplatform.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void test_zen(ref ZEN pStruct);

[DllImport("globalplatform.dll", CallingConvention = CallingConvention.Cdecl)]
public static extern void test_myarraystruct(ref MYARRAYSTRUCT pStruct);

Then, call the C# library above via console project, as follow:

string aidmanager = "A000000003000000";
byte[] AID = HexStringToArrayOfByte(aidmanager);

GlobalPlatform.MYARRAYSTRUCT miown = new GlobalPlatform.MYARRAYSTRUCT();
miown.securityLevel = 0;
miown.secureChannelProtocol = 0;
miown.secureChannelProtocolImpl = 0;
miown.C_MACSessionKey = new byte[32];
miown.R_MACSessionKey = new byte[32];
miown.encryptionSessionKey = new byte[32];
miown.dataEncryptionSessionKey = new byte[32];
miown.lastC_MAC = new byte[16];
miown.lastR_MAC = new byte[8];
miown.keySetVersion = 0;
miown.keyIndex = 0;
miown.invokingAid = new byte[16];
Array.Copy(AID, 0, miown.invokingAid, 0, 8);
miown.invokingAidLength = 8;
miown.sessionEncryptionCounter = 0;
miown.keyLength = 16;
GlobalPlatform.test_myarraystruct(ref miown);

GlobalPlatform.ZEN myStruct = new GlobalPlatform.ZEN();
myStruct.securityLevel = 0;
myStruct.secureChannelProtocol = 1;
myStruct.secureChannelProtocolImpl = 3;
myStruct.C_MACSessionKey = new byte[32];
myStruct.R_MACSessionKey = new byte[32];
myStruct.encryptionSessionKey = new byte[32];
myStruct.dataEncryptionSessionKey = new byte[32];
myStruct.lastC_MAC = new byte[16];
myStruct.lastR_MAC = new byte[8];
myStruct.keySetVersion = 0;
myStruct.keyIndex = 1;
myStruct.invokingAid = new byte[16];
Array.Copy(AID, 0, myStruct.invokingAid, 0, 8);
myStruct.invokingAidLength = 8;
myStruct.sessionEncryptionCounter = 100;
myStruct.keyLength = 24;
GlobalPlatform.test_zen(ref myStruct);

Both C# library and C# console are set to use x86 as its platform target. (Project>Properties>Build>Platform targer: x86)

Finally, this is what I got:

MY->securityLevel: 0
MY->secureChannelProtocol: 0
MY->secureChannelProtocolImpl: 0
MY->C_MACSessionKey
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
MY->R_MACSessionKey
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
MY->encryptionSessionKey
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
MY->dataEncryptionSessionKey
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
MY->lastC_MAC
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
MY->lastR_MAC
0 0 0 0 0 0 0 0
MY->keySetVersion: 0
MY->keyIndex: 0
MY->invokingAid
a0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0
MY->invokingAidLength: 8
MY->sessionEncryptionCounter: 0
MY->keyLength: 16

ZEN->securityLevel: 28
ZEN->secureChannelProtocol: f6
ZEN->secureChannelProtocolImpl: 94
ZEN->C_MACSessionKey
3 0 0 0 f4 ee 6f 0 2c f0 6f 0 70 ef 6f 0 28 ef 6f 0 88 ef 6f 0 de 7 c9 4 60 51 75 2
ZEN->R_MACSessionKey
7c 4b 75 2 28 24 75 2 30 58 75 2 18 57 75 2 1c 58 75 2 18 57 75 2 0 58 75 2 18 57 75 2
ZEN->encryptionSessionKey
d4 57 75 2 18 57 75 2 a8 57 75 2 18 57 75 2 7c 57 75 2 18 57 75 2 50 57 75 2 18 57 75 2
ZEN->dataEncryptionSessionKey
18 57 75 2 e4 55 75 2 f8 ee 6f 0 d0 55 75 2 f8 ee 6f 0 b4 55 75 2 f8 ee 6f 0 88 55 75 2
ZEN->lastC_MAC
f8 ee 6f 0 5c 55 75 2 f8 ee 6f 0 30 55 75 2
ZEN->lastR_MAC
50 4f 75 2 4 4e 75 2
ZEN->keySetVersion: 0
ZEN->keyIndex: 1
ZEN->invokingAid
f8 ee 6f 0 4 55 75 2 f8 ee 6f 0 58 51 75 2
ZEN->invokingAidLength: 1726417387
ZEN->sessionEncryptionCounter: 7335428
ZEN->keyLength: 0

The ZEN members value seems to be assigned with random value and it changes every time its compiled.

Why is the ZEN members have incorrect value but the MYARRAYSTRUCT members is correct?

Any suggestion to fix this?

of32 inc
  • 105
  • 1
  • 7
  • 2
    What is `GP211_SECURITY_INFO` and can't we have a [mcve]? – David Heffernan Jun 16 '22 at 09:13
  • 2
    Start simple. It looks like the first byte is wrong myStruct.securityLevel = 0; I suspect the alignment is wrong. That compiler options are not compatible with data. The structures may have to start on a 8/16/32/64 address boundary and are not matching the dll requirments. Is the dll and c# project using same alignment? – jdweng Jun 16 '22 at 09:50
  • @jdweng in all possible layouts, irrespective of alignment, packing, etc., the first member has offset 0. Any padding is always applied at the end of a member. – David Heffernan Jun 16 '22 at 11:45
  • Might be something on the C side – Charlieface Jun 16 '22 at 12:50
  • @DavidHeffernan : No!!! It has to do with the micro and the micro addressing mode. Alignment is on 8 bit address/ 16 bit address, 32 bit address or 64 bit address. It is the pack option in the strucute : https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.structlayoutattribute.pack?force_isolation=true&view=net-6.0 – jdweng Jun 16 '22 at 13:36
  • 1
    @jdweng wrong, offset of first member is 0. If you disagree, then perhaps you should write an asnwer here: https://stackoverflow.com/questions/16482784/is-the-first-field-of-a-c-structure-always-guaranteed-to-be-at-offsetof-0 These sort of question is best addressed by somebody who has experience of pinvoke. – David Heffernan Jun 16 '22 at 15:03
  • @DavidHeffernan : I have 40 year of experience. If the first address is wrong in a structure the address is wrong. I have designed circuits that uses addressing in x86 processors. What most likely is happening is the code is assembled wrong. – jdweng Jun 16 '22 at 15:55
  • @jdweng it's important to have relevant experience. As we have now established the offset of the first member is zero. – David Heffernan Jun 16 '22 at 20:24
  • @DavidHeffernan ah sorry, i copy pasted the wrong code, i fixed it. – of32 inc Jun 17 '22 at 00:55
  • @jdweng hello can you elaborate more, what same allingment ? how to set this ? I am sorry I have 0 experience in this stuff, I have already edited my post with some additional details. – of32 inc Jun 17 '22 at 01:28
  • 1
    @jdweng In both the C and C++ standards, the address of a structure's first member _must_ be equal to the address of the structure itself. There _cannot_ be padding at the start of a structure. – Etienne de Martel Jun 17 '22 at 01:52
  • @of32inc What are `test_zen` and `test_myarraystruct` doing? – Etienne de Martel Jun 17 '22 at 01:54
  • @EtiennedeMartel hi thx for commenting, I added the code of test_myarraystruct in the post, and test_zen is doing what exactly test_myarraystruct doing. Is how i print the members wrong? – of32 inc Jun 17 '22 at 01:59
  • I wouldn't be surprised if that was the case, yes. How do you print a byte in `test_zen`? – Etienne de Martel Jun 17 '22 at 02:15
  • @EtiennedeMartel exactly the same as how i print the byte in test_myarraystruct – of32 inc Jun 17 '22 at 02:17
  • @EtiennedeMartel : I never said padding at the beginning. I meant the address was wrong which could be an alignment issue. Alignment is the starting address. – jdweng Jun 17 '22 at 12:23
  • @jdweng In your second comment you disagreed with my statement that the offset of the first member is always zero. Which would imply padding at the beginning. The entire point of my statement was to point out that alignment issues could not explain the first member appearing to be wrong. And this was all borne out by the resolution. Whilst alignment could be an issue, it could **never** have explained the error in the first member. – David Heffernan Jun 17 '22 at 15:01

1 Answers1

2

The problem is that you declare ZEN in your C# as class. This makes it a reference type. And so when you pass ZEN as ref then you pass a pointer to a pointer to the structure. That's one level of indirdction too many.

By contrast you declared MYARRAYSTRUCT as struct, a value type. When you pass this as ref, you pass a pointer to the structure.

Change ZEN to be declared as struct.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • ah that is why, I did that because I want to pass NULL reference to the function. Thank you so much! – of32 inc Jun 17 '22 at 07:10
  • If you want to be able to pass null then you can use `class` but you will need to remove the `ref` from the function declaration. – David Heffernan Jun 17 '22 at 07:19