Reversing APKs for fun and profit: "Decrypting Secret.ly"
I found
Secret.ly intersting. this site let's people post-up what's on their
minds
without
getting exposed, by name or any other details as they ("secret" developers) have been announced.
There is
also some bounty for challenging their security related parts of their system,
which boosted my motivation to try defeating this new way of communication.
So, after
stretching my back, and drinking some beer, 2AM, silence at my neighborhood,
time to start working :)
Knowledge Requirements:
1.
Little understanding of how
does android app is built, should be enough, a bit of Java code understanding.
2.
Little bit knowledge in
Reversing.
3.
Some basics in Crypto would
help (knowing what is AES should be enough).
In This Post:
1.
Sniffing and
decrypting some android HTTPS/SSL stuff.
2.
Reversing/Modding
apks (a bit about debugging those also).
3.
Encryption/Decryption
in practical usage.
4.
Some cool,
pure research and fun J
Goals:
1.
Researching secret.ly mechanism, way of communicating with server-side,
encryption
used, and cookies invloved. understanding of the implementation at whole.
2. Afterwards,
managing to challenge the server, trying to:
a. Find out who's the author of
post that is shown to me.
b. Find security problems with
that mechanism, that could make user exposed to attacks,
such as MITM, cookie-stealing and more..
Setup Used:
HTTPS is
mainly used here for communicating with the secret api server
(api.iheartsecret.com)
therefore,
i've arranged:
in my home-pc:
Installed Fiddler,
apktool, smalli, baksmali, jadx, 010Editor (always good), python.
* apktool, baksmali, jadx - used for unpacking/disassembling of apks and dexs,
which contains the main code and resources for every app,
jadx is best i've
found for converting dex to jars (Java-code)!
* in
fiddler, enabled HTTPS decryption of remote client,
setting proxy port for
proxy transportation theough fiddler.
in my android:
Installed Fiddler certificate as CA, SSH Server, ZipSigner, Apk-Installer.
* in my
android i've also configured my wifi connection to use the proxy of fiddler
in my home-pc,
so all traffic will go through it.
Why is it enough for sniffing HTTPS (HTTPS-MITM)?
well, how
does https work? it verifies the domain certificate, so that the domain you're
trying to connect to, as some certificate aythority, that has been signed on
it, and that CA (Certificate Authority) is also installed, already in your
android, in the section of "Valid Certificates". (There is such in
each android OS).
So, when
the app is connecting now in https, fiddler will generate certificate for the
secret' domain, and because the CA signed on it, is installed manually by us,
previously, it will be verified as correct, and the checks will successfully go
through.
The Jibbrish flows in...
Let's
Start! I've opened up my fiddler, and started sniffing connections to the
secret site.
After
clicking the app for some notifications, like this:
So i've
came-across to some uniquely generated gibbrish, fun-fun-fun:
Well man,
that's some unreadable POST-body, ain't it?
Tried check if its:
1.
Gzip – no
2.
Deflate –
no
3.
Zlib of
some level – no
4.
Encrypted?
Encryption
seems the best option, as I've seen this header:
Denote the three parts of it: "ae5" || "34740486…03"
|| "CW-EnV88...=="
1.
Encryption type: Ae5 ~ "Aes"???
Let's check it up, taking lots of POST-body data (denoted as mi),
And then doing:
gcd(len(m1), len(m2), …, len(mn))
Why you do that? Cause if it’s a
block-cipher, as expected, like AES, the cipher data should be aligned to
16bytes size/128bit, which is exactly the block-size of AES.
The result was 16, as expected in bytes, This is AES!
2.
The AES IV? AES nowadays
usually uses IV, its some random number, known to all, not a secret, which used
to encrypt the message. Here, lets view some random "X-Secret-Enc"
headers:
X-Secret-Enc: ae5;3474048679760765703;IhA2j7W7VB1HBHQltjrciQ==
X-Secret-Enc: ae5;3474048679760765703;XquoSMfjKLBlpahxMesciA==
X-Secret-Enc: ae5;3474048679760765703;P7I_R9C-ZKRueWPzpYmqbg==
One can clearly see that only the last part, seems as base64-encoding,
is changed in the header, in some session of
connection (I've sent some requests for pages in the same secret connection).
After looking over the base64 parts, I've found out that they are
really 16bytes of IV data! Doing: base64.urlsafe_decode(last_string), will
reveal that information.
Hmm, wise person would ask, why this is the IV and not the KEY? That's a good question my friend, and we cannot be sure that is
correct until trying out.
3.
The AES Key? That was the
hardest to find, i've assumed it is somehow related to this header, but
didn’t really know how, reversing the apk itself was non-effective, but I've
managed doing a lot of it, a lot of obfuscation – invalid names, strings
encryption, invalid classes, many things probably generated in runtime and
more.
But one thing was sure my friend, the second
part of the X-Secret-Enc, here it was "3474048679760765703", is the X-Whispr ID, its some runtime generated by server id, found that in the
configuration:
<string name="key_sly_user_session_id">3474048679760765703</string>
Notice also, that this part isn’t change each time , made me think it has
something related to the key!
Starting Reversing :
This part
is little bit more technical, For the advanced reader, so you can actually skip
it if you want, it mainly shows my research directions, and various
tries, until reaching the correct result. It also shows my usage experience
with some programs, and conclusions from working with those.
Got the
apk, out of the /data/data/ly.secret… folder, under the name pkg.apk.
So, as, I've said, I used for unpacking all
of the above:
Method
|
Result
|
The downside
|
IDA
|
IDA successfully decompiled the classes.dex
into
|
No cross-refs, ida doesn't manage to fully
comprehend connection, I've passed that by idapython script to view real
xrefs. Also, all data/code is still obfuscated and is dalvik byte-code, and
this isn't a really comfortable environment to work in.
·
IDAPython crossrefs can be found
easily using:
· hex(list(XrefsTo(ScreenEA()))[i].frm)
|
Apktool
|
Apktool d <apkname>, with renaming of
invalid names
|
Not all names were remapped.
|
baksmali
|
Decompiled ok to smali files, seems like IDA
result, no advantage was in using the latest one here.
|
Like in IDA.
|
Jadx
|
Unpacked all sources.. seems complete. Nice
gui J
|
Again, ofsucation, couldn't find any part of
the string: "X-Secret-Enc" / "Secret-Enc" /
"Enc" (in a string)
|
Dex2jar
|
Works ok, no errors…
|
didn't unpack some of the sources I've seen
using jadx
|
Example of .java unpacked class:
This is the SlyRequest, responsible for
wrapping all http-requests, done using the HttpOk library.
Its below the HTTPS, and so, all
Requests that goes in HTTP to the server side.
Strings
searching:
I've searched for many strings, also, in
the original files: apk/res/.dex/decrypted resources/unpacked sources/IDA and
more…
In all of them, I didn't find any of the
words: "X-Secret-Enc" / "Secret-Enc" / "Enc"
(in a string) /
I've found although, two strings with
32-bytes length, filled with hex-letters:
mixpanel
key? - '33a52c4d9051b8385d4492ab74fca9c0'
app key? -
'f5d476357695fc86ceed1524cb4218ca'
·
Those aren't worked as well… L
Going over
function calls to dec/enc/cipher related:
1.
Found 2 relations to Crypto.Cipher (javax.Cipher
and etc.. ) related stuff, but:
a.
Params not found, probably
encrypted/obfuscated in some resources. Flow to it wasn't clear.
b.
Usage isn't really clear, why there is
SHA256 and also SHA1 / MD5.. also I've seen HMACs flowing there, all references
aren't interpreted well in the sources generated, in IDA I can conclude refs,
but then going back and forth from sources and IDA is ineffective, and I wasn’t
in the mood of writing IDA synchronizing script with the sources file although
it's possible.
2.
Done that also for KeySpec class in java
and many more…
3.
Finding do_final / init functions, for
encryption usuall init/enc callers. Didn’t reach the real key used, again,
obfuscation and stuff.
Trying
Decrypting with correct-oriented-tries:
Try many ways to decrypt encrypted POST
data, none worked:
Denote the three parts of it: "ae5" ||
"34740486…03" || "CW-EnV88...==" as
Header || SID || IV (after decoding base64)
·
AES(key=md5(SID), iv=IV).decrypt(POST_DATA)
? NO!
·
AES(key=IV,
iv=md5(SID)).decrypt(POST_DATA) ? NO!
·
AES(key=key_found_in_ida1,
iv=IV).decrypt(POST_DATA) ? NO!
·
AES(key=key_found_in_ida2,
iv=IV).decrypt(POST_DATA) ? NO!
·
AES(key=strings_in_IDA,
iv=IV).decrypt(POST_DATA) ? NO!
Also, I've tried SHA256 of the SID, with
IV given,and even MD5(IV) as the IV. Nothing, all I've got was Gibbrish L
Debugging
of the apk?
This was a thought for the future, and
wasn't tested, although it can be done with the baksmali/smali, Its called
SmaliDebugging, and you can extract and repack your apk in DebuggingMode, cool
stuff J
Until this step, I've been really stuck,
I've searched all the resources for passwords/keys name/strings related try
finding base64/hex strings of 16/32
bytes, or base64 that can make 16byte keys and more… didn't help.
3
ways to keep going further:
1.
Looking at old Secret.ly apks.
2.
Looking at logs.
3.
Debugging the apk!
Reversing Old Secret:
I had an
idea! Why not downloading old-version? Maybe there the app won't be so
obfuscated and even relate to the updated current one?
So I've Downloaded old apk of secret, I've
found 'X-Secret-Enc' Header! but now with 'ae2' is the start!? whats happening
her?!.
Code of encryption there:
static byte[] encrypt(byte[] bArr, byte[]
bArr2) {
byte[] digest =
MessageDigest.getInstance("SHA-256").digest(bArr2);
byte[] a = Bytes.a(bArr, ZeroBytes);
//Bytes.a(a1,a2) = array-appending - a1 to a2.
// ZeroBytes = array of
64bytes of zero.
byte[] bArr3 = new byte[16];
Cipher instance =
Cipher.getInstance("AES/CBC/PKCS7Padding");
instance.init(1, new SecretKeySpec(digest,
"AES"), new IvParameterSpec(bArr3));
bArr3 = instance.doFinal(a);
Key secretKeySpec = new
SecretKeySpec(digest, "HmacMD5");
Mac instance2 =
Mac.getInstance(secretKeySpec.getAlgorithm());
instance2.init(secretKeySpec);
instance2.update(a);
return Bytes.a(bArr3,
instance2.doFinal()); }
·
So here, IV={0, … 0} (16 bytes of zero).
Hmm.. so probably they just have added iv into the equation.
·
But wait, Key here is new 256bits? weird..
The block size we had was 16-bytes only/128bits?!*
·
There is also HMAC-MD5 appended, with
same key used for encryption, over the data.
*I've
actually found out that even key in AES is 256bits, the block size always stays
128bits, so the output is always aligned to 128bits/16bytes and not as I
thought it should, to 32bytes/256bits if the key is 256bits.
AES Parameters
in old APK
public TypedOutput toBody(Object obj) {
….
Log.b("GsonEncryptingConverter", "Magic=" + b[2]); // Logs using android logging-system.
// h =
session id string, b[2] = Magic of the current apk J
return new EncryptedOutput(Crypts.a(bytes,
String.format("%d%s", new Object[]{Integer.valueOf(b[2]),
h}).getBytes(Charsets.c)), Charsets.c.name());
}
·
Viola, Key
in old version is the sha256 of – "MAGIC || SID" !!!
·
Magic is also printed to logs, in old
version, logs can be dumped using ssh/TerminalEmulator app with cmd "logcat –d
> logs_dump.txt"
·
But i don't see any logs! not even the "Magic=%s", one.. :(
· Digging in the code, i've found out, that there is some ke class, that isn't enabling logs on default...
· Let's Mod that apk up!
"Sweet logs of Mine":
·
looking for
debug messages of the program...? to enable logs
·
Logs are
controlled in the ke class, using some boolean!
·
Logs are ENABLED
ONLY IN "STAGING MODE", not IN OUR "PRODCTION MODE"! so lets change
the preference – to true!
·
Added one
line – "const v0, 0x1" that’s it! (0x1 = True in dalvik byte-code)
·
REPACKED
Using APKTOOL: now I've extra debug info, leaked me
some more data on the enc stuff.
o
Just do: apktool d –res apk-path
o
Change
smali bytecode in your favorite text editor, add that
line to the ke init class.
o
Repack
with: apktool b –res folder-to-pack
Logs output Example:
Under-HTTPS Real Encryption Details:
Now, after having the magic, I have tried to assume what the current
encryption would like, I WAS CORRECT after some tries, so here is how Secret
developers encrypt their POST_DATA J:
magic=2006772160
sid=741681943761985306 (it just written after the 'ae5' in the
X-Secret-Enc block).
key=sha256(magic+sid) # seen that in old secret version with
iv=256bytes.
iv=base64.decode(X_Secret_Enc_Header[2])
Encryption-Output := AES(KEY=key, IV=iv).encrypt(POST_DATA ||
zero_array) || HMAC-MD5(KEY=key, data=POST_DATA || zero_array)
·
|| means
strings-concatenation (e.g: "A"
|| "B" == "AB").
·
zero_array
is the 64-bytes of zero array.
Examples of Decryption POST-DATA:
>>> AES.new(hashlib.sha256('2006772160' +
'741681943761985306').digest(), mode=AES.MODE_CBC, IV=base64.urlsafe_b64decode(b[-1])).decrypt(a[-1][1])
'{"Ids":["kdfxmnsurgktwybwqmrydxchzl"],"Headers":{"X-App-Id":"ly.secret.android","X-Client":"Android/10","X-Nonce":"Bv4cf3WxYF8IU1jSNQmOeI8CdIx2ny9PNrFd1418525476965","X-Whispr-Sid":"741681943761985306"}}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x05\x05\x05\x05\x05'
This is standard JSON dictionary, readable, finally! J
*The end of the decryption is trailing of the alignment to 16bytes,
done automatically by calling to the AES/CBC/PKCS7Padding.
Mid-Conclusion:
Done: Encryption/Decryption/Authentication
of packets under HTTPS, in the app-version of the secret.ly site, checked and
reversed using my android version 4.4, secret v10.
Next?
As i have much spare time as for now, i will continue investigating packets send to/from secret server, hoping to find something intersting, now that i can generate my own malformed packets :)
To Be Continued... :)
To Be Continued... :)
References:
·
AES –http://en.wikipedia.org/wiki/Advanced_Encryption_Standard
·
Python libs used – Requests /
Crypto / hashlib / json/ more…
·
APK tools and many more, a real treasure!