This article was written with @MrMoDDoM and is available on his website as well.
A couple of weeks ago we stumbled upon a media renting service employing a custom DRM system. Being curious (and somehow allergic to DRM as a whole), we decided to dig into it and see how deep was the rabbit’s hole. At first sight, the whole system used a normal AES encryption, but we found some interesting mechanics dealing with the key derivation logic.
This article is going to lack many details that may be used to identify the DRM system in question. Our focus will be on the reverse engineering process and the tools used to reach the final goal.
We don’t want to disrupt the business of the companies using this specific DRM, our aim was pointing out how, once again, DRM prove useless against a motivated attacker.
Digital Rights Management are often used to secure digitally distributed media content. A well known example is Widevine by Google, used by Netflix, Amazon Prime Video, HBO, Disney+…
The basic concept of a DRM lies in hiding the actual content behind an encryption or scrambling layer to avoid basic copy-paste redistribution. The only non encrypted parts are user-dependent information needed by the DRM ecosystem to manage and control the digital media itself, in our case: redeem time, media availability and signing data to prevent tampering.
We started the reverse engineering process decompiling the Android app in order to figure out the data flow.
Some basic text search in the codebase, looking for the text used in the password prompt, led us to the part of the code where the password was manipulated by the app. The user input was hashed with SHA256 and stored in a password database for later use. When the app needed plaintext data, it passed the password database along protected content to a native function which returned the decrypted content. We assumed the lib might have been using standard crypto and tried to guess the encryption algorithm via some more text search. Strings in the app pointed out AES256 as a possible candidate, a guess validated by the key size, which matches the output of SHA256. With this basic information, we naively tried to decrypt the content we had with the hash of our passphrase, to no avail.
Given our initial attempt was unsuccessful, we had to take a deeper look at the native library, which at this point was probably doing something more than mere decryption. We imported the binary into Ghidra and started to reverse it the old fashioned way.
Java (and Android runtime specifically) allows executing native code using JNI, the Java Native Interface. Due to how the JNI works internally, the bridging functions can be easily identified in the binary given the method called in Java. This eased our analysis of the library since we knew where to start reading the binary and also knew the parameters being passed to native code.
This excellent article from Maddie Stone’s blog explains how to actually reverse engineer JNI native code and gives some really useful advices to make the code more readable in the decompiler. For example, loading this gist from jcalabres and defining the first parameter as a JNIEnv_ struct type allows Ghidra to show JNI bridge methods such as CallObjectMethod, GetStaticIntField and alike.
Using Frida, we attached the Java function invoking the JNI to retrieve the actual parameters.
// Anonymized code
Java.perform(function() {
var DRM_class = Java.use('org.vendor.drm.sdk');
// We have to attach the DRM class constructor because
// at this point we're sure the .so object is in the classpath
DRM_class.$init.implementation = function() {
this.$init(); // Call actual Java constructor
// Add our own hook
Interceptor.attach(Module.findExportByName('libdrm.so', 'Java_org_vendor_drm_sdk_nativeCheckPassword'), {
onEnter: function(args) {
console.log("Attached CheckPassword");
console.log("jsonData:", readJString(args[2]));
},
onLeave: function(args) {
console.log("Leaving CheckPassword");
}
});
}
});
/* Read a JString (Object passed via the JNI) from C++ code */
function readJString(str_ptr) {
var ret_str;
Java.perform( function () {
var String = Java.use("java.lang.String");
ret_str = Java.cast(ptr(str_ptr), String);
});
return ret_str;
}
Once we knew the parameters being passed, we fixed the stack and redefined data types in Ghidra to make the pseudocode more accurate and readable. Then we started to actually reverse engineer the native function accepting this data. It proved to be basically a simple loop on all the keys in the passphrase database, which was passed by the Android application, checking them one by one decrypting a known plaintext. We could easily identify the crypto algorithm, since a well known C++ crypto library was in use and the crypto parameter clearly stated AES-256/CBC.
Luckily Frida supports native function hooks as well, although the hooking process is fairly more convoluted than with Java. For example, C++ strings are complex objects that require more than a simple “memory read” to be printed.
/* Read a C++ std string (basic_string) to a nomal string */
function readStdString(ptr_str) {
const isTiny = (ptr_str.readU8() & 1) === 0;
if (isTiny) {
return ptr_str.add(1).readUtf8String();
}
return ptr_str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}
/* Read a C++ std vector
* Copied and slightly modified from https://github.com/thestr4ng3r/frida-cpp
*/
class StdVector {
constructor(addr, options) {
this.addr = addr;
this.elementSize = options?.elementSize ? options.elementSize : Process.pointerSize;
this.introspectElement = options?.introspectElement;
}
get myfirst() {
return this.addr.readPointer();
}
get mylast() {
return this.addr.add(Process.pointerSize).readPointer();
}
get myend() {
return this.addr.add(2 * Process.pointerSize).readPointer();
}
countBetween(begin, end) {
if(begin.isNull()) {
return 0;
}
const delta = end.sub(begin);
return delta.toInt32() / this.elementSize;
}
get size() {
return this.countBetween(this.myfirst, this.mylast);
}
get capacity() {
return this.countBetween(this.myfirst, this.myend);
}
toString() {
let r = "std::vector(" + this.myfirst + ", " + this.mylast + ", " + this.myend + ")";
r += "{ size: " + this.size + ", capacity: " + this.capacity;
if(this.introspectElement) {
r += ", content: [";
const first = this.myfirst
if(!first.isNull()) {
const last = this.mylast;
for(let p = first; p.compare(last) < 0; p = p.add(this.elementSize)) {
if(p.compare(first) > 0) {
r += ", ";
}
r += this.introspectElement(p);
}
}
r += "]";
}
r += " }";
return r;
}
}
Once we figured out how to read the parameters, we quickly noticed that the passphrase being used actually to decrypt the data was different from the one provided by Java call. It became clear that some kind of key derivation was being in used.
Still using Frida, we analyzed the input and output of every function separating the JNI call to the decryption routine: to our surprise a function called something along the lines of boring_function_doing_nothing_relevant was responsible for the actual key derivation. This made us waste way more time than we’d like to admit, but we now know better than ever to NEVER trust symbols and developers.
The boring_function_doing_nothing_relevant function turned out to be implemented as a complex state machine, way too annoying to statically reverse with Ghidra.
boring_function_doing_nothing_relevant function graph
Fearing we might have to deal with white-box crypto (which is almost impossible to reverse), we decided to use Unicorn, a powerful framework build upon QEMU, to emulate this portion of code. Once we got the binary to run, and it outputted correct data, our plan was to analyze code flow and stub different parts of the function one at a time. That would allow us to understand the behavior of narrow parts of the code, and then figure out the whole algorithm.
Following the examples in Unicorn’s GitHub repo, we loaded the binary in memory and allocated space for stack and additional data. On the other hand, patching/reimplementing library function calls (such as memcpy) and adjusting Unicorn virtual registers accordingly was a little more challenging.
Eventually, we managed to run correctly every function we needed, but, to our great surprise, the derived key was not matching the one coming from execution in actual hardware.
Dazed and confused we checked our instrumentation code to spot any emulation mistake and found none (to our knowledge), double-checked the stack and external function re-implementation. We then started to trace the function call, in emulation and real hardware execution with GDB, to evaluate possible differences in code flow. No dice, but we started to get a grasp on how the algorithm worked. We finally ended up comparing data on a per-register level upon every executed instruction, instrumenting GDB with this code
import binascii
import struct
import gdb
class RegDumper(gdb.Command):
"""Dumps registers in a parsable manner"""
def __init__(self):
super(RegDumper, self).__init__("regdump", gdb.COMMAND_USER)
self.pagination = gdb.parameter("pagination")
def invoke(self, args, from_tty):
ret_list = []
# Get function's last instruction address (considering ASLR offset). +1100 comes from disasm code
function_ret = gdb.execute('x/1i boring_function_doing_nothing_relevant+1100', to_string=True).strip().split(' ')[0]
this_dic = {}
while this_dic.get('eip', 0) != function_ret:
# Stop stepping as soon as we're leaving the target function
registers = ['eax', 'ebx', 'ecx', 'edx', 'esp', 'ebp', 'esi', 'edi', 'eip']
this_dic = {}
if self.pagination: gdb.execute("set pagination off")
for reg in registers:
reg_str = str(gdb.selected_frame().read_register(reg)).split(' ')[0]
if '0x' in reg_str:
this_dic[reg] = reg_str
else:
this_dic[reg] = '0x'+str(binascii.hexlify(struct.pack('>i', int(reg_str))).decode('ascii'))
ret_list.append(this_dic)
gdb.execute('nexti', to_string=True) # Don't print to term
print(ret_list)
if self.pagination: gdb.execute("set pagination on")
RegDumper()
Then, we finally noticed how the sha256-related functions, built into the library itself, were returning an incorrect hash only while being emulated in unicorn. We didn’t even bother to figure out WHY that was yielding wrong data (we were really done with it), and we just patched it away, replacing it with pycrypto’s SHA256 implementation and shoving the final result directly in memory.
import hashlib
import struct
import hexdump
from unicorn import *
from unicorn.x86_const import * # for accessing the registers
X86_CALL_SIZE = 5 # CALL instruction size in bytes
hash_ctx = None
### REDACTED CODE ###
def sha256_init(uc, address, size, user_data):
print("sha256 INIT")
global hash_ctx
hash_ctx = hashlib.sha256()
uc.reg_write(UC_X86_REG_EIP, uc.reg_read(UC_X86_REG_EIP) + X86_CALL_SIZE) # Move IP to next instr
def sha256_update(uc, address, size, user_data):
global hash_ctx
data_ptr_barray = uc.mem_read((uc.reg_read(UC_X86_REG_ESP) + 0x4), 0x04)
data_size_barray = uc.mem_read((uc.reg_read(UC_X86_REG_ESP) + 0x8), 0x04)
data_ptr = struct.unpack('<I', data_ptr_barray)[0]
data_size = struct.unpack('<I', data_size_barray)[0]
data = uc.mem_read(data_ptr, data_size)
print(f"sha256 UPDATE - src: {data_ptr:x} - size: {data_size:x}")
hexdump.hexdump(data)
hash_ctx.update(data)
uc.reg_write(UC_X86_REG_EIP, uc.reg_read(UC_X86_REG_EIP) + X86_CALL_SIZE) # Move IP to next instr
def sha256_final(uc, address, size, user_data):
global hash_ctx
dst_ptr_barray = uc.mem_read((uc.reg_read(UC_X86_REG_ESP) + 0x4), 0x04)
data_ptr = struct.unpack('<I', dst_ptr_barray)[0]
digest = hash_ctx.digest()
uc.mem_write(data_ptr, digest) # Write actual hash in memory where it is supposed to be
print(f"sha256 FINAL - dest: {data_ptr:x}")
hexdump.hexdump(digest)
hash_ctx = None # Reset global hashing context
uc.reg_write(UC_X86_REG_EIP, uc.reg_read(UC_X86_REG_EIP) + X86_CALL_SIZE) # Move IP to next instr
### REDACTED CODE ###
uc = Uc(UC_ARCH_X86, UC_MODE_32)
uc.hook_add(UC_HOOK_CODE, sha256_init, begin=0xDEADBEEF, end=0xDEADBEEF) # SHA256_Init
uc.hook_add(UC_HOOK_CODE, sha256_update, begin=0xBEEFBABE, end=0xBEEFBABE) # SHA256_Update
uc.hook_add(UC_HOOK_CODE, sha256_final, begin=0xDABBAD00, end=0xDABBAD00) # SHA256_Final
Finally, we got the correct result.
By this time, due to the tedious debugging process we had to go through, we understood how the key derivation worked at a surface level; it can be summed up by the following snippet:
data = input_key
for i in range(0,64):
data += calc_one_byte_somehow()
data = SHA256(data)
We then were still missing out the behavior of the aptly named calc_one_byte_somehow function. To figure out this last bit, we started to feed into our emulation “easy” values such as 32 bytes of zeros or 32 bytes of ones. To our surprise, the result of calc_one_byte_somehow was unchanged. After a couple of extra tests, we concluded that the appended bytes, albeit calculated at runtime, were always the same and completely unlinked from supplied data.
Finally, the whole DRM’s key derivation system was replaced by a 10 lines python script, once we dumped the “secret bytes” via our stubbed SHA functions. We didn’t even bother to understand the bytes generation algorithm.
]]>
I am currently (since a couple of years, actually) in the process of reverse engineering an undocumented “secure” NFC tag with friends. It is widely used, but, to the best of my knowledge, nobody else is actively working on it.
By the way, secure is put into quotation marks as the company’s security model is based upon NDA’d documentation and a custom mutual authentication algorithm.
Since we are standing on the shoulders of giants, I tried to follow the steps taken by other researches such as Nohl, Plötz and Starbug’s. They noticed a serious weakness in the PRNG of Mifare tags, which was exploited by other researchers (de Koning Gans, Hoepman, Garcia) leading to the cryptography being broken. To do so they first had to implement ISO14443-A codec in the Proxmark’s FPGA, in order to have precise timings.
This product is reported to use a “true random number generator” on the vendor website, but I wondered if that was actually true.
The tag I’m dealing with is not compliant to ISO14443-A, but to ISO15693 (iso15 from now on) which is, of course, not implemented in FPGA on the Proxmark. Support for this standard instead relies heavily on the ARM MCU and therefore does not provide precise timings. I did not want to go through the effort of touching once again Proxmark’s codebase without knowing whether this approach could have lead to any useful data. So I resorted to use the CR95HF demo board, one of the cheapest NFC dev kits available on the market. I modified an older script and used the board to:
These 3 commands, in a tight while loop, were sent as HID packets via USB to the demo board. I turned the field off and then back on in order to minimize the entropy fed into the PRNG to, hopefully, maximize hit rate of repeated nonces.
This solution, albeit extremely naive, led to very interesting results:

