NOTE: bài blog này mang tính học tập, không phục vụ cho mục đích trái đạo đức của cá nhân (mặc dù có thể phục vụ được).
TL;DR
Cheat là một việc không thể chấp nhận trong hầu hết các tựa game, nhưng đó là đối với những game multiplayer hoặc co-op vì nó làm ảnh hưởng đến các người chơi khác hoặc đến tổ chức nào đó. Còn đối với game singleplayer (hay còn gọi là game offline) thì việc cheat game có thể được xem là một việc đem đến cảm giác trải nghiệm khác cho người chơi, vì nó không ảnh hưởng đến bất cứ người nào khác nên có thể chấp nhận được việc cheat & mod. Còn mà đem kết quả sau khi cheat ở game singleplayer lên cộng đồng để làm màu hoặc để khè thì lại là một câu chuyện khác.

1. Mod game
Cơ bản trước khi cheat một tựa game chúng ta cần phải biết mod game.
Tôi biết sẽ có người thắc mắc kiểu:
- Mod game là gì? Nó liên quan gì tới cheat game?
Hoặc:
- Mod với Cheat khác nhau à ?
Để trả lời cho các câu hỏi kiểu trên thì tôi sẽ định nghĩa việc mod game và cheat game (nếu có sai mong người đọc có thể nhắc nhở nhẹ).
- Mod game: là việc thay đổi structure của game để làm cho game hoạt động theo hướng mình muốn. Ví dụ như là chỉnh sửa file `Assembly-csharp.dll` trong các dòng game được viết bởi unity chẳng hạn.
- Cheat game: là việc lợi dụng các tính năng sẵn có trong game hoặc xuất phát từ việc lạm dụng việc mod game để đạt được mục đích xấu xa nào đó (Nói chung có nhiều cách để cheat). Ví dụ như bật god mode (bất tử) khi chơi game.
Vậy bài viết này đang nói về mod game hay là cheat game?
Cả 2, như đã nói trên cheat game là việc lạm dụng mod game. Nên bài viết này nói đến cheat game sẽ nói luôn cả mod game.
2. About Unity.
Trước khi đi vào câu chuyện chính, ta cần phải biết được game được tạo bởi Unity sẽ hoạt động như thế nào.
Game được tạo bởi Unity sẽ hoạt động theo scripting backends của Unity. Vậy scripting backends của Unity là gì?
Nó là một famework của Unity dùng để xử lý và thực thi source code được viết trong Unity engine.
Scripting backends của Unity có 2 loại: Mono và IL2CPP.

Mono:
- Mono backend sử dụng JIT (Just-in-time) compilation để thực thi code khi game được mở lên. Mono được sử dụng rộng rãi trên nhiều nền tảng bao gồm Windows, MacOS, và Linux.
- Dấu hiệu nhận biết của một game khi sử dụng Mono là có
Assembly-CSharp.dll
trong folderData>Managed

