Angr in function in the middle

이번에는 함수에 중간에 angr로 들어갈때를 생각해보겠다.

이때는 솔직히 말하면 어차피 몇줄 앞이 success address기 때문에 다른 레지스터나 메모리 값은 아무 의미 없다.

따라서, angr에 다른 심볼릭을 없애기 위해 맨처음 state를 만들때

C
# 스택/프레임 수동 세팅
state = p.factory.blank_state(addr=start_addr)
state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY)
state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS)

와 같이 사용할 수있다.

즉, 초기화되지 않은 메모리/레지스터를 읽을 때 새 심볼릭을 만들지 않고 0으로 간주하게 되서 angr에 경로를 많이 제한 할 수 있다.

또한 함수 중간에 들어가면, rbp-0x10 처럼 rbp base code로 진행된다. 따라서, 내가 스택을 설계를 해준다음에

state에 레지스터로 rbp와 rsp를 적당히 지정해줘야한다.

C
import angr, claripy

p = angr.Project("./prob", auto_load_libs=False)
base = p.loader.main_object.min_addr

# 디스어셈 기준 오프셋들 (PIE면 base 더하기)
start_addr   = base + 0x1517   # vuln 내부의 prologue 이후 첫 명령에 맞춰 주세요
success_addr = base + 0x1569   # "STAFF" 분기로 점프하는 주소
fail_addr    = base + 0x1550   # 실패 메시지 분기 시작 주소(대충 피하면 됨)

# <<< 이 값을 실제 오프셋으로 바꾸세요! >>>
buf_offset = -0x619    # 예시: [rbp-0x80]이 buf라면 -0x80

# 스택/프레임 수동 세팅
state = p.factory.blank_state(addr=start_addr)
state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_MEMORY)
state.options.add(angr.options.ZERO_FILL_UNCONSTRAINED_REGISTERS)

# 임의의 가상 메모리 어드레스에 버퍼 마련
buf_addr = 0x70000000
input_size = 8  # 코드에서 ESI=8
sym = claripy.BVS("buf", input_size * 8)

# 조건: "YISF"가 포함되어야 strstr 통과 -> 가장 단순히 앞 4바이트를 고정
state.solver.add(sym.get_bytes(0,4) == b"YISF")

# 버퍼를 메모리에 써넣음
state.memory.store(buf_addr, sym)

# RBP를 조정해서 [rbp+buf_offset] == buf_addr 가 되게 함
# 즉 rbp = buf_addr - buf_offset
rbp_val = buf_addr - buf_offset
state.regs.rbp = rbp_val

# (선택) RSP도 적당히 잡아주면 안정적
state.regs.rsp = rbp_val - 0x100

# 이제 함수 안에서 lea rax, [rbp+buf] -> 우리가 쓴 buf_addr 로 해석됨
# strstr/puts/exit 등 외부는 SimProc로 처리

simgr = p.factory.simulation_manager(state)
simgr.explore(find=success_addr, avoid=[fail_addr])

if simgr.found:
    st = simgr.found[0]
    model = st.solver.eval(sym, cast_to=bytes)
    print("[+] Found input:", model)
else:
    print("[-] Solution not found")