https://saevo.online/  

해외(?)에서 디스코드 서버로 운영하고 있는 서든어택인데 생각보다 잘만들어서 분석을 해봤다.

별도의 anticheat도 존재했으며 Themida 패킹, 코드 가상화 기법까지 사용했음

 

anticheat의 로직은 nexon의 blackcipher와 유사도가 있었음.

다른 게임의 anticheat들도 이와 같은 방식이거나 다른 방식으로 무력화 시킬 수 있다.

 

 

1. 별도 launcher와 clinet가 존재함 >> discord_game_sdk를 사용해 token을 얻어 unique값으로 활용함

discord 연동

 

 

2. launcher에서는 현재 파일들을 hash, size를 검사해 무결성 검사를 한다. 변조가 되었을 시 해당 서버에서 받아옴. 

   Themida 패킹을 해놓았기 때문에 동적으로 무력화를 해야함.

launcher 로직1
launcher 로직2

 

3. anticheat는 2개의 파일이 존재하며 로그, 설정파일은 암호화시켜 작성 후 서버로 전송시킴.

anticheat file
Themida

 

4.  AHT_Main.dll의 export 함수 중 "Ordinal2"는 실제 행위를 하는 주소이며, VirtualAlloc을 통해 임의의 주소값으로 CRC 행위를 수행한다.

AHT_Main.dll_Ordinal2 Thread

5.  AntiHackTool.dll은 AHT_Main과 같이 VirtualAlloc을 사용해 임의의 주소값으로 AHT_Main의 CRC 검사를 진행한다.

AntiHackTool.dll Thread

6.  Clinet 내부의 anticheat, cshell.dll, d3d, 등 code의 변조를 실시간 감지함.

CRC

7. anticheat들의 return back trace를 살펴보면 호출 흐름을 파악 가능하다. esp trick을 이용해 무력화를 진행한다.

   Sleep 함수 호출 전 esp의 값은 return address가 담기는데 해당 주소를 수행하지 못하게 변경해주는 것이다.

 

# 참고로 Win7이후 kernel32의 API를 호출 시 Kernel32.dll!Sleep > KernelBase.dll!Sleep으로 jmp 후 호출한다.

esp trick

 

8. 위의 방식을 토대로 Code Injection을 해주면 된다. 간단한 Cheat Engine Script로 제작했는데 dll으로 제작해봐야겠당

 

   # 이전에 CRC를 통해 변조를 감지한다고 설명 했었는데 CRC는 내부 모듈의 대해서만 검사를 하므로 system 내의 있      는 모듈의 대해서는 검사를 못한다.

 

이것저것 하는게 너무 재밌어서 이제 악분도 다시 해야겠음..

궁금하신게 있거나 틀린점이 있으면 지적해주십쇼!

'Develop > C, CheatEngine' 카테고리의 다른 글

Five M Cheat Engine Bypass  (4) 2021.09.29
debugge, debugger  (0) 2021.09.28

Fivem Cheat Engine Bypass

 

Fivem은 현재 Windows Station안에 있는 Process를 찾고 CreateFile, ReadFile,GetFileAttributes 등 API를 사용해 패턴 매칭을 하는 것 같다.

 

우회 순서는 Cheat Engine을 실행 후 권한 변경을 통해 Fivem이 탐지 API를 무력화 시키는것임..

 

 

로직의 흐름은 SID 생성하고 권한 할당 후 적용 시키는 방식이다.

 

자료가 많이 없어 좀 해맸다.. 항상 성공하면 뿌듯~ㅋ

 

# AllocateAndInitializeSid

# SetEntriesInAcl

# SetSecurityInfo

 

나중에 까먹을까봐 메모

 

사용 하실분은 쓰세요 궁금하신건 댓글 달아 주세영

#include <windows.h>
#include <stdio.h>
#include <AclAPI.h>
#include <tchar.h>

#pragma comment(lib, "advapi32.lib")

