📜 ⬆️ ⬇️

“Why is it possible for everyone, but I can't?” Or reverse the API and get data from eToken



Once in our company, the task was to increase the level of security when transferring very important files . In general, word for word, we came to the conclusion that it is necessary to transmit using scp, and the private key of the certificate for authorization should be stored on an eToken keychain, since we have accumulated a certain amount of them.

The idea seemed good, but how to implement it? Then I remembered how once the bank client was not working in accounting, cursing the absence of a library with the talking name etsdk.dll, I was curious and I started to pick it.
')
In general, the company-developer on its website distributes the SDK, but for this it is necessary to register as a software company, and this is clearly not me. It was not possible to find documentation on the Internet, but curiosity prevailed and I decided to sort it all out myself. The library is here, there is time, who will stop me?

First of all, I launched the Export Viewer DLL from NirSoft, which showed me a decent list of functions exported by the library. The list looks good, the logic and sequence of actions can be traced when working with tokens. However, one list is not enough, you need to understand what parameters, in what order to transmit and how to get results.



It was then that it was time to remember the youth and launch OllyDbg version 2.01, load the ccom.dll library of the Crypto-Com Cryptosystem into it, used by the client bank and using that very etsdk.dll, and start to figure out exactly how they do it.

Since there is no executable file, the library will be loaded using loaddll.exe from the Olly kit, so we can not even dream of full debugging. In fact, we will use the debugger as a disassembler (yes, there is an IDA, but I have never worked with it, and in general it is paid).

Call the context menu and select Search for> All intermodular calls, arrange the result by name and look for functions starting with ET *, and do not find it. This means that the library connects dynamically, so in the same list we look for the calls to GetProcAddress, we look at them and with a certain attempt we stumble upon trying to find out the address of the ETReadersEnumOpen function, and looking a little further we see loading of all the functions from the etsdk.dll library into memory.



Not bad. The obtained function addresses are saved in memory with commands of the type MOV DWORD PTR DS: [10062870], EAX, select each such command, call the context menu and select Find references to> Address constant. In the opened window, the current command and all places of the function call will be displayed. Let's go over them and write down a comment with the name of the function being called - this will make our future life easier.

It's time to figure out how to properly call these functions. Let's start from the beginning and study getting information about readers. Moving to the place where the ETReadersEnumOpen function is called and, thanks to the comments left, we see that ETReadersEnumOpen, both ETReadersEnumNext and ETReadersEnumClose are concentrated in one function - obviously, she, among other things, is engaged in obtaining a list of readers.

All functions use the cdecl calling convention. This means that the result will be returned in the EAX register, and the parameters will be passed through the stack from right to left. In addition, this means that all parameters have the dimension of a double word, and if they do not have it, they expand to it, which will simplify our life.

Let's look at the neighborhoods of the ETReadersEnumOpen call:



One parameter is passed, which is a pointer to a local variable, and after the call, if the result is not 0, control is transferred to some explicit debugging code, and if it is equal, go further (the JGE command transfers control if the ZF and OF flags are equal, and the flag OF, the TEST command always resets to 0). Thus, I conclude the following order: a variable is passed to the function by reference to which some enum identifier is returned, and as a result, the function returns an error code or 0 if there is no error.

Go to ETReadersEnumNext:



Two parameters are passed to it: the value of a variable obtained using ETReadersEnumOpen (enum identifier) ​​and a pointer to a local variable, where, obviously, the next value is returned. And since the parameters are passed in order from right to left, it is the first parameter - the identifier, and the second - the result pointer. The error code is still returned via EAX, and, judging by the construction of the cycle, it is used not only for the error message, but also for the message that there is nothing more to list.

With ETReadersEnumClose is still simpler: the enumeration identifier is passed to it, but the result doesn’t bother anyone.



It's time to test our understanding of these features. Here I have to make a small digression: the fact is that by profession I am a sysadmin, and therefore serious compiled programming languages ​​are not really mine. For work, I need Bash and Python for Linux more, but if I need to quickly build something under Windows, I use my favorite AutoIt .

The advantages for me are:


Minuses:


This digression was to ensure that examples of the use of functions will be sculpted exactly on AutoIt. Calling functions from external libraries, due to implicit typing in the language, looks somewhat clumsy, but it works.

Let’s get started: frankly, we have no idea what size functions return, so we’ll give a large buffer to begin with, and see what happens. Code to start:

Dim $ETSdkDll=DllOpen('etsdk.dll') Dim $buf=DllStructCreate('BYTE[32]') Func PrintBuf($buf) For $i=1 To DllStructGetSize($buf) ConsoleWrite(Hex(DllStructGetData($buf,'buf',$i),2)&' ') Next ConsoleWrite(@CRLF) EndFunc ConsoleWrite('Buffer before: ') PrintBuf($buf) $result=DllCall($ETSdkDll,'DWORD','ETReadersEnumOpen', _ 'PTR',DllStructGetPtr($buf) _ ) ConsoleWrite('Buffer after: ') PrintBuf($buf) ConsoleWrite('Return value: '&$result[0]&@CRLF) 


Having executed it, we get an output like this:

 Buffer before: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Buffer after: 44 6F C8 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Return value: 0 

We run several times and see that only the first 4 bytes change, which means that a 4-byte integer is used as an identifier, which means we can comb the code for calling this function to such a state:

 Func ETReaderEnumOpen() Local $id=DllStructCreate('DWORD') Local $result=DllCall($ETSdkDll,'DWORD','ETReadersEnumOpen', _ 'PTR',DllStructGetPtr($id) _ ) Return $result[0]?0:DllStructGetData($id,1) EndFunc 

Similar experiments with the ETReadersEnumNext function showed the following: the first 260 bytes of the buffer contain the name of the reader and zeros. The sequential call of this function listed all the readers in the system (for example, three of them were created in advance for ruToken). Readers for eToken are created dynamically, depending on the number of connected tokens and, most interestingly, they are installed in a unit of 261st byte buffer, which, judging by everything, indicates that the reader is compatible with our library. If you look at the disassembled code, you can see that the records with 261st bytes equal to 0 are not processed. All other bytes until the end of the kilobyte buffer for all readers are 0 and do not differ.

So, we dealt with the readers, now we need to understand what's next. After examining the list of functions, I came to the conclusion that the call sequence should be the following: first, we bind the required reader, at this stage we can find out general information about the inserted token, then we login, and after that we get access to the file system. Thus, the following on the queue functions ETTokenBind and ETTokenUnbind.



ETTokenBind looks complicated and incomprehensible, but, after digging for some time, I came to the conclusion that two parameters are passed to the function, the first of which is a pointer to the 328 byte buffer (0x0148), and the second is a pointer to the line with the reader's name. By experiments, it was found that the identifier is returned to the first four bytes of the buffer (hereinafter: the binding identifier). What stands out the rest of the buffer - until the riddle. What tokens I would not experiment with, the remaining 324 bytes of the buffer remained filled with zeros. The specified identifier, which is logical, is successfully used as an argument to the ETTokenUnbind and ETTokenRebind functions.



The next function in the queue is ETRootDirOpen. It takes three parameters: a pointer to the result, a binding identifier and a constant. The function has several features.

First, the return result of this function is checked not only for equality to zero (success), but also for equality of the lower two bytes number 0x6982, and in case the result is equal to this number, control is transferred to the function that subsequently calls ETTokenLogin, and then tries again call ETRootDirOpen. From this we can conclude that 0x6982 is an error code meaning “Authorization Required”. Looking ahead, I’ll say that all the other functions that work with files and folders are the same.

Second: as one of the parameters, this function takes the constant 0xF007. There are no calls with other constants in the code. Perhaps this constant somehow characterizes the information recorded on the token (the set of root folders?). I tried to brute force all the values ​​of the two-byte constant, and the token responded only to the values ​​0x0001, 0xF001-0xF00B (by the way, I never asked for authorization). Later I found out that the same folders are available on the freshly initialized token. After thinking about this for some time, I came to the conclusion that according to the developer’s plan, different root folders are used for different purposes, and somewhere it’s written that 0xF007 is for keys.

