WinDbg 명령어

Dreamhack
#Reverse Engineering

1 views

들어가며

1. 들어가며

  • 지난 강의에서 WinDbg의 기초적인 사용법을 학습했다.
  • 이때 공부한 기능들은 디버깅 작업의 근간이자 동적 분석에서 가장 강력한 부분이다.
    • 이 기능들만 충분히 연마해도 매우 넓은 스펙트럼의 동적 분석 작업을 수행할 수 있지만, 본래 CLI 기반 앱인 만큼 강력하고 편리한 명령어들을 다수 갖추고 있다.
  • 이번 강의에서는 지난 강의에서 다루지 못했던 핵심 WinDbg 명령어들을 소개하고, 이를 활용한 실제 디버깅 실습을 함께해 볼 것이다.

이번 강의에서도 지난 강의의 실습 바이너리인 exercise-lecture.exe를 활용하여 실습한다.


built-in 명령어

1. built-in 명령어

  • built-in 명령어는 WinDbg에 기본적으로 포함되어 있는 내장 명령어로, 프로세스의 흐름 제어와 메모리 및 레지스터 조회 등을 담당한다.
    • 지난 강의에 배웠던 bp, g, r 등의 명령어들이 이에 해당한다.
    • 앞서 살펴본 명령어들 이외에 유용한 내장 명령어들을 알아보자.

2. Enter

  • Enter는 직전에 실행한 명령어를 그대로 실행하게 해 준다.
    • 이 Enter는 메모리를 수정하는 명령어가 아닌 키보드에서 Enter 키를 입력하는 것에 해당한다.
    • 명령어를 여러 번 반복하고 싶은 경우 최초 명령어만 입력하고 이후에는 Enter를 입력하는 것으로 이전 명령어를 쉽게 재실행할 수 있다.
    • 아래db 명령어로 메모리 값을 읽은 뒤 Enter로 명령어를 다시 실행하는 예시이다.
0:000> db @rip L3
00007ff6`72f21330  48 81 ec                                          H..
0:000> 
00007ff6`72f21330  48 81 ec                                          H..

3. lm

  • lmList Loaded Modules의 약자로 메모리에 로드된 모듈들의 목록을 가상 주소와 함께 출력하는 명령어이다.

    • lm 명령어는 다양한 옵션과 함께 사용할 수 있다.
  • lm 명령어를 옵션 없이 사용하면 아래와 같이 메모리에 로드된 모든 모듈들의 목록을 살펴볼 수 있다.

    0:000> lm
    start             end                 module name
    00007ff6`72f20000 00007ff6`72f28000   exercise_lecture C (private pdb symbols)  C:\ProgramData\Dbg\sym\exercise-lecture.pdb\CB34EC2AAFA74582AD0DB4F3932076064\exercise-lecture.pdb
    00007ffa`87cf0000 00007ffa`87d0e000   VCRUNTIME140   (deferred)             
    00007ffa`abb30000 00007ffa`abc7b000   ucrtbase   (deferred)             
    00007ffa`abdf0000 00007ffa`ac1d8000   KERNELBASE   (deferred)             
    00007ffa`ad490000 00007ffa`ad559000   KERNEL32   (pdb symbols)          C:\ProgramData\Dbg\sym\kernel32.pdb\60819BF7E89AA952820421609F5980191\kernel32.pdb
    00007ffa`ae7a0000 00007ffa`aea05000   ntdll      (pdb symbols)          C:\ProgramData\Dbg\sym\ntdll.pdb\C7A9C64B64408F28DE6508D118DED28D1\ntdll.pdb
    
  • 가상 주소 없이 모듈들의 이름만 보고 싶다면 아래와 같이 1m 옵션을 사용하면 된다.

    0:000> lm 1m
    exercise_lecture
    VCRUNTIME140
    ucrtbase
    KERNELBASE
    KERNEL32
    ntdll
    
  • 혹은 lm a [Address]와 같이 사용하여 특정 주소가 어느 모듈에 속해 있는지알 수 있다.

    • 아래는 현재 실행 중인 명령어의 주소인 rip 값이 어느 모듈에 속해 있는지 확인하는 명령어이다.
    0:000> r rip
    rip=00007ffaae8c4789
    0:000> lm a 00007ffaae8c4789
    Browse full module list
    start             end                 module name
    00007ffa`ae7a0000 00007ffa`aea05000   ntdll      (pdb symbols)          C:\ProgramData\Dbg\sym\ntdll.pdb\C7A9C64B64408F28DE6508D118DED28D1\ntdll.pdb
    
  • rip 주소를 직접 입력하는 대신에 @rip 형식으로 작성할 수도 있다.

    • WinDbg에서는 레지스터 이름 앞에 “@”를 붙이는 것으로 레지스터의 값을 표현할 수 있다.
    0:000> lm a @rip
    Browse full module list
    start             end                 module name
    00007ffa`ae7a0000 00007ffa`aea05000   ntdll      (pdb symbols)          C:\ProgramData\Dbg\sym\ntdll.pdb\C7A9C64B64408F28DE6508D118DED28D1\ntdll.pdb
    

