본문 바로가기

Programming

DWARF Byte code in Linux exception handling

DWARF 바이트코드에 대해서 정리를 해보자.


C++ 에서 try - catch 문이 있고, try 내부에서 익셉션이 발생했다고 치자.

이때 실행흐름이 catch 로 가야하는데, 어떻게해서 가게되는가? 이걸 가도록 해주는게

DWARF 바이트코드의 역할이다.


예를들어 아래의 경우를 생각해보자


#include <stdio.h>

#include <iostream>

#include <exception>


void func2(){ throw 1; }

void func1(){ func2(); }


int main(){

try{

func1();

}

catch(int e){

printf("Exception %d\n", e);

}

return 0;

}


throw 1; 에 해당하는 명령이 수행되는 시점에서 stack frame 은 func2 의 스택프레임이다.

여기서 곧바로 EIP 만 catch 속의 부분으로 바꿔버리면 어떻게될까? 당연히 크래시날것이다.

따라서 익셉션이 발생했을때 실행흐름을 catch 쪽의 핸들러로 옮기는것은 간단한 일이 아니다.


libgcc 속의 익셉션 핸들러 마지막부분을 IDA Trace 로 확인해보면 아래와같이 일련의 Stack Unwinding 작업들과 레지스터 값들의 복원작업등을 마치고 마지막에 RCX 를 Push 하고 ret 하는것을 볼 수 있다. 즉 DWARF 바이트코드들을 통해서 스택과 레지스터들을 핸들러쪽으로 급 이동했을때 문제가없도록 맞춰준다는 것이다.


libgcc_s.so.1:00007F28233E19F2 lea     rcx, [rbp+rcx+8]

libgcc_s.so.1:00007F28233E19F7 mov     rdx, [rbp-30h]

libgcc_s.so.1:00007F28233E19FB mov     rbx, [rbp-28h]

libgcc_s.so.1:00007F28233E19FF mov     r12, [rbp-20h]

libgcc_s.so.1:00007F28233E1A03 mov     r13, [rbp-18h]

libgcc_s.so.1:00007F28233E1A07 mov     r14, [rbp-10h]

libgcc_s.so.1:00007F28233E1A0B mov     r15, [rbp-8]

libgcc_s.so.1:00007F28233E1A0F mov     rbp, [rbp+0]

libgcc_s.so.1:00007F28233E1A13 mov     rsp, rcx

libgcc_s.so.1:00007F28233E1A16 retn




throw 시에 일어나는 일은 libgcc 내부에 구현되어있는 DWARF 바이트코드 인터프리터로 실행흐름을 넘기고

해당 throw 가 발생한 영역의 EIP/RIP 에 따라서 eh_frame 섹션의 DWARF 바이트코드를 실행시키는 것이다.


DWARF 바이트코드들은 CIE 랑 FDE 라는 두종류로 구별되는데, CIE 는 모든 익셉션 핸들러들이 공통적으로

실행하게되는 초반의 바이트코드들을 의미하고 FDE 는 익셉션이 발생한 위치별로 catch 로 가기위해 복구해야하는

스택프레임이 달라지는부분이 반영되는 코드들이다.

용어정리를 조금더 하자면 CFA 는 Canonical Frame Address 인데 한마디로 익셉션이 발생한당시의 스택프레임 베이스주소라고 보면된다.


CIE 와 FDE 의 구조는 다음과 같다.


The FDE structure looks like this:


length

CIE_pointer

initial_location

address_range

LSDA pointer (see section 2.3)

instructions

padding


Whereas the CIE structure contains:


length

CIE_id

version

augmentation (string) (see section 2.3)

address_size

segment_size

code_alignment_factor

data_alignment_factor

return_address_register

initial_instructions

padding



자 그러면 DWARF 바이트코드들을 어떻게 덤프해서 볼수있나?


readelf --debug-dump=frames [appname]


이런식으로 볼 수 있다.


