0. 개요

.... 0.1 Windbg (Cdb)

.... 0.2 Gdb

1. Control

.... 1.1 Session

........ 1.1.1 Restart

........ 1.1.2 Quit

.... 1.2 Basic

........ 1.2.1 Trace / Step

........ 1.2.2 Go

.... 1.3 ETC

........ 1.3.1 Symbols

2. Facility

.... 2.1 BreakPoint

........ 2.1.1 BP 기본 명령어

........ 2.1.2 BP 활용

.... 2.2 Memory Assemble / Edit

.... 2.3 Memory Fill / Copy / Compare

.... 2.4 Search

........ 2.4.1 Search memory

........ 2.4.2 Search for disassembly pattern

.... 2.5 Checkpoint

.... 2.6 Reverse Debugging

.... 2.7 Inferior

.... 2.8 Thread

.... 2.9 Signal

.... 2.10 Catch

.... 2.11 Event

.... 2.12 ETC

........ 2.12.1 Help

........ 2.12.2 Clear

........ 2.12.3 Loop

........ 2.12.4 Log

........ 2.12.5 ASLR

........ 2.12.6 Stop-on-solib-events

........ 2.12.7 Alias

........ 2.12.8 Define

........ 2.12.9 Etc

3. Data

.... 3.1 Unassemble

.... 3.2 Register

.... 3.3 Memory

........ 3.3.1 Display Memory

........ 3.3.2 Display words and symbols

........ 3.3.3 Display Referenced Memory

........ 3.3.4 Display Type

........ 3.3.5 Display Debugger Object Model Expression

.... 3.4 Call Stack

.... 3.5 ETC

........ 3.5.1 의사 레지스터

........ 3.5.2 Etc





0. 개요

  Windbg(또는 cdb 및 kd)나 Gdb를 다루는 대부분의 자료들은 커널 모드 디버깅이나 소스 코드 및 디버깅 정보 파일을 통해 디버깅을 하는 내용을 담고 있다. Gdb의 경우는 리눅스 플랫폼 자체가 오픈 소스가 많은 면이 있기 때문인 것으로 보이는데 물론 유저 모드 디버깅과 관련된 자료들 및 PEDA나 GEF 또는 evan's debugger 같은 Gdb의 불편한 인터페이스를 보완해주는 툴들도 존재한다. Windbg의 경우에는 Ollydbg의 영향이 매우 크다고 여겨지며 Ollydbg가 아직 지원하지 않는 64비트 리버싱의 경우에도 x64dbg 같은 디버거들의 개발로 인해 Windbg가 유저 모드 리버싱에서는 거의 사용되지 않는 것으로 보인다.

  Cdb는 CLI 형태의 유저 모드 디버거이며 Kd는 CLI 형태의 커널 디버거이다. Windbg는 Cdb 및 Kd를 통합함과 동시에 간단한 GUI 형태를 제공한다. Gdb는 유저 모드와 커널 모드를 모두 제공하면서 CLI 형태를 갖는다. 이 두 디버거의 공통점으로 윈도우 및 리눅스의 업그레이드와 함께 발전되며 많은 기능을 지원하는 장점이 있는 반면 두 디버거 모두 사용하기 매우 불편하다는 단점도 갖는다. 그렇기 때문에 특히 윈도우의 경우 x86 유저 모드 디버거 중에서 매우 사용자 친화적인 Ollydbg가 대표적으로 많이 쓰이고 있다.

  이 문서에서는 유저 모드 디버깅 즉 유저 모드 악성코드 분석을 위한 리버싱 용도로 Windbg(어떻게 보면 단지 Cdb)와 Gdb의 명령어를 정리하기로 한다. 유저 모드 디버거에서 이 디버거들보다 훨씬 사용자 친화적인 디버거 및 확장이 존재함에도 불구하고 굳이 이 문서를 만드는 이유는 관련 자료들이 많지 않아보이기 때문이다. 마지막으로 본문에서 프롬프트가 >로 시작하는 라인은 Windbg의 명령어를 의미하며 (gdb)로 시작하는 라인은 Gdb의 명령어를 의미한다. 그리고 명령어 옆에 괄호가 있는 경우에는 명령어의 약자를 의미한다.



0.1 Windbg (Cdb)

  Windbg에서 일반적으로 사용되는 명령은 "일반 명령"으로서 디버거 자체에 내장된 명령이다. 이 외에도 "메타 명령"이라고 "."으로 시작하는 명령 및 "!"로 시작하는 "확장 명령"도 존재한다. 

  참고 사항으로서 심볼 설정이 있는데 조금 자세히 설명해 보겠다. Ollydbg에서는 이 설정을 굳이 자세히 하지는 않았지만 이 디버거도 자체적으로 심볼 형태를 제공해 주기 때문에 API 함수의 이름이나 파라미터 이름 등의 정보를 보여줌으로써 우리가 쉽게 리버싱을 하도록 도와주었다. Windbg의 경우 심볼 설정을 해주지 않으면 앞에서 설명한 디버깅을 도와주는 정보를 하나도 얻을 수 없다. 이러한 디버깅 정보는 pdb 형태로 제공된다.

  pdb 파일 즉 심볼 파일은 우리가 직접 프로그램을 개발할 경우에도 디폴트로 만들어지는데 이 pdb 파일이 존재한다면 우리는 디버깅 시에 어셈블리 명령어에 상응하는 소스 코드를 보면서 디버깅을 할 수 있을 정도로 많은 정보가 들어있다. 물론 이것은 우리가 직접 개발하였고 심볼 파일을 생성한 후에 디버거로 읽어들인 경우에 해당하며 악성코드의 경우에는 심볼 파일과 같이 배포될 일이 없기 때문에 이런 기대를 할 수 없다.

  여기서 우리가 심볼 설정을 통해서 얻는 pdb 파일은 ntdll.dll이나 kernel32.dll 같은 시스템 DLL 파일들에 대한 정보이다. pdb 파일도 public 및 private 심볼로 나뉘는데 private 심볼의 경우 위에서 말한 소스 코드 등의 정보까지 많은 내용의 정보가 들어있으며 용량도 상당히 크다. 하지만 MS가 소스 코드를 공개할 일이 없을 것이며 이에 따라 제공되는 심볼 파일은 public 심볼인데 이것은 상당히 제한된 내용만 들어있다. 물론 없는것 보다는 훨씬 낫기 때문에 이렇게 심볼 설정을 하는 것이다. 여기서 얻을 수 있는 정보는 소스 코드는 아니더라도 API 함수의 이름은 얻을 수 있다.

  심볼 관련 설정은 두 가지로 나뉘는데 하나는 MS가 제공하는 모든 심볼 파일(public)들을 다운로드 받아서 특정 디렉토리에 두고 그 디렉토리의 경로를 설정하는 것이 있다. 다른 하나는 MS에서 제공하는 심볼 다운로드 링크 및 내 컴퓨터에 다운로드할 경로를 적어서 디버깅 시에 필요한 심볼 파일만(분석 도중 kernel32.dll로 진입했을 때 이 심볼 파일을 다운로드 받는 형태로) 다운로드 받을 수도 있다. 

