-
Notifications
You must be signed in to change notification settings - Fork 76
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
899 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` và `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! |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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` và `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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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é! |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} | ||
} |
Oops, something went wrong.