GDB shortcut

변수를 읽는 방법

항상 궁금해 하던 disass 다시 보기 → u

Dreamhack pwnable 문제 풀때 SIGALRM 떠서 디버깅 못하는 거 막기

Python
(gdb) handle SIGALRM nostop noprint pass

특정 주소가 어느 섹션에 포함되는지 알기

Python
pwndbg> vmmap 0x7fffffffe228
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x7ffff7ffd000     0x7ffff7fff000 rw-p     2000  38000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
►   0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack] +0x20228

telescope

C
telescope 0x7ffff7d93000 20 

다음과 같이 사용해서 일단 x/gx 20 과 같지만, 연결된 포인터를 보여주면서 출력해줌

thread 관리하기

현재 스레드 목록 보기 → threads

스레드 전환하기 → thread 1 , thread 10 ( 뒤에 숫자는 스레드 목록 참조하면 됨 )

참고로 메인 스레드의 bp가 정확히 걸려있어야 스레드가 함부로 스위칭 되지 않는다

무슨 말이냐면

C
   0x0000000000400baf <+170>:	call   0x400800 <pthread_create@plt>
=> 0x0000000000400bb4 <+175>:	test   %eax,%eax

다음과 같이 pthread_create 실행 후 아래에 bp가 걸려있어야 하며 thread가 시작하는 함수에도 bp가 걸려있어야 pwndbg가 안정적인 상태가 된다.

C
pwndbg> threads
  global_num  name           status    pc        symbol
------------  -------------  --------  --------  --------------
           1  master_canary  stopped   0x400bb4  main+175
           3  master_canary  stopped   0x400a5b  thread_routine

즉 이렇게 안정적인 상태 ( 둘다 stopped ) 로 만들어야지 이후에 thread 명령어로 이동하면서 안정적인 디버깅이 가능하다.

전역변수 위치를 바로 아는 법

C
pwndbg> info address mode 
Symbol "mode" is at 0x602090 in a file compiled without debugging.
pwndbg> x/dx 0x602090
0x602090 <mode>:	0x00000001
pwndbg> i a mode

info address 로 심볼이 있는 전역변수 위치를 알 수 있다.

heap 영역 한번에 보기

C
vis_heap_chunks

bins 들 한번에 보기

C
bins

libc나 구조체등의 심볼 보기

C
pwndbg> info symbol 0x75ff4ba048e8
__free_hook in section .bss of ./libc-2.27.so

libc에서 구조체의 오프셋 확인하기

C
pwndbg> p &(((struct _IO_FILE *)0)->_IO_buf_base)
$20 = (char **) 0x38
C
p/x ((struct _IO_FILE_plus *)stdout)->vtable

이런식으로 값도 확인할 수 있다.

구조체명과 필드명을 각각 _IO_FILE 과 _IO_buf_base 에 바꿔서 넣으면 됨

미리 복사해서 쓰라고 만든 필드 >>

C
 p &(((_IO_FILE *)0)->_IO_buf_base)
 p &(((struct _IO_FILE *)0)->_IO_buf_base)

자식 프로세스 따라안가게 하기

C
set follow-fork-mode parent

다음과 같이 해서 중간에 system 함수 때문에 gdb가 납치되는 것을 막을 수 있다.

추가적으로

C
set follow-fork-mode parent
set detach-on-fork off

이렇게 하면 GDB는 부모 자식 모두 추적하고, 자식 쉘 따라가도 info inferiors로 다시 부모로 돌아올 수도 있다.

세세한 섹션들의 위치 알기

우리가 디버깅 하다가 예를 들어 .dynamic 섹션의 위치를 알고 싶을때 굳이 readelf나 objdump를 사용한뒤 계산하지 않아도

info files 명령어로 현재 프로세스의 각 섹션위치를 알 수 있다.

C
pwndbg> info files
Symbols from "/home/amemoyoi/1day_2pwn/Notepad".
Native process:
	Using the running image of child process 2262447.
	While running this, GDB does not access memory from...