00000068 00000120 00000054 FDE cie=00000018 pc=00400c60..00400e10

  DW_CFA_advance_loc: 2 to 00400c62

  DW_CFA_def_cfa_offset: 16

  DW_CFA_offset: r12 (r12) at cfa-16

  DW_CFA_advance_loc: 1 to 00400c63

  DW_CFA_def_cfa_offset: 24

  DW_CFA_offset: r6 (rbp) at cfa-24

  DW_CFA_advance_loc: 6 to 00400c69

  DW_CFA_def_cfa_offset: 32

  DW_CFA_offset: r3 (rbx) at cfa-32

  DW_CFA_advance_loc: 12 to 00400c75

  DW_CFA_def_cfa_offset: 160

  DW_CFA_advance_loc1: 214 to 00400d4b

  DW_CFA_remember_state

  DW_CFA_def_cfa_offset: 32

  DW_CFA_advance_loc: 1 to 00400d4c

  DW_CFA_def_cfa_offset: 24

  DW_CFA_advance_loc: 1 to 00400d4d

  DW_CFA_def_cfa_offset: 16

  DW_CFA_advance_loc: 2 to 00400d4f

  DW_CFA_def_cfa_offset: 8

  DW_CFA_advance_loc: 1 to 00400d50

  DW_CFA_restore_state

  DW_CFA_val_expression: r7 (rsp) (DW_OP_breg7 (rsp): -648)

  DW_CFA_val_expression: r11 (r11) (DW_OP_breg7 (rsp): 0; DW_OP_deref; DW_OP_constu: 4; DW_OP_plus; DW_OP_deref; DW_OP_constu: 707804966; DW_OP_constu: 32; DW_OP_shl; DW_OP_constu: 1465865297; DW_OP_plus; DW_OP_xor; DW_OP_bra: 8; DW_OP_constu: 4198003; DW_OP_skip: 5; DW_OP_constu: 4196998)

  DW_CFA_val_expression: r12 (r12) (DW_OP_constu: 6296768)

  DW_CFA_val_expression: r13 (r13) (DW_OP_constu: 4197418)

  DW_CFA_val_expression: r14 (r14) (DW_OP_constu: 6296782)

  DW_CFA_val_expression: r15 (r15) (DW_OP_constu: 6296488; DW_OP_deref; DW_OP_lit5; DW_OP_swap; DW_OP_lit24; DW_OP_plus; DW_OP_deref; DW_OP_swap; DW_OP_lit1; DW_OP_minus; DW_OP_dup; DW_OP_bra: -11; DW_OP_drop; DW_OP_dup; DW_OP_deref; DW_OP_swap; DW_OP_lit16; DW_OP_plus; DW_OP_deref; DW_OP_constu: 136; DW_OP_plus; DW_OP_dup; DW_OP_deref; DW_OP_swap; DW_OP_lit16; DW_OP_minus; DW_OP_dup; DW_OP_deref; DW_OP_swap; DW_OP_constu: 32; DW_OP_minus; DW_OP_deref; DW_OP_dup; DW_OP_deref_size: 4; DW_OP_swap; DW_OP_lit8; DW_OP_plus; DW_OP_dup; DW_OP_pick: 2; DW_OP_lit4; DW_OP_mul; DW_OP_plus; DW_OP_constu: 7138214; DW_OP_pick: 3; DW_OP_mod; DW_OP_lit4; DW_OP_mul; DW_OP_pick: 2; DW_OP_plus; DW_OP_deref_size: 4; DW_OP_dup; DW_OP_constu: 24; DW_OP_mul; DW_OP_pick: 6; DW_OP_plus; DW_OP_dup; DW_OP_deref_size: 4; DW_OP_pick: 6; DW_OP_plus; DW_OP_constu: 6296806; DW_OP_over; DW_OP_deref_size: 1; DW_OP_over; DW_OP_deref_size: 1; DW_OP_over; DW_OP_lit0; DW_OP_eq; DW_OP_over; DW_OP_lit0; DW_OP_eq; DW_OP_bra: 6; DW_OP_bra: 31; DW_OP_skip: 6; DW_OP_bra: 36; DW_OP_skip: 22; DW_OP_ne; DW_OP_bra: 18; DW_OP_lit1; DW_OP_plus; DW_OP_dup; DW_OP_deref_size: 1; DW_OP_swap; DW_OP_rot; DW_OP_swap; DW_OP_lit1; DW_OP_plus; DW_OP_dup; DW_OP_deref_size: 1; DW_OP_rot; DW_OP_rot; DW_OP_skip: -43; DW_OP_drop; DW_OP_drop; DW_OP_drop; DW_OP_lit8; DW_OP_mul; DW_OP_over; DW_OP_plus; DW_OP_dup; DW_OP_skip: -77; DW_OP_drop; DW_OP_drop; DW_OP_drop; DW_OP_drop; DW_OP_lit8; DW_OP_plus; DW_OP_deref; DW_OP_rot; DW_OP_drop; DW_OP_drop; DW_OP_rot; DW_OP_drop; DW_OP_drop; DW_OP_rot; DW_OP_drop; DW_OP_drop; DW_OP_plus)


여기를 보면 FDE 섹션임을 알 수 있고, CIE=18 즉 18 번 CIE 를 일단 수행한다음 실행되는 부분임을 알 수 있다.


00000018 00000014 00000000 CIE

  Version:               1

  Augmentation:          "zR"

  Code alignment factor: 1

  Data alignment factor: -8

  Return address column: 16

  Augmentation data:     1b


  DW_CFA_def_cfa: r7 (rsp) ofs 8

  DW_CFA_offset: r16 (rip) at cfa-8

  DW_CFA_nop

  DW_CFA_nop


이와같이 익셉션이 발생한 영역별로 어떤 DWARF 바이트코드들이 libgcc 를 통해서 실행될지를 조사할 수 있다.

이들을 더 효과적으로 조사하고 DWARF 바이트코드를 어셈블하기 위한 프레임워크로는 katana 라는것이 있다.

문제는 DWARF Bytecode 각각의 명령어에 대한 매뉴얼이 있는가? 인데... 이것이 딱히 구글링해도 나오지 않는다.

물론 어느정도 명령어 이름들이 직관적이라 약간의 guessing 으로 무슨 역할을 하는건지 알수있긴 한데

완전한 리스트가 있으면 좋을것 같다. 아마도 katana 가 오픈소스이니 그 속을 조사하면 알수있을것 같긴하다.


* 구글링좀 해보니 GLIBC 소스코드에서 어느정도 의미를 알수있다

http://osxr.org/glibc/source/sysdeps/generic/unwind-dw2.c?v=glibc-2.18


참조문서들

https://github.com/jhector/big-jims-map

https://www.defcon.org/images/defcon-20/dc-20-presentations/Branco-Oakley-Bratus/RodrigoBranco.txt