1. 개요


치트엔진은 유명한 디버거 프로그램 중 하나로서, 다른 프로세스의 메모리에서 값을 검색하고 비교 분석하는 기능을 제공하고 있는 것이 특징입니다. 치트엔진을 이용하면 프로세스가 메모리를 읽고 쓰는 코드를 찾아서 값을 조작하기 쉽습니다.


치트엔진을 설치하면 내용물 중 치트엔진 튜토리얼이라는 것이 함께 포함되어 있는데, 튜토리얼은 간단한 실습과 함께 프로그램의 핵심적인 기능들을 소개하고 있어서 풀어 보는 재미가 있습니다. 치트엔진 6.7버전을 기준으로 튜토리얼은 총 9단계로 구성되어 있습니다. 튜토리얼을 요약하는 의미에서, 앞서 나온 내용을 총망라하는 마지막 단계인 스텝 9의 풀이를 다뤄 보겠습니다.


2. 환경

- 운영체제: Windows 10 Education (64비트)

- 치트엔진: Cheat Engine 6.7

- 튜토리얼: Cheat Engine Tutorial (64-bit) v3.4


3. 문제



튜토리얼의 전문은 아래와 같습니다.


Step 9: Shared code: (PW=31337157)

This step will explain how to deal with code that is used for other object of the same type


Often when you've found health of a unit or your own player, you will find that if you remove the code, it affects enemies as well.

In these cases you must find out how to distinguish between your and the enemies objects.

Sometimes this is as easy as checking the first 4 bytes (Function pointer table) which often point to a unique location for the player, and sometimes it's a team number, or a pointer to a pointer to a pointer to a pointer to a pointer to a playername. It all depends on the complexity of the game, and your luck


The easiest method is finding what addresses the code you found writes to and then use the dissect data feature to compare against two structures. (Your unit(s)/player and the enemies) And then see if you can find out a way to distinguish between them.

When you have found out how to distinguish between you and the computer you can inject an assembler script that checks for the condition and then either do not execute the code or do something else. (One hit kills for example)

Alternatively, you can also use this to build a so called "Array of byte" string which you can use to search which will result in a list of all your or the enemies players

In this tutorial I have implemented the most amazing game you will ever play.

It has 4 players. 2 Players belong to your team, and 2 Players belong to the computer.

Your task is to find the code that writes the health and make it so you win the game WITHOUT freezing your health

To continue, press "Restart game and autoplay" to test that your code is correct



Tip: Health is a float

Tip2: There are multiple solutions


국어로 성심성의껏 번역해 보면 이렇습니다.


Step 9: Shared code: (PW=31337157)

This step will explain how to deal with code that is used for other object of the same type

이 스텝(Step 9)에서는 '클래스(type)는 같지만 서로 다른 객체(object)들'에 접근하는 코드(code)에 대응하는 방법에 대해서 설명해요.


Often when you've found health of a unit or your own player, you will find that if you remove the code, it affects enemies as well.

(게임에서) 어떤 유닛이나 자신 플레이어의 체력을 찾았다고 했을 때, 그 부분을 건드리는 코드를 지워 버리면 자기 편뿐만 아니라 적들에게도 영향을 끼치는 현상을 발견할 거야.

In these cases you must find out how to distinguish between your and the enemies objects.

이런 경우에는 우리 편과 적군의 객체(object)를 구분하는 방법을 알아야 하지.

Sometimes this is as easy as checking the first 4 bytes (Function pointer table) which often point to a unique location for the player, and sometimes it's a team number, or a pointer to a pointer to a pointer to a pointer to a pointer to a playername. It all depends on the complexity of the game, and your luck

이 작업은 때로는 함수 포인터 테이블의 첫 4바이트를 확인하는 정도로 끝날 만큼 쉬울 수도 있어. 그 4바이트는 플레이어만의 어떤 장소를 가리키거나, 또는 팀 번호를 가리키거나, 아니면 플레이어 이름의 포인터의 포인터의 포인터의 포인터의 포인터일 수도 있지. 이건 그 게임이 얼마나 복잡한지와 당신의 운에 달려 있어.