Local exec file:
	`/home/amemoyoi/1day_2pwn/Notepad', file type elf64-x86-64.
	Entry point: 0x400800
	0x0000000000400238 - 0x0000000000400254 is .interp
	0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag
	0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id
	0x0000000000400298 - 0x00000000004002c8 is .gnu.hash
	0x00000000004002c8 - 0x0000000000400478 is .dynsym
	0x0000000000400478 - 0x0000000000400513 is .dynstr
	0x0000000000400514 - 0x0000000000400538 is .gnu.version
	0x0000000000400538 - 0x0000000000400558 is .gnu.version_r
	0x0000000000400558 - 0x00000000004005b8 is .rela.dyn
	0x00000000004005b8 - 0x00000000004006f0 is .rela.plt
	0x00000000004006f0 - 0x000000000040070a is .init
	0x0000000000400710 - 0x00000000004007f0 is .plt
	0x00000000004007f0 - 0x00000000004007f8 is .plt.got
	0x0000000000400800 - 0x0000000000400c82 is .text
	0x0000000000400c84 - 0x0000000000400c8d is .fini
	0x0000000000400c90 - 0x0000000000400d47 is .rodata
	0x0000000000400d48 - 0x0000000000400d84 is .eh_frame_hdr
	0x0000000000400d88 - 0x0000000000400e9c is .eh_frame
	0x0000000000601e10 - 0x0000000000601e18 is .init_array
	0x0000000000601e18 - 0x0000000000601e20 is .fini_array
	0x0000000000601e20 - 0x0000000000601e28 is .jcr
	0x0000000000601e28 - 0x0000000000601ff8 is .dynamic
	0x0000000000601ff8 - 0x0000000000602000 is .got
	0x0000000000602000 - 0x0000000000602080 is .got.plt

point to point 기능 ( 유용하다! )

C
pwndbg> p2p stack heap
00:0000│-a00 0x7fffffffd9e0 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-928 0x7fffffffdab8 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-8f8 0x7fffffffdae8 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-8f0 0x7fffffffdaf0 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-8c8 0x7fffffffdb18 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-8b8 0x7fffffffdb28 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-768 0x7fffffffdc78 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-1e8 0x7fffffffe1f8 —▸ 0x5555555592a0 ◂— 0xfbad2488
00:0000│-118 0x7fffffffe2c8 —▸ 0x5555555592a0 ◂— 0xfbad2488

다음과 같이 p2p 명령어를 통해 Stack 영역중 heap 주소를 가지고 있는 포인터들을 찾아낼 수 있다.

p2p stack heap 외에도 p2p heap stack, p2p heap heap 등 다양하게 써볼 수 있음

C
pwndbg> p2p /home/bypass_valid_vtable/bypass_valid_vtable libc
00:0000│  0x600ff0 —▸ 0x7ffff7a03ab0 (__libc_start_main) ◂— pushq %r13
00:0000│  0x601020 (_GLOBAL_OFFSET_TABLE_+32) —▸ 0x7ffff7a46e80 (printf) ◂— subq $0xd8, %rsp
00:0000│  0x601030 (_GLOBAL_OFFSET_TABLE_+48) —▸ 0x7ffff7a632f0 (setvbuf) ◂— pushq %r13
00:0000│  0x601038 (_GLOBAL_OFFSET_TABLE_+56) —▸ 0x7ffff7a60e30 (fopen64) ◂— pushq %r13
00:0000│  0x601050 (stdout@@GLIBC_2.2.5) —▸ 0x7ffff7dce760 (_IO_2_1_stdout_) ◂— 0xfbad2887
00:0000│  0x601060 (stdin@@GLIBC_2.2.5) —▸ 0x7ffff7dcda00 (_IO_2_1_stdin_) ◂— 0xfbad208b
pwndbg> 

다음과 같이 data,rodata 영역도 vmmap 했을때 뜨는 프로세스 경로로 libc와 연결된 포인터를 알수 도 있다.

dt life hack ( important !!!!! )

Libc 버전 별로 다른 glibc 구조체와 오프셋을 확인하기 번거로웠는데 이 기능을 알고 인생이 달라짐

바로 pwndbg 내에서 dt 기능이다.

당연히 디버깅 심볼이 있어야 한다.

C
pwndbg> dt "struct _IO_FILE"
struct _IO_FILE
    +0x0000 _flags               : int
    +0x0008 _IO_read_ptr         : char *
    +0x0010 _IO_read_end         : char *
    +0x0018 _IO_read_base        : char *
    +0x0020 _IO_write_base       : char *
    +0x0028 _IO_write_ptr        : char *
    +0x0030 _IO_write_end        : char *
중략
    +0x0080 _cur_column          : short unsigned int
    +0x0082 _vtable_offset       : signed char
    +0x0083 _shortbuf            : char [1]
    +0x0088 _lock                : _IO_lock_t *
    +0x0090 _offset              : __off64_t
    +0x0098 _codecvt             : struct _IO_codecvt *
    +0x00a0 _wide_data           : struct _IO_wide_data *
    +0x00a8 _freeres_list        : struct _IO_FILE *
    +0x00b0 _freeres_buf         : void *
    +0x00b8 __pad5               : size_t
    +0x00c0 _mode                : int
    +0x00c4 _unused2             : char [20]

이런식으로 오프셋과 명칭 자료형까지 알려준다.

C
pwndbg> dt "struct _IO_wide_data"
struct _IO_wide_data
    +0x0000 _IO_read_ptr         : wchar_t *
    +0x0008 _IO_read_end         : wchar_t *
    +0x0010 _IO_read_base        : wchar_t *
    +0x0018 _IO_write_base       : wchar_t *
    +0x0020 _IO_write_ptr        : wchar_t *
    +0x0028 _IO_write_end        : wchar_t *
    +0x0030 _IO_buf_base         : wchar_t *
    +0x0038 _IO_buf_end          : wchar_t *
    +0x0040 _IO_save_base        : wchar_t *
    +0x0048 _IO_backup_base      : wchar_t *
    +0x0050 _IO_save_end         : wchar_t *
    +0x0058 _IO_state            : __mbstate_t
    +0x0060 _IO_last_state       : __mbstate_t
    +0x0068 _codecvt             : struct _IO_codecvt
    +0x00d8 _shortbuf            : wchar_t [1]
    +0x00e0 _wide_vtable         : const struct _IO_jump_t *

디버깅 심볼이 있는 모든 것에 대해 적용이 되므로 걍 사기임

C
pwndbg> dt "struct _IO_FILE" 0x7ffff7d93000
struct _IO_FILE @ 0x7ffff7d93000
    0x00007ffff7d93000 +0x0000 _flags               : int
    0x00007ffff7d93008 +0x0008 _IO_read_ptr         : char *
    0x00007ffff7d93010 +0x0010 _IO_read_end         : char *
    0x00007ffff7d93018 +0x0018 _IO_read_base        : char *
    0x00007ffff7d93020 +0x0020 _IO_write_base       : char *
    0x00007ffff7d93028 +0x0028 _IO_write_ptr        : char *
    0x00007ffff7d93030 +0x0030 _IO_write_end        : char *
    0x00007ffff7d93038 +0x0038 _IO_buf_base         : char *
    0x00007ffff7d93040 +0x0040 _IO_buf_end          : char *
    0x00007ffff7d93048 +0x0048 _IO_save_base        : char *
    0x00007ffff7d93050 +0x0050 _IO_backup_base      : char *
    0x00007ffff7d93058 +0x0058 _IO_save_end         : char *
    0x00007ffff7d93060 +0x0060 _markers             : struct _IO_marker *
    0x00007ffff7d93068 +0x0068 _chain               : struct _IO_FILE *
    0x00007ffff7d93070 +0x0070 _fileno              : int
    0x00007ffff7d93074 +0x0074 _flags2              : int
    0x00007ffff7d93078 +0x0078 _old_offset          : __off_t
    0x00007ffff7d93080 +0x0080 _cur_column          : short unsigned int
    0x00007ffff7d93082 +0x0082 _vtable_offset       : signed char
    0x00007ffff7d93083 +0x0083 _shortbuf            : char [1]
    0x00007ffff7d93088 +0x0088 _lock                : _IO_lock_t *
    0x00007ffff7d93090 +0x0090 _offset              : __off64_t
    0x00007ffff7d93098 +0x0098 _codecvt             : struct _IO_codecvt *
    0x00007ffff7d930a0 +0x00a0 _wide_data           : struct _IO_wide_data *
    0x00007ffff7d930a8 +0x00a8 _freeres_list        : struct _IO_FILE *
    0x00007ffff7d930b0 +0x00b0 _freeres_buf         : void *
    0x00007ffff7d930b8 +0x00b8 __pad5               : size_t
    0x00007ffff7d930c0 +0x00c0 _mode                : int
    0x00007ffff7d930c4 +0x00c4 _unused2             : char [20]

이런식으로 주소를 넣어서 사용할수도 있다

C
pwndbg> dt "struct _IO_jump_t" 
struct _IO_jump_t
    +0x0000 __dummy              : size_t
    +0x0008 __dummy2             : size_t
    +0x0010 __finish             : _IO_finish_t
    +0x0018 __overflow           : _IO_overflow_t

참고로 점프테이블도 _IO_jump_t 구조체으로 검색을 해야한다. _IO_file_jump 같은건 심볼명이라 구조체의 이름으로 검색해야함

reverse

C
(gdb) record full
(gdb) rwatch *(char*)0x5555555b7ba0
(gdb) continue
# → 워치포인트 히트

# 이제 이 시점에서 과거로 돌아가며, 누가 이 주소에 값을 넣었는지 확인
(gdb) reverse-stepi        # 한 명령어씩 거꾸로
# 또는
(gdb) reverse-continue     # 큰 단위로 거꾸로

다음과 같이 거꾸로 돌아갈 수 있다.

분석할때 pause()

우리가 pwntools payload를 작성하면서 디버깅을 attach하면 당연히 처음부터 된다. 이때 계속 어디에 bp를 걸고 c 를 연타하기가 상당히 불편하다. 따라서 payload가 어느정도 진행된 시점으로 바로 가고 싶을때가 있다. 이때는

C

create(9,0x40, payload , b"hi zzzz") 
pause()
create(10,0x40, payload , b"hi zzzz") 

다음과 같이 중간에 pause() 를 넣어주면 된다.

그럼

C
[DEBUG] Received 0xa bytes:
    b'content > '
[DEBUG] Sent 0x7 bytes:
    b'hi zzzz'
[*] Paused (press any to continue)

프로그램이 진행되는 셸에서는 다음과 같이 멈추고

C
06:0030│     0x7fffffffee30 ◂— 0x21 /* '!' */
07:0038│     0x7fffffffee38 —▸ 0x7ffff7fc1000 ◂— jg 0x7ffff7fc1047
──────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────
 ► 0   0x7ffff7fe3290 _start
   1              0x1
   2   0x7fffffffefc5
   3              0x0
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg> c
Continuing.
Reading /usr/lib/debug/.build-id/49/0fef8403240c91833978d494d39e537409b92e.debug from remote target...

gdb에서는 다음과 같이 멈추는데

gdb에서 그냥 crtl + c 하면 된다. 그럼 pause가 된 순간에서 디버깅을 할 수 있고 여기서 추가적인 bp를 걸어서 원하는 코드에서 멈춰 상태를 볼 수 있다.

gdbserver_start.sh

전에는 그냥

C
gdb -ex "target remote :31234" "$@"

다음과 같이 gdb만 열어주는 셸 코드 였는데

같은 곳에 bp를 gdb가 실행될때마다 해야 하는 것이 귀찮았다.

#!/bin/bash

PORT=31234

if [ $# -eq 0 ]; then
    # 인자 없이 실행: 그냥 remote 붙기만
    gdb -q -ex "target remote :${PORT}"
else
    # 넘긴 모든 인자를 브포 주소로 사용
    # 예) ./gdbserver_start.sh 0x401020 0x401030
    #  -> b *0x401020, b *0x401030 자동 설정
    CMD=(gdb -q -ex "target remote :${PORT}")

    for BP_ADDR in "$@"; do
        CMD+=(-ex "b *${BP_ADDR}")
    done

    "${CMD[@]}"
fi

따라서 다음과 같이 업그레이드를 했는데 사용법은 다음과 같다

C
root@e8a40ad7311f:/home/pwn# ./gdbserver_start.sh 

→ 그냥 gdb만 열기

C
root@e8a40ad7311f:/home/pwn# ./gdbserver_start.sh 0x555555555346

→ 0x555555555346 에만 bp 걸기

C
root@e8a40ad7311f:/home/pwn# ./gdbserver_start.sh 0x555555555346 0x555555555374 0x555555555391

→ 3곳에 bp 걸기

이런식으로 가변인자만큼 bp를 거는 코드로 바꾸었으니 많은 사용 바란다.

turn on alsr

kernel vmmap

vmlinux 같은 커널을 gdb로 돌릴때는 vmmap이 되지 않는다.

C
 maintenance info sections 

이럴때 다음과 같은 명령어로 커널의 영역을 대략 확인 할 수 있다.