Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
thedemons committed Jul 25, 2022
1 parent ec72f6b commit efa8db3
Show file tree
Hide file tree
Showing 14 changed files with 899 additions and 0 deletions.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# FiddlerMomoPlugin
Một plugin cơ bản dùng cho việc giải mã traffic của app **Momo**.

## Cài đặt
- Tải về plugin này [tại đây](/releases/tag/Release) và giải nén
- Xác định vị trí thư mục chứa **Fiddler**, đối với Fiddler 5 thì mặc định nằm ở `%localappdata%\Programs\Fiddler` hoặc `C:\Program Files\Fiddler`.
- Tiến hành copy 3 file (`FiddlerMomoPlugin.dll, Newtonsoft.Json.dll, và BouncyCastle.Crypto.dll`) vào thư mục `Scripts``Inspectors` nằm trong thư mục chứa **Fiddler**, xin hãy lưu ý là phải copy vào cả 2 thư mục.

## Sử dụng
- Để dùng được Fiddler với thiết bị Android, đầu tiên bạn phải làm theo [hướng dẫn này](/docs/fiddler-on-android/).
- Sau khi cài đặt thành công, nếu bạn đã đăng nhập vào **Momo**, hãy đăng xuất ra trước.
- Sau đó tiến hành đăng nhập vào **Momo**, bây giờ plugin dã load thành công và sẵn sàng để decrypt.
- Việc này là để thay *Momo public key* trong kết quả trả về của request login, hãy xem [Cách thức hoạt động của **FiddlerMomoPlugin**](/docs/technical-details/) để biết thêm chi tiết.

## Nâng cao
- Bạn có thể dùng hai lệnh này trong `QuickExec` của **Fiddler** để debug.
- `momo_debug`: mở console của plugin để xem output.
- `momo_auth`: để lấy Authorization Token của tài khoản **Momo** hiện tại.
- Hãy nhập vào ô ở góc dưới bên trái của **Fiddler** (phím tắt ALT+Q để focus).
- Ảnh xem trước:![console](/img/console.jpg)

## Giấy phép & Điều khoản
- Plugin này được xuất bản dưới [Giấy Phép Công Cộng GNU GPLv3](https://vi.wikipedia.org/wiki/Gi%E1%BA%A5y_ph%C3%A9p_C%C3%B4ng_c%E1%BB%99ng_GNU)
- Tuy nhiên, xin vui lòng không sử dụng plugin này vào các mục đích sau đây:
1) Botting, spamming, tấn công DDOS, hay chủ ý phá hoại máy chủ **Momo** bằng những phương thức tương tự.
2) Tạo tài khoản với danh tính ảo, bao gồm có hoặc không nhằm mục đích xấu, như ẩn danh, cờ bạc, giao dịch trái pháp luật.
3) Khai thác lỗ hỏng của **Momo** khi chưa được sự cho phép của **Momo** và các bên có liên quan.
4) Chỉnh sửa lại plugin nhằm lây lan mã độc, bao gồm việc không công khai mã nguồn.
5) Các mục đích xấu khác nhằm trục lợi, chiếm đoạt, tấn công, hay phá hoại bất cứ tổ chức hoặc cá nhân nào.

## Từ chối trách nhiệm *(Disclaimer)*
- Tác giả của plugin này không chịu trách nhiệm cho mọi hoạt động bất hợp pháp và vi phạm điều khoản như đã nêu ở trên của mọi người dùng.
- Mục đích tác giả tạo ra plugin này là cho việc nghiên cứu & tìm hiểu. Tác giả khuyến khích người dùng hãy khám phá **Momo API** một cách chính đáng với tinh thần học hỏi.