BOOL CreateDirectoryWithUserFullControlACL(LPCTSTR lpPath)
{
    STARTUPINFO si = { 0, };
    PROCESS_INFORMATION pi;

    BOOL CreaP = CreateProcess(lpPath, NULL, NULL, NULL, TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &si, &pi);
    if (CreaP == 0)
    {
        printf("[*]Create Process Faile\n");
        printf("[*]ERROR CODE: %0x%X\n", GetLastError());
        return FALSE;
    }
    printf("[*]Create Success\n");

    HANDLE hDir = CreateFile(lpPath, READ_CONTROL | WRITE_DAC, 0, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
    if (hDir == INVALID_HANDLE_VALUE)
    {
        printf("[*]Create File Fail\n");
        printf("[*]ERROR CODE: %0x%X\n", GetLastError());
        return FALSE;
    }
    printf("[*]Create File Success\n");

    SECURITY_DESCRIPTOR* pSD = NULL;
    DWORD dwRes;
    PSID pEveryoneSID = NULL, pAdminSID = NULL, pUserSID = NULL;
    EXPLICIT_ACCESS ea[2];
    SID_IDENTIFIER_AUTHORITY SIDAuthWorld = SECURITY_WORLD_SID_AUTHORITY;
    SID_IDENTIFIER_AUTHORITY SIDAuthNT = SECURITY_NT_AUTHORITY;

    PACL pACL = NULL;

    if (!AllocateAndInitializeSid(&SIDAuthNT, 2,
        SECURITY_BUILTIN_DOMAIN_RID,
        DOMAIN_ALIAS_RID_ADMINS,
        0, 0, 0, 0, 0, 0,
        &pAdminSID))
    {
        _tprintf(_T("[*]AllocateAndInitializeSid Error %u\n"), GetLastError());
        goto CleanUp;
    }

    ea[0].grfAccessPermissions = GENERIC_ALL;
    ea[0].grfAccessMode = DENY_ACCESS;
    ea[0].grfInheritance = NO_INHERITANCE;
    ea[0].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[0].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    ea[0].Trustee.ptstrName = (LPTSTR)pAdminSID;

    if (!AllocateAndInitializeSid(&SIDAuthNT, 2,
        SECURITY_BUILTIN_DOMAIN_RID,
        DOMAIN_ALIAS_RID_USERS,
        0, 0, 0, 0, 0, 0,
        &pUserSID))
    {
        _tprintf(_T("[*]AllocateAndInitializeSid Error %u\n"), GetLastError());
        goto CleanUp;
    }

    ea[1].grfAccessPermissions = GENERIC_ALL;
    ea[1].grfAccessMode = DENY_ACCESS;
    ea[1].grfInheritance = NO_INHERITANCE;
    ea[1].Trustee.TrusteeForm = TRUSTEE_IS_SID;
    ea[1].Trustee.TrusteeType = TRUSTEE_IS_GROUP;
    ea[1].Trustee.ptstrName = (LPTSTR)pUserSID;

    dwRes = SetEntriesInAcl(2, ea, NULL, &pACL);
    if (dwRes != ERROR_SUCCESS)
    {
        _tprintf(_T("[*]SetEntriesInAcl Error %u\n"), GetLastError());
        goto CleanUp;
    }
    if (pACL)
        SetSecurityInfo(hDir, SE_FILE_OBJECT, DACL_SECURITY_INFORMATION, NULL, NULL, pACL, NULL);

    return TRUE;

CleanUp:
    if (pAdminSID)
        FreeSid(pAdminSID);
    if (pEveryoneSID)
        FreeSid(pEveryoneSID);
    if (pUserSID)
        FreeSid(pUserSID);
    return FALSE;
}
    
int main(int argc, char** argv) {
    if (argv[1] == NULL)
    {
        printf("[*]Invaild Argument.. Input Path\n");
        return -1;
    }
   printf("[*]Path: %s\n", argv[1]);
   BOOL status = CreateDirectoryWithUserFullControlACL(argv[1]);
   if (status == TRUE) 
   {
       printf("[*]Complete\n");
       return 0;
   }
   else 
   {
       printf("[*]Faile\n");
       return -1;
   }
   
}

 

 

 

 

# Reference

https://docs.microsoft.com/en-us/windows/win32/api/accctrl/ns-accctrl-explicit_access_a

 

EXPLICIT_ACCESS_A (accctrl.h) - Win32 apps

Defines access control information for a specified trustee.

docs.microsoft.com

https://deguls.tistory.com/entry/DACL%EA%B3%BC-SACL-%EB%B3%B5%EC%8A%B5

 

DACL과 SACL 복습

출처: http://blog.naver.com/knuabhoony?Redirect=Log&logNo=40014321712 일반적으로 ACL 이라고만 하면 DACL를 뜻함. DACL = discretionary access control list SACL = security access control list DACL에는..

deguls.tistory.com

https://docs.microsoft.com/en-us/windows/win32/fileio/file-security-and-access-rights

 

File Security and Access Rights - Win32 apps

Because files are securable objects, access to them is regulated by the access-control model that governs access to all other securable objects in Windows.

docs.microsoft.com

 

'Develop > C, CheatEngine' 카테고리의 다른 글

saevo 서든어택 anticheat-bypass  (3) 2021.10.06
debugge, debugger  (0) 2021.09.28

예전에 공부하면서 만들어 뒀던 코드임

powershell을 debugge로 걸어두고 cmdline을 실시간 탐지해 Fileless Malware를 탐지했다.

 

#include "windows.h"
#include "stdio.h"
#include "stdlib.h"
#include "tlhelp32.h"
#include "TCHAR.H"
#include "string.h"

#pragma warning(disable : 4996)
#define DEF_PROC_NAME ("powershell.exe")

char* Adr_cmdline, *Adr_copy, *Adr_ori;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE OP_BP = 0xCC, OP_Ret = 0xC3;
DWORD dwPID = 0xFFFFFFFF;
LPVOID Adr_original = NULL;
DWORD WINAPI FindProcessID(LPCSTR szProcname);
void DebugLoop();
int main()
{

	DWORD dwThrdParam;
	DWORD dwThreadId;

	HANDLE hThread = CreateThread(NULL, 0, FindProcessID, &dwThrdParam, 0, &dwThreadId);

	dwPID = FindProcessID(DEF_PROC_NAME);

	if (!DebugActiveProcess(dwPID))
	{

		return 1;

	}
	DebugLoop();
}

void DebugLoop()
{

	DEBUG_EVENT de;
	DWORD dwContinueStatus;

	while (WaitForDebugEvent(&de, INFINITE))
	{
		dwContinueStatus = DBG_CONTINUE;

		if (CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
		{
			OnCreateProcessDebugEvent(&de);

		}
		else if (EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode)
		{
			if (OnExceptionDebugEvent(&de))
				continue;


		}
		else if (EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode)
		{
			break;
		}
		ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
	}
}


BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)
{
	DWORD dwAddrOfBuffer;

	Adr_cmdline = GetProcAddress(GetModuleHandle("kernelbase.dll"), "GetCurrentProcess");
	Adr_original = GetProcAddress(GetModuleHandle("kernelbase.dll"), "GetCurrentProcess");

	Adr_copy = Adr_cmdline - 6;

	VirtualProtect(Adr_copy, 10, PAGE_EXECUTE_READWRITE, &dwAddrOfBuffer);

	memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));

	ReadProcessMemory(g_cpdi.hProcess, Adr_copy, &OP_Ret, 1, NULL);
	WriteProcessMemory(g_cpdi.hProcess, Adr_copy, &OP_BP, 1, NULL); 

	printf("\n[ Install BreakPoint ]  Address : 0x%p OPCODE : 0xC3 -> 0xCC Patched\n", Adr_copy);


	return TRUE;
}

BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
	CONTEXT ctx;
	DWORD dwNumOfBytesToCompare;
	PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;
	char* str = NULL, buffer = NULL, result = NULL;
	SIZE_T bytesRead;

	if (EXCEPTION_BREAKPOINT == per->ExceptionCode)
	{
		if (Adr_copy == per->ExceptionAddress)
		{

			ctx.ContextFlags = CONTEXT_ALL;
			GetThreadContext(g_cpdi.hThread, &ctx);
			WriteProcessMemory(g_cpdi.hProcess, &Adr_copy, &OP_Ret, sizeof(BYTE), NULL);
			ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Eax), &buffer, 1000, &bytesRead);

			Adr_ori = Adr_copy - 5;

			wprintf(L"Command Line : %s\n", &buffer);

			if ((str = wcsstr(&buffer, L"$env")) == NULL)
			{
				MessageBox(NULL, TEXT("Normal"), TEXT("Alert"), MB_OK | MB_TOPMOST);
				return TRUE;
			}
			else
			{
				MessageBox(NULL, TEXT("malicious String : '$' Detected"), TEXT("Warning"), MB_OK | MB_TOPMOST);
				exit(1);

			}

			if ((str = wcsstr(&buffer, L"iex")) == NULL) 
			{
				MessageBox(NULL, TEXT("Normal"), TEXT("Alert"), MB_OK | MB_TOPMOST);
				return TRUE;
			}
			else
			{
				MessageBox(NULL, TEXT("malicious String : 'iex' Detected"), TEXT("Warning"), MB_OK | MB_TOPMOST);
				exit(1);

			}

			ctx.Eip = (DWORD)Adr_ori;
			SetThreadContext(g_cpdi.hThread, &ctx);

			ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
			Sleep(0);

			return TRUE;
		}

	}

	return FALSE;

}

