본문으로 건너뛰기

Auto Assembler 기초: Cheat Engine의 AOB 인젝션 스크립트 이해하기

Cheat-Engine.net 작성자: Cheat-Engine.net 읽는 데 8분 조회 34회

스크립트 항목이 무엇인지, [ENABLE]과 [DISABLE]이 어떻게 작동하는지, 그리고 Cheat Engine의 AOB 인젝션 템플릿(aobscanmodule, alloc, 라벨, 심볼, 와일드카드)을 한 줄씩 살펴봐요.

공유되는 치트 테이블의 유용한 항목 대부분은 단순한 값이 아니라 스크립트예요. 무한 체력, 재장전 없음, 무료 제작 같은 것들이죠. 이런 항목은 Cheat Engine의 Auto Assembler로 작성돼요. Auto Assembler는 x86 어셈블리와 코드를 찾아 패치하는 명령어를 합친 작은 스크립트 형식이에요. 이 가이드에서는 이런 스크립트가 무슨 일을 하는지, Cheat Engine이 만들어 주는 AOB 인젝션 템플릿은 어떻게 생겼는지, 그리고 스크립트를 안정적으로 만드는 규칙을 설명해요.

값을 찾는 방법과 포인터가 무엇인지 이미 알고 있다면 더 도움이 될 거예요. 해당 배경 지식은 포인터 스캔 가이드에서 다뤄요. 스크립트는 실행 중인 게임의 코드를 바꾸므로 싱글 플레이와 오프라인 게임에서만 사용하고, 테스트 전에 저장하고, 세이브 파일을 백업해 두세요.

스크립트 항목이란

스크립트 항목은 주소 목록에서 값 열에 <script>가 표시되는 항목이에요. 값을 가리키는 대신 Auto Assembler 코드가 들어 있어요. 항목의 활성화 체크박스를 켜면 스크립트의 [ENABLE] 섹션이 실행되고, 끄면 [DISABLE] 섹션이 실행돼요. 사용자 입장에서의 설명은 치트 테이블 사용법 가이드에서 다뤄요.

일반적인 스크립트는 값을 한 번만 바꾸지 않아요. 값을 쓰는 게임 자체의 명령어를 바꾸기 때문에 스크립트가 활성화된 동안 효과가 계속 유지돼요. 예를 들어 피해를 입은 뒤 새 체력을 저장하는 명령어를 최대 체력을 저장하는 코드로 돌릴 수 있어요.

스크립트는 단순한 주소보다 큰 장점이 있어요. 게임 코드에서 바이트 패턴을 검색해 대상을 찾기 때문에 포인터 없이도 재시작 후에 작동해요. 대신 그 코드가 그대로여야 하므로 게임 버전에 묶이게 돼요.

[ENABLE]과 [DISABLE]

켜고 끌 수 있는 스크립트에는 두 섹션이 모두 있어요. [ENABLE]은 코드를 찾고, 새 코드를 위한 메모리를 할당하고, 게임의 명령어에서 새 코드로 점프하도록 써 넣어요. [DISABLE]은 이 모든 것을 되돌려요. 원래 바이트를 다시 쓰고, 등록한 이름을 제거하고, 메모리를 해제해요. 잘 작성된 [DISABLE]은 게임을 원래 상태 그대로 돌려놔요. 이 섹션이 없거나 잘못되면 항목을 꺼도 게임을 다시 시작할 때까지 패치되었거나 망가진 상태로 남아요.

명령어 찾기와 템플릿 생성

  1. 조작하려는 값을 찾아 주소 목록에 추가해요.
  2. 오른쪽 클릭 후 Find out what writes to this address를 선택해요. 게임에서 값을 변경시키면 하나 이상의 명령어가 목록에 나타나요.
  3. 명령어를 선택하고 디스어셈블러(Memory Viewer)에서 열어요.
  4. Memory Viewer에서 해당 명령어를 선택한 상태로 Tools → Auto Assemble을 열어요.
  5. Auto Assemble 창에서 Template → AOB Injection을 선택해요. 주소를 확인하고, 기본값이 INJECT인 심볼 이름을 그대로 쓰거나 변경해요.
  6. 스크립트를 편집한 다음, 창의 File 메뉴로 현재 치트 테이블에 할당해요. 주소 목록에 새 스크립트 항목으로 나타나요.

AOB 인젝션 템플릿, 한 줄씩 살펴보기

아래는 템플릿이 만드는 구조의 예시예요. 프로세스 이름, 심볼, 바이트는 설명을 위해 임의로 만든 것이며 실제 게임의 코드가 아니에요.

{ EXAMPLE STRUCTURE ONLY - not code for a real game.
  Process: game.exe   Symbol: INJECT   Bytes: made up }

[ENABLE]
aobscanmodule(INJECT,game.exe,89 83 F0 00 00 00 48 8B 5C 24 ??) // should be unique
alloc(newmem,$1000,INJECT)

label(code)
label(return)

newmem:
  mov eax,#999              // your change: store 999 instead

code:
  mov [rbx+000000F0],eax    // the original instruction
  jmp return

INJECT:
  jmp newmem
  nop
return:
registersymbol(INJECT)

[DISABLE]
INJECT:
  db 89 83 F0 00 00 00      // original bytes: mov [rbx+000000F0],eax

unregistersymbol(INJECT)
dealloc(newmem)

aobscanmodule

aobscanmodule(INJECT,game.exe,...)는 game.exe 모듈의 메모리에서 바이트 패턴을 검색하고, 찾으면 INJECT라는 이름을 패턴이 시작되는 주소로 정의해요. 검색을 한 모듈로 제한하면 더 빠르고 다른 곳에서 잘못 일치하는 일을 피할 수 있어요. 관련 명령어도 있어요. aobscan은 모든 메모리를 검색하고, aobscanregion은 두 주소 사이를 검색해요. 패턴을 찾지 못하면 스크립트가 오류와 함께 활성화에 실패하는데, 게임 업데이트 후에 흔히 나타나는 증상이에요.

alloc

alloc(newmem,$1000,INJECT)는 내 코드를 위해 0x1000바이트의 메모리를 확보하고 이름을 newmem으로 붙여요. 세 번째 매개변수는 Cheat Engine에게 INJECT 근처에 할당하도록 요청해요. 64비트 게임에서는 이것이 중요해요. 짧은 5바이트 jmp는 앞뒤로 약 2GB까지만 닿을 수 있어서, 가까이 할당해야 점프가 원래 명령어 자리에 들어갈 만큼 작게 유지돼요.

label

label(code)와 label(return)은 아래쪽에서 정의되는 위치의 이름을 미리 선언해요. code는 원래 명령어를 다시 실행하는 위치이고, return은 패치된 명령어 바로 다음 명령어로, 실행이 이어지는 위치예요.

newmem과 code

newmem: 아래는 모두 내 코드이고, 그 뒤에 code: 아래의 원래 명령어가 이어져요. 예시에서 mov eax,#999는 레지스터를 999로 설정하고(#은 10진수 표시), 원래의 mov [rbx+000000F0],eax가 그 값을 저장하므로 값은 항상 999로 써져요. 마지막으로 jmp return이 게임 코드로 돌아가요.

INJECT: jmp newmem

찾은 주소에서 스크립트는 원래 명령어를 jmp newmem으로 덮어써요. 점프는 5바이트인데 여기서 원래 명령어는 6바이트였기 때문에 남는 1바이트를 nop이 채워요. 그렇지 않으면 프로세서가 반쪽짜리 명령어를 실행하려 하게 돼요. 템플릿이 이 계산을 대신 해줘요. return:은 바로 뒤에 놓여서 건드리지 않은 다음 명령어를 가리켜요.

registersymbol과 unregistersymbol

registersymbol(INJECT)는 INJECT라는 이름을 Cheat Engine 전체에서 알 수 있게 해요. 이 덕분에 나중에 별도로 실행되는 [DISABLE] 섹션이 다시 스캔하지 않고도 같은 주소를 찾을 수 있어요. 다른 항목에서 참조할 수도 있고요. [DISABLE]의 unregistersymbol(INJECT)는 이 이름을 제거해요.

원래 바이트 복원과 dealloc

[DISABLE]에서 INJECT: 다음의 db 89 83 F0 00 00 00은 원래 바이트를 점프 위에 다시 써서 게임의 명령어를 정확히 복원해요. 그다음 dealloc(newmem)이 내 코드가 들어 있던 메모리를 해제해요. 순서가 중요해요. 먼저 점프를 복원해야 메모리를 해제할 때 게임이 더 이상 newmem을 사용하지 않아요.

와일드카드와 고유한 시그니처가 중요한 이유

aobscanmodule의 바이트 패턴을 시그니처라고 해요. 시그니처는 두 가지 조건을 충족해야 해요.

  • 고유해야 해요. 패턴이 두 번 이상 나타나면 스크립트는 첫 번째 일치 위치를 패치하는데, 이는 전혀 다른 함수일 수 있어요. 이런 식으로 스크립트가 게임을 튕기게 하거나 엉뚱한 것을 바꿔요. 템플릿이 "should be unique"라는 주석을 넣는 건 이를 상기시키기 위해서예요.
  • 작은 변경에도 버텨야 해요. 일부 바이트는 게임 버전 간에, 심지어 실행할 때마다 쉽게 바뀌어요. 명령어 안의 오프셋, 호출과 점프의 상대 주소, 내장된 상수 등이에요. 이런 바이트를 ?? 와일드카드로 바꾸면 그 세부 사항만 바뀔 때도 패턴이 계속 일치해요.

이 두 목표는 서로 상충해요. 바이트가 너무 적으면 패턴이 여러 곳에서 일치하고, 와일드카드가 너무 많으면 다시 일반적인 패턴이 돼요. 실용적인 방법은 오피코드 바이트는 유지하고, 변하기 쉬운 오프셋과 주소는 와일드카드로 바꾸고, 문맥을 위해 뒤따르는 명령어의 바이트를 몇 개 포함하는 거예요. 그런 다음 패턴이 고유한지 확인하세요. 메인 창에서 Value Type을 Array of byte로 설정하고, 패턴을 붙여넣고, 메모리 옵션에서 실행 가능한 메모리를 포함하도록 설정한 뒤 스캔해요. 결과가 정확히 하나 나오는 것이 목표예요. AOB 시그니처 메이커를 사용하면 복사한 바이트를 와일드카드 패턴으로 바꾸고 바뀔 가능성이 높은 바이트를 찾는 데 도움이 돼요.

스크립트 테스트하기

  • 게임을 저장하고, 첫 테스트 전에 세이브를 백업해 두세요.
  • 스크립트를 활성화해 효과를 확인한 다음, 비활성화하고 게임이 다시 정상 작동하는지 확인하세요.
  • 여러 번 켜고 꺼 보세요. 두 번째 활성화에서 실패하는 스크립트는 대개 [DISABLE] 섹션이 [ENABLE]을 완전히 되돌리지 못하는 경우예요.
  • 공유되는 명령어에 주의하세요. 내 체력을 쓰는 명령어가 적의 체력도 쓸 수 있어요. 이 경우 변경 사항이 모두에게 영향을 주므로, 무언가를 바꾸기 전에 경우를 구분해야 해요.

스크립트가 잘 작동하면 항목에 알기 쉬운 설명을 달고 테이블을 저장하세요. 게임이 업데이트되어 스크립트가 활성화되지 않으면 시그니처부터 확인해 보세요.

댓글

0
답글 대상

공개되지 않아요.

댓글은 운영자의 검토를 거친 후에 표시돼요.

아직 댓글이 없어요

가장 먼저 의견을 남겨 보세요.