글을 읽기 전 필요한 지식
- C언어 포인터
- 어셈블리 명령어 (x86 / x64 intel / amd 명령어)
- 컴퓨팅 구조
6단계: 포인터
이전 단계에서는 코드 검색기를 사용하여 위치를 변경하는 방법을 설명했습니다. 그러나 그 방법만으로는 원하는 값을 설정할 주소를 찾기가 어렵습니다. 그래서 포인터를 사용합니다.
아래쪽에는 두 개의 버튼이 있습니다. 하나는 값을 변경하고, 다른 하나는 값을 변경하고 값의 위치도 변경합니다. 이번 단계에서는 어셈블러를 알아야하지 않지만, 알고 있다면 많은 도움이 됩니다.
먼저 값을 찾은 후에는 해당 주소를 접근하는 항목을 찾기 위한 함수를 사용하세요. 값을 다시 변경하면 목록에 항목이 표시됩니다. 해당 항목을 더블 클릭하면(또는 선택한 다음 "자세히"를 클릭하면) 명령이 실행될 때 무슨 일이 일어났는지에 대한 자세한 정보가 포함된 새 창이 열립니다.
어셈블러 명령문에 '['와 ']' 사이에 아무것도 없는 경우 목록의 다른 항목을 사용하세요. 만약 '['와 ']' 사이에 무언가 있다면, 필요한 포인터의 값이 무엇인지 나와있을 것입니다. 메인 Cheat Engine 창으로 돌아가서 (이 추가 정보 창을 열어둘 수 있지만, 닫으면 '['와 ']' 사이의 내용을 기억해야 합니다) 16진수 4바이트 스캔을 사용하여 추가 정보에서 알려준 값으로 검색하세요. 검색이 완료되면 1개 또는 수백 개의 주소가 반환됩니다.
대부분의 경우 필요한 주소는 가장 작은 것일 것입니다. 이제 "주소 수동 추가" 버튼을 클릭하고 포인터 확인란을 선택하세요. 창이 변경되어 포인터 주소와 오프셋을 입력할 수 있게 됩니다. 방금 찾은 주소를 입력하세요. 예: "Tutorial-i386.exe"+xxxxxx(프로세스에 상대적인 주소), 또는 주소 목록에서 해당 주소를 더블 클릭하여 나타나는 절대 주소를 사용할 수도 있습니다.
어셈블러 명령문에 계산(예: [esi+12])이 있다면, 해당 값(예: 12)을 주소 필드 위에 입력하세요. 이것이 오프셋입니다. 그렇지 않으면 0으로 남겨둡니다. 더 복잡한 명령문인 경우 다음 계산식을 확인하세요.
더 복잡한 명령어 예: [EAX2+EDX+00000310] eax=4C 이고 edx=00801234 입니다. 이 경우, EDX가 포인터가 가지는 값이고, EAX2+00000310이 오프셋입니다. 따라서, 채워야 하는 오프셋은 2*4C+00000310=3A8 입니다. (이 모든 것은 16진수이므로, 윈도우의 계산기 프로그램에서 16진수 값을 계산하기 위해 프로그래머 모드를 사용하세요.)
튜토리얼로 돌아가서, 확인 버튼을 클릭하면 주소가 추가됩니다. 모두 제대로 진행됐다면, 주소는 P->xxxxxxx 형태로 표시될 것입니다. 여기서 xxxxxxx는 찾은 값의 주소입니다. 만약 그렇지 않다면, 뭔가 잘못한 것입니다. 이제, 추가한 포인터를 사용하여 값을 5000으로 변경하고 'Active' 열을 클릭하여 그 값을 고정시킵니다. 그런 다음 'Change pointer'를 클릭하면, 모든 것이 제대로 되었다면 다음 버튼이 표시될 것입니다.
추가로, 포인터 스캐너를 사용하여 이 주소의 포인터를 찾을 수도 있습니다. https://cheatengine.org/help/pointer-scan.htm
안녕하세요. 파일입니다. 오늘은 치트엔진 6번 관련 문제 풀이입니다. 5번까지는 대략 겉핥기로 이해했다 쳐도 치트엔진 튜토리얼 문제 풀이의 관문이 되는게 아마 이 6번 문제가 아닌가 싶습니다.
6번 문제 풀이의 주제는 제목에서도 알 수 있듯이 바로 "포인터" 입니다. C언어에서 배우는 그 포인터 개념이 맞습니다. 아마 일반적으로 이 포인터라는 개념은 어셈블리어에서 Memory Addressing Mode 같은걸 배우지 않았다고 가정했을 때 대부분 C언어에서 보셨을 겁니다.
그렇기에 기본적으로 C언어 포인터에 대해 겉핥기가 아닌, 개념 숙지 및 응용을 제대로 할 줄 하셔야 본 글을 이해 가능합니다. 그리고 사실 치트엔진은 C언어 포인터보다 더 Low-Level한 기계의 영역을 탐색하므로 (어셈블리어) 이해가 한번에 잘 되지 않을 수 있습니다.
일단은 C언어 관점에서 쉽게 설명드리고, 이를 기반으로 문제 풀이후 어셈블리 레벨에서 또 한번 깊게 알아보도록 하겠습니다. 이해가 잘 되지 않더라도 반복 학습하면서 익혀보세요!
되도록 "글을 읽기전 필요한 지식" 에 링크해둔 모든 글을 먼저 읽고 본 글을 읽는걸 추천드립니다.
C언어의 변수 접근 방식 (이론설명)
#include <stdio.h>
int main()
{
int a = 10;
printf("변수 a의 주소 : %p\n", &a);
printf("변수 a의 값 : %d", a);
return 0;
}
변수 a의 주소 : 0061FF1C
변수 a의 값 : 10
C언어에서 RAM에 데이터를 기록하고 관리하기 위해선 변수(Variable) 라는 개념을 사용합니다.
위 코드는 a라는 변수 (메모리 공간) 를 만들고, 이를 통해 "a"라는 이름으로 저 메모리 공간에 접근해 읽기 / 쓰기를 해서 데이터를 관리합니다.
값을 출력해보면 알겠지만 a라는 변수는 메모리(RAM) 의 0061FF1C 라는 시작주소에 존재하며, 이곳에 10이라는 값이 저장되어 있습니다.
a라는 변수는 중괄호 안에 존재하므로 지역 변수(Local Variable) 이며 이론상 프로세스의 스택 메모리 영역에 저장되게 됩니다. 복잡한 내용은 다 빼고 RAM위에서 저장된다는거만 고려하면 간단하게 위 그림처럼 나타낼 수 있습니다.
RAM의 0061FF1C라는 시작 주소에 10이라는 값이 저장된 형태입니다.
#include <stdio.h>
int main()
{
int a = 10;
printf("변수 a의 주소 : %p\n", &a);
printf("변수 a의 값 : %d\n", a);
a = 20; // a의 값을 20으로 변경 [직접참조]
printf("변수 a의 주소 : %p\n", &a);
printf("변수 a의 값 : %d\n", a);
return 0;
}
변수 a의 주소 : 0061FF1C
변수 a의 값 : 10
변수 a의 주소 : 0061FF1C
변수 a의 값 : 20
저 a라는 변수의 값을 바꾸기 위해서, C언어는 2가지 방법을 시도할 수 있습니다.
1. a라는 변수의 값을 직접 바꾸기
2. a라는 변수의 주소값을 이용해서, 주소값을 찾아가서 포인터 변수로 바꾸기
일단은 1번에 해당하는 a의 값을 직접 바꾸는 코드의 경우 위처럼 C언어 코드를 작성하면 됩니다.
저 코드를 실행하면 a라는 공간의 10이라는 값이 20이라는 값으로 덮어 씌어져서 최종적으로 a엔 20이 저장되게 됩니다.
C언어에서 a라는 변수 공간의 값을 바꾸려면 대입 연산자(=) 를 사용해서 a = 20; 이라는 한줄짜리 코드를 작성하면 변수의 값을 바꿔낼 수 있습니다.
대부분의 프로그래밍 언어(Java, C#, Python) 는 대입 연산자만 쓰면 변수 값을 쉽게 바꿀 수 있고, 더 이상 변수 값을 바꾸는 다른 기능을 지원하지 않습니다. 다만 C언어 (또는 C++) 에서는 변수 값을 한 가지 바꾸는 방법을 더 제공하는데 변수의 주소를 이용해서 바꾸는 포인터라는 개념입니다.
기존에 a라는 변수명에 직접 대입해서 값을 바꿨다면, 포인터로 바꾼다면 다음과 같은 코드가 됩니다.
#include <stdio.h>
int main()
{
int a = 10;
printf("변수 a의 주소 : %p\n", &a);
printf("변수 a의 값 : %d\n", a);
int * ptr = &a; //ptr 포인터 변수를 이용해 a의 시작 주소를 가리킨다.
printf("포인터 변수 ptr의 주소 : %p\n", &ptr);
*ptr = 20; // a의 값을 20으로 변경 [간접 참조]
printf("변수 a의 주소 : %p\n", &a);
printf("변수 a의 값 : %d\n", a);
return 0;
}
변수 a의 주소 : 0061FF1C
변수 a의 값 : 10
포인터 변수 ptr의 주소 : 0061FF18
변수 a의 주소 : 0061FF1C
변수 a의 값 : 20
아까는 a = 20; 코드를 통해 직접적으로 a의 값을 바꿨다면 이번엔 포인터를 활용해서 ptr이 "a의 시작주소" 를 가리키게 하고, *ptr = 20; 코드를 통해 ptr이 가리키는 곳의 값을 참조, 즉 a의 값에 20을 대입해라! 라는 형태로 값을 바꿔내고 있습니다.
C언어에서 포인터 개념을 배우신 분들이라면 어렵지 않게 이해할 수 있는 내용일겁니다.
그림으로 나타내면 다음과 같습니다. 기존엔 a변수에 직접 접근했다면 이제 ptr이 a의 시작주소를 저장해서 가리키고, 이를 타고 들어가서 a의 값을 바꾸는게 *ptr = 20; 코드의 원리입니다.
보통 컴공 1학년 학부때 배우는 내용이며 지금 위에서 그림으로 이해한 내용이, 이번 치트엔진 튜토리얼 6번 문제 풀이의 핵심입니다.
그림은 또 나올거지만 위의 포인터 변수 원리를 잘 기억해두세요. 그럼 출발해봅시다!
어셈블리 레벨에서 조금 깊은 설명 [읽으실 분들만]
int a;
a = 20;
C언어에선 a라는 변수를 만들고 수정하기 위해선 위 2줄의 코드 정도면 충분합니다.
그러나, C언어의 역사 자체가 어셈블리어로 프로그래밍 하는게 힘들어서 생산성을 높이고자 만든 언어고 (UNIX라는 운영체제를 만들기 위해 만든 언어입니다),
결론적으로 C언어로 컴파일하면 먼저 어셈블리어로 변환되고 최종적으로 기계어로 변환되기 때문에
기계어(어셈블리어) 의 동작을 이해할 필요가 있습니다.
아주 Low-Level에서 기계가 하는 동작을 알아야 합니다.
int a;
a = 20;
일단 위 C언어 코드는 어셈블리어로 변환되면 컴파일러 최적화에 의해
a라는 변수는 레지스터에 저장될 수도, RAM에 저장될 수도 있지만 일단 RAM에 저장된다고 가정해보겠습니다.
현대 컴퓨터 구조에 의해 대부분의 컴퓨팅 작업은 CPU에 의해 총괄되며, RAM위로 올라온 기계어를 읽는것도, RAM에 데이터를 읽고 / 쓰는것도 전부 CPU가 합니다.
중요한점은 CPU가 메모리(DRAM) 에 데이터를 읽거나 쓸 때 전부 주소(Address)를 사용한다는 점입니다.
그렇기에 변수 a 에 20을 대입하는 a = 20; 과 같은 코드 역시 CPU 입장에선 a라는 변수명도 사실 필요 없는 개념이고 RAM의 어디 주소에다가 4바이트만큼 20을 쓸겁니다.
일단은 위 C언어 코드에서 a 변수의 주소 위치가 0061FF1C라고 알려줬으니
"어디 주소" 는 0061FF1C가 되겠네요.
mov [0061FF1C], 20
CPU는 위와 같은 어셈블리어를 실행해서 데이터를 저장할 수 있습니다.
CPU는 애초에 'a' 라는 변수명은 모르는거고 그냥 RAM 주소로 접근해서 4바이트만큼 데이터를 쓰는게 다입니다.
우리가 a라는 변수명으로 a = 20; 과 같이 실행한 코드도 어셈블리 레벨의 CPU & RAM 입장에선 결국에 주소 접근입니다. 마치 포인터처럼요. 변수라고 해도 CPU는 주소를 주고 RAM 위치에 데이터를 읽거나 써야 하니깐요.
그럼 포인터 변수는 어떻게 되냐? 하면 이건 어떻게 보면 주소에 주소 접근입니다.
왜냐면 CPU는 ptr 포인터 변수가 위치한 0061FF18 주소에 가서 그 주소값을 저장한다음에 (아마 레지스터에 저장하게 되겠죠?) 그 저장한 주소값을 다시 타고 들어가서 a의 변수 값을 바꿀태니깐요.
; 현재 0061FF18 주소 위치의 값 [0061FF18] 에는 a 변수의 주소 0061FF1C가 저장되어 있음
mov eax, [0061FF18] ; 포인터 변수가 가리키는 곳의 주소(a의 주소)를 eax 레지스터에 임시 저장한다
mov [eax], 20 ; a의 주소값에 다시 접근해 20을 쓴다.
만약 ptr 포인터 변수가 가리키는 주소를 임시로 eax라는 레지스터에 저장하게 된다면 어셈블리어로 다음과 같이 번역 가능합니다.
처음에 "포인터 변수의 주소" 로 접근해 값을 읽고, 그걸 eax 레지스터에 임시 저장한다음에 다시 eax 레지스터에 저장된 "주소" 를 이용해 a의 값을 바꾸게 됩니다.
컴퓨터 입장에서 C언어의 변수란 개념은 그냥 주소로 접근해 RAM에 데이터를 쓰는거고, C언어의 포인터 변수라는 개념은 포인터 변수의 주소로 접근해, 그 주소가 가리키는 주소로 다시 이동해 값을 쓰는겁니다.
C언어를 활용하면 a = 20; 과 같은 간단한 문법으로 컴파일러 최적화에 의해 컴파일러가 RAM의 적당한 위치에 a 공간 4바이트를 잡고 20을 써주는거고 (만약에 어셈블리어로 프로그래밍 했다면 RAM의 빈 공간 어디에 배치해야할지 프로그래머가 스스로 고민했어야 할 겁니다)
C언어의 포인터 변수 역시 기존 a변수의 주소를 저장해서 다시 "주소의 주소" 로 접근해야 하는 복잡함을 해결해준 셈이 되는 것이죠.
최적화는 전적으로 컴파일러에 맡기고! 프로그래머는 프로그래밍에만 집중할 수 있는겁니다.
물론 C언어 마저도 불편함이 많아서 나중에 가비지 콜렉터(GC) 가 탑제된 Java도 나온거고, C#도 나오고.. 이제는 추상화의 끝판왕인 Python 까지 나오게 된 거지만요.
저 역시 어셈블리어, 기계어로 프로그래밍 하던 시절에 태어나진 않고 꽤나 신식(?) 인 사람이라서 어셈블리어를 처음 공부했을 때 굉장히 난해하고 힘들었습니다. (지금도 쉽다는건 아님;; 잘 안와닿을때 많음)
Solution
일단 이번 문제는 저기 Change value 오른쪽에 보이는 100이라는 값을 5000으로 바꾸면 됩니다.
그러나! 이번에는 Change pointer 라고 포인터를 바꾸는 버튼 역시 새로 생겼기 때문에
저 버튼을 눌러도 여전히 값은 5000으로 고정되어야 합니다.
아직은 프로그램 동작 원리를 모르니 잘 이해가 되지 않을겁니다. 포인터를 바꾼다는 저 버튼이 무슨 의미를 가지는지요.
일단은 앞에서 항상 하던대로 모든 프로그램은 RAM위에서 돌아가니깐, 저 100이라는 데이터가 RAM에 있으며 저장되고 있을것이라는 믿음을 가지고 시작합니다.
이번에도 치트엔진으로 저 100이라는 값이 저장되고 있는 주소를 찾아서 5000으로 바꾸면 될 것입니다.
튜토리얼 프로그램을 Attach(로드) 한 상태로 시작합니다.
일단 100이라는 값을 정확히 알고 있으니 대부분의 윈도우 프로그램에서 사용하는 4 Bytes 로 검색하겠습니다.
111개의 값이 나왔습니다.
Change value 버튼을 이용해 368로 값을 바꾸고 검색해보면 값이 딱 하나 뜹니다.
이게 Change value 옆에서 표시되는 값인거 같네요.
더블클릭해서 밑에 주소 테이블에 등록해주고 Value 부분을 더블클릭해서 창이 나오면 값을 5000으로 바꿔줍니다.
5000으로 분명 값을 바꿨는데도 Next가 활성화 되지 않네요.
일단은 값은 바뀐 게 맞습니다. 값이 바뀐걸 프로그램이 실시간으로 렌더링하고 있진 않기 때문에 값이 안바뀐거 처럼 보이지만요.
일단은 Change value를 계속 눌러보시면 이에 맞게 값은 계속 잘 바뀌면서, 제대로 추적되고 있음을 확인할 수 있습니다.
그러나!
Change pointer 를 누르고 Change value 버튼을 계속눌러보아도 우리가 찾아놓은 값은 전혀 변화하지 않음을 알 수 있습니다. 도대체 무슨 일이 벌어진걸까요?
이 값의 주소가 어디로 갔는지 확인하기 위해 다시 값을 검색해 추적해봅니다.
다시 찾는 과정은 생략하고, 보시다 싶이 값의 주소 위치가 0158A1F0 에서 0158A3F0으로 변화하고 여기에 새로 값이 써지고 있다는것을 알 수 있습니다.
사실 치트엔진 튜토리얼 6번 프로그램의 경우 실제로 포인터를 이용해서 값을 바꾸고 있는데요. 그림으로 한 번 알아보겠습니다.
아까 제일 처음 찾았던 상황으로 돌아가보면 Change value 옆에 표시되는 값의 주소의 경우 0158A1F0 이고, 969 가 저장되어 있습니다.
실제로 이 프로그램은 포인터를 이용해서 값을 읽고 쓰고 있으므로 다음과 같은 그림이 됩니다. 편의상 969 가 저장된 곳을 val1, val1을 가리키는 포인터 변수를 ptr이라고 한다면 그림과 같습니다.
물론 포인터 변수에 저장되는 값이 무조건 0158A1F0 라는 보장은 없습니다. 왜냐하면 0158A1F0 보다 4바이트 이전의 0158A1EC 을 PTR에 저장해놓고 PTR + 4 (0158A1EC + 4) 만큼 연산을 통해 val1을 가리켜서 val1의 값을 바꿀 수 있기 때문입니다. (여기서 +4를 Offset 이라고 표현합니다. 이렇게 된다면 포인터 값은 0158A1EC 이고 오프셋은 +4입니다.)
이외에도 PTR에 val1의 주소값보다 몇바이트 이전 값의 주소를 저장해놔서 가리키도록 한다음에, + - offset 만큼 이동시켜서 실제 값(val1)을 바꿔낼 수도 있단거죠.
그래도 일단 확실한건, 포인터 변수를 활용해서 val1을 바꾸고 있다는 점입니다.
그리고 일단은 이 문제에서는 offset 없이 포인터 변수가 val1을 그대로 가리키고 있으므로 크게 걱정은 안해도 됩니다.
Change value를 눌러서 값이 변경되었다면 다음과 같이 val1 위치의 값이 변경되는겁니다.
현재 ptr이 val1의 주소를 가리키고 있으니, 추정컨데 이 포인터를 활용해서 값을 바꾸는거겠죠.
치트엔진으로 값을 5000으로 바꾼 상황의 경우 다음과 같은 상황입니다.
val1 위치의 값을 5000으로 바꾼것이죠.
이제 Change pointer를 누른 후 상황을 그림으로 이해해봅시다.
Change pointer를 누른 순간 프로그램은 RAM에 빈 공간을 다시 찾아서 그 주소 위치를 포인터가 가리키게 한 후, 포인터 변수를 이용해 새 값을 씁니다. [199] (물론 포인터가 빈 공간의 주소를 가리켜서 값을 쓰는건지, 빈 공간에 값을 쓰고 포인터가 이를 가리키는건지는 어셈블리 코드를 정확히 읽어보긴 해야 합니다)
이제 포인터가 새로운 공간의 주소를 가리키고 있으므로 val1의 주소 공간은 더 이상 사용되지 않으며 새로운 주소인 0158A3F0 (편의상 val2라고 부름) 가 사용되게 됩니다.
이제 새로운 공간 val2 의 값을 튜토리얼 프로그램에 표시할 것이니 이미 찾아놓은 val1 (0158A1F0) 의 값을 어떻게 바꾸던 간에 아무런 효과가 없을겁니다. 그렇다고 다시 val2를 찾아놔도 문제는, Change pointer를 누르면 또 새로운 주소가 잡혀서 포인터 변수 값이 변경될 것입니다.
결론적으로 이 튜토리얼 프로그램은 "포인터" 를 이용해 공간을 가리키고 값을 읽고 쓰므로 바뀌는 변수들을 찾을게 아니라 바뀌는 변수들을 실질적으로 조종하는 "포인터"를 찾아내야 한다는 뜻입니다.
왜냐면 변수들의 값과 주소는 항상 변할 가능성이 있어도, 포인터 변수는 찾아두기만 하면 자신의 값이 항상 변수의 주소 위치를 가리키게 되기 때문에 언제든지 추적이 가능해집니다.
앞에서 나대는 핫바리들을 잡을게 아니라 핫바리들을 실질적으로 조종하는 보스를 찾아내야 한다 이말입니다.
현재 상황으로는 포인터 변수가 0158A3F0 을 보고 있으므로 0158A3F0 라는 위치에 어떤 포인터 변수가 개입하고 있는지 찾아내야 합니다. 현재 주소의 값을 어떤 포인터 변수가 바꾸고 있는지 찾아내려면 값을 오른쪽 클릭하고 Find out what accesses this address를 누릅니다. 저번에는 포인터 변수로 값을 바꾸는게 아니라서 Find out what writes.. 를 사용했었습니다.
포인터 변수를 찾아내려면 what accesses.. 를 사용하면 됩니다.
지금부터 본 주소에 어떤 포인터 변수가 개입하는지 찾아내기 위해 해당 창이 열립니다.
변화를 주기 위해 Change value를 누르겠습니다. (포인터는 아직 안찾았으니깐 Change pointer는 누르지 마셈)
또 머리 아픈 어셈블리어가 나왔네요.
일단 저번에는 대충 넘어갔지만 이번에는 어느정도 어셈블리어를 안다는 가정하에 글을 쓰고 있습니다.
어셈블리어 개념에 대해 잘 모르시면 제가 맨 위에서 링크 해둔 글 읽어보시고 유투브에 스무디님 어셈블리어 강의도 좀 봐보세요.
어쨌던 간에 4줄 짜리 어셈블리어 코드에 의해 0158A3F0 주소의 값이 access 되고 있습니다.
mov [0158A3F0], 바뀔값
일단은 어떻게 하던 간에 0158A3F0 공간에 값을 써야 하니깐 다음과 같은 어셈블리어로 값을 쓸 거 같습니다.
* [주소] 로 하면 메모리 접근 모드라서 인텔 형태의 어셈블리어를 기준으로 주소에 해당하는 RAM "값"에 접근하겠다는 의미입니다. 포인터로 따지면 *ptr 과 같은 뜻
어딘가 RAM 주소에 값을 쓰는 어셈블리어 명령어는 저거 하나밖에 없네요.
저걸 클릭하면 저 명령어가 실행된 시점의 레지스터 값들의 상황이 전부 아래에 표시됩니다.
일단은 RDX 값을 보니깐 역시나 저희가 찾아놓은 val2의 주소 위치고, eax 레지스터 값(RAX의 4바이트 부분)에는 000002CD 가 저장되어 있네요.
현재 바뀐 값이 717 이고, 이게 16진수로 하면 02CD 입니다.
EAX에 Change value 이후 바뀔 값이 담겨서 왔네요.
10002D14C - 89 02 - mov [rdx],eax
일단은 실제로 0158A3F0 의 값을 바꾸는데 관여되는 부분은 저기고, [rdx] 와 같은 형태로 접근했으므로 아마 어딘가에 rdx(val2)를 가리키는 포인터 변수가 위치해 있을것이라고 추정됩니다.
New Scan 이후, 0158A3F0 라는 주소 값을 가리키는 포인터 변수가 있는지 찾아봅니다.
주의 할점은 지금 16진수 형태로 검색을 하고 있으므로 Hex 를 반드시 누르고 검색해야 한다는 점입니다.
찾았다 요놈. 특이한점은 포인터 변수의 주소가 초록색으로 고정값이네요.
아마 static 형태의 포인터 변수로 선언해놓은듯 합니다.
더블클릭하면 이렇게 등록이 되고 "Tutorial-x86_64.exe"+325AD0 으로 계산된 포인터 변수의 주소는 100325AD0 네요.
지금은 포인터 변수기 때문에 변수 주소를 가리키고 있어서 값을 바꾸기가 좀 귀찮은데요, "포인터 변수가 가리키는 주소의 값" 을 참조(=주소로 접근해 값을 얻어내는 방법)하는 방법이 치트엔진에는 존재합니다.
Add Address Manually 를 이용해 수동으로 포인터 변수를 등록해봅시다.
다음과 같은 창에서 Pointer를 체크해서 포인터 변수임을 치트엔진에 알리고, 아래 빨간색으로 밑줄 쳐진 부분에 포인터 변수의 주소를 적습니다. (주의 : 포인터 변수가 가리키는 공간의 주소를 적는게 아니라, 포인터 변수 자체의 주소를 적어야 합니다)
포인터 변수는 아까 보셨듯이 "Tutorial-x86_64.exe"+325AD0 라는 주소 위치에 고정으로 올라옵니다.
그래서 주소에 "Tutorial-x86_64.exe"+325AD0 를 적어줘도 되고 계산된 값인 100325AD0를 직접 적어줘도 됩니다.
보다 싶이 포인터 변수가 추가 되었고, 이놈이 가리키는 공간의 주소에 대한 값이 320 이라는걸 표시해줍니다.
이제 포인터 변수가 가리키는 공간의 값을 5000으로 고정해주면 Change Pointer을 하더라도, 우리는 포인터 변수의 위치를 알고 있으니 성공적으로 값을 고정할 수 있겠죠. 여기서 사용하는 포인터 변수는 언제든 사용하고 있는 변수 공간의 주소를 가리키고 있으니깐요!!
포인터 변수로 가리키는 주소 위치의 값을 5000으로 바꿔줍니다. 그리고 값 고정을 위해 왼쪽에 네모 박스를 체크합니다.
치트엔진에서 값을 등록하고 네모박스로 체크하면, 그 값이 변하지 않고 고정됩니다.
Change pointer을 누른 뒤 Next 버튼이 활성화 되며 튜토리얼 6번을 클리어 한 것을 볼 수 있습니다.
지금까지 한 행위를 그림으로 나타내보면 다음과 같습니다.
원래 값만 바꾸던 행위를 넘어서, 근본적인 포인터 변수를 찾고 치트엔진으로 이를 활용해 값을 바꿔내는 그림입니다!
만약 Change Pointer을 누르더라도 큰 문제가 없습니다. 왜냐면 포인터 변수로 항상 현재 사용중인 변수의 주소 값을 추적할 수 있기 때문이죠. [포인터 변수가 현재 사용하는 공간의 주소를 가리키고 있음.]
출처
https://oper6210.tistory.com/160
"우리는 포인터가 데이터의 시작주소를 저장한다고 정의를 내린 바 있다."
'보안 강좌 > CheatEngine' 카테고리의 다른 글
[Cheat Engine] 치트엔진 튜토리얼 #8 풀이 (0) | 2023.04.04 |
---|---|
[Cheat Engine] 치트엔진 튜토리얼 #7 풀이 (1) | 2023.04.03 |
[Cheat Engine] 치트엔진 튜토리얼 #5 풀이 (0) | 2023.03.25 |
[Cheat Engine] 치트엔진 튜토리얼 #4 풀이 (0) | 2023.03.25 |
[Cheat Engine] 치트엔진 튜토리얼 #3 풀이 (0) | 2023.03.25 |