DWORD WINAPI FindProcessID(LPCSTR szProcname)
{
	while (1) {

		HANDLE hSnapShot = INVALID_HANDLE_VALUE;
		PROCESSENTRY32 pe;
		szProcname = "powershell.exe";

		pe.dwSize = sizeof(PROCESSENTRY32);
		hSnapShot = CreateToolhelp32Snapshot(TH32CS_SNAPALL, NULL);

		Process32First(hSnapShot, &pe);
		do
		{
			if (!strcmp(szProcname, pe.szExeFile))
			{
				dwPID = pe.th32ProcessID;
				goto end;
			}

		} while (Process32Next(hSnapShot, &pe));

		WaitForSingleObject(hSnapShot, INFINITE);
	}
end:
	return dwPID;
}

'Develop > C, CheatEngine' 카테고리의 다른 글

saevo 서든어택 anticheat-bypass  (3) 2021.10.06
Five M Cheat Engine Bypass  (4) 2021.09.29

실제 회원제로 운영되고 있고 판매하고 있는 프로그램인듯..

 

 

우선 로직은 엄청 복잡하게 짜놓았다.

 

파일 목록

 

 

 

C++로 제작 되어 있고 MFC UI로 제작되었다.

 

PE info

 

 

 

 

 

 

정상적으로 아무 ID/PW 입력을하고 로그인을 했고 메세지 박스가 출력된다.

 

 

 

단순 API BP만으로 쉽게 찾을수 있을거 같았지만 dummy code가 너무 많았다.

 

 

 

IDA로 확인을 했지만 동적 호출이 너무 많았고, back trace 과정에서 직접 디버깅을 해서 봐야했다.

 

ida1
ida2

 

 

 

 

서버측에 ID 존재 유무를 질의를 한 후 계정에 유무를 확인한다.

 

 

 

전체 로직에서 검증하는 과정이 2개가 존재 했으며 2개 전부 복잡한 로직 수행 뒤 값 검증을 했다.  

Check1: [ESI+2] 값을 통해 test 명령 뒤 분기를 하는데 1값이 아니면 에러 메세지를 뱉었다.

Check2: [EBP+8] 값이 3인지에 대한 여부를 검사한다. 단, 로그인 성공시 에는 1값이여야 정상 로직을 수행했다.

Check1

 

Check2

 

 

 

로직의 흐름은 전부 파악 했고 패치 작업을 해줘야 한다. Code Section은 W권한이 없기때문에 패치가 안된다. VirtualProtect 함수를 이용해 권한 할당을 해줘도 되지만 간단하게 W권한이 있는 DS 영역에 패치를 해줬다.

memorymap
.data
jmp to ds
Code Cave

Code Cave 방식을 택했으며 다른 방식은 값을 변경하기 애매한 디스어셈블들이 많았다.

 

 

최근에 게시글을 많이 못썼는데 앞으로는 자주 올릴예정....ㅠ

 

 

크랙킹 영상도 첨부했습니다.

 

'Crack' 카테고리의 다른 글

CodeEngn Advance RCE L08  (0) 2021.04.09
CodeEngn Advance RCE L09  (0) 2021.04.09
CodeEngn Advance RCE L02  (0) 2021.04.05

문제: Key 값이 5D88-53B4-52A87D27-1D0D-5B09 일때 Name은 무엇인가
힌트 : Name은 두자리인데.. 알파벳일수도 있고 숫자일수도 있고..

 

 

 

1. Name과 Key 입력 창이 나온다. 문제대로 Key값에는 5D88-53B4-52A87D27-1D0D-5B09을 넣고 Name은 아무값을 입력한다.

 


 

2. cmp eax, 2 부분은 패치를 한 파일을 분석을 하는거지만 기존에는 cmp eax, 3으로 되어 있다. Name 입력값이 3자리 이하이면 종료시키는 분기점이다.

 

 


 

3. Tracing을 하다보면 0x45B850 주소에서 시리얼 값이 Return이 된다.

 

 


4. 해당 주소로 이동을 하게 되면 연산과정이 여러개 나온다. 4-4-8-4-4 의 Serial key를 제작하는 로직이다. 그 중 첫번째 자리 연산 과정을 살펴보면 name에 입력했던 11값을 1byte씩 연산을 하고 첫번째 byte 결과값을 두번째 byte와 더해 edx에 넣어둔다.

 

 

 


 

5. Serial key 첫번쨰 자리 결과가 나오게 되는데 총 4byte이며 0x4086c8 주소에서 word 값으로 분리시켜 Return한다.


 

6. 입력했던 name에 11 값으로 5번의 연산을 통해 Serial key를 만든다.

 


 