## **FiddlerMomoPlugin** có giúp ích cho bạn?
Hãy cho repository này một ⭐ nếu nó đã giúp bạn nhé! Điều này sẽ làm cho tác giả có động lực duy trì cập nhật và sửa lỗi!
13 changes: 13 additions & 0 deletions docs/fiddler-on-android/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
## Cách sử dụng Fiddler với thiết bị Android
Đã có khá nhiều hướng dẫn trên mạng cho việc này rồi. [Đây](https://youtu.be/mXN0dUDz0qk) là một video khá dễ hiểu và trực quan.

Hiện tại mình chưa có thời gian hoàn thiện hướng dẫn này, mình sẽ cập nhật trong tương lai, nhưng hãy tóm tắt nhé:
- Đầu tiên mở `Tools->Options` trong Fiddler, ở tab `Connections`, nhập `8888` vào ô `Fiddler listens on port`. Sau đó bật `Allow remote computers to connect`. Chuyển sang tab `HTTPS`, bật `Capture HTTPS CONNECTs`, `Decrypt HTTPS traffic``Ignore server certificate errors (unsafe)`.
- Cũng trong Fiddler, bạn nhìn phía trên bên phải sẽ có một icon máy tính và chữ Online, di chuột vào đó sẽ hiện ra thông tin IP. Có thể sẽ có nhiều IP hiện ra, bạn hãy ghi lại IP nào có dạng `192.168.x.x` xuất hiện đầu tiên (cái nằm ở trên cùng).
- Bây giờ hãy mở thiết bị Android lên, **lưu ý là thiết bị phải được ROOT mới có thể cài**, cài app [Root Certificate Manager(ROOT)](https://play.google.com/store/apps/details?id=net.jolivier.cert.Importer&hl=en&gl=US).
- Sau đó vào `Settings (Cài đặt)` của thiết bị, đi tới phần `Wifi (hay Kết nối)`, nhấn giữ vào tên wifi đang kết nối, sẽ có option hiện lên, click vào `Modify network (Chỉnh sửa kết nối)`. Ở phần Proxy, hãy chọn `Manual (Thủ công)`, sau đó nhập vào IP mà bạn lấy được ở bước ở trên, và Port là `8888`.
- Bây giờ mọi kết nối trên thiết bị đều được proxy qua fiddler, hãy thử mở trình duyệt trên thiết bị và truy cập `ipv4.fiddler:8888`, nếu ra một trang web thì bạn đã thành công. Còn nếu không được, hãy kiếm tra lại IP bạn nhập vào Proxy đã đúng chưa. Như trường hợp của mình proxy không hoạt động trên giả lập LDPlayer, chuyển sang Nox thì lại được, nếu gặp lỗi này bạn hãy thử dùng thiết bị khác nhé.
- Nếu đã vào được trang `ipv4.fiddler:8888`, bạn hãy nhấn vào link `FiddlerRoot certificate` ở cuối trang để download certificate. Sau khi download xong thì nhấn vào file đó để tiến hành cài certificate, ở ô `Certificate name` bạn có thể nhập tên bất kỳ.
- Bây giờ hãy mở app `Root Certificate Manager(ROOT)`, nhấn vào icon Folder ở trên bên phải (kế icon kính lúp) và chọn file certificate fiddler bạn vừa download. Nhấn Import.

Vậy là bạn đã kết nối thành công thiết bị với Fiddler.
94 changes: 94 additions & 0 deletions docs/technical-details/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@

# Cách thức hoạt động của **FiddlerMomoPlugin**
Đây là chi tiết kỹ thuật về cách mà plugin này giải mã traffic.

## Momo mã hóa như thế nào?
- Đầu tiên hãy xem qua một request đã được mã hóa
- ![encrypted_request](../../img/encrypted_request.jpg)
- Ở phần request header ta thấy có một trường `requestkey` dưới dạng base64, đây là `aes_key` đã được mã hóa bằng **RSA** sử dụng `MOMO_PUBLIC_KEY`.
- `MOMO_PUBLIC_KEY` là RSA Public Key được **Momo** trả về khi người dùng đăng nhập.
- Phần request và response body được mã hóa bằng `AES-256-CBC` sử dụng `aes_key`, với iv là 16 null bytes
- Dưới đây là pseudo code mô tả lại quá trình encrypt:
```python
aes_key = urandom(32) # aes_key là một dãy random 256 bits
requestkey = base64(rsa_encrypt(aes_key, MOMO_PUBLIC_KEY))
encrypted_request = aes_256_cbc_encrypt(plain_request, aes_key) # plain_request là data chưa mã hóa
request_body = base64(encrypted_request) # request body được post lên server
```
- Quá trình decrypt trên server:
```python
aes_key = rsa_decrypt(requestkey, MOMO_PRIVATE_KEY) # chúng ta không thể biết được private key của momo
encrypted_request = from_base64(request_body)
decrypted_request = aes_256_cbc_decrypt(encrypted_request, aes_key)
...
encrypted_response = aes_256_cbc_encrypt(plain_response, aes_key)
```
- Sau khi server response, client sẽ decrypt như thế này:
```python
# aes_key được tạo ở trên, lúc gửi request đi
decrypted_data = aes_256_cbc_decrypt(from_base64(response_body), aes_key)
```

## FiddlerMomoPlugin giải mã như thế nào nếu không có RSA Private Key của Momo?
- Nếu như bạn chưa biết về [RSA](https://vi.wikipedia.org/wiki/RSA_(m%C3%A3_h%C3%B3a)), thì nó là một loại [Mã hóa bất đối xứng](https://en.wikipedia.org/wiki/Public-key_cryptography) - nền tảng cho hầu hết mọi kết nối internet hiện tại (https).
- Về cơ bản, sẽ có 1 cặp key được tạo ra ngẫu nhiên, một Public và một Private. Khi dữ liệu được mã hóa bằng Public Key thì chỉ có thể được giải mã bằng Private Key và ngược lại.
- Dưới đây là pseudo code mô tả lại cách thức mã hóa:
```python
public_key, private_key = rsa_generate_key_pair() # tạo ra 1 cặp key ngẫu nhiên
plain_text = "Hello World!"

encrypted_text = rsa_encrypt(plain_text, public_key) # mã hóa bằng public key
decrypted_text = rsa_decrypt(encrypted_text, private_key) # và giải mã bằng private key
assert(decrypted_text == plain_text) # nếu không có lỗi gì xảy ra thì chúng ta đã giải mã thành công

# và ngược lại!
encrypted_text = rsa_encrypt(plain_text, private_key) # mã hóa bằng private key
decrypted_text = rsa_decrypt(encrypted_text, public_key) # và giải mã bằng public key
assert(decrypted_text == plain_text)
```
- Nếu tới đây bạn thắc mắc, nếu như `requestkey` đã được mã hóa RSA bằng `MOMO_PUBLIC_KEY`, vậy thì làm sao plugin này có thể giải mã nó? Thì đúng rồi đấy, điều đó là bất khả thi.
- Nhưng điều plugin này có thể làm là tráo `MOMO_PUBLIC_KEY` bằng một Public Key được chúng ta tạo ra, và chúng ta có Private Key tương ứng để có thể giải mã nó!
- Khi chúng ta đã có thể giải mã `requestkey` để lấy `aes_key`, thì việc còn lại chỉ là giải mã AES-256-CBC cho request và response body.

## Cách thức chi tiết việc giải mã
- Đầu tiên plugin này can thiệp vào request đăng nhập tới `htttps://owa.momo.vn/public/login`. API này sẽ trả về `MOMO_PUBLIC_KEY`, từ đó ta có thể thay thế nó bằng Public Key được chúng ta kiểm soát:
```csharp
string body = Encoding.UTF8.GetString(oSession.ResponseBody);
dynamic response = JsonConvert.DeserializeObject<dynamic>(body);

// lưu lại MOMO_PUBLIC_KEY
string publicKeyPEM = response.extra.REQUEST_ENCRYPT_KEY;
StringReader stream = new StringReader(publicKeyPEM);
momoPublicKey = (AsymmetricKeyParameter)new PemReader(stream).ReadObject();

// thay thế Public Key của chúng ta vào response body
response.extra.REQUEST_ENCRYPT_KEY = injectedPublicKeyPEM;
string newResponse = JsonConvert.SerializeObject(response);
oSession.ResponseBody = Encoding.UTF8.GetBytes(newResponse);
```
- Khi đã thay thế được `MOMO_PUBLIC_KEY`, chúng ta có thể tiến hành giải mã với Private Key tương ứng. Nhưng không chỉ thế, chúng ta cần phải mã hóa lại `requestkey` bằng `MOMO_PUBLIC_KEY` để server có thể giải mã được. Phải làm những thứ này trước khi request được gửi lên server:
```csharp
// giải mã requestKey với Private Key của chúng ta
string aes_key = RSADecryptWithInjectedPrivateKey(oSession.oRequest["requestkey"]);

// mã hóa lại bằng MOMO_PUBLIC_KEY
oSession.oRequest["requestkey"] = RSAEncryptWithMomoPublicKey(aes_key);

// lưu lại aes_key vào header của request vì chúng ta
// không có cách khác để lưu dữ liệu vào một session của Fiddler
// đây là một giới hạn, việc này có thể khiến Momo phát hiện ra
// chúng ta đang giải mã, nhưng nếu xảy ra việc đó, chúng ta sẽ
// tìm cách khác, còn bây giờ thì nó vẫn đang hoạt động ổn
oSession.oRequest["requestkey_decrypted"] = aes_key;

// giải mã request body bằng aes_key
string decrypted_data = AESDecrypt(Encoding.UTF8.GetString(oSession.RequestBody), aes_key);
```
- Sau khi server trả về response, chúng ta sẽ giải mã như sau:
```csharp
// giải mã response body
string encrypted_body = Encoding.UTF8.GetString(value);
string decrypted_body = MomoPlugin.AESDecrypt(encrypted_body, headers["requestkey_decrypted"]);
```

- Vậy là xong, nếu bạn đã đọc tới đây thì xin cảm ơn 🎉 hãy cho mình một ⭐ nhé!
Binary file added img/console.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/encrypted_request.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/quickexec.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 25 additions & 0 deletions src/FiddlerMomoPlugin.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.32002.261
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FiddlerMomoPlugin", "FiddlerMomoPlugin\FiddlerMomoPlugin.csproj", "{0052A8E9-ED84-43AE-985C-4409CA05582E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0052A8E9-ED84-43AE-985C-4409CA05582E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0052A8E9-ED84-43AE-985C-4409CA05582E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0052A8E9-ED84-43AE-985C-4409CA05582E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0052A8E9-ED84-43AE-985C-4409CA05582E}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {1B8D84DA-33B7-4448-B9ED-85B47CAC0ADA}
EndGlobalSection
EndGlobal
159 changes: 159 additions & 0 deletions src/FiddlerMomoPlugin/CConsole.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
using System;
using System.IO;
using Microsoft.Win32.SafeHandles;
using System.Runtime.InteropServices;

// Straight up stolen from https://stackoverflow.com/questions/15604014/no-console-output-when-using-allocconsole-and-target-architecture-x86
// Shoutout to the author

namespace CConsoleW
{
static class CConsole
{
private static bool _isOpen = false;

static public bool isOpen{ get { return _isOpen; } }

static public bool Initialize(bool alwaysCreateNewConsole = true)
{
if (_isOpen) return false;

_isOpen = true;

bool consoleAttached = true;
if (alwaysCreateNewConsole
|| (AttachConsole(ATTACH_PARRENT) == 0
&& Marshal.GetLastWin32Error() != ERROR_ACCESS_DENIED))
{
consoleAttached = AllocConsole() != 0;
}

if (consoleAttached)
{
InitializeOutStream();
InitializeInStream();
}

return true;
}

static public void LogWithColor(string text, ConsoleColor color, bool newLine = true)
{
ConsoleColor oldColor = Console.ForegroundColor;
Console.ForegroundColor = color;

if (newLine)
Console.WriteLine(text);
else
Console.Write(text);
Console.ForegroundColor = oldColor;
}

static public void LogRed(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.Red, newLine);
}
static public void LogGreen(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.Green, newLine);
}
static public void LogBlue(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.Blue, newLine);
}
static public void LogYellow(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.Yellow, newLine);
}
static public void LogCyan(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.Cyan, newLine);
}
static public void LogMagenta(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.Magenta, newLine);
}
static public void LogGray(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.DarkGray, newLine);
}
static public void LogWhite(string text, bool newLine = true)
{
LogWithColor(text, ConsoleColor.White, newLine);
}

