Skip to content

Instantly share code, notes, and snippets.

@danielkucera
Last active July 3, 2023 23:02
  • Star 13 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save danielkucera/fa9df248f6e0ea5dc2908743360d5cb7 to your computer and use it in GitHub Desktop.
compal-decrypt.c
/*
Program for decrypting Compal CH7465LG private key
Compilation:
gcc -o compal-decrypt compal-decrypt.c -lcrypto
Running:
./compal-decrypt
usage: ./compal-decrypt <infile> <outfile>
Author: Carlo Meijer <carlo@youcontent.nl>
*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <string.h>
#include <openssl/des.h>
static unsigned char g_abKey[] = {
0xBE, 0x26, 0x01, 0xC4, 0x52, 0x76, 0x84, 0x4C, 0x9C, 0xDE,
0x13, 0x4D, 0xE7, 0x60, 0x2B, 0xD1, 0xDC, 0xA3, 0xAB, 0x87,
0x9A
};
int sprinkle(unsigned char *a1, unsigned char *a2) // straight from IDA decompiler
{
int result; // r0
signed int v3; // r3
int v4; // r2
*a2 = *a1 & 0xFE;
a2[1] = (*a1 << 7) + ((a1[1] >> 1) & 0x7E);
a2[2] = (a1[1] << 6) + ((a1[2] >> 2) & 0x3E);
a2[3] = 0x20 * a1[2] + ((a1[3] >> 3) & 0x1E);
a2[4] = 0x10 * a1[3] + ((a1[4] >> 4) & 0xE);
a2[5] = 8 * a1[4] + ((a1[5] >> 5) & 6);
a2[6] = 4 * a1[5] + ((a1[6] >> 6) & 2);
a2[7] = 2 * a1[6];
result = 0;
do
{
v3 = 1;
v4 = (unsigned char)(a2[result] ^ 1);
for ( a2[result] = v4; ; v4 = (unsigned char)a2[result] )
{
if ( (v4 >> v3) & 1 )
a2[result] = v4 ^ 1;
if ( ++v3 == 8 )
break;
}
++result;
}
while ( result != 8 );
return result;
}
void des3ABC_CBC_decrypt(
unsigned char *lpKey1,
unsigned char *lpKey2,
unsigned char *lpKey3,
unsigned char *lpIv,
unsigned char *lpInput,
unsigned int dwLength,
unsigned char *lpOutput
) {
int v12; // r7
int v13; // r2
int v14; // r2
unsigned char abKey1[8]; // [sp+8h] [bp-40h]
unsigned char abKey2[8]; // [sp+10h] [bp-38h]
unsigned char abKey3[8]; // [sp+18h] [bp-30h]
char v18[40]; // [sp+20h] [bp-28h]
DES_key_schedule stKey1, stKey2, stKey3;
sprinkle(lpKey1, abKey1);
sprinkle(lpKey2, abKey2);
sprinkle(lpKey3, abKey3);
DES_set_key((DES_cblock *)abKey1, &stKey1);
DES_set_key((DES_cblock *)abKey2, &stKey2);
DES_set_key((DES_cblock *)abKey3, &stKey3);
if ( (dwLength + 7) >> 3 )
{
v12 = 0;
do
{
DES_ecb3_encrypt((const_DES_cblock *)lpInput, (DES_cblock *)v18, &stKey1, &stKey2, &stKey3, DES_DECRYPT);
v13 = 0;
if ( v12 )
{
do
{
lpOutput[v13] = v18[v13] ^ lpInput[v13 - 8];
++v13;
}
while ( v13 != 8 );
}
else
{
do
{
lpOutput[v13] = v18[v13] ^ lpIv[v13];
v14 = v13 + 1;
if ( v14 == 8 )
break;
lpOutput[v14] = v18[v14] ^ lpIv[v14];
v13 = v14 + 1;
}
while ( v13 != 8 );
}
++v12;
lpInput += 8;
lpOutput += 8;
}
while ( (dwLength + 7) >> 3 != v12 );
}
}
int main(int argc, char **argv) {
if(argc != 3) {
goto _usage;
}
struct stat stInfile;
FILE *hInfile, *hOutfile;
unsigned char *lpInput, *lpOutput;
unsigned char abIv[8];
memset(abIv, 0, 8);
unsigned int dwLength, dwOutLength;
hInfile = fopen(argv[1], "rb");
if (hInfile == NULL) {
perror(argv[1]);
return 1;
}
fstat(fileno(hInfile), &stInfile);
dwLength = stInfile.st_size;
lpInput = malloc((dwLength + 7) & ~7);
lpOutput = malloc((dwLength + 7) & ~7);
fread(lpInput, dwLength, 1, hInfile);
if (dwLength & 8) {
memset(lpInput + dwLength, 0, 8 - (dwLength & 8));
}
des3ABC_CBC_decrypt(g_abKey, &g_abKey[7], &g_abKey[14], abIv, lpInput, dwLength, lpOutput);
if (lpOutput[20] != 0x30 || lpOutput[21] != 0x82) {
printf ("[-] invalid format\n");
return 1;
}
dwOutLength = lpOutput[23] + 4 + (lpOutput[22] << 8);
hOutfile = fopen(argv[2], "wb");
if (hOutfile == NULL) {
perror(argv[2]);
return 1;
}
fwrite(lpOutput + 20, dwOutLength, 1, hOutfile);
fclose(hInfile);
fclose(hOutfile);
free(lpInput);
free(lpOutput);
printf ("[+] success!\n");
return 0;
_usage:
printf("usage: %s <infile> <outfile>\n", argv[0]);
return 0;
}
@GMMan
Copy link

GMMan commented Sep 22, 2020

High level overview in case anyone stumbles across this:

The 3DES implementation used is very standard. The only thing different from a normal implementation is the keys it takes don't have parity bits. What sprinkle() does is expand the key and calculate parity bit for each byte of the expanded key. Other than that, both the 3DES and CBC mode are implemented as you'd expect.

The decrypted blob consists of a SHA-1 hash of the key and the DER-encoded PKCS#8 private key. The hash is to the length of the key, not to the end of the file. The check for 0x3082 is for a DER SEQUENCE with length specified in 2 bytes. This happens to be what private keys in this application start with. Next two bytes are length, and you add 4 to it to get the size of the entire private key encoding.

In case you want to use a regular 3DES implementation, here's the key with the parity bits filled in:

bf 13 80 38 45 92 da 08
4c 4f 37 c2 34 6e 9d c1
2a e9 76 94 3b 5d 1f 34

IV is 8 bytes of zeroes (CBC mode of operation):

00 00 00 00 00 00 00 00

This should work on firmware based on Texas Instruments/Intel Puma SDKs. This includes some Motorola/Arris modems.

@qubanac
Copy link

qubanac commented Sep 21, 2021

Thanks for your work.
Is it possible to make an encryption program using this method?
I'm interested in putting a non-encrypted private key from a different modem to this one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment