Debugger.godMode() – 디버거로 JVM 응용 프로그램 해킹하기

다른 언어: English Español Português 中文

옛날에 컴퓨터 게임은 달랐습니다. 그래픽과 메카닉이 진화했을 뿐만 아니라, 오늘날의 게임에서는 매우 흔하지 않은 특성을 가지고 있었습니다 : 치트 코드.

치트 코드는 특별한 무언가를 제공하는 키의 순서였습니다. 예를 들어 무한 탄약 또는 벽을 통해 걸어다니는 능력 등입니다. 가장 보편적이고 강력한 것 중 하나는 “갓 모드”였습니다. 당신을 무적으로 만드는 것입니다.

갓 모드가 활성화 된 Doom의 해병의 스크린샷

이것은 당신의 캐릭터가 Doom 에서 갓 모드를 위한 키 조합인 IDDQD를 입력했을 때 어떻게 보일지를 보여줍니다. 사실, 이 특정 키 순서는 그 게임 그 자체를 넘어 meme가 되고 인기를 얻을 정도로 매우 인기가 있었습니다.

갓 모드가 과거에는 게임에서 널리 퍼져 있었지만, 게임 속의 IDDQD meme 시대가 지나가는 것으로 보인다면, 현대의 대응하는 것은 무엇일지 궁금할 것입니다. 개인적으로 IDDQD의 현대적인 해석은 디버거입니다. 게임과 직접적으로 관련이 있다기보다는, 슈퍼파워를 가지고 있다는 같은 느낌을 깨웁니다.

스페이스 인베이더

저의 주장을 뒷받침하려면 이런 재미있는 시나리오를 생각해 보세요. Doom에 익숙하지 않더라도 여러분은 이 더 오래된 게임인 Space Invaders를 가볍게 본 적이 있을 것입니다. Doom처럼 그것의 줄거리도 우주에서의 침략자들과 싸우는 주제를 중심으로 합니다.

저의 친구이자 동료인 Eugene Nizienko 가 이 게임을 에디터에 바로 플레이할 수 있는 IntelliJ IDEA plugin을 작성했습니다. 인덱싱이 완료되는 동안 시간을 보내는 좋은 방법입니다.

IntelliJ IDEA의 편집기에서 스페이스 인베이더 플레이하기 IntelliJ IDEA의 편집기에서 스페이스 인베이더 플레이하기

이 게임에는 갓 모드가 없지만, 우리가 정말 결심하면 우리 스스로 추가할 수 있을까요? 디버거로 프로그램을 해킹하는 클래식한 전통을 되돌아보려면 알아보겠습니다!

Info icon

책임감있게 행동하세요! 입 프로그램을 수정하기 전에 유진의 동의를 받았습니다. 자신의 코드가 아닌 디버거를 사용할 경우, 윤리적으로 행동해야 합니다. 그렇지 않으면 그냥 하지 마세요.

도구 준비하기

메타 경험을 준비 하세요 – 우리는 자체 디버거를 사용하여 IntelliJ IDEA를 디버깅하려 합니다.

하지만 작은 문제가 있습니다 : IntelliJ IDEA를 디버깅하려면, 우리는 그것을 정지해야 하는데, 이로 인해 IDE는 응답하지 않게 됩니다. 따라서 우리는 기능적인 추가 IDE 인스턴스를 필요로 합니다.

여러 IDE 인스턴스를 관리하기 위해, 저는 JetBrains Toolbox App을 사용할 것입니다. 이 응용 프로그램은 여러분의 JetBrains IDE들을 조직화 합니다. 이를 통해 같은 IDE의 여러 버전을 설치하거나 다른 VM 옵션 집합으로 실행하기 위한 단축키를 생성할 수 있습니다.

IntelliJ IDEA의 두 인스턴스를 설치합시다 :

Space Invaders와 Debug라는 두 인스턴스의 IntelliJ IDEA를 포함한 여러 JetBrains IDE를 보여주는 JetBrains Toolbox Space Invaders와 Debug라는 두 인스턴스의 IntelliJ IDEA를 포함한 여러 JetBrains IDE를 보여주는 JetBrains Toolbox

두 인스턴스 모두 같은 IDE 버전을 사용하는 경우, 두 인스턴스의 시스템, 구성, 로그 디렉토리를 다르게 지정하는 것이 중요합니다. Tool actions | Settings | Configuration에서 설정할 수 있습니다. 이 페이지에서 IDE 인스턴스에 이름을 지정할 수도 있습니다. 저는 그것들을 ‘Space Invaders’와 ‘Debug’라고 이름을 붙였습니다.

Space Invaders 인스턴스를 디버깅 할 수 있도록 하려면, 그 근처에 Tool actions를 클릭한 다음 Settings | Edit JVM options로 이동합니다. 열린 파일에 다음 줄을 붙여넣습니다 :

-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005
IDE 인스턴스에 전달될 VM 옵션이 있는 파일 IDE 인스턴스에 전달될 VM 옵션이 있는 파일

이것은 목표 JVM이 디버그 에이전트로 실행되게 하고 포트 5005에서 들어오는 디버거 연결을 대기하게 합니다.

게임 실행하기

’Space Invaders’ 인스턴스를 실행하고, 게임을 설치하고 실행하기 위해 Space Invaders 액션을 실행하세요. 액션을 찾으려면 Shift를 두 번 누르고 Space Invaders를 입력하세요 :

Shift를 두 번 누를 경우 나타나는 대화상자에서 Space Invaders 액션을 실행하기 Shift를 두 번 누를 경우 나타나는 대화상자에서 Space Invaders 액션을 실행하기

조금 플레이하고 수정하고 싶은 동작을 관찰합시다 : 우주선에 적 미사일이 맞으면, 우리는 왼쪽 상단 모서리의 건강 바에서 건강을 잃습니다.

고정하고 일시 중지

디버깅 여행은 새로운 Kotlin 프로젝트를 설정하고 ‘Debug’ IDE 인스턴스를 열어 시작합니다. 이 프로젝트는 주로 디버거를 실행할 수 없다는 사실 때문에 필요합니다.

또한, IntelliJ IDEA는 Java/Kotlin 표준 라이브러리를 새 프로젝트 템플릿에 포함시키며, 이것을 나중에 사용할 수 있을 것입니다. 나중에 표준 라이브러리의 사용에 대해 자세히 설명하겠습니다.

프로젝트를 생성한 후 주 메뉴로 이동하고 실행 | 프로세스에 연결 (Run | Attach to Process)를 선택합니다. 디버거 attach 요청을 기다리는 로컬 JVM의 목록이 표시됩니다. 목록에서 다른 실행 중인 IDE를 선택합니다.

로컬에서 실행 중인 JVM 목록이 나타난 팝업 로컬에서 실행 중인 JVM 목록이 나타난 팝업

다음과 같은 메시지가 콘솔에 표시되어 디버거가 목표 VM에 성공적으로 연결되었음을 확인해야 합니다.

Connected to the target VM, address: 'localhost:5005', transport: 'socket'

이제 흥미로운 부분이 시작됩니다 : 어떻게 하면 프로그램을 중지 할 수 있을까요?

일반적으로 응용 프로그램 코드에 중단점을 설정할 것이지만, 이 경우에는, 우리는 IntelliJ IDEA와 Space Invaders 플러그인 모두에 대한 소스를 가지고 있지 않습니다. 이것은 우리가 중단점을 설정하지 못하도록 하는 것 뿐만 아니라, 프로그램의 작동 방식을 이해하는 것을 복잡하게 만듭니다. 처음보기에는 검사하거나 통과할 것처럼 보이지 않습니다.