After 460.000 attempts (line count not shown), which were harvested in something less than 50 minutes, there were already more than 1000 repetitions of the same nonce. A couple of other nonces had a slightly lower occurrence rate, but still way higher than the average, thus something’s off with the PRNG.
Now, while this approach was already yielding promising results, it would be nice to improve it. 1/500 hit rate is not bad, but it isn’t that good either. Also, there were issues with the reliability: sometimes there were no repetitions at all.
As you can see from the below oscilloscope capture, the beginning of modulation is quite “wobbly”, drifting in time in a 10 μs windows. This was probably due to the fact the board was directly driven from my laptop via USB HID and so many things could go wrong there.

Other than being cheap, the CR95HF demo board has the great advantage of being fully open source.
Here is a diagram from the firmware documentation to explain how communication between the computer and the CR95HF front end works.

The firmware running on the STM32F103 is, of course, downloadable as well, and it can be built with ARM-MDK importing the DEMO_BOARD_CR95HF.uvprojx file in the project archive.
At this point a friend joined me, and we started to hack the firmware. We implemented a very basic jitter buffer in the firmware, adding our own custom HID_SPI_ATOMIC_POWER_CYCLE command to integrate HID_SPI_SEND_CR95HFCMD, the one I was using in my script earlier. The new command should be fed 4 parameters:
This way all the waits could be performed on the STM32 MCU, without any overlapping task which could take precedence over our packets and, hopefully, delivering repeatable results. We also had the possibility to edit all the parameters on the computer side, without the need to recompile the firmware every time we wanted to try new timings or add normal commands in between.
After some wrestling with the libraries provided by ST (the FieldOn function does not behave as expected and the wait_us interrupt Autoreload, AKA interrupt period, is halved) we got a working build. It could be easily flashed on the board with an ST-Link via uVision (the MDK version!) itself.
So, how accurate was the result?