7. 연산과정을 전부 다 아는건 시간이 오래 소요 되므로 간단한 코드를 짜서 0x0~0x7F 까지의 값을 무작위 대입해 2개의 배열에서 연산을 통해 값을 찾아낸다.

 

 

 


8. 작성 했던 코드를 돌려보면 문제에 나와있던 Serial key 첫번째 값 0x5D88과 일치하는 값들이 1개 출력이 된다. 첫번째 값 0x43, 두번째 값 0x36

 

9. hex to ascii를 해보면 C6 두자리 수가 나온다. 이 값을 입력을 해보면 성공인걸 알 수 있다.

'Crack' 카테고리의 다른 글

로스트아크 오토핫키 프로그램 crack  (3) 2021.07.20
CodeEngn Advance RCE L09  (0) 2021.04.09
CodeEngn Advance RCE L02  (0) 2021.04.05

# 너무 쉬운문제는 풀기만하고 기억에 남는 문제만 작성합니다.

 

문제: Password는 무엇인가

 

 

1. 파일 실행시 Username, Password를 입력해야 하며 틀리면 에러 문구가 나온다.

 


 

2. Tracing을 하다보면 Username을 비교하는 로직이 나온다. ecx 포인터 값과 0x6E(n) 을 1byte 비교한다. 

하지만 이상한 부분이 ecx값 안에는 0x00(null)값이다.

 


 

3. 다음 분기로 진행하기 위해 ecx 값과 같은 0x00으로 변경 해준다.


 

4. sete bl 어셈블리는 ZF 1 이면 bl 값을 1로 변경해준다. 차후 분기문에서 영향을 준다. cmp 어셈블리 명령에서 Password 부분을 매칭을 하게 되는데 기존 dump에 저장되어 있는 0x88228F(8921743) 값과 비교한다.

 

 

5. 입력했던 값은 decimal 123이며 hex로는 7B가 된다.

 


 

6. 마지막 Username에 대한 매칭을 한번 더 이루어진다. 글쓴이는 name으로 입력을 했는데 DonaldDuck과 비교한다. 

 

 

 


7.  정상적으로 로직을 통과하게 되면 이전에 sete bl으로 설정했두었던 bl 값을 test연산을 통해 분기를 나눈다.

    최종적으로 Username에서 맨 앞 1byte는 null 이여야 하며 Username은 DonaldDuck이고 Password는 8921743이다.

 

 

'Crack' 카테고리의 다른 글

로스트아크 오토핫키 프로그램 crack  (3) 2021.07.20
CodeEngn Advance RCE L08  (0) 2021.04.09
CodeEngn Advance RCE L02  (0) 2021.04.05

문제: 정답은 무엇인가

 

 

1. 파일을 실행시키면 Access_Violation 에러가 난다. 포인터 주소에서 잘못 된 값을 읽어와 이러한 현상이 일어난다.


 

2. 해당 주소로 이동을 하면 덤프영역에 저장되어 있는 DLL 이름을 가져와 API들을 호출시킨다.

 


3. [eax+18] 포인터 주소에 00 null 값이 존재한다.

에러 부분


4. ax에는 105E값이 존재하는데 Imagebase 주소를 없앤 RVA 값이다. 1171값을 비교해 프로세스를 종료 시킨다.

RVA 비교
RVA


5. RVA 1171값은 나올수가 없으므로 105E값으로 수정 후 패치해준다.

Patch


6. 패치한 파일을 다시 실행 시켜보면 정상적으로 작동을 한다. Password 입력란에 아무값이나 입력해본다.

 

 


7. Password 매칭하는 로직이며 1byte씩 비교를 시킨다.

  - 0x43,0x52,0x41,0x41,0x41,0x43,0x4B,0x45,0x44,0x21

Password


8. Unicode table을 참조하면 Password를 찾을수 있다.

   - CRAAACKED!

 

 

reference: www.tamasoft.co.jp/en/general-info/unicode.html

'Crack' 카테고리의 다른 글

로스트아크 오토핫키 프로그램 crack  (3) 2021.07.20
CodeEngn Advance RCE L08  (0) 2021.04.09
CodeEngn Advance RCE L09  (0) 2021.04.09

#  assembly [sti]

 

sti exception

ring0 레벨 명령으로 ring3 레벨에서 디버깅시 예외 발생

 

skip exception

1. x64dbg

 

Ignored Exceptions ragne 추가

 

2. ollydbg

 

StrongOD Plugin : Skip Some Exceptions 옵션

'PE' 카테고리의 다른 글

PE 구조 [메모]  (0) 2021.03.02

+ Recent posts