[DH] Unsimple_C_calculator
introduction
ACSC에 출제된 Unsimple_C_calculator에 대한 writeup입니다. 해당 writeup에서는 필요했던 리소스만 정리합니다.
Resources
eh_frame_hdr
struct unw_eh_frame_hdr
{
unsigned char version;
unsigned char eh_frame_ptr_enc;
unsigned char fde_count_enc;
unsigned char table_enc;
};
frame_hdr_cache
static struct frame_hdr_cache_element
{
_Unwind_Ptr pc_low;
_Unwind_Ptr pc_high;
_Unwind_Ptr load_base;
const ElfW(Phdr) *p_eh_frame_hdr;
const ElfW(Phdr) *p_dynamic;
struct frame_hdr_cache_element *link;
} frame_hdr_cache[FRAME_HDR_CACHE_SIZE];
CIE Payload
11 00 00 00 ; length = 17
00 00 00 00 ; CIE id = 0
01 ; version
7A 50 00 ; "zP\0"
01 ; code_alignment_factor = ULEB128(1)
78 ; data_alignment_factor = SLEB128(-8)
10 ; return_address_register = ULEB128(16)
09 ; augmentation data length = 9 (1 + 8)
00 ; personality pointer encoding = DW_EH_PE_absptr
AA AA AA AA AA AA AA AA ; personality 함수의 절대주소
Monologue
UW 과정은 현재 frame에 대한 초기화 후 uw_frame_state_for를 통해 한 겹씩 unwinding을 진행한다. 이 부분에 RCE를 하기 위해선 첫번째 initialization을 통과한 후 personality function을 통해 호출하는 경로를 이용해야 한다. 제대로 DWARF Oriented Programming을 하려면 http://savannah.nongnu.org/projects/katana를 이용하면서 작업해야 한다. 그러나 현재는 win 함수만 트리거 하면 되기 때문에 간단하게 구현한다. 물론 그 과정도 상당히 까다롭다.
uw_init_context_1
은 code = uw_frame_state_for (context, &fs);
로 시작된다.
이후
/* Force the frame state to use the known cfa value. */
_Unwind_SetSpColumn (context, outer_cfa, &sp_slot);
fs.regs.cfa_how = CFA_REG_OFFSET;
fs.regs.cfa_reg = __builtin_dwarf_sp_column ();
fs.regs.cfa_offset = 0;
uw_update_context_1 (context, &fs);
/* If the return address column was saved in a register in the
initialization context, then we can't see it in the given
call frame data. So have the initialization context tell us. */
context->ra = __builtin_extract_return_addr (outer_ra);
uw_update_context_1
라는 함수를 한 번 더 통과하고 나서야
initialization 단계가 끝난다.
그러나, 이 과정만 통과하면, unwinding loop에서
code = uw_frame_state_for (&cur_context, &fs);
에서_URC_NO_REASON
반환fs.personality = &win
으로 설정 이렇게 끝낼 수 있다.
이를 위해서 총 2회 가짜 frame을 생성해야 한다. 그러나 initialization step에서 그냥 [PIE: PIE + 0xFFFFFFFF]구간을 캡쳐하도록 하면, 계속 해당 frame이 호출되므로 한번만 짜면 해결이다.
search_object 단계 (우리의)목표 (initialization 단계)
- ra를 등록해서 이후 단계 함수를 trigger하는 것이 목표.
- s->b.sorted = 1
- s->b.encoding = 0x50 // aligned
여기까지
binary_search_single_encoding_fdes
에 접근 b.sorted
는fde_vector
로 해석되고,count
를 1로 설정u.sort=fde_vector
의array
에 순서대로p1
,p2
저장p1
은 시작 주소 (0x0)를 저장한 곳을 가리키고,p2
는 길이 (-1)를 저장한 곳을 가리키도록 한다. 이렇게 하면 pc가 무조건 범위 내로 들어와서 통과- 최종적으로
p1
이 반환
p1
은 FDE로 해석되기에 FakeFDE1을 올려둔 writeable space여야 한다.
이후 _Unwind_Find_FDE
에서
bases->tbase = ob->tbase;
bases->dbase = ob->dbase;
encoding = ob->s.b.encoding;
if (ob->s.b.mixed_encoding) // 해당 없음
encoding = get_fde_encoding (f);
// func에 f->pc_begin->
read_encoded_value_with_base(encoding, base_from_object(encoding, ob), f->pc_begin, &func);
bases->func = (void *) func;
위처럼 context의 bases를 업데이트하고 f(=fde에 저장)를 반환한다.
read_encoded_value_with_base(e,b,p,t)는 t에 p가 가리키는 값을 저장한다.
이후 uw_frame_state_for
로 돌아오면 fs->pc = context->bases.func
를 통해 fs->pc
애 값이 저장됨을 확인할 수 있다.
아래는 CIE 변조 이전까지의 내용을 정리한 것이다. writeable space는 hdr_cache를 사용하기로 했다.
그곳 또한 DWARF-OP에 사용될 수 있으나 add.1
변조가 불가하여 cache를 사용한 방법대신 위처럼 직접 initialize하는 방법을 사용한다.
Credits
https://releases.llvm.org/15.0.0/docs/AMDGPUDwarfExtensionsForHeterogeneousDebugging.html