IL2CPP:
- Khi build một game sử dụng IL2Cpp backend (thay thế Mono) Unity engine sẽ chuyển đổi MSIL (Microsoft Intermediate Language)(ví dụ như c#) thành code c++, sau đó sử dụng code c++ để compile ra các file native binary tùy theo platform. Kiểu compile này của Unity được gọi là ahead-of-time (AOT) compilation.
- Sự xuất hiện của
GameAssembly.dll
trong folder game chính là dấu hiệu của Il2CPP.

Theo kinh nghiệm chơi game thì thấy game sử dụng IL2CPP sẽ nhanh hơn là Mono ¯\_(ツ)_/¯.
Ok, đến đây là đủ biết cách phân biệt các loại game tạo bởi Unity sử dụng loại backend nào để từ đó có thể biết đường mà đi tiếp.
3. It’s time to chít (cheat).
Bây giờ sẽ là câu chuyện chính của bài viết này. Bài viết này sẽ đi theo hướng game sử dụng IL2CPP backend.
Bạn có thể thắc mắc tại sao không chọn Mono mà là IL2CPP. Đơn giản vì hồi còn là newbie thì tôi đã viết rồi ( ͡° ͜ʖ ͡°). Link bài viết:
CHỌN TARGET:
Để bắt đầu một cuộc cheat, tôi thường sẽ đi tìm cho mình một thể loại game mà tôi thích chơi vì khi thích chơi thì tôi sẽ có đam mê cố gắng cheat nó ( ͡° ͜ʖ ͡°). Target của lần này sẽ là một tựa game offline sinh tồn: “Delivery from the Pain:Survival”. Link mua game:
Tất nhiên là game này sẽ sử dụng IL2CPP nên tôi mới dùng làm target cho bài viết này.

Sau khi chọn target, tôi sẽ trải nghiệm game 1 thời gian tầm 15–30 phút để mình có thể hiểu được lối chơi của game cũng như sẽ nắm rõ được một số chức năng của game sẽ hoạt động thế nào. Ví dụ như khi tấn công sẽ bị tiêu hao gì hoặc khi tạo một item sẽ cần điều kiện gì,….
Tóm lại quy trình cheat game của tôi sẽ là:
Chọn target -> Trải nghiệm -> Reverse game + chọn mục đích cheat -> Mod game (cheat)-> Design cheat engine cho target (option)
REVERSE GAME + chọn mục đích cheat:
Để reverse được game, cần tìm 2 file sau trong game: global-metadata.dat (nằm trong folder DeliveryFromThePain\DeliveryFromThePain_Data\il2cpp_data\Metadata
) và GameAssembly.dll (nằm chung chỗ với file .exe tại folder game)


- global-metadata.dat chứa thông tin về metadata, type information, Method, … của game. File này được sử dụng để giúp cho game khi chạy có thể nhận dạng đúng thứ mà nó cần. Ví dụ về 1 hành động di chuyển sẽ cần function chịu trách nhiệm di chuyển thì file này sẽ cung cấp thông tin function đó ở đâu trong
GameAssembly.dll
và function đósử dụng nhưng thông tin gì. - GameAssembly.dll là một file binary chứa toàn bộ native code được convert từ code được viết cho game (c#).
Từ 2 file này, tôi có thể dump ngược lại thành các file dll (Assembly-CSharp.dll, UnityEngine.dll,…), thông tin địa chỉ của các function của game do dev viết ra. Tool tôi sử dụng để dump là Il2CppDumper.
Sau khi dump, tôi có:
- Những DLL mà Mono backend sử dụng.

- Thông tin của toàn bộ function, có tên file là script.json :

- il2cpp.h: chứa toàn bộ thông tin các struct được convert từ class.

Sau khi dump xong, tôi ném file Assembly-CSharp.dll vào dnspy. Nhìn thì thấy nó không hề bị obfucate cái gì

Lúc này tôi bắt đầu load GameAssembly.dll vào IDA, sau đó sẽ run script ida_py3.py (script của Il2CppDumper support cho IDA)với input là script.json (file thông tin của tất cả function). Vì thời gian load data vào IDA rất lâu nên tôi ưu tiên việc này trước.

Trong quá trình đợi load data vào IDA, tôi sẽ lướt qua các class + function
của Assembly-CSharp.dll
để xem những function nào đáng chú ý để tôi có thể lợi dụng để cheat. Nhưng class + function
rất nhiều, tôi không thể đọc hết được vì tôi rất lười, cho nên tôi sẽ tìm kiếm vào thứ tôi muốn.
Trong game có một tính năng là tạo item các thứ. Hình minh họa:

Tôi sẽ tìm kiếm các function có chứa keyword make
hoặc là build
. Tôi chú ý đến một function là OnBtn_MakeClick()
nằm trong class MakeView
.

Tại sao tôi lại chú ý đến function này?
> Tại vì theo kinh nghiệm cheat và code game thì những function bắt đầu bằng OnBtn
thì thường là handler function cho các event khi bấm vào button, và ở đây sẽ là button make.
Tôi sẽ confirm chú ý của tôi bằng cách debug…
Search function đó trong script.json
, tôi có fullname của function này (MakeView$$OnBtn_MakeClick
). Có thể search nó thẳng trong IDA, nhưng sẽ có rủi ro gây ra lag hoặc crash ida.

Sau khi IDA hoàn thành việc load script.json
, tôi search fullname của function mà tôi target trong IDA và set breakpoint trong nó.

Vì GameAssembly.dll
được load vào trong game nên tôi có thể attach debug process của game.

Sau khi tôi đã attach process, tôi sẽ click vào button để make item (button có chữ Manufacture
).

Sau khi nhấn xong tôi đã trigger được breakpoint
=> Nhờ sự kinh nghiệm và trãi nghiệm, tôi đã đúng.

Tiếp tục bước này, tôi detach khỏi process để phân tích code tiếp.
Trong function handle event click button, có 1 dòng if để đi theo Make
hoặc Repair
. Chỗ này nhìn cũng đủ hiểu (nếu có để ý trong game một chút), đây là để biết mình đang lựa chọn Make
(Manufacture) hay làRepair
(Repair).


Vì mục đích của tôi là Make
nên tôi sẽ deep sâu vào MakeView_Make
. MakeView_Make
là function handle chính của việc make item.

MakeView_Make
Tôi chú ý được các function sau:
- LearnSystem__IsLearnedByUnlockItemID: check Item đã được learn hay chưa. Trong game, khi muốn tạo 1 item thì phải learn item trước đó. Tôi sẽ mod lại chỗ này bằng cách cho if luôn luôn true vậy thì tôi sẽ cheat được tạo item không cần phải learn trước đó. Chú ý là không cần phải mod lại nguyên function check, bởi vì có thể sẽ ảnh hưởng về sau chơi game.
- MakeSystem__GetMakeCount: Function này dùng để lấy số lượng hiện tại của item, mục đích là check xem số lượng item đã đạt đến ngưỡng tối đa chưa. Trong game có một số item sẽ giới hạn lại số lượng, khi đạt ngưỡng tối đa thì không thể make được nữa. Chỗ này có thể cheat hoặc không cũng được ¯\_(ツ)_/¯.
- MakeSystem__IsEnoughElectric: Function này sẽ check xem player có đủ điện để tạo item không, vì trong game sẽ có một số item khi make sẽ có thêm điều kiện là electronic (hầu hết thì không cần điều kiện này). Chỗ này bắt buộc phải cheat.
Sau khi hiểu context của 3 function trên, tôi có một câu hỏi: “Vậy làm sao để biết được item này có bị giới hạn không?” hay là “Làm sao để biết được item này có cần điều kiện electronic hay không?”. Để trả lời câu hỏi tôi sẽ quyết định làm clean code hơn.
Quay lại với Script.json
, tôi thấy function sẽ theo dạng sau:void MakeView__Make (MakeView_o* __this, const MethodInfo* method)
.

Trước khi tôi define lại type của function MakeView__Make trong IDA, thì tôi cần phải import các struct của game từ file il2cpp.h vào IDA.
Có thể sử dụng script ida_with_struct_py3.py với input là il2cpp.h, nhưng tôi có một lời khuyên là không nên làm thế bởi vì nó rất lâu, chưa kể là có thể crash IDA làm cho tốn thời gian trước đó. Solution cho vấn đề này là: import bằng tay những struct liên quan với struct MakeView_o hoặc chia nhỏ file il2cpp.h thành nhiều file nhỏ và sử dụng ida_with_struct_py3.py. Nếu cảm thấy làm biếng + tự tin vào chiếc máy tính đủ trâu thì có thể cho nó load và để đó rồi đi ngủ, cuối cùng tôi chọn đi ngủ¯\_( ͡° ͜ʖ ͡°)_/¯.
Sau khi load xong, đây là 1 phần struct tôi đã import vào IDA:

Lúc này tôi define lại type của function MakeView__Make để clean code. Và thành quả:

Khi clean code xong và đọc chỗ check điều kiện thì tôi đã trả lời được câu hỏi trong đầu:
- Vậy làm sao để biết được item này có bị giới hạn không?
>>> Sẽ check field makeLimit của item, nếu là 0 thì unlimited, ngược lại là đó là limit của số lượng item. - Làm sao để biết được item này có cần điều kiện electronic hay không?
>>> Check field electricEnergy của item, true là có điều kiện cần, ngược lại thì không.
Quay lại với việc phân tích, sau khi check một số điều kiện môi trường của item, lúc này function sẽ bắt đầu kiểm tra từng material có đủ không. Ví dụ khi tạo 1 tập tài liệu sẽ cần 10 tờ giấy và 1 cây bút, thì lúc này nó check có đủ 10 tờ giấy và 1 cây bút hay không. Vậy thì lúc này tôi đoán được từ LABEL_53 là quá trinh tạo ra item.

Để cheat được chỗ này (tạo item không cần material), thì tại dòng 104 tôi chỉ cần cho nó jump đến LABEL_53 vô điều kiện là được.
MOD && CHEAT:
Ok, đây là lúc sẽ thực hiện kế hoạch đã đề ra. Bắt đầu mod……..
- Cheat make item không cần learn:
Chuyển sang đọc assembly ở nơi call LearnSystem__IsLearnedByUnlockItemID, ta thấy loc_7946720
sẽ là nơi tôi không nến jump đến vì đó là trả về thông báo item not learn.

Tôi sẽ patch lại các byte 0F 84 35 02 00 00
thành 90 90 90 90 90 90
, 90 là byte của instruction nop
, instruction này thực chất sẽ không làm gì hết vì vậy sẽ không có chuyện mà nó có thể sẽ jump đến nơi tôi không muốn.

- Cheat bypass unlimited:
Tại địa chỉ 0x07946501, nó có thể sẽ rẽ nhánh đi xuống thông báo đến giới hạn của item nên tôi sẽ cho nó luôn luôn jump đến loc_794650F


- Cheat make item không cần electronic:
Như trên, tôi cho nó jmp vô điều kiện luôn.


- Cheat make item không cần material:
Cũng như trên, tôi sẽ cho nó jump vô điều kiện lên chỗ tạo item thành công.


Xong xuôi, tôi sẽ apply những gì đã patch lên GameAssembly.dll
và thế là tôi đã mod xong. Nhưng trước đó tôi cần phải tạo một file backup để tránh trường hợp phát sinh ngoại lệ.

Apply patch và thử nghiệm:


Có vẻ như lúc này tôi đã làm sai chỗ nào đó (nghi ngờ là Cheat make item không cần material vì những chỗ patch trước đó tôi đã đọc rất kĩ không hề sai sót), cho nên tôi sẽ bắt đầu lại bằng việc restore lại chỗ cheat không cần material và đọc kĩ lại code. Sau một vài phút tôi cố gắng clean code và đọc kĩ + debug.

Tôi phát hiện rằng, nơi tôi đoán đó là function tạo item thực chất nó là throw exception. Nôm na đoạn code trên sẽ là sau khi lấy material ids nó sẽ check từng material (bằng vòng while) có đủ để make item hay không và tôi chắc chắn rằng từ dòng 86 -> 98 là tạo item. Cheat chỗ này rất đơn giản, chỉ cần cho if ( indexMaterial >= sizeMaterial )
luôn luôn true là được.
Mod again:


Apply patch again:

Test, tôi thử tạo 2 món đồ bất kì:

Ok, cheat đã thành công. Vậy là từ nay tôi không còn lo phải vất vả tìm nguyên liệu để tạo item nữa (〜 ̄▽ ̄)〜(〜 ̄▽ ̄)〜.
CREATE CHEAT MANAGER
Khi đang chơi game với lib đã mod gọi tắt là cheat game thì một nhân cách lương thiện khác cố gắng ngăn cản hành vi trái đạo đức này, với trường hợp tôi không nghe nhân cách lương thiện khuyên can thì không có gì để nói. Nhưng nếu tôi nghe theo chẳng lẽ tôi phải thoát game và lấy DLL đã backup trước đó để sử dụng, tệ hơn là tôi sẽ xóa và cài lại game nếu như tôi quên backup. Rất bất tiện ಠ_ಠ.
Vậy nên bây giờ tôi sẽ viết một cheat manager để linh hoạt trong việc nghe lời nhân cách lương thiện.
Trước khi viết thì tôi sẽ nói sơ qua cơ chết cheat manager hoạt động:
- Cheat manager sẽ luôn luôn lắng nghe từ keyboard với phím được chọn (tôi sẽ chọn là F2). Khi có event bấm phím đã chọn sẽ bắt đầu patch memory và từ đó có thể cheat, nếu bấm lần nữa thì hết cheat.
Cheat manager sẽ được viết là một DLL. Vì sao là DLL? Vì tôi muốn nó nằm trong process của game và tôi không cần phải alt+tab khi muốn cheat và tắt cheat. Để có thể đưa dll Cheat manager vào process game thì tôi cần phải có một injector để inject (kỹ thuật này gọi là inject dll, tham khảo thêm tại đây).
Code injector tôi viết sẵn, vì tôi đi lụm từ nhiều nguồn nên có thể sẽ thấy vài chỗ quen ༼ つ ◕_◕ ༽つ
#include <iostream>
#include <Windows.h>
#include <tchar.h>
#include <stdio.h>
#include <psapi.h>
#include <tlhelp32.h>
void injectDll(HANDLE hProc, char* dll_path)
{
DWORD exitCode;
void* lpBaseAddress = VirtualAllocEx(hProc, NULL, strlen(dll_path) + 1, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProc, lpBaseAddress, dll_path, strlen(dll_path) + 1, NULL);
HMODULE kernel32base = GetModuleHandle("kernel32.dll");
HANDLE thread = CreateRemoteThread(hProc, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(kernel32base, "LoadLibraryA"), lpBaseAddress, 0, NULL);
WaitForSingleObject(thread, INFINITE);
GetExitCodeThread(thread, &exitCode);
VirtualFreeEx(hProc, lpBaseAddress, 0, MEM_RELEASE);
CloseHandle(thread);
CloseHandle(hProc);
}
bool checkProcessNameWithPID(DWORD processID, char* procname) {
TCHAR szProcessName[MAX_PATH] = TEXT("<unknown>");
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processID);
if (NULL != hProcess) {
HMODULE hMod;
DWORD cbNeeded;
if (EnumProcessModules(hProcess, &hMod, sizeof(hMod), &cbNeeded)) {
GetModuleBaseName(hProcess, hMod, szProcessName, sizeof(szProcessName) / sizeof(TCHAR));
}
}
CloseHandle(hProcess);
if (!memcmp(szProcessName, procname, strlen(procname)))
{
_tprintf(TEXT("%s (PID: %u)\n"), szProcessName, processID);
return true;
}
return false;
}
int main(int argc, char* argv[])
{
DWORD aProcesses[1024];
DWORD cbNeeded;
DWORD cProcesses;
HANDLE hProc;
unsigned int i;
if (argc != 3)
{
MessageBox(NULL, "Bonk !!!\ninjector.exe <process name> <path of dll>", "ERROR", MB_ICONERROR);
return -1;
}
if (!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
return 1;
cProcesses = cbNeeded / sizeof(DWORD);
for (i = 0; i < cProcesses; i++)
{
if (checkProcessNameWithPID(aProcesses[i], argv[1]))
{
hProc = OpenProcess(PROCESS_ALL_ACCESS, false, aProcesses[i]);
injectDll(hProc, argv[2]);
}
}
return 0;
}
Ok, đã có injector rồi thì bây giờ tôi sẽ viết file dll cheat manager.
Tại DllMain, tôi chỉ cho nó handle khi có event DLL_PROCESS_ATTACH, đây là event khi load dll vào process. Khi load dll vào process thành công tôi sẽ cho nó 1 cái MessageBox để biết là quá trình inject thành công sau đó sẽ tạo một thread mới, đây là thread hoạt động chính của Cheat manager.

function mainThread sẽ đợi phím F2 như khi nãy tôi đã nói:

Trước khi tiếp tục, tôi cần phải nói đến 2 thông tin.
First: Có thể bạn chưa biết hoặc bạn đã biết, sau khi process đã load DLL vào memory thì sẽ có 1 section là nơi chứa code chạy, section này luôn luôn có quyền execute (X), như trong hình dưới đây là tại địa chỉ 0x511000 là section chứa code. Và section này chính là section tôi muốn modify.

Địa chỉ thì luôn luôn thay đổi nhưng offset thì luôn luôn giữ nguyên qua các process chạy. Vậy nên tôi có offset của section này là 0x1000.
Nếu mà để ý tí thì IDA cũng đã thể hiện cái này (địa chỉ 0x6161000 mới có code nên base address của cái này là 6160000).

Ok, đó là điều thứ nhất.
Second: Điều thứ 2 là để bật tắt được cheat thì tôi sẽ xor byte code với một cái array key vì (A^B=C <-> C^B=A <-> C^A=B)
.
Phép xor thật hữu dụng ( ͡° ͜ʖ ͡°).
Trong IDA ở windows Patched Bytes cũng có ghi thông tin tôi patch, tôi sẽ sử dụng thông tin này tạo ra offset để patch và key cần xor.

offset patch + key xor:
0x2364e5: {0x9f, 0x14, 0xa5, 0x92, 0x90, 0x90}
0x236501: {0x97}
0x236521: {0x9e}
0x236605: {0xe6, 0x4c, 0xc0}
0x23660A: {0x90}
Sau khi đã có thông tin trên, tôi tiếp tục code. Trước khi đi vào function cheatMakeItem tôi cần một function dùng để lấy base address của module (ở đây là GameAssembly.dll). Function lấy Base address:
DWORD GetBaseAddressModule(HANDLE hProc, char* ModuleName)
{
HMODULE hMods[1024];
DWORD cbNeeded;
PTCHAR pTmp, pTmp1;
DWORD baseAddress;
if (EnumProcessModules(hProc, hMods, sizeof(hMods), &cbNeeded))
{
for (int i = 0; i < (cbNeeded / sizeof(HMODULE)); i++)
{
TCHAR szModName[MAX_PATH];
if (GetModuleFileNameEx(hProc, hMods[i], szModName, sizeof(szModName) / sizeof(TCHAR)))
{
pTmp1 = szModName;
pTmp = strtok(szModName, "\\");
while (pTmp)
{
pTmp1 = pTmp;
pTmp = strtok(NULL, "\\");
}
baseAddress = (DWORD)hMods[i];
if (!strcmp(pTmp1, ModuleName))
{
return baseAddress;
}
}
}
}
return NULL;
}
Tiếp tục tôi sẽ tạo một function dùng để patch memory (modify code):
void patch(HANDLE hProc, DWORD address, BYTE keys[], int sizeKeys)
{
BYTE* bytesMemory = (BYTE*)malloc(sizeKeys + 1);
ReadProcessMemory(hProc, (LPCVOID)address, bytesMemory, 6, NULL);
for (int i = 0; i < sizeKeys; i++)
{
bytesMemory[i] ^= keys[i];
}
WriteProcessMemory(hProc, (LPVOID)address, bytesMemory, 6, NULL);
}
Cuối cùng là cheatMakeItem:
void cheatMakeItem()
{
char moduleName[] = "GameAssembly.dll";
DWORD pid = GetCurrentProcessId();
HANDLE hProc;
DWORD baseAddress;
int len;
DWORD offsets[] = { 0x2364e5, 0x236501, 0x236521, 0x236605, 0x23660A };
BYTE keys[100][100] = {{0x9f, 0x14, 0xa5, 0x92, 0x90, 0x90}, {0x97}, {0x9e}, {0xe6, 0x4c, 0xc0}, {0x90}};
hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
if (!hProc)
{
MessageBox(NULL, "open currently process failed!", "ERROR", MB_ICONERROR);
return;
}
baseAddress = GetBaseAddressModule(hProc, moduleName);
if (!baseAddress)
{
MessageBox(NULL, "Module not found!", moduleName, MB_ICONERROR);
return;
}
for (int i = 0; i < sizeof(offsets) / sizeof(DWORD); i++)
{
patch(hProc, baseAddress + offsets[i], keys[i], strlen((char*)keys[i]));
}
return;
}
Đến đây là xong rồi, chỉ cần compile ra file dll và dùng injector để inject dll Cheat manager này vào game là có thể tha hồ bật tắt cheat.
Và đây là thành quả:

FINAL TITLE
Bài viết tuy ngắn nhưng khi làm thì tôi đã tốn rất nhiều thời gian, đa phần là thời gian chờ. Cho nên nếu bạn có ý định thử nghiệm để học tập thì hãy nhớ optimize quỹ thời gian trước rồi hãy làm, nếu bạn đã quá rảnh không có gì bận thì thôi coi như hãy quên lời tôi nói đi ╰( ̄ω ̄o).
Một lần nữa tôi hy vọng bài viết này sẽ mang tính học tập hơn là lợi dụng để làm những hành vi trái đạo đức. Nếu có ý định đi phá làng phá xóm thì tôi cũng không thể cản được, chỉ là những người xung quanh bạn sẽ dần xa lánh bạn hơn thôi ԅ(¯﹃¯ԅ).