The easiest method is finding what addresses the code you found writes to and then use the dissect data feature to compare against two structures. (Your unit(s)/player and the enemies) And then see if you can find out a way to distinguish between them.

가장 쉬운 방법은 네가 발견한 코드가 어떤 주소에 쓰기 접근을 하는지 찾고 난 뒤에, 치트엔진의 "Dissect data" 기능을 이용해서 두 구조체를 비교하는 거야. (여기서 구조체란 게임상에서 아군/적군의 플레이어/유닛과 같은 객체.) 그런 다음 그 객체들을 구분해 낼 수 있는 방법이 있는지 찾아보도록 해.

When you have found out how to distinguish between you and the computer you can inject an assembler script that checks for the condition and then either do not execute the code or do something else. (One hit kills for example)

너와 적군(컴퓨터 플레이어)의 객체를 서로 구분해 내는 방법을 찾았다면, 조건을 검사해서 해당하는 코드를 실행할지 말건지 결정하는 어셈블러 스크립트를 끼워 넣을 수 있지. (예를 들면 특정한 대상만 한 방에 죽이는 코드라던지 말이야.)

Alternatively, you can also use this to build a so called "Array of byte" string which you can use to search which will result in a list of all your or the enemies players

이걸 이용하는 또다른 방법을 예로 들면, 소위 "Array of byte"(바이트 배열)이라고 하는 문자열을 만들어서, 아군과 적군 모두의 플레이어를 한 곳에 나열하는 검색 용도로 쓸 수 있다는 거지.

In this tutorial I have implemented the most amazing game you will ever play.

이번 튜토리얼에는 내가 세상에서 최고로 멋진 게임을 구현해 두었다.

It has 4 players. 2 Players belong to your team, and 2 Players belong to the computer.

이 게임에는 4명의 플레이어가 존재하고 두 명은 우리 편, 나머지 둘은 컴퓨터 편이야.

Your task is to find the code that writes the health and make it so you win the game WITHOUT freezing your health

목표는 플레이어의 체력(the health)에 쓰기 접근을 시도하는 코드를 찾아서, 게임을 이길 수 있게 만드는 거야. 이 때 치트엔진의 메모리 편집기를 이용해서 체력 데이터 값을 인위적으로 수정하면 안 돼. 반드시 체력 데이터에 대한 값 "freezing" 없이 게임을 이겨야 해.

To continue, press "Restart game and autoplay" to test that your code is correct

계속하려면, "Restart game and autoplay"를 눌러서 너의 코드(답안)가 맞는지 확인하도록 해.



Tip: Health is a float

Tip2: There are multiple solutions

도움말: 체력은 float 형으로 설정해 뒀음.

도움말2: 하나가 아니라 여러 가지 해결 방법이 있음.


어셈블리어, C, 운영체제, 객체지향 언어 하나 이상 이렇게 공부한 적 있어야 이해할 법한 단어들이 나옵니다.


이 튜토리얼은 요약하자면 구조체 데이터의 분석을 다루고 있는 것입니다.


이 튜토리얼의 이전 스텝에서는 코드를 수정하는 방법에 대해서 다룬 바 있습니다. 그 기법을 코드 인젝션이라고 부른다고 했습니다. 여기서도 마찬가지 기법을 사용하는데, 어떤 코드가 여러 개의 객체에 대해서 접근하는 경우에는 그 객체들을 구분해서 조건분기시켜야 할 필요가 있다는 말이죠.


객체를 짚고 넘어가자면 보통 게임에서 말하는 게임 객체는 유닛이나 플레이어 같은 게임 세계의 요소일 것입니다. 한편으로는 객체지향에서의 객체라고 하면 클래스의 인스턴스를 가리킵니다. 또 클래스가 있는데, 디버깅할 때 클래스는 C의 구조체와 거의 완전히 동일하게 취급됩니다. 왜냐하면 객체지향에서 말하는 객체라는 것은 철학적인 의미도 가미되어 있습니다만은, 저수준에서는 모든 것이 코드와 데이터로 분해되기 때문에 효력이 없다는 것입니다. 객체는 메모리에 올라간 순간부터 그냥 멤버 변수들의 묶음일 뿐이며, 이 함수가 이 객체의 전용 함수이니라 하고 클래스에 정의한 것들도 아무런 의미가 없습니다. 사람의 직관으로 실세계의 사물에 사상시켜 그려 볼 수 있는 객체란 것은 컴파일하기 전까지만 존재하는 추상적인 개념이고 프로그램의 인스턴스가 메모리에 적재되었을 때면 코드를 빼고는 그 데이터의 자료형 정도만 남게 됩니다. 그러니까 클래스는 구조체나 다름 없고 그 객체는 데이터 섹션의 어디부터 어디까지 훑으면 나오는 구조체 데이터라고 할 수 있습니다.


튜토리얼에 따르면 치트엔진에는 어떤 코드가 어떤 데이터에 접근하는지 추적하는 기능뿐만 아니라 구조체 데이터들을 비교 분석하는 기능이 있다고 합니다. 그 기능의 이름은 "Dissect data"입니다. 이를 이용해서 객체를 구분하고 각각의 객체에 다르게 적용되는 코드를 끼워 넣을 수 있을 것입니다.


4. 풀이



일단 어떤 게임인지부터 봐야겠죠.


플레이어는 4명이고 피통이 불공평하게 우리 팀 100, 100과 적 팀 500, 500으로 시작합니다.


각 플레이어 이름 아래 할당된 "Attack" 버튼을 누르면 맞은 것처럼 그 사람 체력이 깎입니다.


근데 우리 애들은 한 번 칠 때마다 데미지가 4~5씩 박히고 적은 1만 깎여요.


위 스샷은 넷 다 각각 한 번씩 눌러 놓은 상태입니다.


"Restart game" 버튼을 누르면 게임이 초기화되면서 체력 100, 100, 500, 500으로 다시 시작하는 것 같습니다.



"Restart game and autoplay" 버튼을 누르면 자동전투를 벌이는데 이길 수가 없죠.


플레이어는 체력이 0이 되면 죽습니다.


전투는 어느 한 팀이 다른 한쪽을 전멸시킬 때까지 계속하는 것 같습니다.



일단 치트엔진으로 튜토리얼 프로세스를 여는 것부터 시작합니다.


왼쪽 상단에 강조 표시가 있는 아이콘을 누릅니다.



그런 다음 팝업으로 나타나는 프로세스 리스트 창에서 튜토리얼을 선택하고 Open 버튼을 누릅니다.


프로세스를 성공적으로 열었다면 상단에 해당 항목의 이름이 표시됩니다.



일단 스캔을 때립니다.


플레이어 3으로 있는 HAL의 체력을 검색해서 찾았습니다.


Value TypeAll로 설정하면 자료형 관계 없이 모든 값을 검색합니다.


예컨대 4 Byte로 검색하면 실수 데이터는 찾아내지 못할 거에요.


HAL의 피통은 083D35F8Float형으로 저장되어 있었습니다.


앞선 튜토리얼 설명에서 체력은 Float이라고 했으니까 맞네요.



이제 HAL의 체력에 접근하는 코드를 찾을 것입니다.


HAL의 체력 데이터를 우클릭하고 "Find out what accesses this address"를 선택합니다.


"Find out what writes this address"는 이 주소에 쓰는 명령어만 찾지만, "Find out what accesses this address"는 읽기와 쓰기 모두 감지해 냅니다.


사실 이 경우에는 체력을 수정하는 명령어에만 관심이 있기 때문에 "Find out what writes this address" 기능을 써도 관계는 없지만 그냥 보통 많이 사용하는 것을 썼습니다.



Confirmation

This will attach the debugger of Cheat Engine to the current process. Continue?


디버거를 붙여도 괜찮냐고 묻는 팝업이 뜹니다. Yes 눌러 줍니다.



그러면 위와 같은 작은 창이 하나 더 생겨요.



HAL을 존내 때려서 피를 깎고 어떻게 되나 봅시다.



HAL의 피통 데이터에 접근하는 명령어들이 찍힙니다.