4. ?

  • ? 명령어는 인자로 전달된 표현식의 값을 계산해 출력하며, ? [Expression] 형태로 사용할 수 있다.
    • 이때 표현식은 기본적으로 MASM (Microsoft Macro Assembler) 표현식 문법을 따라야 한다.

MASM (Microsoft Macro Assembler) 표현식

MASM은 마이크로소프트의 x86 계열 아키텍처의 어셈블러이다. WinDbg에서는 이 MASM 표현식 문법을 사용하여 식을 표현하고 있다. 전반적으로 C, C++과 비슷하지만 레지스터와 메모리를 다루는 규칙이 다른 것이 특징이다.

기본적으로 WinDbg는 숫자를 16진수로 해석하며, “0x” 접두사(16진수), “0n” 접두사(10진수), “0t 접두사(8진수)” 또는 “0y 접두사(이진)”를 지정하여 다른 진법으로 숫자를 표현할 수 있다.

레지스터의 값을 표현할 때는 @rax와 같이 @<레지스터명>으로 표현하며, 특정 주소의 메모리 값을 표현할 때는 다음 연산자를 활용하여 표현한다.

  • by: 1byte
  • wo: 2byte
  • dwo: 4byte
  • qwo: 8byte
  • poi: 포인터 크기

예를 들어, rip가 가리키는 주소에서 2byte 값을 표현하기 위해서는 wo(@rip)와 같이 작성할 수 있다.

이외에는 대부분 C, C++과 비슷한 문법을 지니고 있으며 자세한 내용은 마이크로소프트 공식 문서에서 확인할 수 있다.

  • 예를 들어, ? 명령어를 아래와 같이 계산기 대용으로 사용할 수 있다.

    0:000> ? 1 + 2 * 3
    Evaluate expression: 7 = 00000000`00000007
    
  • 그 외에도 MASM 표현식 문법을 이용해 디버깅 작업 중 마주치는 여러 값들을 계산해 볼 수 있다.

    0:000> ? exercise_lecture!main + 0x20
    Evaluate expression: 140696467149648 = 00007ff6`72f21350
    

5. bl

  • blBreakpoint List의 약자로 설정한 중단점들의 목록을 출력하는 명령어이다.

    • 아래bl 명령어를 실행한 예시이다.
    image.png
  • 가장 왼쪽의 번호는 중단점의 ID로, 중단점을 식별하는 식별자 역할을 한다.

    • “Disable”과 “Clear”의 경우 클릭할 수 있으며, 클릭할 경우 해당 중단점을 일시적으로 비활성화(Disable) 하거나 삭제(Clear)할 수 있다.

6. bc

  • bcBreakpoint Clear의 약자로 중단점을 삭제하는 명령어이다.
    • bc [ID] 형태로 사용하며, 인자로 전달된 ID의 중단점을 삭제하게 된다.
    • 여러 개의 중단점을 동시에 삭제하고 싶다면 bc [ID1] [ID2] … 처럼 동시에 여러 ID를 인자로 전달하면 된다.