다행히도, IntelliJ IDEA에는 프로그램 일시 중지 (Pause Program)라는 기능이 있습니다. 이 기능을 사용하면 코드의 해당 라인을 지정하지 않고도 프로그램을 임의의 시점에 정지시킬 수 있습니다. 디버거 도구 툴바 또는 메인 메뉴에서 찾을 수 있습니다 : 실행 | 디버그 액션 | 프로그램 일시 중지 (Run | Debugging Actions | Pause Program).

정지된 Space Invaders 인스턴스에 대한 Debug 도구 창 정지된 Space Invaders 인스턴스에 대한 Debug 도구 창

응용 프로그램이 일시 중지되면 디버깅을 시작할 수 있습니다.

Tip icon

프로그램 일시 중지 (Pause Program)는 몇 가지 고급 시나리오에서 특히 도움이 되는 매우 강력한 기술입니다. 자세히 알아보려면, 관련 기사를 확인하세요 :

관련 객체 찾기

우리의 목표를 프로그래밍 용어로 보면, 우주선의 건강이 줄어들지 않게 하며, 이것을 필요로 하는 객체를 찾아야 합니다.

우리는 플러그인 코드에 대해 전혀 모르지만, 그래도 IntelliJ IDEA 디버거의 메모리 (Memory) 보기를 사용하여 heap를 직접 검사할 수 있습니다 :

Debug 도구 창의 오른쪽 상단에서 Layout Settings를 클릭하면 나타나는 메뉴 Debug 도구 창의 오른쪽 상단에서 Layout Settings를 클릭하면 나타나는 메뉴

이 기능은 현재 살아있는 모든 객체에 대한 정보를 제공합니다. invaders 를 입력하고 찾을 수 있는지 보겠습니다 :

'invaders'를 Search 피트에 입력하면 'spaceinvaders' 패키지에 속한 클래스의 객체가 보여집니다. 'invaders'를 Search 피트에 입력하면 'spaceinvaders' 패키지에 속한 클래스의 객체가 보여집니다.

명백하게 plugin 클래스들은 com.github.nizienko.spaceinvaders 패키지 안에 있습니다. 이 패키지 안에는 GameState 라는 클래스가 있고 이 클래스는 여러 개의 살아있는 인스턴스들이 있습니다. 처음 봤을 때 이것이 우리가 찾는 것 같습니다.

GameState 를 더블 클릭하면 이 클래스의 모든 인스턴스가 표시됩니다 :

살아있는 GameState 인스턴스를 보여주는 대화상자 살아있는 GameState 인스턴스를 보여주는 대화상자

사실, 이것은 열거형인데 – 이것은 우리가 찾던 것이 아닙니다. 이것을 계속 찾다보면 Game 의 하나의 인스턴스를 발견하게 됩니다.

노드를 확장하면 인스턴스의 필드를 조사할 수 있습니다 :

객체의 필드를 보여주는 메모리 보기의 확장된 객체 노드 객체의 필드를 보여주는 메모리 보기의 확장된 객체 노드

health 속성이 여기에서 우리에게 관심이 있을 것 같습니다. 그것의 필드 중 하나는 _value 로 불립니다. 제 경우, 값은 100 으로, 게임을 중지했을 때 건강 바가 가득 찼을 때와 일치 합니다. 따라서 이것이 고려해야 할 올바른 필드이며 그 값은 0 에서 100 까지 범위를 가집니다.

이 가설을 테스트 해 봅시다. _value 를 마우스 오른쪽 버튼으로 클릭한 후 값 설정 (Set Value)를 선택합니다. 현재의 값과 다른 값을 선택하세요. 예를 들어, 저는 50 를 선택하겠

사용자 입력 값 50을 포함하는 'health' 필드에 대한 텍스트 필드가 있는 메모리 보기 사용자 입력 값 50을 포함하는 'health' 필드에 대한 텍스트 필드가 있는 메모리 보기

이 단계에서 일시 정지 액션 이후 메서드를 평가할 수 없음 (Cannot evaluate methods after Pause action)이라는 오류를 발견합니다:

'Pause 후에 methods를 평가할 수 없음'이라는 오류 메시지 'Pause 후에 methods를 평가할 수 없음'이라는 오류 메시지