왼쪽 Count에 주목해 주세요. 4대 때렸더니 Count가 4번으로 찍혀 있네요.


명령어가 5개나 있는데 HAL의 체력 값을 수정하는 명령어는 3번째 명령어 하나입니다. 왜냐하면 나머지 넷은 메모리에서 레지스터로 값을 읽는 명령이고 3번째가 레지스터의 값을 메모리에 쓰고 있거든요.


여기서 또 어셈블리어를 기본적으로 알아야 할 것 같은데 상식 수준의 아주 주요적인 명령어만 기억해도 나머지는 자주 쓰지 않기 때문에 대부분 짐작이 갑니다. 제가 알고 있는 것은 ADD, SUB, XOR, MOV, CMP, JMP, JE, JNE, NOP, CALL 정도가 있네요. MOV A, B는 B의 값을 A에 쓰는 명령어고, 대괄호는 별표(*)로 표기하는 C의 참조 연산자처럼 어떤 주소의 데이터를 참조한다는 뜻입니다. 구글로 살짝 검색해 보니 실수에 대한 연산을 수행하는 명령어는 SS가 붙는다는군요. 그러니까 MOVSS [RBX+08], XMM0XMM0라는 실수 레지스터에 담긴 값을 [RBX+08]에 저장하는 명령어로 보입니다. 우측에는 이 명령어에 대한 설명이 나타납니다. (move scalar single-fp) 아무튼 이 쓰기 접근 명령어가 우리가 관심이 있는 명령어입니다.


이 명령어가 수행되는 시점의 RBX 레지스터가 담고 있는 값을 보면 083D35F0라고 합니다. HAL 체력의 주소가 083D35F8이고, 083D35F0+08=083D35F8이니까, 번지수가 제대로 맞습니다. 또 RBX가 담은 083D35F0에 +오프셋의 형식으로 HAL의 체력 데이터의 주소가 표현되고 있다는 것을 알 수 있습니다.


이 창에서 "Show disassembler" 버튼을 클릭해서, 100002E577에 위치한 명령어를 메모리 뷰어(Memory Viewer)를 통해서 자세히 볼 수 있도록 합니다.



메모리 뷰어(Memory Viewer)에서는 코드/데이터를 주소로 직접 찾아보고 수정할 수 있습니다.



HAL의 체력을 수정하는 코드라고 생각하는 것을 한 번 다른 것으로 바꿔서 이게 내가 생각한 게 맞는지 확인해 볼 것입니다.


어셈블러 코드를 작성하기 위해서는, 메모리 뷰어에서 명령어를 우클릭하고 "Assemble" 항목을 선택합니다.



어셈블을 위한 작은 창이 나타납니다.


원래 적혀 있던 명령어는 지웁니다.



간단하게 NOP 명령어로 채워서 작동하지 않게 해 봅니다.



Confirmation

The generated code is 1 byte(s) long, but the selected opcode is 5 byte(s) long! Do you want to replace the incomplete opcode(s) with NOP's?


선택한 명령어의 길이는 5바이트인데 새로 입력한 코드는 1바이트 짜리라서 4바이트가 남는다고 합니다.


나머지를 NOP으로 채우시겠습니까?


Yes 누릅니다.



MOVSS [RBX+08], XMM0가 5개의 NOP으로 교체되었습니다.



그래서 체력을 갱신하는 코드를 수정한 뒤로는 HAL을 매우 쳐도 피가 깎이지 않는 것을 확인했습니다.


또 수정한 명령어는 접근 횟수 카운트가 올라가지 않는 것을 볼 수 있습니다.


반면 HAL의 체력에 읽기 접근을 수행하는 명령어는 여전히 카운트가 오릅니다.



확인이 되었으니 NOP 처리한 명령어가 다시 작동하도록 원래대로 되돌려 보겠습니다.


맨 위의 NOP을 어셈블합니다.



원래의 명령어를 입력합니다.



Confirmation

The generated code is 5 byte(s) long, but the selected opcode is 1 byte(s) long! Do you want to replace the incomplete opcode(s) with NOP's?