Third: the value returned by the function is not visible in the screenshot, but returns to the middle of the 328-byte buffer that was allocated earlier, from which it can be concluded that the buffer is a structure that stores a variety of identifiers and data relating to the token in question.

Once the authorization attempt has gone, time to figure it out. The ETTokenLogin function takes two parameters: a binding identifier and a buffer pointer. At first I thought that the buffer is used to output some result, but the experiments showed that the following algorithm is used: if the pointer is zero or points to an empty string, the library draws an interface window asking for a password, but if it points to a non-empty string, this line used as a password. ETTokenLogout takes only one parameter: a binding identifier.

The following group of functions: ETDirEnumOpen, ETDirEnumNext and ETDirEnumClose. You can try to unravel them without looking at the code. In general, they should work in the same way as ETReadersEnum *, with the only difference that in ETDirEnumOpen the current folder identifier will also be passed as a parameter. Check - works.

The group of ETFilesEnumOpen, ETFilesEnumNext and ETFilesEnumClose functions are simply obliged to work the same, however, we cannot yet verify this with certainty, since in the root folder of the token being examined, apparently, there are no files, which means that it’s time to go deep into the folder tree using the ETDirOpen function.



This API seems to have drawn a tradition, according to which, the first parameter is used to return the result, so suppose that this is also true this time. The second parameter, before being passed to the function, is modified using the MOVZX EDI, DI command, i.e. the word expands to a double word. Obviously, this is necessary in order to transfer the two-byte folder name in a four-byte parameter. Well, the third parameter according to the logic of things should be the identifier of the open folder. We try - it turned out. ETDirClose is guessed without surprises: 1 parameter - folder identifier.

So, we have learned enough to list all the files and folders on the token. The following simple code will do just that (I do not do the description of the DllCall call here - it will be for all functions in the module text at the end of the article):

 Func PrintDir($Id,$Prefix) Local $EnumId=ETDirEnumOpen($Id) While 1 Local $dir=ETDirEnumNext($EnumId) If @error Then ExitLoop ConsoleWrite($Prefix&'(dir)'&Hex($dir,4)&@CRLF) Local $DirId=ETDirOpen($dir,$Id) PrintDir($DirId,$Prefix&@TAB) ETDirClose($DirId) WEnd ETDirEnumClose($EnumId) $EnumId=ETFilesEnumOpen($Id) While 1 Local $file=ETFilesEnumNext($EnumId) If @error Then ExitLoop ConsoleWrite($Prefix&'(file)'&Hex($file,4)&@CRLF) WEnd ETFilesEnumClose($EnumId) EndFunc Local $EnumId=ETReaderEnumOpen() If $EnumId Then While 1 Local $reader=ETReaderEnumNext($EnumId) If @error Then ExitLoop If Not $reader[1] Then ContinueLoop Local $BindId=ETTokenBind($reader[0]) ConsoleWrite($reader[0]&':'&@CRLF) ETTokenLogin($BindId,'123456') Local $DirId=ETRootDirOpen($BindId) PrintDir($DirId,@TAB) ETDirClose($DirId) WEnd EndIf ETReaderEnumClose($EnumId) 

Result in the console:

 Aladdin Token JC 0: (dir)1921 (dir)DDDD (file)0002 (file)0003 (file)0004 (file)0001 (file)A001 (file)B001 (file)C001 (file)AAAA (file)D001 

Fine!

Well, we learned to open and view folders, it's time to learn how to open and read files. ETFileOpen accepts 3 parameters, so for the beginning we try to do the same as for ETDirOpen: result, file name, folder identifier and breaks: the developers swapped the last two parameters. Well, at least ETFileClose works without surprises.

ETFileRead. The worst function of all, because perceives as many as 5 parameters. Where so much? Let's try to list what we need: where to read (file), where to read (buffer), how much to read and where to start from where to read. Let's try to figure out what and how:



As you can see, the third parameter passed to the ETFileRead function is always 0xFFFF, so I am inclined to assume that this is the length of a readable piece of data. The remaining 4 parameters come to the function that I called FileReadHere from the outside in the same manner. Below is a picture of a neighborhood calling this function. The value of the first parameter is taken from the memory at ESI + 8. A pointer to this address is used in the FileOpenHere function (named for the same principle) and there, obviously, the identifier of the open file is written. The second parameter is zero, so we assign it responsible for the starting point of reading the file. The third parameter (the fourth one for ETFileRead) is muddy, so we assign it a pointer to the result buffer. The fifth parameter is unusual at all. It contains the word from the ESI + 12 address, expanding to a double word - this is unusual, since so far, all the offsets I've seen have been multiples of 4 (12 is not a multiple of 4, because it is 0x12, that is, 18 in decimal). The address of ESI + 10 is not mentioned anywhere in the vicinity, but ESI + 0C is transferred to FileGetInfoHere, so you will have to deal with the ETFileGetInfo function first. It is simple, the first parameter is the file identifier, the second is a pointer to the result buffer. After a call in the buffer 1, 2, 3, 7 and 8 bytes are changed. Looking ahead, I will say that it turns out that the last two bytes are the file size. It is this value that is passed to the ETFileRead function and to the function that initializes the output buffer for it. The first two bytes of the ETFileGetInfo result were the file name. I did not understand the meaning of the third, but it was set to 1 for only one file on the token. Thus, the following order of parameters emerges: file identifier, read start point, maximum number of read bytes, pointer to buffer, buffer size.

Since we have touched on ETFileGetInfo, we should immediately implement ETDirGetInfo: the order of the parameters is the same, only the folder ID is involved, not the file. Return value: folder name by ID.

At this point we finished reading from the token, it's time to write on the token. Start by creating a folder. ETDirCreate function parameters: a pointer to the result (obviously, after the folder is created, the identifier opens and returns here), the folder name, the parent folder identifier and 0. The fourth parameter is hard coded in the code and I still do not understand what it affects. Folders are successfully created for any value. ETDirDelete takes only 1 parameter, so this is obviously the ID of the open folder. ETFileCreate takes five parameters: a pointer to the result, similar to ETDirCreate, a folder identifier, a file name, a file size, and a fifth parameter. If the fifth parameter is set to a non-zero value, then the next ETFileGetInfo call for this file, the third byte of the result (the same, incomprehensible) will be set to 1. After thinking, I conducted an experiment and made sure that when the attribute is set, you need to enter password, if not, it is not necessary. It's funny that on the token with which I experimented, there was only one such file. I hope that all other files are encrypted on the key of this. ETFileDelete works without surprises, similar to ETDirDelete.

The last function referenced in this library is ETFileWrite. It takes 4 arguments: file identifier, zero (experiment shows that it is confusing relative to the beginning of the file), a pointer to the data buffer and data size. It is important to remember that the file does not expand. If the sum of the offset and file length exceeds the file size, the recording does not occur, so if the file size needs to be changed, the file will have to be deleted and recreated with the new size.

Further: if we recall the library export table, then there are 5 more functions in it, however, their call is not implemented in this library, which works with Cryptographic Information System Crypto-Com. Fortunately for us, the same bank also distributes the library for working with Message-Pro - mespro2.dll, which can also work with tokens and there is a little more in it, namely the ETTokenLabelGet call.



The screenshot shows that there are two function calls that differ in that in the first case, the second parameter is zero, and in the second, to some number. The third parameter is always a pointer, so suppose that this is the result, and the first one would be logical to assume that the identifier is a binding with a token. We try to start with zero as the second parameter - the first 4 bytes in the buffer have changed to the value 0x0000000A, i.e. 10, and this is exactly the length of the name "TestToken" with a zero byte at the end. But if a double word is returned by the pointer to the third parameter, it turns out that the pointer to the buffer of the desired size must be passed to the second parameter. Therefore, we conclude this order: the first time we run the function so that the second parameter is a null pointer, and the third is a pointer to a double word. Then we initialize the buffer of the desired size and run the function a second time, while the second parameter is a pointer to the buffer.

But the call for 4 more functions is not implemented here, so I got their implementation by brute force and intuition: I found that if the called function passes too few parameters, it causes a critical error when executing the program, it allows you to experimentally select the number of parameters of the remaining functions:

ETTokenIDGet: 3
ETTokenMaxPinGet: 2
ETTokenMinPinGet: 2
ETTokenPinChange: 2

ETTokenIDGet takes too many parameters to return some simple value, so we run it in the same way as ETTokenGetLabel - it turns out on the first attempt and returns a string with a number written on the side of the token.

ETTokenMaxPinGet and ETTokenMinPinGet, on the contrary, have a number of parameters, ideal for returning a single numeric value. We try the first parameter - the binding identifier, the second - the pointer to the number. As a result, we obtain the maximum and minimum possible password lengths specified in the token settings.

ETTokenPinChange, based on the name, serves to change the password to a token, respectively, should only accept the bundle identifier and a pointer to the string with the new password. We try the first time, we get the error code 0x6982, which, as we know, means the need to perform a login on the token. Is logical. Repeat with login and short password - we get error 0x6416. We conclude that the length of the password does not comply with the policy. Repeat with a long password - fulfills.

Now we reduce all the functions into one module and save it - we will embed it in other projects. The text of the module is as follows:

etsdk.au3
 ;Func ETReadersEnumOpen() ;Func ETReadersEnumNext($EnumId) ;Func ETReadersEnumClose($EnumId) ;Func ETTokenBind($ReaderName) ;Func ETTokenRebind($BindId) ;Func ETTokenUnbind($BindId) ;Func ETTokenLogin($BindId,$Pin='') ;Func ETTokenPinChange($BindId,$Pin) ;Func ETTokenLogout($BindId) ;Func ETRootDirOpen($BindId,$Dir=0xF007) ;Func ETDirOpen($Dir,$DirId) ;Func ETDirCreate($Dir,$DirId) ;Func ETDirGetInfo($DirId) ;Func ETDirClose($DirId) ;Func ETDirDelete($DirId) ;Func ETDirEnumOpen($DirId) ;Func ETDirEnumNext($EnumId) ;Func ETDirEnumClose($EnumId) ;Func ETFileOpen($File,$DirId) ;Func ETFileCreate($File,$DirId,$Size,$Private=0) ;Func ETFileGetInfo($FileId) ;Func ETFileRead($FileId) ;Func ETFileWrite($FileId,$Data,$Pos=0) ;Func ETFileClose($FileId) ;Func ETFileDelete($FileId) ;Func ETFilesEnumOpen($DirId) ;Func ETFilesEnumNext($EnumId) ;Func ETFilesEnumClose($EnumId) ;Func ETTokenLabelGet($BindId) ;Func ETTokenIDGet($BindId) ;Func ETTokenMaxPinGet($BindId) ;Func ETTokenMinPinGet($BindId) Const $ET_READER_NAME=0 Const $ET_READER_ETOKEN=1 Const $ET_FILEINFO_NAME=0 Const $ET_FILEINFO_PRIVATE=1 Const $ET_FILEINFO_SIZE=2 Dim $ETSdkDll=DllOpen('etsdk.dll') Func ETReadersEnumOpen() Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETReadersEnumOpen', _ 'PTR',DllStructGetPtr($Out) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETReadersEnumNext($EnumId) Local $Reader=DllStructCreate('CHAR name[260]; BYTE etoken;') Local $CallRes=DllCall($ETSdkDll,'WORD','ETReadersEnumNext', _ 'DWORD',$EnumId, _ 'PTR',DllStructGetPtr($Reader) _ ) Local $Result[2]=[ DllStructGetData($reader,'name'), _ DllStructGetData($reader,'etoken')] Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :$Result EndFunc Func ETReadersEnumClose($EnumId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETReadersEnumClose', _ 'DWORD',$EnumId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETTokenBind($ReaderName) Local $In=DllStructCreate('BYTE['&(StringLen($ReaderName)+1)&']') Local $Out=DllStructCreate('DWORD') DllStructSetData($In,1,$ReaderName) Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenBind', _ 'PTR',DllStructGetPtr($Out), _ 'PTR',DllStructGetPtr($In) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETTokenRebind($BindId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenRebind', _ 'DWORD',$BindId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETTokenUnbind($BindId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenUnbind', _ 'DWORD',$BindId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETTokenLogin($BindId,$Pin='') Local $In=DllStructCreate('BYTE['&(StringLen($Pin)+1)&']') DllStructSetData($In,1,$Pin) Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLogin', _ 'DWORD',$BindId, _ 'PTR',DllStructGetPtr($In) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETTokenPinChange($BindId,$Pin) Local $In=DllStructCreate('CHAR['&(StringLen($Pin)+1)&']') DllStructSetData($In,1,$Pin) Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenPinChange', _ 'DWORD',$BindId, _ 'PTR',DllStructGetPtr($In) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETTokenLogout($BindId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLogout', _ 'DWORD',$BindId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETRootDirOpen($BindId,$Dir=0xF007) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETRootDirOpen', _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$BindId, _ 'DWORD',$Dir _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETDirOpen($Dir,$DirId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirOpen', _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$Dir, _ 'DWORD',$DirId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETDirCreate($Dir,$DirId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirCreate', _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$Dir, _ 'DWORD',$DirId, _ 'DWORD',0 _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETDirGetInfo($DirId) Local $Out=DllStructCreate('BYTE[8]') Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirGetInfo', _ 'DWORD',$DirId, _ 'PTR',DllStructGetPtr($Out) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETDirClose($DirId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirClose', _ 'DWORD',$DirId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETDirDelete($DirId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirDelete', _ 'DWORD',$DirId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETDirEnumOpen($DirId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirEnumOpen', _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$DirId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETDirEnumNext($EnumId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirEnumNext', _ 'DWORD',$EnumId, _ 'PTR',DllStructGetPtr($Out) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETDirEnumClose($EnumId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETDirEnumClose', _ 'DWORD',$EnumId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETFileOpen($File,$DirId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileOpen', _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$DirId, _ 'DWORD',$File _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETFileCreate($File,$DirId,$Size,$Private=0) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileCreate', _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$DirId, _ 'DWORD',$File, _ 'DWORD',$Size, _ 'DWORD',$Private _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETFileGetInfo($FileId) Local $Out=DllStructCreate('WORD name;WORD private;WORD;WORD size') Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileGetInfo', _ 'DWORD',$FileId, _ 'PTR',DllStructGetPtr($Out) _ ) Local $Result[3]=[ DllStructGetData($Out,'name'), _ DllStructGetData($Out,'private'), _ DllStructGetData($Out,'size')] Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :$Result EndFunc Func ETFileRead($FileId) Local $FileInfo=ETFileGetInfo($FileId) If @error Then Return SetError(@error,0,False) Local $Out=DllStructCreate('BYTE ['&$FileInfo[$ET_FILEINFO_SIZE]&']') Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileRead', _ 'DWORD',$FileId, _ 'DWORD',0, _ 'DWORD',0xFFFF, _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$FileInfo[$ET_FILEINFO_SIZE] _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETFileWrite($FileId,$Data,$Pos=0) $Data=Binary($Data) Local $DataSize=BinaryLen($Data) Local $In=DllStructCreate('BYTE['&$DataSize&']') DllStructSetData($In,1,$Data) Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileWrite', _ 'DWORD',$FileId, _ 'DWORD',$Pos, _ 'PTR',DllStructGetPtr($In), _ 'DWORD',$DataSize _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETFileClose($FileId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileClose', _ 'DWORD',$FileId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETFileDelete($FileId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETFileDelete', _ 'DWORD',$FileId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETFilesEnumOpen($DirId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETFilesEnumOpen', _ 'PTR',DllStructGetPtr($Out), _ 'DWORD',$DirId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETFilesEnumNext($EnumId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETFilesEnumNext', _ 'DWORD',$EnumId, _ 'PTR',DllStructGetPtr($Out) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETFilesEnumClose($EnumId) Local $CallRes=DllCall($ETSdkDll,'WORD','ETFilesEnumClose', _ 'DWORD',$EnumId _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :True EndFunc Func ETTokenLabelGet($BindId) Local $Out1=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLabelGet', _ 'DWORD',$BindId, _ 'PTR',0, _ 'PTR',DllStructGetPtr($Out1) _ ) If $CallRes[0] Then Return SetError($CallRes[0],0,False) Local $Out2=DllStructCreate('CHAR['&DllStructGetData($Out1,1)&']') $CallRes=DllCall($ETSdkDll,'WORD','ETTokenLabelGet', _ 'DWORD',$BindId, _ 'PTR',DllStructGetPtr($Out2), _ 'PTR',DllStructGetPtr($Out1) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out2,1) EndFunc Func ETTokenIDGet($BindId) Local $Out1=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenIDGet', _ 'DWORD',$BindId, _ 'PTR',0, _ 'PTR',DllStructGetPtr($Out1) _ ) If $CallRes[0] Then Return SetError($CallRes[0],0,False) Local $Out2=DllStructCreate('CHAR['&DllStructGetData($Out1,1)&']') $CallRes=DllCall($ETSdkDll,'WORD','ETTokenIDGet', _ 'DWORD',$BindId, _ 'PTR',DllStructGetPtr($Out2), _ 'PTR',DllStructGetPtr($Out1) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out2,1) EndFunc Func ETTokenMaxPinGet($BindId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenMaxPinGet', _ 'DWORD',$BindId, _ 'PTR',DllStructGetPtr($Out) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc Func ETTokenMinPinGet($BindId) Local $Out=DllStructCreate('DWORD') Local $CallRes=DllCall($ETSdkDll,'WORD','ETTokenMinPinGet', _ 'DWORD',$BindId, _ 'PTR',DllStructGetPtr($Out) _ ) Return $CallRes[0] _ ?SetError($CallRes[0],0,False) _ :DllStructGetData($Out,1) EndFunc 


So, we can do whatever we want with the token file system. To demonstrate this, I wrote a simple script that will copy the contents from one token to another. The “Proof-of-concept” level script, i.e. there will not be a lot of checks that should have been in the “right” application, but will allow us to get the second valid token.

eTokenCopy.au3
 #include <etsdk.au3> #include <GUIConstantsEx.au3> #include <StaticConstants.au3> #NoTrayIcon Opt('MustDeclareVars',1) Opt('GUIOnEventMode',1) Opt('GUIDataSeparatorChar',@LF) Const $Title='eToken Copy' Const $GUISize[2]=[250,100] Dim $SrcCtrl,$DstCtrl,$ListTimer Func TokenCopyDir($SrcId,$DstId) Local $Name,$SrcSubId,$DstSubId,$SrcInfo,$SrcData ;      Local $EnumId=ETDirEnumOpen($SrcId) While 1 $Name=ETDirEnumNext($EnumId) If @error Then ExitLoop $SrcSubId=ETDirOpen($Name,$SrcId) $DstSubId=ETDirOpen($Name,$DstId) If @error Then $DstSubId=ETDirCreate($Name,$DstId) EndIf TokenCopyDir($SrcSubId,$DstSubId) ETDirClose($SrcSubId) ETDirClose($DstSubId) WEnd ETDirEnumClose($EnumId) ;    $EnumId=ETFilesEnumOpen($SrcId) While 1 $Name=ETFilesEnumNext($EnumId) If @error Then ExitLoop $SrcSubId=ETFileOpen($Name,$SrcId) $SrcInfo=ETFileGetInfo($SrcSubId) $DstSubId=ETFileOpen($Name,$DstId) If Not @error Then ETFileDelete($DstSubId) EndIf $DstSubId=ETFileCreate($Name,$DstId,$SrcInfo[$ET_FILEINFO_SIZE],$SrcInfo[$ET_FILEINFO_PRIVATE]) ETFileWrite($DstSubId,ETFileRead($SrcSubId)) ETFileClose($SrcSubId) ETFileClose($DstSubId) WEnd ETFilesEnumClose($EnumId) EndFunc Func TokenCopy() Local $Src=GUICtrlRead($SrcCtrl) Local $Dst=GUICtrlRead($DstCtrl) If $Src=='' Or $Dst=='' Then MsgBox(0x10,$Title,'   ') Return False EndIf ;       $Src=StringMid($Src,StringLen($Src)-8,8) $Dst=StringMid($Dst,StringLen($Dst)-8,8) If $Src==$Dst Then MsgBox(0x10,$Title,'      ') Return False EndIf ;    Local $SrcBindId=False,$DstBindId=False Local $EnumId=ETReadersEnumOpen() While 1 Local $Reader=ETReadersEnumNext($EnumId) If @error Then ExitLoop If Not $Reader[$ET_READER_ETOKEN] Then ContinueLoop Local $BindId=ETTokenBind($Reader[$ET_READER_NAME]) If ETTokenIDGet($BindId)==$Src Then $SrcBindId=$BindId ElseIf ETTokenIDGet($BindId)==$Dst Then $DstBindId=$BindId Else ETTokenUnbind($BindId) EndIf WEnd ETReadersEnumClose($EnumId) If Not ETTokenLogin($SrcBindId) Then MsgBox(0x10,$Title,'   -') Return False EndIf If Not ETTokenLogin($DstBindId) Then MsgBox(0x10,$Title,'   -') Return False EndIf ;   TokenCopyDir(ETRootDirOpen($SrcBindId),ETRootDirOpen($DstBindId)) ETTokenUnbind($SrcBindId) ETTokenUnbind($DstBindId) MsgBox(0x40,$Title,' ') EndFunc Func GetTokenList() Local $Reader, $BindId, $Result='' Local $EnumId=ETReadersEnumOpen() While 1 $Reader=ETReadersEnumNext($EnumId) If @error Then ExitLoop If Not $Reader[$ET_READER_ETOKEN] Then ContinueLoop $BindId=ETTokenBind($Reader[$ET_READER_NAME]) $Result&=@LF&ETTokenLabelGet($BindId)&' ('&ETTokenIDGet($BindId)&')' ETTokenUnbind($BindId) WEnd ETReadersEnumClose($EnumId) Return $Result EndFunc Func UpdateTokenList() Local $Tokens=GetTokenList() GUICtrlSetData($SrcCtrl,$Tokens,GUICtrlRead($SrcCtrl)) GUICtrlSetData($DstCtrl,$Tokens,GUICtrlRead($DstCtrl)) EndFunc Func onClose() Exit EndFunc Func GUIInit() GUICreate($Title,$GUISize[0],$GUISize[1],(@DesktopWidth-$GUISize[0])/2,(@DesktopHeight-$GUISize[1])/2) GUISetOnEvent($GUI_EVENT_CLOSE,'onClose') GUICtrlCreateLabel(':',8,8,64,-1,$SS_RIGHT) GUICtrlCreateLabel(':',8,32,64,-1,$SS_RIGHT) $SrcCtrl=GUICtrlCreateCombo('',76,6,$GUISize[0]-84,-1) $DstCtrl=GUICtrlCreateCombo('',76,30,$GUISize[0]-84,-1) GUICtrlCreateButton('',8,54,$GUISize[0]-16,$GUISize[1]-62) GUICtrlSetOnEvent(-1,'TokenCopy') GUISetState(@SW_SHOW) EndFunc GUIInit() UpdateTokenList() $ListTimer=TimerInit() While 1 ;      3  If TimerDiff($ListTimer)>3000 Then UpdateTokenList() $ListTimer=TimerInit() EndIf Sleep(100) WEnd 




I tried all the SKPIs that I could reach: Crypto Com, Crypto Pro, Message Pro, Signature and even Verba. All these keys successfully copied and worked.

But how so? Shouldn't the keys be non-recoverable from the token? The answer lies in the eToken specifications: the fact is that there is a non-recoverable key, but it serves only for crypto-transformations using the RSA algorithm. None of the reviewed SKZI ... no, like this: none of the SKZI approved by the FSB for use on the territory of the Russian Federation (seemingly) uses RSA, and all of them use GOST- * based crypto-transformations, therefore eToken is nothing more than a flash drive with password and intricate interface.

Source: https://habr.com/ru/post/276057/


All Articles