문제는 우리가 중단점 대신 프로그램 일시 중지 (Pause Program)를 사용했기 때문에 발생하며, 이 기능은 일부 제한 사항이 있습니다. 그러나 이를 해결하기 위한 작은 방법을 사용할 수 있습니다.

이를 이전 포스트 중 하나에서 볼 수 있습니다. 여기에서는 프로그램 일시 중지 (Pause Program) 동작을 다룹니다. 만약 놓쳤다면, 다음이 필요한 작업입니다: 애플리케이션을 일시 중지한 후, 스테핑 동작을 수행합니다. 이러한 것은 스텝인투 (Step Into) 또는 스텝오버 (Step Over)와 같은 것입니다. 이렇게 하면 값 설정 (Set Value) 및 표현식 평가 (Evaluate Expression)와 같은 고급 기능을 사용할 수 있게 됩니다.

이제 health 의 값을 설정할 수 있어야 합니다. 값을 수정해 보고, 애플리케이션을 계속 실행하여 건강 바가 변화를 보여주는지 확인해 보세요.

그러므로 우리는 관련 상태를 보유하는 객체를 찾아냈습니다. 최소한, 우리는 가끔 건강 바를 수동으로 리필할 수 있습니다. 우리는 아직 완전히 안전하지는 않지만, 우리는 거기에 도달하고 있습니다.

레이블 및 표현식

이제 집중할 객체를 식별했으므로, 그것을 표기하는 것이 좋습니다. 디버그 레이블에 익숙하지 않은 경우, 표시된 객체는 다음과 같습니다:

User 객체의 배열을 보여주는 변수 탭, 그 중 하나는 User_Charlie라는 디버그 레이블로 표시됨 User 객체의 배열을 보여주는 변수 탭, 그 중 하나는 User_Charlie라는 디버그 레이블로 표시됨

레이블은 많은 방면에서 유익할 수 있습니다. 이 기사의 맥락에서, 관련 객체를 표시하면 현재 실행 컨텍스트에 종속되지 않고 기능을 사용할 수 있습니다. 예를 들면 표현식 평가 (Evaluate Expression)가 있습니다.

불행하게도, _value 를 직접 표시하는 것은 불가능하지만, 그것을 포함하는 객체를 표시할 수 있습니다. 이렇게 하려면, health 를 마우스 오른쪽 버튼으로 클릭하고, 힙 객체 표시 (Mark Object)를 선택하고 이름을 지정하십시오.

사용자에게 객체의 이름을 입력하라는 Select Object Label 대화상자 사용자에게 객체의 이름을 입력하라는 Select Object Label 대화상자

이제 우리는 레이블이 어떻게 작동하는지를 다른 곳에서 테스트할 수 있습니다. 표현식 평가 대화 상자를 열고 health_object_DebugLabel 을 표현식으로 입력합니다. 보시다시피, 객체는 프로그램의 어디서나 평가 (Evaluate) 대화상자를 통해 접근할 수 있습니다:

표현식으로 디버그 레이블이 입력된 평가 대화상자 표현식으로 디버그 레이블이 입력된 평가 대화상자

우주선의 건강 상태를 평가 (Evaluate)에서 변경하는 것은 어떨까요? health_object_DebugLabel._value = 100 은 작동하지 않습니다.

동시에, _value 는 코틀린 속성의 백업 필드로 보입니다. 이게 사실이라면, 코틀린은 대응하는 getter를 생성했을 것입니다:

health_object_DebugLabel.getValue()

평가 (Evaluate) 대화상자는 이것이 유효한 코드라고 생각하지 않지만, 우리는 그것을 시도해 볼 것입니다:

평가 대화 상자에서 디버그 레이블을 통해 속성을 참조 평가 대화 상자에서 디버그 레이블을 통해 속성을 참조

표현식은 현재 우주선의 건강 상태를 반환하므로 이 방법은 작동합니다! 예상대로, 세터도 작동합니다:

health_object_DebugLabel.setValue(100)

세터를 평가한 후에는 애플리케이션을 계속 실행하고 변경 내용이 적용되었는지 확인합시다. 응, 전체 건강 바를 볼 수 있습니다!

표현식 연결

우리의 목표에 도달하기 위해 남은 유일한 단계는 상태 수정을 자동화하여 건강 리필이 뒷편에서 일어나게 하는 것입니다. 이렇게 하면 우리는 게임 플레이를 방해받지 않고 즐길 수 있습니다.

이는 비잠금 중단점을 사용하여 수행할 수 있습니다. 이 타입의 중단점은 일반적으로 로깅에 사용됩니다; 그러나, 로깅 표현식은 반드시 순수할 필요는 없습니다. 따라서, 우리는 로깅 표현식 내에서 원하는 부작용을 도입할 수 있습니다. 하지만 소스 코드가 없다면, 중단점을 설정할 곳이 어디인지 알 수 없습니다.

어떻게 했는지 기억하시나요 Java/Kotlin 표준 라이브러리의 소스를 사용할 수 있을 것이라고 말했습니다. 여기가 아이디어입니다: IntelliJ IDEA와 관련 플러그인들은 Java/Kotlin으로 작성되었고, Swing을 UI 프레임워크로 사용합니다. 결과적으로, Space Invaders는 반드시 이들 의존성의 코드를 호출할 것입니다. 이는 우리가 중단점을 설정하기 위해 소스를 사용할 수 있다는 것을 의미합니다.

Info icon

간단하게 하기 위해, 우리는 JDK 버전을 지정하지 않았습니다. 대신 우리는 IntelliJ IDEA가 제안한 버전으로 프로젝트를 초기화했습니다. 그러나, 최고의 결과를 얻기 위해, 우리는 프로그램을 실행하는 데 사용된 버전에 가깝게 맞는 소스를 사용하는 것을 권장합니다.

중단점을 설정할 수 있는 많은 위치가 있습니다. 나는 java.awt.event.KeyListener::keyPressed 에서 메소드 중단점을 설정하기로 결정했습니다. 이것은 우리가 키를 누를 때마다 부작용을 트리거 합니다:

java.awt.event.KeyListener::keyPressed에 대한 로깅 중단점을 보여주는 중단점 대화상자 java.awt.event.KeyListener::keyPressed에 대한 로깅 중단점을 보여주는 중단점 대화상자
Info icon

표현식이있는 중단점을 뜨거운 코드에 설정하면 대상 응용 프로그램의 속도를 크게 느리게 할 수 있습니다.

Space Invaders로 돌아가서 우리의 직접 만든 IDDQD가 작동하는지 확인해 봅시다. 동작합니다!

IntelliJ IDEA에서 Space Invaders를 플레이하는 모습 - 우주선이 맞을 때마다 건강 바가 자동으로 리필됩니다

결론

이 글에서는 디버거를 사용하여 애플리케이션이 어떻게 작동하는지 파악했습니다. 그리고 우리는 메모리를 통해 탐색하고 기능을 수정할 수 있었고, 애플리케이션의 소스에 손대지 않고도 이를 수행할 수 있었습니다! 디버거를 IDDQD에 비교한 것이 너무 건방진 것으로 받아들여지지 않았기를 바라며, 디버깅 과제에서 여러분에게 도움이 될 기술들을 배웠기를 바랍니다.

Eugene Nizienko에게 Space Invaders 플러그인 작성에 대한 칭찬을 전하고 싶고, Egor Ushakov에게 디버깅과 프로그래밍에서 지속적인 영감의 원천이라는 것에 대해 감사하고 싶습니다. 그들같은 인물들 덕분에 컴퓨터는 두 배로 재미있습니다.

다가오는 포스트에서 제가 다루길 원하는 디버깅 관련 문제가 있다면 알려주십시오!

행복한 해킹 되세요!

all posts ->