7. bd & be

  • bdBreakpoint Disable의 약자로 중단점을 비활성화하는 명령어이다.
    • bd [ID] 형태로 사용하며 인자로 전달한 ID를 가진 중단점을 비활성화 할 수 있다.
    • 이 경우 다시 활성화하기 전까지 프로세스가 중단점에 도달해도 중단되지 않는다.
  • 중단점을 비활성화하더라도 중단점 삭제와는 다르게 중단점 목록에 여전히 남아 있으며, Breakpoint Enable의 약자인 be 명령어를 이용해 비활성화된 중단점을 다시 활성화 할 수 있다.
    • bebd와 동일하게 be [ID] 형태로 사용한다.

8. k

  • k 명령어는 스택 백트레이스를 출력하는 명령어이다.
    • 스택 백트레이스란 현재 중단된 지점까지 어떤 함수들이 어떤 순서로 호출되어 왔는지를 스택 프레임을 따라 역순으로 나열한 정보이다.
image.png
  • 위 그림과 같이 함수가 호출 될 때마다 스택에는 함수의 반환 주소가 함께 적재되며, k 명령어를 수행할 시 스택에 적재되어 있는 함수들의 반환 주소들을 모아서 출력하게 된다.

    • 이 정보들을 통해 지금 실행하고 있는 지점까지 어떤 함수들이 어떤 순서로 호출되어 왔는지 한 눈에 확인할 수 있기에 디버깅 중 매우 유용하게 사용할 수 있다.
  • 아래ripexercise_lecture!gen_flag()에 위치한 상태에서 k 명령어를 수행한 예시이다.

    0:000> k
     # Child-SP          RetAddr               Call Site
    00 00000085`0054fc58 00007ff6`72f21370     exercise_lecture!gen_flag
    01 00000085`0054fc60 00007ff6`72f215e0     exercise_lecture!main+0x40
    02 (Inline Function) --------`--------     exercise_lecture!invoke_main+0x22
    03 00000085`0054fd20 00007ffa`ad4be8d7     exercise_lecture!__scrt_common_main_seh+0x10c
    04 00000085`0054fd60 00007ffa`ae7dc34c     KERNEL32!BaseThreadInitThunk+0x17
    05 00000085`0054fd90 00000000`00000000     ntdll!RtlUserThreadStart+0x2c
    
  • 해당 출력을 일부 해석해보면, 가장 윗 줄에 현재 실행 중인 함수인 exercise_lectrue!gen_flag()가 있는 상황이고, 이 함수의 반환 주소가 0x00007ff672f21370임을 알 수 있다.

    • 또한 Call Site 상으로 바로 아랫줄에 exercise_lecture!main()이 있는 것을 확인할 수 있으며 이를 통해 exercise_lecture!main()에서 exercise_lecture!gen_flag()를 호출했음을 알 수 있다.

meta 명령어

1. meta 명령어

  • 메타 명령어(meta command)란 WinDbg 명령어의 디버거 환경을 설정하는 명령어들과 조건 분기, 반복, 예외 처리, 흐름 제어 등을 가능하게 하는 명령어들을 종합적으로 지칭하며 대부분 .(dot)으로 시작한다.
    • 메타 명령어를 이용하면 복잡한 디버깅 작업을 하나의 스크립트로 자동화하거나, 루프와 분기를 이용한 세밀한 제어 흐름을 작성할 수 있다.

2. .cls

  • Clear Screen의 약어로 디버거 콘솔의 출력을 비운다.

    • 윈도우의 파워쉘이나 리눅스 셸 등에서 사용하는 clear 명령과 동일하다. 아래는 명령어를 수행하기 전과 후를 비교한 것이다.
  • Before .cls

    image.png
  • After .cls

    image.png

3. 프로세스 제어 명령어

  • .kill, .restart, .detach 세 가지 명령어는 디버깅 대상 프로세스의 실행 상태를 제어하기 위한 핵심 도구이다.
  • .kill 명령어는 디버거가 연결된 대상 프로세스를 즉시 종료할 때 사용한다.
    • 이 명령을 실행하면 WinDbg는 해당 프로세스에 중단 신호를 보내어 프로세스를 종료시킨다.
  • .restart 명령어는 현재 디버깅 중인 프로세스를 재시작한다.
    • 이 과정에서 중단점과 디버거 설정은 유지되므로 초기 상태로 돌아가 반복 검증하거나 수정 사항을 확인하는 데 유용하다.
  • .detach 명령어는 디버거의 제어만 해제하고 대상 프로세스는 그대로 실행되도록 할 때 사용한다.
    • 이 명령어는 프로세스를 중단하지 않고 디버거와의 연결만 해제하므로, 디버깅 작업을 마친 후 프로세스에 영향을 주지 않고 디버거만 종료하고자 할 때 사용할 수 있다.

4. 출력 명령어

  • .echo.printf는 WinDbg 스크립트 내에서 사용자 정의 메시지나 변수를 출력할 때 사용하는 명령어이다.

    • 이 둘은 비슷해 보이지만, 출력 형태와 유연성 면에서 차이를 보인다.
  • .echo 명령어는 단순한 문자열 출력에 최적화된 명령어로, 변수 치환이나 서식 지정 기능이 없다.

    • 고정된 텍스트를 그대로 콘솔에 출력할 때 사용하며, 스크립트 진행 중 로그 형태로 상태를 표시하거나 메시지를 구분할 때 유용하다.
    0:000> .echo hello world
    hello world
    
  • .printf 명령어는 C 언어의 printf()와 유사한 서식 지정 기능을 제공한다.

    • 문자열 내에%d, %x, %c 등 서식 문자를 사용하여 레지스터 값이나 메모리 내용을 포맷팅해 출력할 수 있으므로, 보다 정교한 디버깅에 적합하다.
    • 아래rax 레지스터의 값을 포맷팅해 출력하는 예시이다.
    0:000> .printf "RAX=0x%x\n", @rax
    RAX=0x2c
    

5. 로그 명령어

  • WinDbg에서는 메타 명령어를 이용해 디버거 콘솔의 출력을 로깅할 수 있다.

    • 먼저 .logopen은 명령어 실행 시점부터 디버거 콘솔의 모든 출력을 지정한 파일로 새로 생성해 기록한다.
    • 명령어의 형식은 아래와 같다.
    .logopen [FileName] 
    .logopen /d
    
  • 디버거 콘솔의 출력을 기록할 파일명을 인자로 전달할 수 있고, 인자로 파일명 대신 /d를 전달하면 파일명을 자동으로 지정해 .log 확장자 파일에 기록한다.

  • 만약 이미 존재하는 파일명을 인자로 전달할 경우 해당 파일을 덮어쓰게 되고, 덮어쓰지 않고 이어서 기록하기를 원한다면 .logappend를 사용해야 한다.

    • .logappend의 명령어 형식은 .logopen과 동일하다.
    • 만일 로깅을 마쳤다면 .logclose로 기록을 중단할 수 있다.
  • 아래와 같이 .logopen 명령어로 로그 파일을 생성하고 몇 가지 명령어를 실행한 뒤 .logclose 명령어로 로그 파일을 닫아 보겠다.

    0:000> .logopen C:\WinDbg\test.log
    Opened log file 'C:\WinDbg\test.log'
    0:000> db @rip L20
    00007ffe`d0b24419  cc eb 00 48 83 c4 38 c3-cc cc cc cc cc cc cc 48  ...H..8........H
    00007ffe`d0b24429  83 ec 28 65 48 8b 0c 25-60 00 00 00 33 d2 41 b8  ..(eH..%`...3.A.
    0:000> .logclose
    Closing open log file C:\WinDbg\test.log
    
  • 이후 생성된 로그 파일인 _test.log_를 열어 보면 아래와 같이 디버거 콘솔의 출력을 그대로 기록하고 있음을 알 수 있다.

    Opened log file 'C:\WinDbg\test.log'
    0:000> db @rip L20
    db @rip L20
    00007ffe`d0b24419  cc eb 00 48 83 c4 38 c3-cc cc cc cc cc cc cc 48  ...H..8........H
    00007ffe`d0b24429  83 ec 28 65 48 8b 0c 25-60 00 00 00 33 d2 41 b8  ..(eH..%`...3.A.
    0:000> .logclose
    .logclose
    Closing open log file C:\WinDbg\test.log
    

6. 흐름 제어 명령어

  • WinDbg에서는 특정 조건을 만족하는 경우에만 명령어를 실행하거나 반복문으로 명령어를 반복 실행할 수 있는 문법을 지원한다.
    • 이를 잘 활용하면 WinDbg 명령어들을 스크립트처럼 작성하여 디버깅을 자동화할 수 있다.

6.1 if 조건문

  • if문과 else문은 아래와 같이 구현할 수 있다.

    .if ( Condition ) { Command }
    .if ( Condition ) { Command } .else { Command } 
    .if ( Condition ) { Command } .elsif ( Condition ) { Command }
    .if ( Condition ) { Command } .elsif ( Condition ) { Command } .else { Command }
    
  • 위 문법을 이용하면 특정 조건에 따라 WinDbg 명령어를 실행할 수 있다.

    • 이때 Condition은 기본적으로 MASM 표현식 문법을 따르게 된다.
    • 예를 들어 rax의 값이 0이 되는 경우에만 “RAX=0”을 출력하고 싶은 경우 다음과 같이 명령어를 작성할 수 있다.
    .if (@rax==0) {.echo RAX=0}
    
  • 아래와 같이 WinDbg에서 실행해보면 rax 값에 따라서 .echo 명령어의 실행 여부가 결정됨을 확인할 수 있다.

    0:000> r rax
    rax=0000000000000000
    0:000> .if (@rax==0) {.echo RAX=0}
    RAX=0
    0:000> r rax=1
    0:000> r rax
    rax=0000000000000001
    0:000> .if (@rax==0) {.echo RAX=0}
    

6.2 while 반복문

.while ( Condition ) { Command }
  • while 반복문은 조건문을 만족하는 동안 중괄호 안의 명령어를 반복해 실행한다.

    • 예를 들어 중단점을 설정한 뒤 중단점에서 값이 0일 때만 중단하고 싶은 경우 다음과 같이 작성할 수 있다.
    .while (@rax!=0) { g }
    
  • 명령어를 실행하면 중단점에 도달했을 때의 rax 값이 0이 아닌 경우 g 명령어가 반복적으로 실행되며, rax 값이 0일 때에만 프로그램이 중단된다.

6.3 for 반복문

.for (InitialCommand ; Condition ; IncrementCommands) { Command }
  • WinDbg에서는 .for를 이용해 C와 유사하게 반복문을 작성할 수 있다.

    • 예를 들어, 반복문을 돌면서 반복 횟수를 출력하는 명령어를 아래와 같이 작성할 수 있다.
    0:000> .for (r $t0 = 0; @$t0 < a; r $t0 = @$t0 + 1) { .printf "Loop index = %d\n", @$t0 }
    Loop index = 0
    Loop index = 1
    Loop index = 2
    Loop index = 3
    Loop index = 4
    Loop index = 5
    Loop index = 6
    Loop index = 7
    Loop index = 8
    Loop index = 9
    

$t0은 무엇인가요?

$t0은 WinDbg에서 제공하는 의사 레지스터(pseudo-register)의 일종으로써 그 중 $t0, $t1, … $t19까지 20개의 의사 레지스터는 사용자가 변수처럼 정의하여 사용할 수 있도록 제공하는 사용자 정의 의사 레지스터이다. 의사 레지스터에는 일반 레지스터와 마찬가지로 r 명령어를 사용할 수 있으며, 레지스터명 앞에 “@”를 붙여 @$t0과 같은 형식으로 의사 레지스터의 값을 표현할 수 있다. 위 반복문과 같이 반복문 변수를 선언하는 목적으로 의사 레지스터를 사용할 수 있다.

WinDbg는 사용자 정의 의사 레지스터 외에도 여러 의사 레지스터들을 정의하고 있으며, 자세한 내용은 마이크로소프트 공식 문서에서 확인할 수 있다.


중단점 명령어

1. Breakpoint

  • WinDbg의 중단점은 단순히 중단점을 설정하는 것에 더해 여러 가지 옵션을 제공한다.

    • 예를 들어, 중단점에 도달했을 때 특정 명령어를 실행하거나 중단점이 걸리는 조건을 추가할 수 있다.
    • 이런 다양한 옵션들을 제공하기 위해, 중단점 명령어는 다음의 형식을 가지고 있다.
    bp[ID] [Options] [Address [Passes]] ["CommandString"]
    

1.1 Address

  • [Address]는 중단점 명령어에서 유일하게 필수적으로 전달되야 하는 인자로 중단점을 설정할 지점을 의미한다.

    • 주소를 직접 숫자로 표현하여 작성하여도 되지만, 심볼을 이용하거나 모듈명과 상대 가상 주소를 이용하여 주소를 직접 계산하지 않고 중단점을 설정할 주소를 표현할 수 있다. 아래는 그 예시이다.
    # 숫자로 표현하여 중단점 설정
    bp 00007ff6`936b1330
    
    # 심볼 이용하여 중단점 설정
    bp exercise_lecture!main
    
    # 상대 가상 주소 이용하여 중단점 설정
    bp exercise_lecture+0x1330
    

1.2 ID

  • ID는 중단점에 대한 식별자를 의미하며 10진수 숫자로 표현된다.
    • 중단점을 설정할 때 ID를 별도 지정하지 않으면 WinDbg가 임의로 식별자를 부여하게 된다.

1.3 Options

  • Options는 WinDbg의 중단점 기능을 한 단계 업그레이드하는 기능이다.
    • WinDbg는 중단점에 대한 여러 가지 옵션을 제공하며, “/”로 시작하는 옵션 명령어들로 옵션을 지정할 수 있다.
    • 별도의 설명이 없으면 두 개 이상의 옵션을 동시에 지정할 수 있다.
옵션기능
/1한 번 중단된 이후 삭제되는 일회성 중단점을 설정한다.
/w "식"인자로 전달된 조건을 만족하는 경우만 중단되도록 한다.
/c N함수 호출 스택의 깊이가 N보다 작아지는 경우 중단되도록 한다. /C 옵션과 동시에 사용할 수 없다.
/C N함수 호출 스택의 깊이가 N보다 커지는 경우 중단되도록 한다. /c 옵션과 동시에 사용할 수 없다.
  • 아래/w 옵션을 이용해 조건부 중단점을 설정하는 예시이다.

    • eax 레지스터의 값이 8이 될 때 중단점이 설정되도록 한다.
    0:000> bp /w "@eax == 8" exercise_lecture+0x1275
    0:000> g
    Breakpoint 0 hit
    exercise_lecture!gen_flag+0xb5:
    00007ff6`d0e71275 ffc0            inc     eax
    0:000> r eax
    eax=8
    
  • 중단점이 걸린 이후 eax 레지스터 값이 8인 모습을 확인할 수 있다. 8이 아닌 경우 중단되지 않고 그대로 실행된다.

1.4 Passes

  • 중단점에서 중단하지 않고 건너뛸 횟수를 지정한다.
    • 예를 들어 bp [Module]![Symbol] 5로 중단점을 설정한 경우 중단점에 4번째 도달하는 시점까지는 프로세스가 중단되지 않고 실행되다가, 5번째 도달했을 때 비로소 프로세스가 중단된다.

1.5 CommandString

  • 중단점에 도달했을 때 특정 WinDbg 명령어를 실행하고 싶을 경우 사용한다.

    • 주로 중단점에 도달했을 때 .echo 등의 명령어를 활용하여 자동으로 프로세스 상태를 출력하게 하거나 함수가 실행될 때 함수의 인자 및 반환값을 출력하는 등 다양한 목적으로 사용할 수 있다.
    • 여러 명령어를 실행하고 싶은 경우 세미콜론(;)으로 각 명령어를 구분하여 넣어줄 수 있다.
    • 아래는 반복문 내부에 중단점을 설정하면서 중단점에 도달할 때마다 “BREAKPOINT!”를 출력함과 동시에 g로 실행을 재개하는 명령어를 추가한 예시이다.
    0:000> bp exercise_lecture+0x1275 ".echo BREAKPOINT!; g"
    0:000> g
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    BREAKPOINT!
    
  • 위와 같이 g 명령어를 활용하면 중단점에서 프로세스가 중단되었을 시 수행할 디버깅 작업을 자동화하는 효과도 얻을 수 있다.

2. Access Breakpoint

  • WinDbg에서는 단순히 특정 명령어가 실행될 때 중단점을 설정하는 것 이외에도 특정 메모리에 접근할 때 중단 되도록 설정하는 것이 가능하다.

    • 이를 Access Breakpoint라고 하며, 읽기, 쓰기, 실행의 접근에 대해 중단되도록 설정할 수 있다.
    • ba 명령어를 통해 중단점을 설정할 수 있으며 명령어의 형식은 아래와 같다.
    ba[ID] [Access] [Size] [Options] [Address [Passes]] ["CommandString"]
    
  • [Access][Size]를 제외한 나머지 인자들의 사용법은 bp 명령어와 동일하다.

2.1 Access

  • 읽기, 쓰기, 실행 중 어느 접근에 대해 중단할 지 설정할 수 있다.
    • [Access]의 경우 다음 중 하나의 옵션을 필수로 사용해야 한다.
옵션기능
eCPU가 특정 주소의 명령어를 실행할 때 중단된다.
rCPU가 특정 주소의 메모리를 읽거나 쓸 때 중단된다.
wCPU가 특정 주소의 메모리에 쓸 때 중단된다.

2.2 Size

  • 64비트 운영체제에서는 1, 2, 4, 8만 [Size] 인자로 허용한다.

    • 다만, [Access]e인 경우 크기는 1이어야 한다.
  • 아래는 현재 rcx 레지스터에 담긴 주소의 4 바이트 메모리에 읽거나 쓰기 작업이 일어날 경우 중단되도록 하는 명령어 예시이다.

    0:000> ba r 4 @rcx
    0:000> g
    Breakpoint 1 hit
    exercise_lecture!gen_flag+0x125:
    00007ff6`72f212e5 488b442408      mov     rax,qword ptr [rsp+8] ss:000000c8`0852fa78=000000c80852fae0
    
  • 실행하면 와 같이 중단된 지점과 함께 중단된 지점의 명령어를 확인할 수 있다.

  • ba 명령어는 특정 메모리에 읽기 또는 쓰기 접근이 언제 일어나는지 추적하는 용도로 많이 사용된다.

    • 예를 들어, 랜섬웨어 등 암호화 루틴이 포함된 바이너리를 분석하는 경우 평문 메모리 버퍼를 찾은 뒤 해당 메모리에 읽기 중단점을 걸고 실행하면 평문 버퍼에 접근하는 지점을 쉽게 찾을 수 있으며, 이 지점이 곧 암호화 루틴의 시작점이 될 것이다.
    • 그 외에도 버퍼 오버플로가 일어나는지 찾아보거나, 구조체의 멤버 변수에 중단점을 설정하여 값이 바뀌는 시점을 추적하는 등 특정 상황들에서 매우 효과적인 디버깅이 가능하도록 하는 유용한 기능이다.

WinDbg 스크립트

1. WinDbg 스크립트

  • WinDbg 명령어를 작성하다 보면 길어지는 경우가 매우 많다.
    • 특히 .if.for 등의 조건 분기문을 작성하는 경우 개행 없이 명령어를 작성하는 것은 상당히 불편하며 복잡한 작업을 수행하는 데 큰 걸림돌이 되기 마련이다.
    • WinDbg는 이를 위해 복잡한 명령어나 여러 가지 명령어를 한 번에 실행하기 쉽도록 디버거 명령어 스크립트를 지원하고 있다.

1.1 $$><

  • 스크립트 파일을 작성한 뒤 아래와 같이 $$>< 명령어를 이용해 스크립트를 실행할 수 있다.

    $$>< [ScriptFile]
    
  • 이 명령어는 ScriptFile의 개행을 모두 세미콜론(;)으로 변환하여 실행하는 단순한 원리로 동작한다.

  • 기존에 .for (r $t0 = 0; @$t0 < a; r $t0 = @$t0 + 1) { .printf "Loop index = %d\n", @$t0 }와 같은 형태로 작성된 .for 명령문의 경우 아래와 같이 스크립트 파일로 작성할 수 있다.

    .for (r $t0 = 0; @$t0 < a; r $t0 = @$t0 + 1) {
      .printf "Loop index = %d\n", @$t0
      .printf "Scripting..\n"
    }
    
  • 이를 저장하고 WinDbg에서 실행하면 위 스크립트는 .for (r $t0 = 0; @$t0 < a; r $t0 = @$t0 + 1) { ; .printf "Loop index = %d\n", @$t0; .printf "Scripting..\n"; }와 같이 변환되어 실행된다.

    • 실행한 결과는 아래와 같다.
    0:000> $$>< C:\WinDbg\script.dbg
    Loop index = 0
    Scripting..
    Loop index = 1
    Scripting..
    Loop index = 2
    Scripting..
    Loop index = 3
    Scripting..
    Loop index = 4
    Scripting..
    Loop index = 5
    Scripting..
    Loop index = 6
    Scripting..
    Loop index = 7
    Scripting..
    Loop index = 8
    Scripting..
    Loop index = 9
    Scripting..
    

Cheat Sheet

1. Built-in 명령어

명령어기능
bp중단점을 설정한다. 옵션과 명령어 등을 조합하면 다양하게 중단점을 활용할 수 있다.
ba특정 메모리에 Read, Write, Execute 세 종류의 접근이 일어날 때 중단되도록 설정할 수 있다.
g프로세스의 실행을 재개한다.
bl중단점의 목록을 출력한다.
bc특정 중단점을 삭제한다.
bd & be특정 중단점을 비활성화하거나 활성화한다.
pStep Over를 수행한다.
tStep Into를 수행한다.
guStep Out을 수행한다.
r레지스터 값을 확인하거나 변경한다.
`d{ab
`e{ab
Enter이전 명령어 창의 명령어를 그대로 실행한다.
lm메모리에 로드된 모듈들의 목록을 출력한다.
?MASM 표현식을 계산하여 결과를 출력한다.
k함수 호출 스택을 출력한다.
$$><WinDbg 명령어 스크립트의 줄바꿈을 모두 세미콜론(;)으로 변환하여 실행한다.

2. meta 명령어

명령어기능
.cls명령어 창의 출력을 비워준다.
.kill현재 디버깅 중인 프로세스를 종료한다.
.restart현재 디버깅 중인 프로세스를 재시작한다. 프로세스만 재시작하는 명령어로 디버거에서 설정한 중단점은 유지된다.
.detach대상 프로세스와 디버거의 연결만 끊고, 프로세스는 실행을 재개하도록 한다.
.echo인자로 전달된 문자열을 그대로 출력한다.
.printfC에서의 포맷 스트링과 동일한 기능으로 전달된 포멧 스트링과 추가 인자들에 맞게 출력한다.
.logopen로그 파일을 생성하여 디버거 명령어 창의 모든 출력을 기록한다.
.logappend기존 로그 파일에 덧붙여서 디버거 명령어 창의 모든 출력을 기록한다.
.logclose.logopen 혹은 .logappend의 기록을 중단한다.
.if & .elsif & .else조건에 맞게 WinDbg 명령어를 실행한다.
.forC 형태의 for 반복문이다.
.while조건을 만족하는 동안 반복하는 반복문이다.

마치며

  • 이번 강의에서는 WinDbg가 가지고 있는 유용한 기능들을 소개하였다.
    • 이번 강의에서 소개한 기능들 외에도 WinDbg에는 수많은 기능들이 구현되어 있으며 이러한 기능들 덕분에 WinDbg는 윈도우 생태계에서 가장 널리 사용되는 디버거로서 자리매김하고 있다.
  • 디버깅은 리버스 엔지니어링 및 익스플로잇에 있어서 매우 강력한 무기지만 사용 역량을 갖추기 위해서는 실제 바이너리들을 직접 디버깅해보면서 학습한 명령어들과 기능들을 실제로 적용해 보아야 한다.
  • WinDbg의 공식 문서와 강의 내용을 곁에 두며 WinDbg 기능들을 실제로 활용하는 경험을 쌓아가다 보면 어느새 WinDbg를 능숙하게 활용할 수 있을 것이다.

Loading comments...