private static void InitializeOutStream()
{
var fs = CreateFileStream("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, FileAccess.Write);
if (fs != null)
{
var writer = new StreamWriter(fs) { AutoFlush = true };
Console.SetOut(writer);
Console.SetError(writer);
}
}

private static void InitializeInStream()
{
var fs = CreateFileStream("CONIN$", GENERIC_READ, FILE_SHARE_READ, FileAccess.Read);
if (fs != null)
{
Console.SetIn(new StreamReader(fs));
}
}

private static FileStream CreateFileStream(string name, uint win32DesiredAccess, uint win32ShareMode,
FileAccess dotNetFileAccess)
{
var file = new SafeFileHandle(CreateFileW(name, win32DesiredAccess, win32ShareMode, IntPtr.Zero, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, IntPtr.Zero), true);
if (!file.IsInvalid)
{
var fs = new FileStream(file, dotNetFileAccess);
return fs;
}
return null;
}

#region Win API Functions and Constants
[DllImport("kernel32.dll",
EntryPoint = "AllocConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern int AllocConsole();

[DllImport("kernel32.dll",
EntryPoint = "AttachConsole",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern UInt32 AttachConsole(UInt32 dwProcessId);

[DllImport("kernel32.dll",
EntryPoint = "CreateFileW",
SetLastError = true,
CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr CreateFileW(
string lpFileName,
UInt32 dwDesiredAccess,
UInt32 dwShareMode,
IntPtr lpSecurityAttributes,
UInt32 dwCreationDisposition,
UInt32 dwFlagsAndAttributes,
IntPtr hTemplateFile
);

private const UInt32 GENERIC_WRITE = 0x40000000;
private const UInt32 GENERIC_READ = 0x80000000;
private const UInt32 FILE_SHARE_READ = 0x00000001;
private const UInt32 FILE_SHARE_WRITE = 0x00000002;
private const UInt32 OPEN_EXISTING = 0x00000003;
private const UInt32 FILE_ATTRIBUTE_NORMAL = 0x80;
private const UInt32 ERROR_ACCESS_DENIED = 5;

private const UInt32 ATTACH_PARRENT = 0xFFFFFFFF;

#endregion
}
}
Loading

0 comments on commit efa8db3

Please sign in to comment.