[ SRV*c:\symbols*http://msdl.microsoft.com/download/symbols ]

  Windbg의 File -> Symbol File Path에 위와 같은 값을 입력하면 MS가 제공하는 심볼 서버에서 필요할 때마다 해당 심볼 파일을 c:\symbols 디렉토리에 다운로드 받아서 저장해 준다. 물론 미리 심볼 파일들 전체를 다운로드 받은 경우에는 디렉토리 경로만 적어주어도 된다. cdb를 사용하거나 커맨드 명령어를 통해 심볼 관련 설정을 하는 방식은 아래에서 명령어를 소개할 때 설명하도록 하겠다.

  실행 중인 프로세스를 어태치(Attach)하기 위해서는 cdb 실행 시 인자로 -p와 pid를 주면 된다.



0.2 Gdb

  Gdb로 디버깅하는 경우 특정 주소(일반적으로 EP)에 BP를 걸고 run 명령어를 사용할 수도 있지만 보통은 start 명령어를 이용한다.

(gdb) start

  이 명령어는 main()에 temporary breakpoint를 걸고 실행을 시작한다. 일반적으로 디버깅을 시작할 때 가장 먼저 사용하는 명령어로서 이 명령어를 통해 EP로 이동하여 디버깅을 시작할 수 있다.

  또 다른 참고 사항으로 디버기를 인자와 함께 실행시키는 경우에는 다음과 같이 사용한다. aaa는 실행 파일 이름이고 arg1과 arg2는 인자들로서 --args 옵션과 함께 사용한다.

$ gdb --args aaa arg1 arg2

  실행 중인 프로세스를 어태치(Attach)하기 위해서는 attach 명령어에 인자로 pid를 주면 된다.

(gdb) attach [pid]







1. Control

1.1 Session

1.1.1 Restart

> .restart

(gdb) run

  디버깅을 재시작한다.


1.1.2 Quit

> q

(gdb) quit    /    q

  디버깅을 종료한다.





1.2 Basic

1.2.1 Trace / Step

> t

(gdb) stepi    /    si

  Trace. Ollydbg의 Step Into와 같다.


> p

(gdb) nexti    /    ni

  Step. Ollydbg의 Step Over와 같다.


> wt [WatchOptions] [= StartAddress] [EndAddress]

> wt 0040107f

  Trace and watch data. EndAddress까지 실행하면서 여러가지 통계 정보를 보여준다. Start Address를 지정하지 않을 경우 현재 EIP를 기준으로 한다. 보여주는 통계 정보들로는 어떤 함수들이 호출되었는지와 그 횟수, 몇 개의 명령어들이 실행되었는지 그리고 몇 개의 시스템 호출이 실행되었는지 등이 있다.


> ta 0x<addr>    /    pa 0x<addr>

> ta 00401020

> pa 00401020

  Trace / Step to address. Ollydbg에서 F4 키와 같다고 할 수 있다. 즉 해당 주소까지 진행한다. ta의 경우에는 trace 방식을, pa의 경우에는 step 방식을 통해 진행한다.


> tc    /    pc

  Trace / Step to next call. 다음 call 명령어가 올 때까지 진행한다.


> tt    /    pt

  Trace / Step to next return. 다음 ret 명령어가 올 때까지 진행한다.


> tct    /    pct

  Trace / Step to next call or return. 다음 call 또는 ret 명령어가 올 때까지 진행한다.


> th    /    ph

  Trace / Step to next branching instruction. 다음 분기 명령어가 올 때까지 진행한다. 분기 명령어로는 call이나 ret 뿐만 아니라 jump 명령어도 포함된다.



1.2.2 Go

> g

  Go. 디버기 애플리케이션을 실행시키는 명령어로서 올리디버거의 F9와 같다. 일반적으로 BP를 걸어놓고 실행해야하며 그렇지 않는 경우에는 끝까지 실행되서 종료되어 버린다.

(gdb) continue    /    c

  Go. Windbg의 g 명령어와 같이 다음 BP를 만날때 까지 계속 진행한다.


> gc

  Go from conditional breakpoint. Conditional BreakPoint와 관련해서 사용되는 명령어이다. 이것은 뒤에 "2.1.2 BP 활용" 절에서 자세히 설명한다.


> gu

  Go up. 현재 함수가 반환할 때 까지 실행. 즉 현재 함수가 끝나는 ret까지 실행해서 caller로 복귀한다. 실수로 step over할 함수 내부로 step into한 경우에 이 명령을 사용해 간단하게 복귀할 수 있다.

(gdb) finish

(gdb) return

  현재 스택 프레임을 종료한다. 즉 현재 스택 프레임(함수)이 return할 때까지 실행하여 caller로 복귀한다. Windbg의 gu 명령어와 같다.


> gh

  Go with Exception Handled. 이것은 프로그램의 예외 핸들러를 호출하지 않고 직접 수동으로 예외 상황을 고칠 때 사용한다. 즉 예외가 발생되는 명령어를 실행해서 first chance 예외가 발생한 경우에 해당 명령어를 고치고 난 후에 다시 재개할 때 사용한다. first chance 예외 발생 시 고치지 않고 gh 명령어를 사용하면 계속 다시 first chance 예외가 발생한다.


> gn

  Go with Exception not Handled. 예외 발생 시 디버거가 받은 제어를 프로그램의 예외 핸들러가 처리하도록 프로그램에게 넘긴다. Ollydbg에서 Shift+F7 같이 다시 프로그램에게 넘겨주는 것이다.

  예를들어 보자면 발생할 예외의 핸들러에 미리 bp를 걸고 g 하면 first chance 예외가 발생하게 된다. 이 때 다시 g 하면 핸들러에 도착해서 bp에 걸린다. 하지만 애초에 gn을 하면 first chance 예외에 걸리지 않고 바로 핸들러까지 가서 이곳에서 bp에 걸린다.


* First Chance Exception / Second Chance Exception

  디버거의 구조 상 프로그램에서 발생한 이벤트를 받게 된다. 만약 예외가 발생한다면 당연히 디버거가 예외를 받게 된다. 이 때 first chance 예외가 발생한다. First chance 예외 발생 이후 프로그램에 예외 핸들러가 존재한다면 예외를 프로그램에게 넘길 수 있고(gn) 그렇지 않다면 디버거로 예외가 발생한 부분을 직접 수정해서 예외가 발생하지 않게 만든 이후 다시 실행을 재개(gh)할 수 있다. 어쨌든 프로그램에서 적절한 핸들러가 없어서 예외를 제대로 처리하지 못하면 다시 예외가 발생하게 되고 디버거는 이러한 unhandled 예외를 다시 잡게 되는데 이것을 second chance 예외라고 한다. 만약 디버거가 존재하지 않는다면 프로그램은 crash할 것이다.


* 예외 분석

  예를들어서 분석 시에 SEH를 이용한 안티 디버깅을 만났다고 가정해보자. 이 경우에 보통 SEH 핸들러의 주소에 BP를 걸고 실행할 것이다. 이 때 예외를 일으키는 명령어를 만나면 first chance 예외가 발생한다. 그리고 다시 g나 gn 명령어를 실행하면 BP에 도착할 것이다. 물론 gh 명령어를 사용한다면 계속 first chance 예외가 발생할 것이다.





1.3 Etc

1.3.1 Symbols

> .symfix c:\symbols

  심볼이 존재하는 디렉터리 경로를 지정한다.


> .reload

  경로 지정 후 심볼을 다시 로드한다.







2. Facility

2.1 BreakPoint

2.1.1 BP 기본 명령어

> bp 00407725

  Breakpoint. 0x00407725 주소에 BreakPoint를 건다.

> bp $exentry

  EP에 브레이크포인트를 설정할 때 사용한다. Windbg의 경우 디폴트 옵션에서는 처음 바이너리를 불러오면 ntdll.dll의 LdrInitializeThunk()에 BP가 걸리기 때문에 이 명령어를 통해 EP에 BP를 걸고 실행함으로써 간단하게 EP로 바로 이동할 수 있다. 

  참고로 Ollydbg의 경우에는 옵션에서 EP에 BP를 걸게 해주는 옵션이 있고 이것이 디폴트 옵션이라서 바이너리를 불러오면 자동으로 EIP가 EP에 위치하고 있기 때문에 처음 Windbg를 사용할 때 익숙치 않아서 당황할 수 있다. 

  Windbg로 바이너리를 불러오자 마자 이 명령어를 사용하면 아마 에러가 뜰 것이다. 그렇기 때문에 이 명령어는 한 번 실행 후 프롬프트가 돌아올 때 까지 약간 기다린 후에 한번 더 입력한다. 그리고 g 명령어를 사용하면 EP에 위치하는 것을 볼 수 있다.


(gdb) break *0x407725    /    b *0x407725

  Set breakpoint.


(gdb) tbreak

  Temporary breakpoint. 한 번 사용되면 자동으로 삭제되는 BP이다.


(gdb) hbreak

  Hardware breakpoint. Windbg는 하드웨어 BP라는 이름을 갖는 기능은 없다. 대신 ba를 이용하는데 뒤에서 ba 항목을 정리한다.


(gdb) thbreak

  Temporary hardware breakpoint.


> bl

(gdb) info break

  Breakpoint list. BP들의 목록을 볼 수 있다. 뒤의 명령어를 보면 알겠지만 목록에 나온 BP의 번호를 가지고 삭제하거나 활성화/비활성화를 할 수 있다.


> bc 0

(gdb) delete 0    /    d 0 

  Breakpoint clear. 0번 bp를 삭제한다.


> be 1

(gdb) enable 1

  Breakpoint enable. 1번 bp를 활성화한다.


> bd 2

(gdb) disable 2

  Breakpoint disable. 2번 bp를 비활성화한다.


> ba r4 00407725

  Break on access. 메모리 BP로서 해당 BP가 걸린 주소를 읽거나 쓰거나 실행할 경우에 발동한다. windbg의 경우 이것을 통해 하드웨어 BP와 같은 기능을 제공한다. 옵션에서 r은 read를 의미하며 w는 write, e는 execute이다. 뒤의 숫자는 BP가 걸리는 메모리의 크기로서 dword 즉 4바이트의 메모리에 BP를 걸려면 4를 입력한다. 참고로 e(execute) 옵션에서는 반드시 1바이트여야 한다. 그리고 메모리의 boundary를 맞추어야 하는데 예를들면 0x00407740의 주소에는 4바이트를 지정할 수 있지만 0x00407742의 경우에는 2바이트만 지정할 수 있다. 다시 0x00407744는 4바이트를 지정할 수 있고 0x00407746은 2바이트를 지정할 수 있다. 물론 1바이트는 아무 곳이나 지정할 수 있다.


(gdb) watch *0x00407725

(gdb) rwatch *0x00407725

(gdb) awatch *0x00407725

(gdb) info watch


  Watchpoint. Gdb에서는 메모리 BP를 watchpoint라는 개념을 통해 사용한다. 그래서 목록 확인도 info breakpoint 대신 info watch를 사용한다. 그냥 watch의 경우에는 write, rwatch는 read 그리고 awatch는 read/write를 의미한다. 



2.1.2 BP 활용

- windbg

  Conditional breakpoint. 예를들어 0x00401020에서 ecx 레지스터의 값이 2인 경우에 BP가 걸리게 하는 방식은 다음과 같다. 참고로 조건부 BP 명령어에는 j 명령어도 있지만 여기에서는 .if ~ .else만 고려하기로 한다.

> bp 00401020 ".if (@ecx == 0x2) {} .else {gc}"

  여기서 g 대신 gc를 사용하는데 이것은 만약 조건이 맞지 않는 경우에 g를 사용한다면 끝까지 실행되어 버리기 때문이다. 이것은 p 같은 명령어로 한 줄씩 트레이싱 하고 있는 경우에도 해당된다. 대신 gc를 사용한다면 조건이 맞지 않아도 다음 줄에서 브레이크가 걸린다. 즉 gc는 이러한 조건부 BP에서만 사용된다. 만약 bp가 걸린 경우에도 특정 명령어를 수행하고 싶다면 아래와 같이 사용할 수도 있다.

> bp 00401020 ".if (@ecx == 0x2) {lm ; u} .else {gc}"

  이 경우에는 조건 bp가 걸리면 lm 명령어와 u 명령어가 자동으로 실행된다. 물론 else 구문에도 명령어를 실행시킬 수 있다.


- gdb

  Conditional breakpoint. 예를들어 0x08043214에서 eax 레지스터의 값이 0인 경우에 BP가 걸리게 하는 방식은 다음과 같다.

(gdb) break *0x08043214 if $eax == 0

또는 다음과 같이 사용할 수도 있다.

(gdb) break *0x08043214

(gdb) info b

2 breakpoint ...

(gdb) condition 2 $eax == 0

  commands 명령어는 BP가 걸릴 때 실행시킬 명령어를 지정해준다.


----------------------------------------------------------------

(gdb) break *0x40081d

Breakpoint 2 at 0x40081d

(gdb) commands 2

Type commands for breakpoint(s) 2, one per line.

End with a line saying just "end".

>pwd

>end

(gdb) c

Continuing.

Breakpoint 2, 0x000000000040081d in main (0

Working directory /root/hw

----------------------------------------------------------------


  위와 같이 BP 2번 명령어를 인자로 넣고 commands 명령어를 실행하면 실행시킬 명령어를 받을 수 있다. 여기서는 pwd 명령어를 넣었다. 이후 실행시킬 명령어를 다 썼으면 end를 입력하여 끝낸다. 이제 continue 명령어를 입력하면 실행되다가 BP에 걸리고 동시에 pwd 명령어가 실행된 결과를 볼 수 있다.





2.2 Memory Assemble / Edit

> a 00407725

  Assemble. 명령어 실행 즉 Enter 후에 새로운 명령어의 입력을 받아들이는데 이 때 push 18 같은 방식으로 입력하면 해당 주소가 자동으로 수정된다. 이후에는 다음 주소에서 또 어셈블을 시도하는데 공백으로 입력 즉 Enter를 다시 한 번 입력하면 자동으로 종료된다.


> e 004077aa 6a 6d

> ea 00417000 "hello world"

  Edit. Assemble과 달리 명령어가 아닌 메모리를 수정한다. 디스어셈블리를 수정할 경우에도 edit 명령어를 사용할 수 있겠지만 assemble 명령어가 훨씬 편할 것이고 일반적인 메모리를 수정할 경우에는 edit 명령어를 사용한다. e만 사용할 경우에는 eb와 같아서 byte 즉 1바이트의 바이너리 값을 수정할 수 있고 뒤에 붙는 값에 따라 여러가지 방식으로 수정할 수 있다. 뒤에 붙는 w는 word, d는 dword, q는 qword, f는 부동 소수점(single precision - 4bytes)이며 D도 부동 소수점(single precision - 8bytes)이다. 이 외에도 위와 같이 a를 이용해 아스키 문자열을, za를 통해 NULL 종료 아스키 문자열, u는 유니코드 문자열, zu는 NULL 종료 유니코드 문자열 값으로 수정할 수 있다.

  참고 사항으로 e나 eb의 경우에 입력으로 11 22 33 44를 받는다고 하자. 그렇다면 해당 주소를 기준으로 "11 22 33 44" 이대로 각각의 바이트가 수정된다. ew의 인자로 "11 22 33 44"를 받는 경우는 좀 다르다. 이 때는 해당 주소부터 시작해서 "11 00 22 00 33 00 44 00" 이런식으로 변경된다. 즉 word는 2바이트이므로 1바이트만 입력받으므로 나머지는 "00"으로 채우는 것이다. word 형태로 바꾸고 싶다면 "1122 3344" 형태로 입력해야 한다. 그러면 원하는대로 1122 3344 형태로 수정된다. 마찬가지로 ed를 저런 식으로 입력한다면 "11 00 00 00 22 00 00 00 ..." 형태로 입력되며 "11223344" 형태로 입력해야 "11 22 33 44"와 같이 수정된다.


(gdb) set (*0x400820) = 0x6c

(gdb) set {char}0x001d3bb4 = 0x33

(gdb) set {int}0x001d3bb5 = 0x33333333

  gdb에서는 마땅한 명령어가 없는것으로 보인다. 즉 위와 같은 불편한 방식으로 사용할 수 밖에 없다.





2.3 Memory Fill / Copy / Compare

> f 00403000 l20 41 42 43

  Fill memory. 0x00403000부터 20바이트를 "0x41, 0x42, 0x43"의 연속으로 채운다.


> m <Start Range> <End Range> <Addr>

  Move memory. 

> m 00403000 00403003 00403010

  0x00403000부터 0x00403003의 메모리 값을 0x00403010에 옮긴다. 즉 복사 및 삽입이라고 할 수 있다. 참고로 처음 두 인자로부터 범위를 구할 수 있으므로 굳이 복사할 위치 즉 목적지 메모리의 끝 주소를 받을 필요는 없다.


> c <Start Range> <End Range> <Addr>

  Compare memory.

> c 00403000 00403001 00403010

  0x00403000부터 0x00403001의 메모리와 0x00403010부터 0x00403011의 메모리를 비교한다. 마찬가지로 처음 두 인자로부터 범위를 구할 수 있으므로 굳이 비교 대상 메모리의 끝 주소를 받을 필요는 없다.





2.4 Search

2.4.1 Search memory.

- windbg

s -[d|w|b|a] [ Range ] [ Pattern ]

> s [시작주소] [끝주소] [값]

> s 0019ffe0 0019fffc 'H' 'e'

  주소 사이에서 문자 H와 e가 들어간 것을 찾는다.

> s esp l200 77 d3

  esp 주소를 시작으로 200의 범위만큼 "77 d3" 값이 들어간 메모리를 찾는다.

> s -w esp l200 77d3

  또는 위와 같이 77d3이 word이므로 -w 옵션을 사용할 수 있다.

> s -d 00400000 00403000 00000000

  00000000인 값을 찾으므로 dword인 -d 옵션을 사용한다.

> s -a 0019fae0 l200 "hi"

  -a는 아스키를 말하며 l은 길이를 말하므로 0019fce0 까지이다. "hi" 문자열을 찾는다.

> s -sa 00417000 00418000

  아스키 스트링들을 찾아준다.


- gdb

(gdb) find [/sn] start_addr, +len, val1 [, val2, …]

(gdb) find [/sn] start_addr, end_addr, val1 [, val2, …]

  /b 옵션은 byte, /h는 harfwords(2 bytes), /w는 words(4 bytes), /g는 giant words(8 bytes)를 의미한다. 두 번째 인자는 시작 주소이며 세 번쨰 인자는 끝 주소 또는 길이를 의미한다. 마지막 인자가 찾을 메모리이다.


----------------------------------------------------------------

(gdb) find /b 0x400814, 0x40083a, 0xe5

0x400814 <main>

1 patterns found

----------------------------------------------------------------


  위와 같이 사용하면 주소 0x400814부터 0x40083a까지 0xe5 값을 찾는다.


----------------------------------------------------------------

(gdb) find /w 0x400814, +1000, 0xe5894855

0x400814 <main>

0x40083b <_Z41__static_initialization_and_destruction_0ii>

0x40087b <_GLOBAL__I_main>

0x400930 <__do_global_ctors_aux>

4 patterns found.

----------------------------------------------------------------



2.4.2 Search for disassembly pattern

> # [Pattern] [Address [ L Size ]] 

----------------------------------------------------------------

00401011 cmp ecx, dword ptr [edx+0ch]

00401307 add edx, eax

00401344 mov eax, dword ptr fs:[00000018h]

-------------------------------------------------------------

> # cmp*ecx 00401000 l30

> # cmp 00401000

> # ecx 00401000

> # 00401011 00401000 l30

> # ecx*word

> # ecx*dword*ptr

  위의 명령어들은 모두 다음 디스어셈블리를 찾기 위한 것이다. 참고로 4번째 예제는 주소도 검색 결과에 포함된다는 것을 보여준다.

00401011 cmp ecx, dword ptr [edx+0ch]

> # edx*eax 00401300

00401307 add edx, eax

> # dword*ptr*fs

00401344 mov eax, dword ptr fs:[00000018h]





2.5 Checkpoint

  Gdb는 체크포인트라는 기능을 제공한다. 이것은 현재 상태를 스냅샷으로 저장한 이후에 진행하다가 이전에 저장한 스냅샷으로 복귀할 수 있는 기능을 제공함으로써 디버깅을 편리하게 해준다. 실질적으로는 fork()를 통해 똑같은 프로세스를 만드는 것이다.

(gdb) checkpoint

  디버기의 현재 상태를 스냅샷으로 저장한다. 

(gdb) info checkpoint

  체크포인트 정보를 보여준다. 이후에는 여기서 보이는 체크포인트 번호를 사용한다.

(gdb) restart [ 체크포인트 번호 ]

  다른 체크포인트로 변경할 수 있다. 즉 이전에 저장한 체크포인트로 복귀할 수 있다.

(gdb) delete checkpoint [ 체크포인트 번호 ]

  체크포인트를 지운다.





2.6 Reverse Debugging

  Gdb는 역디버깅을 제공한다. 즉 디버깅 과정 중 저장을 시작한 후부터 다시 역으로 되돌아갈 수 있다는 것이다.

(gdb) target record

  역디버깅을 시작한다. 즉 상태 및 명령어 로그 저장은 여기서부터 시작한다.

(gdb) info record

  record 상태를 볼 수 있다. 저장된 명령어들이 몇 개인지 같은 정보를 볼 수 있다.

(gdb) ni

  ni나 si 등의 명령어를 사용하며 디버깅을 진행할 것이다.

(gdb) rc

  c 즉 continue 명령어의 반대이다. 반대 방향으로 c 명령어를 진행한다.

(gdb) rsi

  si 명령어의 반대이다.

(gdb) rni

  ni 명령어의 반대이다.

(gdb) record stop

  record를 중단한다. stop한 경우에는 지금까지 저장했던 로그가 모두 삭제된다. 즉 그냥 없었던 일이 되는 것이다.

(gdb) record save

  현재 record를 로그 파일로 저장한다.

(gdb) record restore [ 로그 파일 이름 ]

  로그 파일에서 복구한다. 참고로 복구하면 record의 begin 즉 record를 시작한 곳에서 시작한다.

(gdb) record goto end

  record의 마지막으로 간다. 여기서 마지막은 record로 저장된 마지막을 의미한다.

(gdb) record goto [ n ]

  record의 번호 n으로 간다.

(gdb) record goto begin

  record의 시작으로 간다.

(gdb) record delete 

  현재 위치 이후부터 end까지를 삭제한다. 현재 위치를 record의 end로 만든다.

(gdb) show exec-direction

  rsi, rni, rc 같은 명령어가 있어서 큰 의미는 없어 보이지만 실행 방향을 설정하는 모드도 존재한다. reverse와 forward가 있다. 

(gdb) set exec-direction reverse

  reverse로 설정한 경우에는 ni, si, c 같은 명령어가 rni, rsi, rc 처럼 반대로 진행된다. 참고로 "target record"를 통해 역디버깅을 시작한 후에 이 모드를 설정할 수 있다. 그냥 rni 같은 명령어를 사용하면 되므로 큰 의미는 없어보이는 모드이다.

(gdb) set exec-direction forward

  모드를 foward로 설정한다.

(gdb) set pagination off

  출력 결과가 화면을 넘어가는 경우 전체를 보여주지 않고 다음과 같이 부분만 출력한다. pagination 옵션을 off하면 화면을 넘어가는 긴 출력 결과도 전체를 보여준다.

---Type <return> to continue, or q <return> to quit---





2.7 Inferior

  리눅스에서는 fork()를 통해 자식 프로세스를 생성할 수 있다. Gdb의 경우 디버깅 도중 fork()로 인해 자식 프로세스가 생성된 경우 부모 프로세스 뿐만 아니라 자식 프로세스도 디버깅할 수 있는 기능을 제공한다.

  먼저 이것과는 상관 없지만 배경 지식을 위해 다음 모드를 살펴보겠다. 다음은 fork() 이후 각각 parent 또는 child 프로세스를 디버깅하겠다는 설정이다. 즉 부모 프로세스를 디버깅하는 도중 자식 프로세스가 생성된 경우에 둘 중 하나를 선택하여 그것을 디버깅하겠다는 것이다. 참고로 fork() 외에도 exec()도 마찬가지이다. same은 그대로 디버깅을 계속하고 new는 exec()을 통해 생성된 프로세스를 디버깅하겠다는 의미이다.

(gdb) set follow-fork-mode parent

(gdb) set follow-fork-mode child

(gdb) show follow-fork-mode

(gdb) set follow-exec-mode same

(gdb) set follow-exec-mode new

(gdb) show follow-exec-mode

  detach-on-fork 모드를 off시키면 fork() 시에 부모 또는 자식 프로세스를 선택하고 나머지를 detach 시키는 것을 막는다. 이에 따라 inferior는 부모 프로세스 와에도 자식 프로세스도 포함된다.

(gdb) show detach-on-fork

(gdb) set detach-on-fork off

  이제 inferior의 개념을 알게 되었으니 어떤 방식으로 inferior를 디버깅할지 살펴보겠다.

(gdb) info inferiors

  위의 명령어를 통해 inferior들의 목록을 구할 수 있다. 이 결과로 나온 번호를 통해 다음 명령어를 실행한다.

(gdb) inferior [ inferior 번호 ]

  이 명령은 디버깅 대상을 다른 inferior로 옮기는 역할을 한다. 만약 자식 프로세스의 번호를 사용하는 경우 부모 프로세스를 그대로 둔 상태에서 이제 자식 프로세스를 디버깅할 수 있는 것이다. 그리고 다시 부모 프로세스의 번호를 입력하여 부모 프로세스로 돌아갈 수도 있다.

(gdb) detach inferior [ inferior 번호 ]

  쓸모가 없는 경우에는 inferior를 detach 시킬 수 있다.

(gdb) kill inferior [ inferior 번호 ]

  또는 위의 명령을 사용해 inferior를 종료시킬 수 있다.





2.8 Thread

(gdb) info threads

  모든 스레드 리스트 및 번호를 보여준다.

(gdb) thread [ thread 번호 ]

  현재 스레드에서 다른 스레드로 변경한다.

(gdb) thread apply [ thread 번호 | all ] [ command ]

  기본적으로 명령어를 사용하면 현재 스레드에만 적용되겠지만 인자로 받은 thread 번호를 통해 명시된 스레드에도 동일한 command를 적용할 수 있다. 인자가 all인 경우 모든 스레드가 입력된 명령어를 수행한다.

(gdb) thread apply all bt

  위와 같은 명령어를 사용하면 모든 스레드의 콜스택을 보여준다(bt 명령어).





2.9 Signal

  리눅스에만 존재하는 signal과 관련된 명령어들이다.

(gdb) info signals

(gdb) info handle

  모든 종류의 시그널 목록과 이 시그널 발생 시 GDB각 각각의 항목에 따라 어떠한 행위를 할지에 대한 설정을 보여준다. 이것은 다음 명령어를 통해 각각에 맞는 키워드를 넣어서 설정할 수 있다.

(gdb) handle [ signal 이름 ] [ keyword ]

  키워드는 세 종류가 있다. 시그널 발생 시 프로그램을 중지시킬지에 따라 nostop, stop이 존재하며, 시그널 발생 시 메시지를 표시할지 여부에 따라 print, noprint가 존재하고 마지막으로 받은 시그널을 프로그램으로 다시 넘겨줄지 여부에 따라 pass(noignore), nopass(ignore)가 존재한다. 즉 예를 들면 다음과 같이 사용한다.

(gdb) handle SIG117 nostop

  signal 명령어는 프로그램에 인자로 받은 시그널을 넘김과 동시에 continue한다. 실제로 사용되는 예시를 들어보겠다. 만약 특정한 시그널이 발생하였고 거기에 대해 stop이 설정되어 있다면 프로그램은 정지한다. 이 때 다시 프로그램을 진행시키면 프로그램은 이 시그널을 받은 상태로 진행되기 때문에 만약 그 시그널이 프로그램을 종료시키는 중대한 에러였다면 프로그램은 종료된다. 이런 경우에 받은 시그널을 없애고 진행(continue)하고 싶은 경우에 다음 명령어를 사용한다.

(gdb) signal 0

  마지막으로 catch 명령어를 통해 signal에 대한 캐치포인트를 지정할 수도 있다. 이것은 Catchpoint 항목에서 설명한다.

(gdb) catch signal [ 시그널 번호 ]





2.10 Catch

  Gdb는 Catchpoint를 제공한다. 캐치포인트는 이벤트들에 대한 BP와 비슷한 개념이다. 즉 특정한 이벤트가 발생했을 때 BP를 걸어주는 것이다. 이벤트들로는 throw, catch, exception, exec, fork, vfork, syscall 등이 있다. 예를들어 간단하게 "catch syscall"을 사용함으로써 모든 시스템 호출에 catchpoint를 걸 수 있으며 fork()나 exec()도 마찬가지이다. 이 외에도 여러 C++ 예외에도 유용하게 사용할 수 있다.

(gdb) catch [ event ]

  여러가지 이벤트들에 대한 catchpoint를 설정한다. 

(gdb) info break

  catchpoint도 breakpoint와 같이 이 명령어를 통해 확인할 수 있다.

(gdb) tcatch [ event ]

  catch와 같지만 temporary로서 한 번만 처리된다.





2.11 Events and Exceptions for Windows

  sx* 명령어들은 디버기에서 예외 또는 이벤트가 발생할 때 디버거가 취할 행동을 제어한다. 즉 Break Status와 Handling Status를 설정한다.

> sx

  이 명령어는 현재 프로세스의 예외 목록 및 비-예외 이벤트들의 목록을 보여줌과 동시에 각 예외 및 이벤트에 대한 디버거의 디폴트 행위를 보여준다.

  먼저 Break Status부터 보겠다. sxe(이제부터 괄호 안은 cdb에서 사용되는 명령어로 하겠다) 명령어는 Break Status를 Break 즉 Enabled를 의미한다. 이것은 예외가 발생할 때 즉시 디버거로 넘겨주는 것을 의미하며 first-chance exception이라고도 불린다. sxd(-xd) 명령어는 Second chance break로 설정한다. 즉 Disabled를 의미한다. 이것은 first-chance exception 발생 시 메시지만 표시하고 break하지 않으며 에러 핸들러가 이 예외를 처리하지 못해 다시 예외가 발생했을 때 실행을 멈추고 다시 디버거로 넘겨주는 것이다. sxn(-xn) 명령어는 Output으로 설정한다. 즉 Notify를 의미한다. 이것은 예외 발생 시 메시지만 표시하는 것이다. sxi(-xi) 명령어는 ignore로 설정한다. 이것은 예외 발생 시 디버기 프로그램 자체에서 처리하지 디버거로 넘겨주지 않는다는 것을 의미한다.

  이제 Handling Status를 살펴보겠다. sxe -h 명령어는 Handling Status를 Handled로 설정한다. 이것은 실행을 재개할 시에 이벤트가 처리되었다고 여기게 해준다. sxd(또는 sxn, sxi) -h 명령어는 Not Handled로 설정한다. 이것은 실행이 재개되었을 때 이벤트가 처리되지 않았다고 여기게 해준다. 참고로 Single-step exception 같이 디버기와 디버거간의 통신에 사용되는 예외들은 Handled로 설정되어 있으며 디버거가 알아서 처리해 주지만 다른 예외들은 모두 Not Handled로 설정되어 있으며 일반적으로 그래야 한다. 특별한 경우가 아닌 경우에 일반적인 예외들을 Handled로 설정한다면 모든 first-chance 예외 및 second-chance 예외 발생 시에 모든 예외 처리 루틴들을 건너뛰기 때문이다.

  이렇게 sx* 명령어를 통해 Break Status를, sx* -h 명령어를 통해 Handling Status를 설정할 수 있다. 참고로 특별한 몇몇 이벤트 코드들은 -h 옵션을 사용하지 않고 sx* 명령어를 통해 Break Status 대신 Handling Status를 설정한다. 예를들면 아래에 나오는 hc의 경우에는 sx* hc 처럼 -h 옵션이 없어도 Handling Status를 설정한다는 것을 의미한다.다음은 일반적인 이벤트들이다.

----------------------------------------------------------------

asrt : Assertion failure.

av : Access violation.

dm : Data misaligned.

dz : Integer division by zero.

c000008e : Floating point division by zero.

eh : C++ EH exception.

gp : Guard page violation.

ii : Illegal instruction.

iov : Integer overflow.

ip : In-page I/O error

isc : Invalid system call.

lsq  Invalid lock sequence.

sbo : Stack buffer overflow.

sov : Stack overflow.

wkd : Wake debugger.

aph : Application hang.

3c : Child application termination.

ch, hc : Invalid handle. 참고로 ch는 Break Status, hc는 Handling Status를 설정할 때 사용된다.

Number : Any numbered exception.

dbce : Special debugger command exception.

vcpp : Special Visual C++ exception.

wos : WOW64 single-step exception.

wob : WOW64 breakpoint exception.

sse, ssec : Single-step exception. sse는 Break Status, ssec는 Handling Status를 설정할 때 사용된다.

bpe, bpec : Breakpoint exception. bpe는 Break Status, bpec는 Handling Status를 설정할 때 사용된다.

cce, cc : CTRL+C or CTRL+BREAK. cce는 Break Status, cc는 Handling Status를 설정할 때 사용된다.

----------------------------------------------------------------

  다음 이벤트들은 예외가 아니므로 Handling Status는 상관이 없기 때문에 Break Status만 설정할 수 있다.

----------------------------------------------------------------

ser : System error.

cpr : Process creation.

epr : Process exit.

ct : Thread creation.

et : Thread exit.

ld : Load module.

ud : Unload module.

out : Target application output.

ibp : Initial breakpoint.

----------------------------------------------------------------

  예들들어 보겠다. 다음은 ld 이벤트와 관련된 예제이다. ld 이벤트 코드는 예외가 아니므로 Break Status만 설정할 수 있다.

ld [ module, driver 이름 ] : 모듈(.dll)이나 드라이버(.sys)가 로드되었을 때의 이벤트 설정.

ud [ module, driver 이름 ] : 모듈(.dll)이나 드라이버(.sys)가 언로드되었을 때의 이벤트 설정.

  올리디버거의 옵션에는 모듈 로드 시에 자동으로 BP를 걸어주는 옵션이 있다. 즉 악성코드가 LoadLibraryA() API를 사용해 특정한 모듈을 로드할 시에 BP를 거는데 사용될 수 있는 편리한 기능이다. 이 기능을 Windbg에서 구현해보도록 하겠다. 

  Windbg에서는 ld라는 이벤트 코드가 있는데 이것은 모듈이 로드되었을 때 발생하는 이벤트이다. 이 이벤트를 sxe 명령어를 사용해 Break Status를 Break로 걸어준다. 이를 통해 특정 모듈이 로드된 경우에 BP를 자동으로 걸어주는 원리이다. 


----------------------------------------------------------------

> sxe ld user32.dll kernel32.dll

ld - Load module - break

(only break for user32.dll kernel32.dll)

> g

ModLoad: 749c0000 74afc000   C:\WINDOWS\SysWOW64\User32.dll

eax=00000000 ebx=00000000 ecx=000028b4 edx=0041a6e3 esi=00000000 edi=002d0000

eip=77731fec esp=0019d12c ebp=0019d180 iopl=0         nv up ei pl nz na po nc

cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202

ntdll!NtMapViewOfSection+0xc:

77731fec c22800          ret     28h

----------------------------------------------------------------


  위에서는 두 dll을 사용했는데 이 명령어는 user32.dll과 kernel32.dll을 차례대로 입력하면 통하지 않기 때문이다. 즉 마지막에 입력된 명령어로 수정된다. 그래서 여러개를 입력하고 싶은 경우에는 한 번에 위처럼 여러개를 같이 써야 한다. 만약 모든 모듈을 설정하고 싶다면 " sxe ld *.dll "과 같이 사용할 수도 있다. 비슷한 원리로 ud 이벤트 코드를 사용해 모듈이 언로드되었을 때 BP가 걸리게 할 수 있다. 참고로 Gdb에서도 이와 동일한 기능을 제공하는데 이것은 2.12.6 Stop-on-solib-events 절에서 설명하겠다.

  이번에는 sx 명령어를 입력하여 디폴트로 설정된 것들을 살펴보겠다. 보니까 ibp 이벤트 코드 즉 Initial breakpoint가 break로 설정되어 있다. 이 설정을 보면서 우리가 Windbg로 새로운 실행 파일을 로드한 경우에 자동으로 초기 BP가 걸려서 거기에서 멈춰있던 것을 생각할 수 있다.

  또한 위의 리스트에서 dbce부터 cce의 항목까지 보면 모두 디폴트로 Break Status는 break, Handling Status는 Handled인 것을 볼 수 있다. 이것은 당연하게도 디버거와 디버기가 통신하는데 사용되는 예외이기 때문이다. 단적인 예로 sse는 Single-step exception을 의미한다.





2.12 ETC

2.12.1 Help

> ?

  모든 명령어들과 연산자들의 목록을 보여준다. 그것 외에는 없다.

> .help

> .help /D wake

  meta-command help. 메타 명령어들에 대한 설명을 보여준다. 디폴트로 사용할 시 사용 가능한 모든 메타 명령어들의 목록을 보여준다. 두 번째 줄은 .wake 메타 명령어에 대한 간단한 설명을 볼 수 있다. 물론 각 알파벳을 클릭하여 해당 알파벳으로 시작하는 모든 메타 명령어들에 대한 간단한 설명을 볼 수 있다. .hh를 통해 자세한 설명을 보는 것을 추천한다.

> !help

  확장 명령어들의 목록을 보여준다.

> .hh

> .hh !gle

  Open html help file. HTML help 문서 파일을 열어준다. 인자로 명령어를 넣는다면 해당 명령어에 관한 설명을 찾아주며 일반 명령어든 메타 명령어든 확장 명령어든 문서에 있는 명령어는 모두 찾아준다. 참고로 왼쪽에서 검색 후 체크된 항목을 더블클릭해야 볼 수 있다.


----------------------------------------------------------------

(gdb) help

List of classes of commands:


aliases -- Aliases of other commands

breakpoints -- Making program stop at certain points

data -- Examining data

files -- Specifying and examining files

internals -- Maintenance commands

obscure -- Obscure features

running -- Running the program

stack -- Examining the stack

status -- Status inquiries

support -- Support facilities

tracepoints -- Tracing of program execution without stopping the program

user-defined -- User-defined commands

----------------------------------------------------------------


  인자 없이 사용하면 명령어들의 클래스를 보여준다. 즉 모든 명령어들을 보여주는 것이 아니라 각 명령어들이 속한 분류인 클래스를 보여준다.

(gdb) help data

  여기서는 data 클래스를 선택하였는데 이 data 클래스에 속한 모든 명령어들을 간단한 설명과 함께 보여준다. data 클래스의 명령어 중 dump 명령어를 보려면 아래와 같이 입력한다.

(gdb) help dump

  위와 같이 인자로 명령어를 넣으면 명령어에 대한 조금 더 자세한 설명을 볼 수 있다. 

(gdb) apropos breakpoint

  인자로 받은 문자열과 관련된 명령어들을 보여준다. 즉 위와 같은 경우에는 breakpoint 관련 명령어들의 목록이 결과로 나온다.


2.12.2 Clear

> .cls

  화면을 clear해 준다.

(gdb) shell clear

  gdb의 명령어도 아니고 특정한 환경(xterm)에 통하는 명령이지만 화면을 clear해 준다. 직접적으로 제공되는 gdb 명령어는 없다.


2.12.3 Loop

> z(expression)

  Execute while. 일종의 반복문으로서 괄호 안이 참일때 까지 반복한다. 다음과 같이 사용하는데 이렇게 사용하면 ecx가 0이될 때 까지 p 명령어를 실행한다.

> p; z(ecx != 0)


2.12.4 Log

- Windbg

  Windbg에서는 로그 파일을 만들어서 텍스트 형태로 기록할 수 있다. 즉 로그 파일을 오픈한 이후부터 닫을 때 까지 입력한 명령어 및 결과가 해당 텍스트 파일에 모두 기록된다. 


----------------------------------------------------------------

> .logopen /t C:\Users\monto\Desktop\log.txt

Opened log file 'C:\Users\monto\Desktop\log_252c_2017-06-15_10-58-50-192.txt'

// Open log file

> ...

> .logfile

Log 'C:\Users\monto\Desktop\log_252c_2017-06-15_10-58-50-192.txt' open

// Display log file status

> .logclose

Closing open log file C:\Users\monto\Desktop\log_252c_2017-06-15_10-58-50-192.txt

// Close log file

----------------------------------------------------------------


- Gdb

(gdb) set logging on

  로깅을 시작한다. 디폴트로 gdb.txt 파일에 저장된다.

(gdb) set logging off

  로깅을 중지한다.

(gdb) show logging

  현재 로깅 설정을 보여준다. 설정들은 뒤에서 나열하겠지만 어떤 이름의 파일에 저장할지, 로그를 append 즉 이어쓸지 아니면 overwrite할지, 마지막으로 출력을 터미널에도 보여주고 로그 파일에도 저장할지 아니면 로그 파일에만 저장할지이다.

(gdb) set logging file [ 파일 이름 ]

  로그 파일의 이름을 새로 지정할 수 있다. 디폴트는 gdb.txt 파일이다.

(gdb) set logging overwrite [ on|off ]

  로그를 overwrite할지 여부를 결정한다. 디폴트는 append이다.

(gdb) set logging redirect [ on|off ]

  로그의 출력을 로그 파일에만 보낼지 지정한다. 디폴트는 로그 파일 및 터미널 모두로 보내는 것이다.


2.12.5 ASLR

  디버기 실행 시에 커널에서 제공되는 ASLR 기능을 활성화할지 여부를 결정한다.

(gdb) show disable-randomization

  disable-randomization의 on/off 설정을 보여준다.

(gdb) set disable-randomization on 

  디버기 프로세스의 ASLR을 비활성화시킨다.

(gdb) set disable-randomization off

  디버기 프로세스의 ASLR을 활성화시킨다.


2.12.6 Stop-on-solib-events

(gdb) show stop-on-solib-events

  위에서 설명하였듯이 Gdb에서도 공유 라이브러리가 로드 또는 언로드 될 시에 BP를 걸어주는 기능을 제공한다. 위의 명령어는 현재 그 기능이 켜져있는지 여부를 보여준다. 

(gdb) set stop-on-solib-events 0

  Stop-on-solib-events 기능을 끈다.

(gdb) set stop-on-solib-events 1

  Stop-on-solib-events 기능을 켠다.


2.12.7 Alias

> as 또는 aS

  Set Alias. 아래의 예에서는 !teb 명령어의 alias로 !t를 만들었다. 이후에는 !teb 외에 !t 명령어만 사용해도 디버거는 !teb와 동일하게 받아들인다.

> as !t !teb

> !t


(gdb) alias -a di = disas

(gdb) di

  gdb에서는 alias 명령어가 존재한다.


2.12.8 Define

(gdb) define adder

> print $arg0 + $arg1 + $arg2

> end

(gdb) adder 1 2 3

$1 = 6

  gdb에서는 사용자 정의 명령어를 정의하기 위해 define 명령어를 사용한다. define 명령어 이후 새로운 명령어의 이름을 적으면 명령어의 내용을 받을 수 있다. 이후 마지막으로 end를 입력하면 종료된다.


2.12.9 Etc

> .printf

  c언어의 printf 문과 같다고 생각하면 된다. 유용하게 사용할 수 있다.


> poi()

  포인터 참조 명령어로서 c언어에서 *와 같다. 즉 인자로 주소를 받는다고 여기고 그 주소의 값을 반환한다.


> .printf "%y \n", poi(00413014)

KERNEL32!GetVersionExAStub (747456d0)


  00413014는 IAT 영역에 위치한 주소로서 GetVersionExAStub()의 주소가 들어가 있다. 조금 더 자세히 말하면 현재 PE가 로드되어 있으므로 IAT에는 각 임포트하는 API들의 주소가 들어가 있다. 코드 영역에서 api를 호출할 경우 이 IAT의 주소를 사용해 call dword ptr [ 00413014 ] 같은 방식으로 호출한다. 이 00413014가 어떤 함수인지를 보기 위해서 poi()를 이용해 해당 주소에 들어있는 값을 알 수 있다. 즉 747456d0이 .printf로 넘어온다.

  .printf의 포맷스트링을 보면 %y이다. 이것은 인자로 디버거 심볼의 주소를 받아서 해당 심볼의 이름을 보여준다. 즉 현재 kernel32.dll의 심볼이 있고 이 중에서 747456d0의 주소를 넘겨 받았으므로 이 주소에 해당하는 심볼인 KERNEL32!GetVersionExAStub 문자열을 출력해 준다. 물론 뒤에 실제로 받은 값이 747456d0도 보여주며 만약 심볼이 없다면 이 값만 보여준다. 이렇게 poi()와 %y를 통해 간단하게 IAT 주소를 가지고 api 이름까지 알아낼 수 있다.


> .echo [ string ]

  일반적인 echo와 같다고 생각하면 된다.

> .echo windbg echo

windbg echo







3. Data

3.1 Unassemble

> u

  Unassemble을 보여준다.

> u 00401000 0040107A

  위에서 지정한 주소만큼 Unassemble을 보여준다.

> u eip l30

  Unassemble을 30라인 만큼 보여준다.

> u ntdll!LdrUnloadDLL

  심볼이 존재한다면 해당 api 함수의 주소를 구하는데 u 명령어를 사용할 수 있다. 예를들면 Ollydbg에서 "Ctrl + G" 명령어를 통해 찾는 것과 같은 방식이다.


> ub

  u가 특정 주소 이후를 보여준다면 ub는 반대로 이전을 보여준다.

> ub eip l20


(gdb) disas

(gdb) disas $eip

  gdb의 경우 ub 같은 명령어가 없다.


(gdb) show disassembly-flavor

  디스어셈블리 스타일이 AT&T인지 아니면 Intel인지를 보여준다.

(gdb) set disassembly-flavor [ intel ]

  디스어셈블리 스타일을 Intel로 설정한다.





3.2 Register

> r

  기본 레지스터들의 값을 보여준다.

> r $proc

  현재 프로세스의 PEB 주소. 위와 같은 "의사 레지스터"는 항목 "3.5.1 의사 레지스터"에서 설명한다.

> r @eip=0x00401000

> r $ip=0x00401000

  두 명령어는 모두 EIP 값을 바꾸는 명령이다. 이것도 "의사 레지스터" 항목을 참고하자.


(gdb) info reg    /    i r

  Gdb의 경우 위와 같이 사용한다.

(gdb) set $eip = 0x00401000

(gdb) set $rip = 0x00401000

  두 명령어는 레지스터 즉 eip 또는 rip의 값을 변경하는 명령이다.


  GDB와 Windbg 모두 EFLAGS 레지스터 관련한 정보는 다음 링크를 확인한다. [ http://sanseolab.tistory.com/44 ]





3.3 Memory

3.3.1 Display Memory

- windbg

----------------------------------------------------------------

> d* <addr>

- *

a : ascii chars

u : unicode chars

b : byte + ascii (디폴트 d와 같다)

w : word

W : word + ascii

d : dword

c : dword + ascii

q : qword

f : floating point (single precision - 4b)

D : floating point (double precision - 8b)

b : binary + byte

d : binary + dword

s : string struct

S : unicode string

----------------------------------------------------------------


> dc 00401700

  Ollydbg의 디폴트 메모리 창처럼 메모리를 dword 형태로 아스키 값과 함께 출력한다. dW 명령어를 사용해도 띄어쓰기 즉 보여주는 단위의의 차이만 있지 실질적인 차이는 없다.


- gdb

(gdb) x/[Length][Format] [Address expression]

  x/ 바로 뒤에오는 숫자는 길이를 나타낸다. 아래의 예를 보면 알겠지만 10개 단위를 보여준다. 즉 여기서는 word 10개 단위를 보여준다. 두 번째는 출력 포맷을 의미한다. i는 인스트럭션(disas 명령어처럼), x는 익숙한 16진수 값, c는 char, s는 문자열이다. 세 번째는 출력 포맷의 크기이다. 즉 b(byte), h(halfword, 2bytes), w(word, 4bytes), g(giant word, 8bytes)가 있다. 다음 예제는 x86 아키텍처의 예이며 Ollydbg처럼 메모리를 4바이트 단위로 16진수 포맷을 통해 보여준다. 하지만 Ollydbg나 Windbg처럼 아스키 또는 유니코드 문자열을 보여주지는 못하고 메모리 값만 보여준다.


----------------------------------------------------------------

(gdb) x/10xw 0x400814

0x400814 <main>:        0xe5894855  0x400988be  0x0d20bf00  0xc1e80060

0x400824 <main+16>:    0xbefffffe    0x00400708  0xe8c78948  0xfffffec4

0x400834 <main+32>:    0x000000b8  0x55c3c900

----------------------------------------------------------------


(gdb) x/30i 0x08048320

  위와 같이 사용하면 disas 명령과 같이 인스트럭션들을 보여준다.

(gdb) x/16wx $sp

(gdb) x/16gx $sp

  첫 번째 예제는 x86 아키텍처에서 스택 창을 보여주며 두 번째 예제는 x64 아키텍처에서 보여준다. 마찬가지로 Ollydbg나 Windbg와는 다르게 값만 보여주고 심볼 또는 문자열 같은 정보는 보여주지 않는다.



3.3.2 Display words and symbols

- windbg

> d*s <addr>

  dds는 dword, dqs는 qword, dps는 아키텍처 표준 단위로 보여주며 특정 단위와 연관된 심볼도 함께 보여준다. 즉 해당 메모리 주소에 들어있는 값 및 이 값과 연관된 심볼을 보여준다. 예를들어 들어있는 값이 특정 함수의 주소인 경우에는 관련 함수의 이름 같은 심볼을 보여준다.

> dds esp

> dqs rsp

  x86 아키텍처에서 위와 같이 사용하면 esp를 dword 단위로 보여줌과 동시에 각 dword 주소 별로 값을 보여주며 만약 그 값과 연관된 심볼이 존재하는 경우 심볼도 보여주어서 Ollydbg의 스택 메모리 창처럼 보여줄 수 있다. x64의 경우에는 dqs 명령어를 사용한다.



3.3.3 Display Referenced Memory

> d** <addr>

  인자로 받은 해당 주소 영역에서 포인터를 보여주고 해당 포인터로 참조되는 메모리를 보여준다. 예를들어 현재 eax에 0x00685ce8가 들어있다고 하자. 이 때 "ddp eax" 명령을 실행한다면 결과는 다음과 같다.


----------------------------------------------------------------

00685ce8    006853f8    554c4c41

00685cec    00685be8    44505041

...

----------------------------------------------------------------


  즉 0x00685ce8을 포인터가 위치한 주소로 여기고 여기에 들어있는 값인 0x006853f8을 포인터로 생각한다. 마지막 3번째는 해당 포인터에서 참조되는 메모리이다. 이 명령어를 이렇게만 사용한다면 큰 의미가 없을테지만 다음과 같은 기능을 제공한다. "dds eax"를 실행해보면 다음과 같다.


----------------------------------------------------------------

00685ce8  006853f8 "ALLUSERSPROFILE=C:\ProgramData"

00685cec  00685be8 "APPDATA=C:\Users\monto\AppData\Roaming"

...

----------------------------------------------------------------


  즉 세번째 문자를 a로 넣는다면 참조되는 메모리를 아스키 문자열로 보여준다. 참고로 Ollydbg의 경우에 스택 메모리 창을 보면 함수 이름 및 파라미터 이름 같은 심볼 외에도 문자열로 여겨지는 경우에는 문자열도 보여준다. 그렇기 때문에 스택에 "dda esp" 명령도 유용하게 사용할 수 있다. 다음은 두번째 및 세번째 문자열로 들어갈 수 있는 값들이다.


dd* : 해당 값을 32비트 포인터로 생각한다. 즉 x86 아키텍처에서는 dd*를 사용한다.

dq* : 해당 값을 64비트 포인터로 생각한다. 즉 x64 아키텍처에서는 dq*를 사용한다.

dp* : 해당 아키텍처에 맞는 표준 사이즈를 자동으로 설정한다.

d*a : 참조되는 메모리를 아스키 문자열로 보여준다.

d*u : 참조되는 메모리를 유니코드 문자열로 보여준다.

d*p : 참조되는 메모리를 보여준다.



3.3.4 Display Type

  지역 변수, 전역 변수 또는 데이터 타입에 대한 정보를 보여준다.

> dt [-DisplayOpts] [-SearchOpts] [module!]Name [[-SearchOpts] Field] [Address] [-l List] > dt [-DisplayOpts] Address [-l List] > dt -h


----------------------------------------------------------------

> dt _PEB 0x0026a000

> dt ntdll!_PEB


   +0x000 InheritedAddressSpace : 0 ''

   +0x001 ReadImageFileExecOptions : 0 ''

   +0x002 BeingDebugged    : 0x1 ''

   +0x003 BitField         : 0 ''

   +0x003 ImageUsesLargePages : 0y0

   +0x003 IsProtectedProcess : 0y0

   +0x003 IsImageDynamicallyRelocated : 0y0

   +0x003 SkipPatchingUser32Forwarders : 0y0

   +0x003 IsPackagedProcess : 0y0

   +0x003 IsAppContainer   : 0y0

   +0x003 IsProtectedProcessLight : 0y0

   +0x003 IsLongPathAwareProcess : 0y0

   +0x004 Mutant           : 0xffffffff Void

   +0x008 ImageBaseAddress : 0x00400000 Void

   +0x00c Ldr              : 0x77757be0 _PEB_LDR_DATA

----------------------------------------------------------------



3.3.5 Display Debugger Object Model Expresison

  NatVis 확장 모델을 이용하여 C++ Expression을 보여준다.

> dx


  자세한 r# 옵션은 recursion Level이다. 다음과 같이 사용하여 _PEB_LDR_DATA 구조체 0x77757be0에 대한 정보를 확인하였다.

----------------------------------------------------------------

> dx -r1 ((ntdll!_PEB_LDR_DATA *)0x77757be0)


    [+0x000] Length           : 0x30 [Type: unsigned long]

    [+0x004] Initialized      : 0x1 [Type: unsigned char]

    [+0x008] SsHandle         : 0x0 [Type: void *]

    [+0x00c] InLoadOrderModuleList [Type: _LIST_ENTRY]

    [+0x014] InMemoryOrderModuleList [Type: _LIST_ENTRY]

    [+0x01c] InInitializationOrderModuleList [Type: _LIST_ENTRY]

    [+0x024] EntryInProgress  : 0x0 [Type: void *]

    [+0x028] ShutdownInProgress : 0x0 [Type: unsigned char]

    [+0x02c] ShutdownThreadId : 0x0 [Type: void *]

----------------------------------------------------------------





3.4 Call Stack

  여기서는 스택 프레임 즉 콜스택과 관련된 정보를 볼 수 있는 명령어들을 다룬다. 

- Windbg

> k

  Display stack backtrace.

> kb

  인자도 같이 보기.


- Gdb

(gdb) backtrace

(gdb) bt

(gdb) info stack

(gdb) where

  위의 명령어 모두 전체 call stack을 보여준다. 

(gdb) bt [ n ]

  특정 순서의 프레임만 보여준다.

(gdb) bt full

  전체 콜 스택에 대하여 로컬 변수를 포함해서 보여준다. 물론 심볼이 있어야 하기 때문에 여기서는 의미가 없다.

  다음에 설명하는 명령어들은 각 스택 프레임에 대한 정보를 보여주는 명령어들이다. 참고로 info args나 info locals 같은 명령어들은 심볼이 있는 경우에 정보를 보여줄 수 있다.

(gdb) info frame

  현재 선택된 스택 프레임에 대한 정보를 보여준다.

(gdb) info args    /    info locals

  심볼이 있는 경우에 각각 현재 프레임의 인자와 로컬 변수를 보여준다. 심볼이 있어야 하기 때문에 여기서는 의미가 없다.

  다음에 설명하는 명령어들은 콜 스택에서 각각의 스택 프레임을 선택하는 명령들이다.

(gdb) frame [ frame 번호 ]

  프로그램의 진행과 관련 없이 현재 스택 프레임을 변경한다.

(gdb) select-frame [ frame 번호 ]

  frame 명령어와 같지만 별도로 내용을 출력하지 않는다.

(gdb) up / down

  up 명령어는 현 프레임이 호출한 스택 프레임을 선택하고 down 명령어는 현 프레임을 호출한 스택 프레임을 선택한다. 간단히 말해서 bt 명령어를 통해 볼 수 있는 콜스택에서 위 아래로 선택하는 명령들이다.





3.5 ETC

3.5.1 의사 레지스터

  Windbg는 특정한 값을 갖는 의사 레지스터들을 지원한다. 이것은 dollar ($) 기호를 앞에 붙임으로써 구별 가능하다. 참고로 특정한 레지스터를 의미하는 at (@) 기호와 호동될 수 있다. 앞에서 특정한 레지스터를 표현할 때 다음과 같이 사용하였다.

> bp 00401020 ".if (@ecx == 0x2) {} .else {gc}"

  즉 의사 레지스터는 $ 기호를, 구체적인 레지스터는 @ 기호를 붙인다. 다음은 의사 레지스터들의 예시이다.


- $ip : 현재 디버거 타겟 아키텍처의 명령 포인터 레지스터의 이름을 나타낸다. 즉 x86에서는 eip 레지스터이며 @eip와 같은 의미이고 x64에서는 rip 레지스터 즉 @rip와 같은 의미이다.

- $ra : 현재 함수의 복귀 주소.

- $retreg : 주요한 값을 가지는 레지스터를 나타낸다. 즉 x86에서는 eax 레지스터(@eax), x64에서는 rax 레지스터(@rax)와 같은 의미이다.

- $csp : 현재 스택의 포인터. x86에서는 esp 레지스터(@esp), x64에서는 rsp 레지스터(@rsp)를 의미한다.

- $proc : 현재 프로세스. 유저 모드에서 PEB의 주소나 커널 모드에서 현재 프로세스의 EPOCESS 구조체의 주소.

- $thread : 현재 스레드. 유저 모드에서 TEB의 주소나 커널 모드에서 현재 스레드의 ETHREAD 구조체의 주소

- $tpid : PID

- $tid : TID

- $t0 ~ $t9 : 사용자 정의 의사 레지스터.


> ? <expression>

> ? $ip

> ? @eip

  ? <expression> 명령어는 해당 표현식의 결과를 보여준다. 바로 아래에 나온 예제를 보면 x86 환경에서는 두 명령어 모두 같은 값 즉 eip 레지스터의 값을 의미하며 ? 명령어의 결과도 동일하다.



3.5.2 Etc

3.5.2.1 Module

> lm

  List loaded modules. 참고로 윈도우의 심볼 서버를 설정한 경우를 가정하고 처음 이 명령어를 사용하면 ntdll만 심볼이 보이고 나머지는 deferred로 된다. 이것은 지금까지 분석한 모듈이 ntdll 밖에 없고 그래서 심볼 서버로 ntdll만 심볼을 다운로드 받았기 때문이다. 추후에 kernel32.dll 같은 곳에 들어가게 되면 우니도우의 심볼 서버에서 그것을 다운받기 때문에 심볼이 있는 환경에서 분석을 할 수 있게 된다. 물론 여기서는 악성코드 분석을 가정하기 때문에 실행 파일에 대한 심볼은 없을 수 밖에 없다. 


(gdb) info share

  로드된 공유 라이브러리들을 보여준다.


> !lmi ntdll.dll

  모듈 상세 정보


> !dlls

  로드된 모든 DLL 출력


> !dh -f [imgbase]

  해당 이미지의 헤더를 분석해서 보여준다. (-f는 파일 헤더, -s는 섹션 헤더, -a는 모든 헤더 정보)


3.5.2.2 Memory Map

> !address

  Memory Map을 보여준다.

> !vadump

  !address가 이것의 결과를 포함한 훨씬 많은 정보를 보여준다.

> !vprot 0013ff34

  인자로 받은 특정 메모리 주소의 속성을 보여준다.

(gdb) info program

(gdb) shell cat /proc/24084/maps

  뒤에서 나오지만 "info program" 명령어를 통해 PID를 얻는다. 이렇게 얻은 PID가 24084라고 하자. 그리고 shell 명령어도 뒤에 나오지만 셸 명령어를 실행시켜주는 역할을 한다. 위와 같이 사용하면 프로그램의 Memory map 정보를 얻을 수 있다.



3.5.2.3 etc

> !peb

  PEB 관련 정보

> !teb

  TEB 관련 정보

> !handle

  핸들 값 출력.

> !heap

  힙 관련 정보를 보여준다.

> !exchain

  SEH들을 보여준다

> !gle

  API의 마지막 에러 코드. 즉 GetLastError().

> .tlist

  현재 실행 중인 모든 프로세스들의 목록 및 PID를 보여준다.

> ~.

  현재 스레드 정보.

> ~*

  프로세스의 모든 스레드

> .imgscan

  Find image headers. 로드된 모듈들의 이미지 헤더 주소를 찾아준다.

> !tls [Slot]

  명시한 TLS slot을 보여준다.

> .shell

  셸 명령어를 사용할 수 있다. 인자 -x는 해당 프로세스 실행 이후 디버거에서 detach하는 옵션이다.


(gdb) pwd

  현재 작업중인 디렉토리를 보여준다.

(gdb) shell

  셸 명령어를 사용할 수 있다.

(gdb) show commands [ 번호 ]

  명령어 사용 히스토리를 보여준다.

(gdb) show env

  환경변수를 보여준다.

(gdb) show args

  인자를 보여준다.

(gdb) info proc [ keyword ]

  현재 프로세스의 /proc 프로세스 정보를 보여주며 디폴트로는 PID 등 같단한 정보만 보여준다. 키워드로는 mappings(매핑된 메모리 영역 목록), stat(프로세스 정보), status(프로세스 정보), all(/proc을 통해 얻을 수 있는 모든 정보)이 있다.

(gdb) info program

  프로그램의 실행 상태. PID, 현재 BP 정보, 현재 정지한 위치의 주소. 아주 간단한 정보만 보여준다.

(gdb) info files

  로드된 이미지들 즉 바이너리 및 모듈들의 섹션 별 주소 정보.

(gdb) info functions

  로드된 이미지들 즉 바이너리 및 모듈들의 함수들 목록 및 주소 정보.



'악성코드 분석' 카테고리의 다른 글

악성코드가 감염되기까지  (2) 2017.08.13
리눅스 안티바이러스 구현에 관한 정리  (0) 2017.07.04
VC++ 옵션 정리  (0) 2017.06.03
다형성 바이러스  (4) 2017.05.16
API Sets  (0) 2017.05.12
Posted by SanseoLab

블로그 이미지
Malware Analyst
SanseoLab

태그목록

공지사항

Yesterday
Today
Total

달력

 « |  » 2024.4
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

최근에 올라온 글

최근에 달린 댓글

글 보관함