Sub-1 μs precision can be considered bang on, as far as I’m concerned. The modified firmware can be found on GitHub.
Once the firmware had been patched, the easy part was over. Now it came the time to figure out what was going on inside the silicon.+
We started to peek around with different timings, trying to replicate the results, but we never succeeded. Given the really unsatisfactory results, we even tried the old unreliable HID script again, to no avail. Multiple days of unsuccessful tests later, we figured out that the positioning of the tag on the reader was critical: a 2° rotation of the card around the center of the reader antenna could mean the difference between zero and thousands of repetitions. For this reason, the setup process is extremely tedious, but luckily, when they happen, collisions are quick, so few (couple of thousands) nonce requests are enough to see the first repetitions. We had best luck with the card center aligned on the CR95HF’s antenna center and a ~20° clockwise rotation, YMMV.
This behavior tells us that most likely the tag contains a TRNG, which is fed with some kind of entropy from the NFC field. This is indeed an intelligent solution, since in a real world scenario the tag is being moved by the user within the field in a non-deterministic manner, thus yielding actually random values.
On the other hand, when the tag is power cycled in a controlled environment, collisions happen, so the TRNG probably does not retain state.
Trying to figure out if the random generation had a specific period (as it happened with Mifare tags), we tried to identify a possible counter overflow. Waiting for too much time to request a nonce after the field power-up resulted in no collisions, so probably the TRNG diverges from a clean state as time passes. Using a fixed short time didn’t yield wonderful results either, so we tried to “sweep” waits in order to intercept the best timing.
The holes in the field are the modulated bits in reader->tag communication
As soon as a repeated nonce is found, the current wait time is used as a reference and other nonces are requested in the surrounding of the same interval. This approach led to some improvements, but it was mostly inconclusive, to be honest.
Also, it’s worth noting that every time a tag was removed and reseated on the reader, the collisions happened with different nonces. If on one run the most common nonce was 32f0c2a660da4c, on the following it might be 45e5d88e833c28. This could be very well related to the inaccuracy of our test setup (we didn’t have a repeatable positioning jig), but it’s worth noting since it shows how the TRNG is heavily influenced by the field and/or other still unknown factors.
My first test was exceptionally lucky: if no repetitions occurred I wouldn’t have spent more times diving into this issue, thanks to that lucky draw, we now know that this tag is flawed.
We didn’t perform further testing since this project was started early 2020, then COVID hit, and we were stuck at home. I’d like to work on this thing again sooner or later, but I’m unsure if conclusive results could be gathered from black box analysis only. Most likely the only way to know what’s going on would be analyzing the silicon.
By the way, our firmware is backwards compatible with the demo software provided by ST or with scripts you can find online.
]]>
I was working on a new codec for the ChameleonMini and it become necessary to debug in a reasonable fashion (eg. with something better than a blinking led).
The project uses an Atmel XMega 128A4U MCU, so the first choice for debugging would probably be Atmel Studio. However, the project hasn’t been setup to use the proprietary IDE, but rather a makefile. For the life of me I haven’t been able to successfully import the Makefile and build with Atmel Studio itself. Also, given that I use linux daily, it bothered me to reboot in windows every time I wanted to develop.
Luckily my debug board, the cheapo Atmel ICE PCB, is supported by avarice, an open source bridge between Atmel Jtag/PDI and GDB.
I was less lucky as the 128A4U MCU is not in supported by upstream avarice, but there is an unmerged patch which adds support for the chip.
Avarice should be built from latest svn version as the snapshot on Sourceforge isn’t building, so you
svn checkout http://svn.code.sf.net/p/avarice/code/trunk
Then the above patch should be applied to src/devdescr.cc, for your convenience you can download a formatted .patch file here. Once done you can build it with
./Bootstrap
./configure --prefix=/usr
make
Once the build is done it’s finally possible to connect the Atmel board (don’t forget to power up the chameleon as well), then run the following command
sudo ./avarice --edbg --ignore-intr --part atxmega128a4 --pdi :4242
sudo is needed as I haven’t configured udev rules and otherwise avarice would simply segault
You’ll now need the avr gdb flavor, which should be available in your distribution’s repositories under the name avr-gdb.
You can now open the ChameleonMini/Firmware/Chameleon-Mini folder in VSCode (or modify accordingly the executable path specified below) and finally add the debug configuration via the appropriate menu (Debug -> Add Configuration -> GDB). This configuration should work for the avarice command written above and with current ChameleonMini’s makefiles output path.
{
"version": "0.2.0",
"configurations": [
{
"type": "gdb",
"request": "attach",
"name": "ChameleonMini debug",
"target": ":4242",
"remote": true,
"cwd": "${workspaceRoot}",
"executable": "${workspaceRoot}/Chameleon-Mini.elf",
"valuesFormatting": "parseText",
"gdbpath": "avr-gdb"
}
]
}
Now you should be able to debug the software as if you were using Atmel Studio, but you’re not tied to a specific IDE or Operating System. Microsoft’s VSCode documentation explains its capabilites better than I could ever do.
Pay attention to Atmel-ICE pinout if you plan to buy the bare PCB version as I did, it might not be as obvious as it should.
]]>
Questo post è disponibile in italiano
SSL pinning is almost omnipresent in android apps, but can be easily defeated using Xposed modules or Frida scripts (more on Frida later).
So, what if a dev wanted to be extra-safe about what went through the net?
First of all a brief explanation of what SSL and SSL pinning are. If you’re already familiar with the topic you can skip to the next chapter, nothing new here.
SSL (Secure Sockets Layer) is a protocol made to guarantee an encrypted connection between user (be it an app or a web browser) and server. SSL dates back to 1995 and has been deprecated since 2015, it was replaced by TLS but TLS encrypted traffic is commonly referred as SSL traffic, I’ll do the same from now on. The link formed with SSL ensures that all data passed from the client to the server (and vice versa) remain private and integral.
This protocol itself is safe (Mostly), but does not solve the underlying Key Distribution Problem. When you want to be sure no one is playing MITM and injecting his own certificates, you pin the public key to the host. If you receive a key different from the expected one, probably someone is between you and the server, so you stop the communication.
Alerts in Burp when not unpinning certificates
Once you used one of the well-known methods linked at the beginning of this post, all the errors should disappear and you’ll find clear text traffic in your sniffing tool.
When you really do not want anyone to read the traffic, you can embed client-side certificates in your app and configure the server to require them while performing the TSL handshake. This procedure is explained thoroughly on IBM website.
You can go to great extent and hide the cert somewhere in the app, maybe already encoded as a Java KeyStore file (.jks) with a password to open it. The password could be hidden with some fancy method such as the (now unmaintained) Cipher.so project.
All these steps are probably enough to wear an attacker which is not motivated enough, because he/she has to find where the keystore is saved, reverse engineer the password and find a way to extract data in an usable format (PKCS12, probably).
This is where Frida comes into play
Frida is a tool which gives us the possibility to hook to classes and modify methods on runtime.
Dynamically.
With JavaScript.
Yes, it is as magical as it sounds.
With the power of dynamic instrumentation we can do anything we want with code. We can output the parameters a function is called with, we can downgrade security on the fly (think about a signature check function) or misuse proper code (think about signing your tampered message with the proper sign function to get it approved by the server). Brida is a nice example of how this analysis method could ease your life.
Android KeyStores have a load method which is used to load (you don’t say) the instantiated keystore with some data. This method is overloaded:
load(KeyStore.LoadStoreParameter param)load(InputStream stream, char[] password)The app I had to work used the second one with a jks object passed as a stream, so my code is made for this scenario. As you can see the second method accepts a char array as password, which means that, regardless how bad the password is encrypted in the apk file, it is available in memory in plain text. This is good: no more need to reverse engineer some possibly nasty white box crypto.
Frida helps us and supports a nice overload method to let us choose which one of the two load we’ll modify.
I’ll ignore python code which is just a wrapper for housekeeping, let’s go through the js code which is injected in the app.
setTimeout(function() {
Java.perform(function () {
var keyStoreLoadStream = Java.use('java.security.KeyStore')['load'].overload('java.io.InputStream', '[C');
/* following function hooks to a Keystore.load(InputStream stream, char[] password) */
keyStoreLoadStream.implementation = function(stream, charArray) {
/* sometimes this happen, I have no idea why, tho... */
if (stream == null) {
/* just to avoid interfering with app's flow */
this.load(stream, charArray);
return;
}
/* just to notice the client we've hooked a KeyStore.load */
send({event: '+found'});
/* read the buffer stream to a variable */
var hexString = readStreamToHex (stream);
/* send KeyStore type to client shell */
send({event: '+type', certType: this.getType()});
/* send KeyStore password to client shell */
send({event: '+pass', password: charArray});
/* send the string representation to client shell */
send({event: '+write', cert: hexString});
/* call the original implementation of 'load' */
this.load(stream, charArray);
/* no need to return anything */
}
});
},0);
/* following function reads an InputStream and returns an ASCII char representation of it */
function readStreamToHex (stream) {
var data = [];
var byteRead = stream.read();
while (byteRead != -1)
{
data.push( ('0' + (byteRead & 0xFF).toString(16)).slice(-2) );
/* <---------------- binary to hex ---------------> */
byteRead = stream.read();
}
stream.close();
return data.join('');
}
setTimeout
Is a Frida function to run our code after a 0ms delay
Java.perform
Runs some java code in Frida’s Java runtime
Java.use('java.security.KeyStore')['load'].overload('java.io.InputStream', '[C');
We choose which class we want to hook to (java.security.KeyStore) and which method of this class (load). Also, we specify that we only need the method which takes an InputStream and a Char as inputs. This method will be referenced via the keyStoreLoadStream variable.
keyStoreLoadStream.implementation = function(stream, charArray)
Once we have selected the method we specify what we want to do with it: we want to change the implementation with our custom function. stream and charArray are formal parameters that can be referenced from inside the function.
readStreamToHex (stream);
The parameter stream is passed over to readStreamToHex which uses the Java function read() to read the next byte in the stream until an error is received (byteRead !=-1). One by one bytes are converted to their ASCII hex counterpart and pushed into an array that will be joined and returned to the caller on finish.
send({event: '+type', certType: this.getType()}); send({event: '+pass', password: charArray}); send({event: '+write', cert: hexString});
When we’re done reading the stream we use this.getType() from Java to get the cert type, the result is passed to the client in order to inform the user. If the type is PKCS12 we also set the ext to .jks in client.
Password and the ASCII representation of InputStream are now passed to the client and the stream is written to file.
this.load(stream, charArray);
As a last step the real implementation of load is called with the original parameters so that app’s flow is not interrupted and the certificate gets actually loaded into the keystore.
First you have download and run ./frida-server & on your target device, then execute my script on your computer and you should get something like this
Script output example
We now have a keystore entity saved on client’s hard disk. This entity is (probably, with current code) a jks file which should be converted to a pkcs12 binary certificate to be used with other tools. You can perform the conversion with keytool, a part of your Java SDK install (it should be bundled also with the JRE, but I’m not sure).
keytool -keystore keystore0.jks -list
keytool -importkeystore -srckeystore keystore0.jks -destkeystore dest_pkcs12_crt.p12 -deststoretype PKCS12 -srcalias CERT_ALIAS -deststorepass YOURPASS -destkeypass YOURPASS
The first command will give you a list of available aliases in the keystore and you should supply them one by one to the second command to extract all the certificates. You’ll need to specify a password for the newly created certificate.
Clear traffic
Profit
]]>
When working on NFC systems it is common to find MACs used to prevent users from fiddling with the data stored in the chip.
Still, it’s good fun to fiddle with data. Let’s see if we can figure out a MAC used in a real world application.
MAC collisions
We can be quite sure this is not a complex algo simply looking at a single (albeit lucky) dump. There are many collisions (highlighted in different colors).
Given the first 3 sectors are all zeroed and still we have AA as the checksum we could think to a final sum or xor. Sums might overflow, though, then xor is our favorite contender. We now have the probable “final xor” (as it is called in CRC)
But what now?
Since we’re using a xor (probably) as a final operation, it might be used elsewhere. 00 xor 00 is 00, actually, which goes along with our first sectors.
The collisions can help us now. What’s different among the two lines with 50 as a CRC? Odd bytes stay the same, while even bytes change: BC becomes BF and BD is BE. What if we XOR these 2 values together? BC xor BD = 1 and BF xor BE = 1, too, and the same goes for the other relevant collision. This is good, when there is a collision, the even bytes xorred together give a constant. This might be the right track.
It’s now quite obvious how this MAC could possibly be generated. You have to xor together odd bytes, then do the same for even bytes. What if we finally xor these 2 together and finally xor the result with AA (we know this thanks to the first line)?
83 xor 33 xor 00 xor 00 xor 00 xor 00 xor 00 xor 02 = B2
92 xor BC xor 40 xor 04 xor BD xor 9F xor 00 = 48
B2 xor 48 = FA
FA xor AA = 50
BINGO!
Using your system’s calculator is fun and all, but as humans we’re prone to errors (I am especially good at it), you don’t want your test to fail because you put somewhere a byte wrongly. Python does not fail (at least in this case)
#!/usr/bin/python3
import sys
if (len (sys.argv) is not 2):
print ("Missing input string or too many args")
print ("CRC.py <15 bytes hex string>")
exit (1)
input_trimmed = sys.argv[1].replace(" ", "")
if (len(input_trimmed) is not 30):
print ("Hex string is not 15 bytes long")
exit (1)
even_bytes = 0x00
odd_bytes = 0x00
for i in range (2, 27, 4):
even_bytes ^= int(input_trimmed[i:i+2], 16)
for i in range (0, 30, 4):
odd_bytes ^= int(input_trimmed[i:i+2], 16)
print ("Even bytes: " + hex(even_bytes) + "\t\tOdd bytes: " + hex(odd_bytes))
xor_even_odd = even_bytes ^ odd_bytes
MAC = xor_even_odd ^ 0xaa
print ("MAC:\t" + hex(MAC))
This was a quick one, I’m not even sure it was worth blogging, but I thought this site was dying and wanted to do something about it…
]]>
Generally, running a Linux distro on your PC is a straightforward process, you put the iso in the thumb drive, set it as default boot device and you’re ready to rock. Generally.
In another universe lives a happy student with his Surface. He really loves his tablet-PC hybrid. It’s lightweight, it’s powerful, it has got pen support (which is nice to take notes during lectures) and good battery life. He was a happy camper. Until he wanted to try and play with NFC tags. Libnfc is great, mfcuk and mfoc are good at their work, but they do not work with Windows. Our student could have tried Cygwin, but at that moment he didn’t have at home enough vodka. VMs could have been another option, but sadly libusb does not like with virtualization (at least in his limited experience).
So, he decided that installing a proper Linux environment was the best choice. “How hard could that be?” Fool.
As I said the Surface is a great device, but being good has its perks downsides. It’s a device with highly customized hardware and, apparently, Linux does not play nice with it. At least you can boot Ubuntu straight from UEFI using nothing but a standard live USB and a left swipe. It’s not that easy with Kali, or at least it hasn’t been for me, maybe you’re luckier than I was and everything works fine in the first place.
To finally boot Kali (let alone install it and make the Type Cover work, it’s going to be another chapter I still have to test on) I had to install rEFInd and then fine tune a USB stick prepared with rufus.
I am writing this post to make things easier for someone with the same intention, so let’s start with the guide itself.
This great piece of software is the first step into our headache. It is a boot manager (not a bootloader) developed for EFI/UEFI systems (from now on EFI will suffice) to make out life a little less miserable.
You can download rEFInd from the official website, go for the binary archive as we’ll have to install it manually. Extract the content in a folder of your choice.
The install procedure is well documented on the apposite page, I advice you to read it and understand the steps before going further.
Please note I won’t be responsible for any damage to your precious device, be sure to know what you’re doing! Make a backup of your EFI partition, even with EasyUEFI, it might save your ass!
First of all you need to disable secure boot in the UEFI screen. Press and hold Power+Volume UP keys while booting your device and, in the Security tab, Change configuration and then select None.
This is the TL;DR version of that page.
Run terminal as admin and:
cd <your refind-VersionNO folder path>
mountvol S: /S
xcopy /E refind S:\EFI\refind\
S:
cd EFI\refind
rename refind.conf-sample refind.conf
bcdedit /set {bootmgr} path \EFI\refind\refind_x64.efi
bcdedit /set {bootmgr} description "rEFInd boot manager"
No, {bootmgr} is not a variable, you have to input that exact string.
We’re all set. rEFInd is installed now. After a reboot you should be welcomed by something similar to the following, but with only a single entry: Windows.
rEFInd default screen
Yeah, I know it’s not important, but it’s still nice to have something which goes along well with the rest of the environment. You can install a theme for rEFInd made especially for surface devices, which mimics Microsoft design choices.
The theme has been shared with this reddit post but I’ve archived the rar file locally. Once you’ve extracted the archive, run terminal as admin and:
cd <theme extraction path>
mountvol S: /S
xcopy /E surface-theme S:\EFI\refind\surface-theme
We now want to edit rEFInd configuration in order to make it more convenient. My main OS is still going to be windows, even after I’ll have installed Kali (please, don’t hate me Linux fans), so I want my tablet to boot automatically in Windows if I don’t press any key in a couple of seconds after the OS selection screen appears. To do so, we have to edit rEFInd configuration file, refind.conf.
Boot in windows, run another terminal as admin and:
mountvol S: /S
S:
cd EFI\refind
refind.conf
You’ll be asked to choose the default application to open .conf file if it is not already assigned to a program in your computer. Even notepad could do, but why not notepad++? If you don’t have it already installed it might be the right time to get it!
Now, once we’re able to edit the file, you should:
Then save and close this file. Again, in the same terminal window as before:
cd surface-theme
theme.conf
Here you should change “resolution 2160 1440” to “resolution 2736 1820”, otherwise you’d get an error from rEFInd.
Once you’ve saved this file we’re ready for the next part.
It sounds a bit pompous, doesn’t it?
To create an install device you simply have to download the ISO from Kali website and use rufus (default settings are ok) to make a bootable USB. “Iso image mode” worked for me, so it should do for you too.
–Note– Rufus up to 2.12 is working with default settings, I haven’t been able to make newer version work but I haven’t even tried, actually…
The USB made with rufus won’t be recognised by rEFInd.
Create a folder
/EFI/boot
in your install medium.
Download from fedora’s archives these 2 files:
BOOTX64.EFI
grubx64.efi
and place them in the boot folder we’ve just made.
Create a file grub.cfg in the same folder with this content
# Config file for GRUB2 - The GNU GRand Unified Bootloader
# /boot/grub/grub.cfg
# DEVICE NAME CONVERSIONS
#
# Linux Grub
# -------------------------
# /dev/fd0 (fd0)
# /dev/sda (hd0)
# /dev/sdb2 (hd1,2)
# /dev/sda3 (hd0,3)
#
# root=UUID=dc08e5b0-e704-4573-b3f2-cfe41b73e62b persistent
set menu_color_normal=yellow/blue
set menu_color_highlight=blue/yellow
function load_video {
insmod efi_gop
insmod efi_uga
insmod video_bochs
insmod video_cirrus
insmod all_video
}
load_video
set gfxpayload=keep
# Timeout for menu
set timeout=5
# Set default boot entry as Entry 0
set default=0
set color_normal=yellow/blue
menuentry "Kali - Live Non-persistent" {
set root=(hd0,1)
linuxefi /live/vmlinuz boot=live noconfig=sudo username=root hostname=kali
initrdefi /live/initrd.img
}
menuentry "Kali - Live Persistent" {
set root=(hd0,1)
linuxefi /live/vmlinuz boot=live noconfig=sudo username=root hostname=kali persistence
initrdefi /live/initrd.img
}
menuentry "Kali Failsafe" {
set root=(hd0,1)
linuxefi /live/vmlinuz boot=live config memtest noapic noapm nodma nomce nolapic nomodeset nosmp nosplash vga=normal
initrdefi /live/initrd.img
}
menuentry "Kali Forensics - No Drive or Swap Mount" {
set root=(hd0,1)
linuxefi /live/vmlinuz boot=live noconfig=sudo username=root hostname=kali noswap noautomount
initrdefi /live/initrd.img
}
menuentry "Kali Graphical Install" {
set root=(hd0,1)
linuxefi /install/gtk/vmlinuz video=vesa:ywrap,mtrr vga=788
initrdefi /install/gtk/initrd.gz
}
menuentry "Kali Text Install" {
set root=(hd0,1)
linuxefi /install/vmlinuz video=vesa:ywrap,mtrr vga=788
initrdefi /install/initrd.gz
}
Or download it from here
We’re done now! Your drive should be recognized by rEFInd when you plug it in the USB port or even through a USB hub, which was incompatible with the official boot manager. It simply hang, which made it impossible to install the OS because the installer did not have Type Cover support.
I’d like to thank John Ramsden for his guide about installing Arch on a SP4, and because he pointed me in the right direction suggesting to give rEFInd a try.
Also, thanks to Rnihton for his guide on Kali forums. Yes, I know he called it “Beginner Way”, but it works. I’m not ashamed.
Thanks to frebib, the creator of the Surface Theme for rEFInd.
And, for last, I’d like to thank you for making it to the end of this guide, ignoring my not-so-perfect English (what a nice euphemism).
PS Sorry for that introduction, I won’t do that again, I promise.
]]>