Great work!
And here's a version without brute force.
/*
** rigol ds2000 keygen / cybernet & the-eevblog-users
**
** to compile this you need MIRACL from [url]https://github.com/CertiVox/MIRACL[/url]
** download the master.zip into a new folder and run 'unzip -j -aa -L master.zip'
** then run 'bash linux' to build the miracle.a library
**
** BUILD WITH:
**
** gcc rikey.c -I../MIRACL ../MIRACL/miracl.a -o rikey
**
** adapt -I and path to miracl.a to your environment
**
** more info: https://www.eevblog.com/forum/testgear/sniffing-the-rigol's-internal-i2c-bus/
**
** then fetch private key from EEV Blog and put into "private_key[]=" below, do not prefix with 0x
** supply your serial and wanted options, and enjoy !
**
**
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <stdio.h>
#include "miracl.h"
#define RIGOL_DS2000
// START OF SETTINGS FOR ECC
#ifdef RIGOL_DS2000
unsigned char private_key[]="8..."; // <- RILOL FILL ME (no 0x prefix !)
unsigned char prime1[]="AEBF94CEE3E707";
unsigned char prime2[]="AEBF94D5C6AA71";
unsigned char curve_a[]="2982";
unsigned char curve_b[]="3408";
unsigned char point1[]="7A3E808599A525";
unsigned char point2[]="28BE7FAFD2A052";
#endif
// END OF SETTINGS FOR ECC
unsigned char vb[]={'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', '2', '3', '4', '5', '6', '7', '8', '9'};
void show_help(void)
{
printf("./rikey <DSA2XXXXXXXXX> <OPTS>\n\n");
printf("<DSA2XXXXXXXXX> - serial number of device\n");
printf("<OPTS> - \n");
printf("\t\tDSA? for permanent options\n");
printf("\t\tVSA? for temporary options\n");
printf("\n\n");
}
/*
** take serial and options make sha1 hash out of it
*/
static void hashing(unsigned char *opt_str,big hash)
{ /* compute hash function */
char *p;
char h[20];
int ch;
sha sh;
shs_init(&sh);
p=opt_str;
while(*p)
{
shs_process(&sh,*p);
p++;
}
shs_hash(&sh,h);
bytes_to_big(20,h,hash);
}
/*
** sign the secret message (serial + opts) with the private key
*/
int ecssign(unsigned char *serial, unsigned char *opt, unsigned char *lic1, unsigned char *lic2)
{
FILE *fp;
char ifname[50],ofname[50];
big a,b,p,q,x,y,d,r,s,k,hash;
epoint *g;
long seed;
int bits;
miracl *mip;
unsigned char *serial_options;
/* get public data */
mip=mirsys(0x320, 0x10); /* Use Hex internally */
mip->IOBASE=16;
a=mirvar(0);
b=mirvar(0);
p=mirvar(0);
q=mirvar(0);
x=mirvar(0);
y=mirvar(0);
d=mirvar(0);
r=mirvar(0);
s=mirvar(0);
k=mirvar(0);
hash=mirvar(0);
instr(p,prime1); /* modulus */
instr(a,curve_a); /* curve parameters */
instr(b,curve_b);
instr(q,prime2); /* order of (x,y) */
instr(x,point1); /* (x,y) point on curve of order q */
instr(y,point2);
/* randomise */
seed=1;
irand(seed);
ecurve_init(a,b,p,MR_PROJECTIVE); /* initialise curve */
g=epoint_init();
if (!epoint_set(x,y,0,g)) /* initialise point of order q */
{
printf("1. Problem - point (x,y) is not on the curve\n");
exit(0);
}
/* calculate r - this can be done offline,
and hence amortized to almost nothing */
bigrand(q,k);
ecurve_mult(k,g,g); /* see ebrick.c for method to speed this up */
epoint_get(g,r,r);
divide(r,q,q);
/* get private key of signer */
instr(d, private_key);
/* calculate message digest */
serial_options=calloc(128,1);
strcpy(serial_options, serial);
strcat(serial_options, opt);
hashing(serial_options,hash);
/* calculate s */
xgcd(k,q,k,k,k);
mad(d,r,hash,q,q,s);
mad(s,k,k,q,q,s);
cotstr(r,lic1);
cotstr(s,lic2);
return 0;
}
/*
** convert string to uppercase chars
*/
unsigned char *strtoupper(unsigned char *str)
{
unsigned char *newstr, *p;
p = newstr = (unsigned char*) strdup((char*)str);
while((*p++=toupper(*p)));
return newstr;
}
unsigned char * find_match5(unsigned char *code5)
{
unsigned long long b=0;
unsigned char *out;
int i=0;
out=calloc(5,1);
// hex2dez
while (code5[i] != '\0') {
if (code5[i]>='1' && code5[i]<='9')
b=b*16+code5[i]-'0';
else if (code5[i]>='A' && code5[i]<='F')
b=b*16+code5[i]-'A'+10;
else if (code5[i]>='a' && code5[i]<='f')
b=b*16+code5[i]-'a'+10;
i++;
}
for (i=3;;i--) {
out[i]=vb[b & 0x1F];
if (i==0) break;
b>>=5;
}
out[4]='\0';
return(out);
}
int main(int argc, char *argv[0])
{
unsigned char *options,*lic1_code, *lic2_code, *lic_all;
unsigned char *out,*chunk,*temp,*final;
unsigned char *lic1_key, *lic2_key;
unsigned char *serial;
int v,i=0;
if (strlen(private_key)<14)
{
printf("\n\n");
printf("set the private_key variable on top of this file\n");
printf("you can find it here: https://www.eevblog.com/forum/testgear/sniffing-the-rigol's-internal-i2c-bus/msg264690/#msg264690\n");
printf("\n\n");
exit(-1);
}
if (argc != 3)
{
show_help();
exit(-1);
}
serial=strtoupper((unsigned char*)argv[1]);
options=strtoupper((unsigned char*)argv[2]);
if (strlen(serial)<13)
{
printf("\nINVALID SERIAL LENGTH\n");
show_help();
exit(-1);
}
if (strlen(options)!=4)
{
printf("\nINVALID OPTIONS LENGTH\n");
show_help();
exit(-1);
}
printf("serial: %s\n", serial);
printf("options: %s\n", options);
/* sign the message */
lic1_code=calloc(64,1);
lic2_code=calloc(64,1);
ecssign(serial,options,lic1_code, lic2_code);
printf("lic1-code: %s\n", lic1_code);
printf("lic2-code: %s\n", lic2_code);
lic_all=calloc(128,1);
temp=calloc(128,1);
chunk=calloc(6,1);
final=calloc(128,1);
lic1_key=calloc(20,1);
lic2_key=calloc(20,1);
strcpy(lic_all, lic1_code);
strcat(lic_all, "0");
strcat(lic_all, lic2_code);
printf("target-code: %s\n", lic_all);
// split in 5 byte groups
// run for lic1_code
strcat(lic1_code,"0");
while(i<strlen(lic1_code))
{
memcpy(chunk,lic1_code+i,5);
out=find_match5(chunk);
if (out)
{
strcat(temp, out);
}
i=i+5;
}
strcpy(lic1_key, temp);
// run for lic2_code
strcpy(temp,"");
i=0;
while(i<strlen(lic2_code))
{
memcpy(chunk,lic2_code+i,5);
if (strlen(chunk)<5)
{
for(v=0;v<5-strlen(chunk);v++)
strcat(chunk,"0");
}
out=find_match5(chunk);
if (out)
{
strcat(temp, out);
}
i=i+5;
}
strcpy(lic2_key, temp);
strcpy(temp, lic1_key);
strcat(temp, lic2_key);
// now add the options
memcpy(final, temp, 1);
final[1]=options[0];
memcpy(final+2, temp+1,7);
final[9]=options[1];
memcpy(final+10, temp+8,7);
final[17]=options[2];
memcpy(final+18, temp+15,7);
final[25]=options[3];
memcpy(final+26, temp+22,4);
printf("----------------------------------------------------\n");
printf("your-license-key: ");
for(i=0;i<strlen(final);i++)
{
if (i%7==0 && i>0) printf("-");
printf("%c", final[i]);
}
printf("\n");
printf("----------------------------------------------------\n");
}
because some have asked for it, and it might prove valueable to other equipment,
here are the basic steps to perform something like this.
1. find a good forum on the internet with clever ppl -> that one here is great
2. see what others have done, but dont keep that direction (e.g. FRAM, great start but to cumbersome for most)
3. if a device is firmware updateable you own 50% already
4. if u can tinker with a device while its running (memory, cpu, breakpoints, watchpoints) you own another 49% (->JTAG !)
5. study the datasheets, and esp. the engineering notes carefully (know your enemy) - companies rarely dev their own stuff if they have something proven premade available.
in rigols case thats ethernet, webserver, usb, filesystem thats 100% AD VSDK sourcekit - its damn easy to spot this functions and link em to c files/headers.
6. check if there are copyprotection or "lockbox" features as AD calls it - that could prove a showstopper - but luckly not a single memory reference to those registers
from that point on i was 100% sure its doable.
7. i choose the path to reverse the crypto stuff instead of googling some obvious numbers, because i thought it was fun to do (and it was, learned a lot - e.g. the AD compiler produces crap code)
8. the real kickoff was when some guy posted that one of the routines is a sha1 hash function (it uses distinct values to initialise), made sense, pasted it into google,
and what came back as first hit was the MIRACL toolkit, and it was damn f*ck me i've seen that structures before ->
9. i could match the subs almost 1:1 by just looking at them at that point - so this made it clear that they use ECC - which was a bummer because ECC is rather secure if implemented correctly.
10. the A/B curve values looked *tiny* and the 56bits of keys .. are well below standard ... so there was a slim chance that it would lead somebody with more math foo to a private key.
i played several hours with ecssign,ecsgen, etc swapped keys and primes here and there ,but nothing, maybe i had it but missed it - anyhow a email and pm told me to try another key (the right one)
and again i could not make it work (i had the wrong Q value as i used the one from ROM, not the one from ./schoof) - that cleared with a look into the forum where ppl where able to get riglol to verify the
key. so a good chunk of kudos goes to the key finders (there are some ppl who mailed them !
)
im pretty damn sure that the entire lineup of rigol as long as its BFIN based, shares a good deal of common code - so apply the same principles to any other device and u will have success sooner or later.
i plan to get a DG4062 next ;-)
Truly incredible work!
I can only say a big thank you to everyone involved!
I built the miracl.a lib and the rikey app with the priv key inserted, and ran it on my DS2102's serial with DSAZ as option.
It curiously only produced two characters in the fourth (final) character-group, and it's not accepted by the scope since it lacks the required number of characters.
But using DSAR instead for a maxed out DS2102, did produce a correct license key, and the options-list now shows "Offcial Version" [sic] on every line.
I did this while on 00.00.01.00.05 if it matters.
Afterwards I upgraded to 00.01.01.00.02, and the options-list still shows "Offcial Version" on everything.
My DS2102 serial is 13 characters long btw, and the serial was not changed by entering the DSAR license.
When upgrading from the old firmware, the scope reverted to 2072 which is why I lost 2ns timebase.
Since then I can go forward, backward, reboot in the middle, flash with bootloader, install keys, uninstall, install trial or permanent LLLL and power cycle, install trial or permanent LLLL and uninstall, doesn't matter - it's always a 2202 now with sn #1.
Not a big deal, just weird / frustrating trying to change it.