선택한 명령어의 길이랑 생성하는 명령어의 길이가 다르다고 하면 무슨 말인지 읽지도 않고 Yes 버튼을 누릅니다.



다시 HAL을 때려서 그의 체력을 깎아 봅니다.



코드가 원래대로 잘 작동하는 것을 확인합니다.



HAL의 체력이 메모리의 어디쯤에 어떻게 들어있는지 메모리 뷰어를 통해서 보고 싶습니다.


우클릭하고 "Browse this memory region"을 선택합니다.



메모리 덤프 하단에서는 HAL의 체력인 083D35F8 이후 4바이트가 float 445.00임을 보입니다.


이렇게 보니까 확실하네요.


체력이 저장된 곳 한 줄 아래에 HAL의 이름이 적혀 있습니다.



HAL의 체력을 갱신하는 코드가 다른 플레이어의 체력이 갱신될 때도 작동하는 것 같습니다.


확인을 위해서 KITT의 체력도 검색해 봅니다.



KITT의 체력은 083FAA48에 위치하고 있습니다.



KITT의 체력을 접근하는 코드를 확인하기 위해서 "Find out what accesses this address"를 실행합니다.



KITT을 Attack 버튼으로 쳐 보면 HAL의 체력에 접근했던 코드가 KITT의 체력에도 마찬가지로 읽기/쓰기 접근을 시도합니다.



메모리 뷰어에서 KITT의 체력 데이터를 봅니다.



체력과 이름이 HAL과 동일한 형식으로 저장되어 있음을 알 수 있습니다.


이것이 플레이어 구조체라면, 튜토리얼 설명에 나와 있던 대로 '타입은 같지만 서로 다른 객체'를 찾은 것입니다.



구조체를 분석하기 위해서, 아군과 적군을 구분하지 않고 '타입은 같지만 서로 다른 객체' 모두에게 적용되는 코드를 이용합니다.


메모리 뷰어에서 명령어를 우클릭하고 "Find out what addresses this instruction accesses"를 선택합니다.


이는 "Find out what accesses this address"가 데이터를 접근하는 코드를 찾는 것과 반대로 코드가 접근하는 데이터를 찾는 기능입니다.



그러면 작은 창이 나타납니다.


이곳에 검색 결과가 나열될 것입니다.



플레이어를 각각 한 번씩 쳐 봅니다.



여기에 4명의 체력이 모두 찍힌 것 같네요.


값이 이상한데 실수형 데이터를 4 Bytes의 정수형으로 해석하기 때문입니다.


우측 하단에서 자료형을 Float로 변경합니다.



4명의 체력이 확실한 것 같습니다.



한 번 더 때려 봅니다.



카운트가 오르는 것을 확인할 수 있습니다.



검색된 주소들을 모두 선택하고 우클릭하여 "Open dissect data with selected addresses"를 선택합니다.


치트엔진의 Dissect는 구조체를 분석하는 기능입니다.



Dissect 창을 선택하라는 팝업이 뜹니다.


적당히 OK 누릅니다.



새 창이 뜨면서 분석할 구조체의 이름을 지어 달라고 하면 역시 적당히 OK를 누릅니다.



Confirmation

Do you want Cheat Engine to try and fill in the most basic types of the struct using the current address?


치트엔진이 구조체 분석시에 기본적인 자료형을 추론할 것이라고 합니다.


물논이지.


Yes 누릅니다.



Structure define

Please give a starting size of the struct (You can change this later if needed)


팝업이 자꾸 뜨는데 기본값 그대로 두고 대충 OK 누릅니다.



구조체 Dissect 기능을 이용하면 객체의 멤버가 한 눈에 들어옵니다.


메모리 덤프에서 하나씩 비교해 가면서 분석할 수도 있지만 이렇게 하는 게 훨씬 편하죠.


이 구조체는 플레이어 객체일 것입니다.


추리해 봅시다.


첫 번째 멤버는 포인터인데 무엇인지는 모르겠지만 넷 다에게 같기 때문에 별로 관심이 가지 않습니다. 여기서는 우리 팀과 적을 구분하는 것이 중요합니다.


