hhkb.dev

BLOG

2025-10-16 / 어셈블리 · 08

레지스터 조작을 통한 실행 흐름 제어

디버거를 쓰면 실행 중인 프로그램의 메모리나 레지스터 값을 직접 바꿀 수 있다. 이런 식으로 실행 도중 상태를 바꾸는 걸 런타임 패치라고 부른다. 인증 우회나 취약점 검증처럼 실제 흐름을 시험해볼 때 자주 쓴다.

1. 값 변경: set 명령어

GDB에서는 set 명령으로 레지스터 값을 바로 바꿀 수 있다.

set $레지스터 = 값

2. 실행 흐름 조작(RIP)

RIP는 다음에 실행할 명령어 주소를 담고 있다. 그래서 이 값을 바꾸면 실행 흐름 자체를 강제로 옮길 수 있다.

(gdb) set $rip = 0x401234

예를 들어 로그인 실패 루틴 직전에 RIP를 성공 루틴 주소로 바꾸면 인증 우회가 가능할 수 있다. 다만 스택 상태가 맞지 않으면 바로 크래시가 날 수 있다.

3. 조건 분기 조작(EFLAGS)

대부분의 분기문은 RFLAGS 상태를 보고 움직인다. 그래서 플래그만 조작해도 분기 결과를 바꿀 수 있다.

test rax, rax   ; rax가 0인지 확인
jz fail         ; 0이면(ZF=1) fail로 점프

원래라면 fail로 가야 하는 상황이어도, ZF를 강제로 바꾸면 다음 코드로 흐름을 틀 수 있다.

; ZF(Zero Flag) 비트를 강제 해제
set $eflags &= ~(1 << 6)

이 패치는 메모리에서만 유효하다. 프로그램을 다시 실행하면 원래 상태로 돌아간다. 영구적으로 바꾸려면 바이너리 자체를 수정해야 한다.

NOP (No Operation) 패치
흐름을 점프시키는 대신, 특정 명령 자체를 무력화하고 싶을 때는 기계어를 0x90(NOP)으로 덮어쓴다. 예를 들어 인증 확인용 call을 지워버리는 식이다. 보통은 이런 식의 수정을 NOP 패치라고 부른다.