diff --git a/r0capture/script.js b/r0capture/script.js new file mode 100644 index 0000000..86a6d32 --- /dev/null +++ b/r0capture/script.js @@ -0,0 +1,339 @@ +/** + * Initializes 'addresses' dictionary and NativeFunctions. + */ +"use strict"; +rpc.exports = { + setssllib: function (name) { + console.log("setSSLLib => " + name); + libname = name; + initializeGlobals(); + return; + } +}; + +var addresses = {}; +var SSL_get_fd = null; +var SSL_get_session = null; +var SSL_SESSION_get_id = null; +var getpeername = null; +var getsockname = null; +var ntohs = null; +var ntohl = null; +var SSLstackwrite = null; +var SSLstackread = null; + +var libname = "*libssl*"; + +function uuid(len, radix) { + var chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split(''); + var uuid = [], i; + radix = radix || chars.length; + + if (len) { + // Compact form + for (i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]; + } else { + // rfc4122, version 4 form + var r; + + // rfc4122 requires these characters + uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'; + uuid[14] = '4'; + + // Fill in random data. At i==19 set the high bits of clock sequence as + // per rfc4122, sec. 4.1.5 + for (i = 0; i < 36; i++) { + if (!uuid[i]) { + r = 0 | Math.random() * 16; + uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]; + } + } + } + + return uuid.join(''); +} +function return_zero(args) { + return 0; +} +function initializeGlobals() { + var resolver = new ApiResolver("module"); + var exps = [ + [Process.platform == "darwin" ? "*libboringssl*" : "*libssl*", ["SSL_read", "SSL_write", "SSL_get_fd", "SSL_get_session", "SSL_SESSION_get_id"]], // for ios and Android + [Process.platform == "darwin" ? "*libsystem*" : "*libc*", ["getpeername", "getsockname", "ntohs", "ntohl"]] + ]; + // console.log(exps) + for (var i = 0; i < exps.length; i++) { + var lib = exps[i][0]; + var names = exps[i][1]; + for (var j = 0; j < names.length; j++) { + var name = names[j]; + // console.log("exports:" + lib + "!" + name) + var matches = resolver.enumerateMatchesSync("exports:" + lib + "!" + name); + if (matches.length == 0) { + if (name == "SSL_get_fd") { + addresses["SSL_get_fd"] = 0; + continue; + } + throw "Could not find " + lib + "!" + name; + } + else if (matches.length != 1) { + // Sometimes Frida returns duplicates. + var address = 0; + var s = ""; + var duplicates_only = true; + for (var k = 0; k < matches.length; k++) { + if (s.length != 0) { + s += ", "; + } + s += matches[k].name + "@" + matches[k].address; + if (address == 0) { + address = matches[k].address; + } + else if (!address.equals(matches[k].address)) { + duplicates_only = false; + } + } + if (!duplicates_only) { + throw "More than one match found for " + lib + "!" + name + ": " + s; + } + } + addresses[name] = matches[0].address; + } + } + if (addresses["SSL_get_fd"] == 0) { + SSL_get_fd = return_zero; + } else { + SSL_get_fd = new NativeFunction(addresses["SSL_get_fd"], "int", ["pointer"]); + } + SSL_get_session = new NativeFunction(addresses["SSL_get_session"], "pointer", ["pointer"]); + SSL_SESSION_get_id = new NativeFunction(addresses["SSL_SESSION_get_id"], "pointer", ["pointer", "pointer"]); + getpeername = new NativeFunction(addresses["getpeername"], "int", ["int", "pointer", "pointer"]); + getsockname = new NativeFunction(addresses["getsockname"], "int", ["int", "pointer", "pointer"]); + ntohs = new NativeFunction(addresses["ntohs"], "uint16", ["uint16"]); + ntohl = new NativeFunction(addresses["ntohl"], "uint32", ["uint32"]); +} +initializeGlobals(); + +function ipToNumber(ip) { + var num = 0; + if (ip == "") { + return num; + } + var aNum = ip.split("."); + if (aNum.length != 4) { + return num; + } + num += parseInt(aNum[0]) << 0; + num += parseInt(aNum[1]) << 8; + num += parseInt(aNum[2]) << 16; + num += parseInt(aNum[3]) << 24; + num = num >>> 0;//这个很关键,不然可能会出现负数的情况 + return num; +} + +/** + * Returns a dictionary of a sockfd's "src_addr", "src_port", "dst_addr", and + * "dst_port". + * @param {int} sockfd The file descriptor of the socket to inspect. + * @param {boolean} isRead If true, the context is an SSL_read call. If + * false, the context is an SSL_write call. + * @return {dict} Dictionary of sockfd's "src_addr", "src_port", "dst_addr", + * and "dst_port". + */ +function getPortsAndAddresses(sockfd, isRead) { + var message = {}; + var src_dst = ["src", "dst"]; + for (var i = 0; i < src_dst.length; i++) { + if ((src_dst[i] == "src") ^ isRead) { + var sockAddr = Socket.localAddress(sockfd) + } + else { + var sockAddr = Socket.peerAddress(sockfd) + } + if (sockAddr == null) { + // 网络超时or其他原因可能导致socket被关闭 + message[src_dst[i] + "_port"] = 0 + message[src_dst[i] + "_addr"] = 0 + } else { + message[src_dst[i] + "_port"] = (sockAddr.port & 0xFFFF) + message[src_dst[i] + "_addr"] = ntohl(ipToNumber(sockAddr.ip.split(":").pop())) + } + } + return message; +} +/** + * Get the session_id of SSL object and return it as a hex string. + * @param {!NativePointer} ssl A pointer to an SSL object. + * @return {dict} A string representing the session_id of the SSL object's + * SSL_SESSION. For example, + * "59FD71B7B90202F359D89E66AE4E61247954E28431F6C6AC46625D472FF76336". + */ +function getSslSessionId(ssl) { + var session = SSL_get_session(ssl); + if (session == 0) { + return 0; + } + var len = Memory.alloc(4); + var p = SSL_SESSION_get_id(session, len); + len = Memory.readU32(len); + var session_id = ""; + for (var i = 0; i < len; i++) { + // Read a byte, convert it to a hex string (0xAB ==> "AB"), and append + // it to session_id. + session_id += + ("0" + Memory.readU8(p.add(i)).toString(16).toUpperCase()).substr(-2); + } + return session_id; +} + +Interceptor.attach(addresses["SSL_read"], + { + onEnter: function (args) { + var message = getPortsAndAddresses(SSL_get_fd(args[0]), true); + message["ssl_session_id"] = getSslSessionId(args[0]); + message["function"] = "SSL_read"; + message["stack"] = SSLstackread; + this.message = message; + this.buf = args[1]; + }, + onLeave: function (retval) { + retval |= 0; // Cast retval to 32-bit integer. + if (retval <= 0) { + return; + } + send(this.message, Memory.readByteArray(this.buf, retval)); + } + }); + +Interceptor.attach(addresses["SSL_write"], + { + onEnter: function (args) { + var message = getPortsAndAddresses(SSL_get_fd(args[0]), false); + message["ssl_session_id"] = getSslSessionId(args[0]); + message["function"] = "SSL_write"; + message["stack"] = SSLstackwrite; + send(message, Memory.readByteArray(args[1], parseInt(args[2]))); + }, + onLeave: function (retval) { + } + }); + +if (Java.available) { + Java.perform(function () { + function storeP12(pri, p7, p12Path, p12Password) { + var X509Certificate = Java.use("java.security.cert.X509Certificate") + var p7X509 = Java.cast(p7, X509Certificate); + var chain = Java.array("java.security.cert.X509Certificate", [p7X509]) + var ks = Java.use("java.security.KeyStore").getInstance("PKCS12", "BC"); + ks.load(null, null); + ks.setKeyEntry("client", pri, Java.use('java.lang.String').$new(p12Password).toCharArray(), chain); + try { + var out = Java.use("java.io.FileOutputStream").$new(p12Path); + ks.store(out, Java.use('java.lang.String').$new(p12Password).toCharArray()) + } catch (exp) { + console.log(exp) + } + } + //在服务器校验客户端的情形下,帮助dump客户端证书,并保存为p12的格式,证书密码为r0ysue + Java.use("java.security.KeyStore$PrivateKeyEntry").getPrivateKey.implementation = function () { + var result = this.getPrivateKey() + var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName(); + storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue'); + var message = {}; + message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue'; + message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); + var data = Memory.alloc(1); + send(message, Memory.readByteArray(data, 1)) + return result; + } + Java.use("java.security.KeyStore$PrivateKeyEntry").getCertificateChain.implementation = function () { + var result = this.getCertificateChain() + var packageName = Java.use("android.app.ActivityThread").currentApplication().getApplicationContext().getPackageName(); + storeP12(this.getPrivateKey(), this.getCertificate(), '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12', 'r0ysue'); + var message = {}; + message["function"] = "dumpClinetCertificate=>" + '/sdcard/Download/' + packageName + uuid(10, 16) + '.p12' + ' pwd: r0ysue'; + message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); + var data = Memory.alloc(1); + send(message, Memory.readByteArray(data, 1)) + return result; + } + + //SSLpinning helper 帮助定位证书绑定的关键代码a + Java.use("java.io.File").$init.overload('java.io.File', 'java.lang.String').implementation = function (file, cert) { + var result = this.$init(file, cert) + var stack = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()); + if (file.getPath().indexOf("cacert") >= 0 && stack.indexOf("X509TrustManagerExtensions.checkServerTrusted") >= 0) { + var message = {}; + message["function"] = "SSLpinning position locator => " + file.getPath() + " " + cert; + message["stack"] = stack; + var data = Memory.alloc(1); + send(message, Memory.readByteArray(data, 1)) + } + return result; + } + + + Java.use("java.net.SocketOutputStream").socketWrite0.overload('java.io.FileDescriptor', '[B', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount) { + var result = this.socketWrite0(fd, bytearry, offset, byteCount); + var message = {}; + message["function"] = "HTTP_send"; + message["ssl_session_id"] = ""; + message["src_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop())); + message["src_port"] = parseInt(this.socket.value.getLocalPort().toString()); + message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop())); + message["dst_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop()); + message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); + var ptr = Memory.alloc(byteCount); + for (var i = 0; i < byteCount; ++i) + Memory.writeS8(ptr.add(i), bytearry[offset + i]); + send(message, Memory.readByteArray(ptr, byteCount)) + return result; + } + Java.use("java.net.SocketInputStream").socketRead0.overload('java.io.FileDescriptor', '[B', 'int', 'int', 'int').implementation = function (fd, bytearry, offset, byteCount, timeout) { + var result = this.socketRead0(fd, bytearry, offset, byteCount, timeout); + var message = {}; + message["function"] = "HTTP_recv"; + message["ssl_session_id"] = ""; + message["src_addr"] = ntohl(ipToNumber((this.socket.value.getRemoteSocketAddress().toString().split(":")[0]).split("/").pop())); + message["src_port"] = parseInt(this.socket.value.getRemoteSocketAddress().toString().split(":").pop()); + message["dst_addr"] = ntohl(ipToNumber((this.socket.value.getLocalAddress().toString().split(":")[0]).split("/").pop())); + message["dst_port"] = parseInt(this.socket.value.getLocalPort()); + message["stack"] = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); + if (result > 0) { + var ptr = Memory.alloc(result); + for (var i = 0; i < result; ++i) + Memory.writeS8(ptr.add(i), bytearry[offset + i]); + send(message, Memory.readByteArray(ptr, result)) + } + return result; + } + + if (parseFloat(Java.androidVersion) > 8) { + Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { + var result = this.write(bytearry, int1, int2); + SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); + return result; + } + Java.use("com.android.org.conscrypt.ConscryptFileDescriptorSocket$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { + var result = this.read(bytearry, int1, int2); + SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); + return result; + } + } + else { + Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLOutputStream").write.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { + var result = this.write(bytearry, int1, int2); + SSLstackwrite = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); + return result; + } + Java.use("com.android.org.conscrypt.OpenSSLSocketImpl$SSLInputStream").read.overload('[B', 'int', 'int').implementation = function (bytearry, int1, int2) { + var result = this.read(bytearry, int1, int2); + SSLstackread = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Throwable").$new()).toString(); + return result; + } + + } + } + + ) +}