두 번째 멤버는 Float형의 체력입니다. 체력은 8번째 바이트부터 Float형으로 위치합니다. 8이라고 하니까 체력에 접근하는 코드가 +08 식으로 표기하던 오프셋이 기억나네요. 다시 생각해 보면, MOVSS [RBX+08], XMM0 명령어의 RBX는 바로 객체의 시작 주소를 가리키고, 그 뒤의 오프셋은 멤버의 위치를 뜻하고 있었던 것입니다.


3, 4번째 멤버는 뭔지 잘 모르겠습니다.


+14의 4개 바이트는 2 또는 1인데, 팀 번호를 나타내는 것이라고 생각해도 되겠네요.


+19에는 플레이어의 이름이 적혀 있네요.


이름이나 팀 번호를 이용하면 피아를 구분할 수 있으니까, 코드에서 조건분기로 걸러낼 수 있을 것입니다.



다시 메모리 뷰어에서 100002E577MOVSS [RBX+08], XMM0 명령어로 이동합니다.


이 지점에서 코드 인젝션을 수행하기 위해서, Tools - Auto Assemble을 선택합니다.



작성한 어셈블리 프로그램을 기입할 수 있는 Auto assemble 창이 나타납니다.


그런데 어떻게 하지, 나는 텅텅 빈 종이에 어셈블리어로 코딩을 술술 할 만큼의 귀재가 아닌데... 흑흑



Template - Full Injection 항목을 선택하면 어셈블리 프로그램의 기본 양식이 제공됩니다.


훨씬 편하네요. 한시름 놨어요.


이제 당신도 어셈블리어로 쉽게 코딩할 수 있습니다!



Code inject template

On what address do you want the jump?


또 팝업이 뜨는데 읽지 않고 기본값 그대로 두고 OK를 누릅니다.



기본 양식입니다.


{ Game   : Tutorial-x86_64.exe
  Version: 
  Date   : 2018-03-16
  Author : nanit

  This script does blah blah blah
}

define(address,"Tutorial-x86_64.exe"+2E577)
define(bytes,F3 0F 11 43 08)

[ENABLE]

assert(address,bytes)
alloc(newmem,$1000,"Tutorial-x86_64.exe"+2E577)

label(code)
label(return)

newmem:

code:
  movss [rbx+08],xmm0
  jmp return

address:
  jmp newmem
return:

[DISABLE]

address:
  db bytes
  // movss [rbx+08],xmm0

dealloc(newmem)

{
// ORIGINAL CODE - INJECTION POINT: "Tutorial-x86_64.exe"+2E577

"Tutorial-x86_64.exe"+2E54E: E8 6D AE 12 00                 -  call Tutorial-x86_64.exe+1593C0
"Tutorial-x86_64.exe"+2E553: E9 9D 00 00 00                 -  jmp Tutorial-x86_64.exe+2E5F5
"Tutorial-x86_64.exe"+2E558: F3 0F 2A C6                    -  cvtsi2ss xmm0,esi
"Tutorial-x86_64.exe"+2E55C: F3 0F 10 4B 08                 -  movss xmm1,[rbx+08]
"Tutorial-x86_64.exe"+2E561: F3 0F 5C C8                    -  subss xmm1,xmm0
"Tutorial-x86_64.exe"+2E565: F3 0F 10 05 6B 8B 1D 00        -  movss xmm0,[Tutorial-x86_64.exe+2070D8]
"Tutorial-x86_64.exe"+2E56D: 0F 2F C8                       -  comiss xmm1,xmm0
"Tutorial-x86_64.exe"+2E570: 7A 02                          -  jp Tutorial-x86_64.exe+2E574
"Tutorial-x86_64.exe"+2E572: 72 03                          -  jb Tutorial-x86_64.exe+2E577
"Tutorial-x86_64.exe"+2E574: 0F 28 C1                       -  movaps xmm0,xmm1
// ---------- INJECTING HERE ----------
"Tutorial-x86_64.exe"+2E577: F3 0F 11 43 08                 -  movss [rbx+08],xmm0
// ---------- DONE INJECTING  ----------
"Tutorial-x86_64.exe"+2E57C: F3 0F 10 43 08                 -  movss xmm0,[rbx+08]
"Tutorial-x86_64.exe"+2E581: 0F 2F 05 50 8B 1D 00           -  comiss xmm0,[Tutorial-x86_64.exe+2070D8]
"Tutorial-x86_64.exe"+2E588: 7A 14                          -  jp Tutorial-x86_64.exe+2E59E
"Tutorial-x86_64.exe"+2E58A: 75 12                          -  jne Tutorial-x86_64.exe+2E59E
"Tutorial-x86_64.exe"+2E58C: 48 8B 4B 60                    -  mov rcx,[rbx+60]
"Tutorial-x86_64.exe"+2E590: 48 8B 15 71 06 17 00           -  mov rdx,[Tutorial-x86_64.exe+19EC08]
"Tutorial-x86_64.exe"+2E597: E8 94 77 08 00                 -  call Tutorial-x86_64.exe+B5D30
"Tutorial-x86_64.exe"+2E59C: EB 49                          -  jmp Tutorial-x86_64.exe+2E5E7
"Tutorial-x86_64.exe"+2E59E: F3 0F 10 4B 08                 -  movss xmm1,[rbx+08]
"Tutorial-x86_64.exe"+2E5A3: 48 8D 4D E0                    -  lea rcx,[rbp-20]
}



