On Software Reverse Engineering
Obfuscation Method
Next we research how seeds and keys (yes, vendor keys
too!) are obfuscated in the target. Our weapon is data flow analysis, that is,
to trace the vendor and job structure[10]
all the way from initialization to l_string_key() by
debugging cmath.exe.
VENDORCODE
after calling l_n36_buf():
[004AEA20]
- 00000004 ....
[004AEA24]
- aa3342a8 .B3.
[004AEA28]
- 8d112f1f ./..
[004AEA2C]
- 74df9bc4 ...t obfuscated
VENDOR_KEY1
[004AEA30]
- b19bfb18 .... obfuscated
VENDOR_KEY2
[004AEA34]
- 94011f00 .... obfuscated
VENDOR_KEY3
[004AEA38]
- 054a2ed9 ..J. obfuscated
VENDOR_KEY4
[004AEA3C]
- 00020009 ....
[004AEA40]
- 39300020 .09
[004AEA44]
- 0000302e .0..
job
after calling lc_init():
[00887630]
- 00000066 f...
[00887634]
- 00000000 ....
[00887638]
- 00000000 ....
[0088763C]
- 00000000 ....
[00887640]
- 00000000 ....
// above are structs after lc_new_job(), then they are passed to
// lc_checkout() ®
l_checkout() ® lm_start_real() ® l_good_lic_key()
VENDORCODE
after calling l_xorname():
[0012EE20]
- 00000004 ....
[0012EE24]
- aa3342a8 .B3.
[0012EE28]
- 8d112f1f ./..
[0012EE2C]
- 7c2adb6a j.*| true
VENDOR_KEY1
[0012EE30]
- b927f5a9 ..'. true
VENDOR_KEY2
[0012EE34]
- 9cf311f8 .... true
VENDOR_KEY3
[0012EE38]
- 0dbf7621 !v.. true
VENDOR_KEY4
[0012EE3C]
- 00020009 ....
[0012EE40]
- 39300020 .09
[0012EE44]
- 0000302e .0..
[0012EE48]
- 00000000 ....
VENDORCODE
after calling l_n36_buff():
[0012EE20]
- 00000004 .... int type;
[0012EE24]
- 52ed15b8 ...R 52xxxxb8, xxxx is random, different at
each run
[0012EE28]
- 75cf780f .x.u 75yyyy0f,
yyyy is random, different at each run
[0012EE2C]
- 7c2adb6a j.*| VENDOR_KEY1
[0012EE30]
- b927f5a9 ..'. VENDOR_KEY2
[0012EE34]
- 9cf311f8 .... VENDOR_KEY3
[0012EE38]
- 0dbf7621 !v.. VENDOR_KEY4
[0012EE3C]
- 00020009 .... FLEXlm
version (here is 9.2)
[0012EE40]
- 39300020 .09
[0012EE44]
- 0000302e .0..
[0012EE48]
- 00000000 ....
job
after calling l_n36_buff():
[00887630]
- 00000066 f... int type;
[00887634]
- 0089008e .... char
*mem_ptr2;
[00887638]
- a06aa84e N.j. unsigned
char mem_ptr2_bytes[12];
[0088763C]
- 00c3a047 G...
[00887640]
- 00660000 ..f.
[00887644]
- 00000000 ....
[00887648]
- 00000000 ....
[0088764C]
- 00000000 ....
[00887650]
- 00000000 ....
[00887654]
- 54414d43 CMAT
[00887658]
- 00000048 H...
// above are structs after l_good_lic_key() ® l_sg(), then they are passed to
// l_good_lic_key() ® l_crypt_private() ®
real_crypt() ® l_string_key()
VENDORCODE and job are changed at 3 functions: l_n36_buf(), l_xorname() and l_n36_buff() (job
initialization in lc_init() is not very interesting for it
only fills zero). The last two are called within l_good_lic_key():
l_good_lic_key(LM_HANDLE*
job, CONFIG* conf, VENDORCODE* key)
{
... ...
memcpy(&vc, key,
sizeof(vc));
if (!(job->flags & LM_FLAG_CLEAR_VKEYS))
l_xorname(job->vendor,
&vc);
l_sg(job,
job->vendor, &vc); /*
l_sg() would call l_n36_buff() */
... ...
code = l_crypt_private(job, conf, sdate, &vc);
... ...
}
For the 4 vendor keys, it is obvious that they are
obfuscated in l_n36_buf() at initialization and
de-obfuscated in l_xorname(), which does nothing but some
xor operations. We all know the properties of xor: x ^
x = 0, x ^ 0 = x, x ^ 1 = ~x, so the
same xor operations performed twice would cancel each other. This nice property
makes xor perfect for encoding and decoding. If our guess is right, then l_n36_buf() should also
call l_xorname() in the same manner as l_good_lic_key() does.
Since l_n36_buf() resides in lm_new.c, which
is generated by lmnewgen.exe, it’s better to examine lmnewgen.c
directly.
[10] There is
a third structure – config, but it’s not important for our purpose here.