저는 기존 코드 윗 부분에 이렇게 3줄을 추가했습니다.


newmem:
  cmp [rbx+14],1
  je return
  subss xmm0,xmm0

code:
  movss [rbx+08],xmm0
  jmp return


cmp [rbx+14],1은 객체의 멤버를 조사해서 1팀이 맞는지 검사합니다.


1팀이 맞다면, je return에 의해서 체력의 갱신을 수행하는 코드를 건너뜁니다.


이로써 1팀의 플레이어는 공격 받아도 체력이 깎이지 않고 무적입니다.


1팀이 아니라면, 그대로 수정된 코드를 실행합니다.


subss는 빼기 연산을 수행하는 sub의 실수 버전입니다.


movss로 값을 저장하기 전에 subss xmm0,xmm0를 실행해서, 저장하는 값을 0으로 만듭니다.


그렇게 하면 체력의 갱신이 일어나는 시점에 1팀이 아닌 플레이어는 그 즉시 무조건 죽게 되겠죠. 히히.


작성이 끝났으면 Execute 버튼을 눌러서 바로 적용합니다.



Confirmation

This code can be injected. Are you sure?


문법 오류가 없다면 이런 메시지 박스가 나타납니다.


Yes 선택해서 넘어갑니다.



Information

The code injection was successfull

newmem=FFFF0000

Go to FFFF0000?


코드 인젝션이 잘 되었다네요.


정말 잘 되었는지 구경하기 위해서 Yes를 눌러 따라들어가 봅니다.



FFFF0000에 내가 작성한 코드가 아주 잘 들어가 있음을 확인할 수 있습니다.



이 코드 조각이 리턴하는 곳을 찾아가 봅니다.


"Go to address" 기능을 이용합니다.



"Go to address"의 팝업에 주소를 입력하면 메모리 뷰어가 그 지점을 보여 줍니다.



원래의 코드가 jmp FFFF0000으로 대체되고 바로 그 다음 수행되는 명령어의 주소가 인젝션 코드의 리턴 주소인 것을 확인할 수 있습니다.



이제 불공평한 게임으로 돌아와서 Eric을 공격해 보면 체력이 깎이지 않습니다.



HAL은 클릭 한 방에 죽습니다.



KITT도 한 방에 죽습니다.



자동전투로 승리하자 말 없이 조용히 Next 버튼만 활성화됩니다.



Tutorial End

Well done, you've completed the tutorial of Cheat Engine.

Just play around with the tutorial and learn how the other scanmethods work.

ys check out the Cheat Engine Forum for useful information and ask for help

Cheat Engine Forum


끝이네요. 히히.


Top