$ file FUck_binary
FUck_binary: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=583997ea22be0b90670780cb91b36de37e1100d2, not stripped
shared library를 로드할 수 없다고 오류가 뜬다.
임의로 shared library를 만들어서 오류를 해결한다.
#include <stdio.h>
#include <string.h>
char *flag = "EKO{THIS_IS_FLAG_FOR_LOCAL}";
void get_flag(char *str){
strcpy(str, flag);
}
gcc -fpic -shared fakse_getflag.c -o libget_flag.so
ldd FUck_binary 로 바이너리가 로드하는 라이브러리 경로 확인하고 적당한 위치에 임의로 만든 라이브러리를 복사해놓으면 오류가 해결된다.
.. 분석해보자.
main() 함수 초기에는 team name을 입력하라는 문구를 출력하고, v418에 0x12C 바이트(300 바이트)만큼 입력받는다.
v22=&v18로 v22에 v18 주소를 저장하는데 v22는 rsi 레지스터, 즉 read 함수의 두 번째 인자임을 알 수 있다.
이후 사용자 입력을 가리키는 v22를 가지고 많은 검증을 한다. 이 모든 constraint를 manual하게 구할 수는 없을 것이다.
string view를 보면 “Your flag is %s”로 flag를 출력하는 string이 있다.
main 함수 내 해당 코드를 확인해보면, get_flag 함수 호출 후 v417 값을 출력해준다. get_flag 함수는 libget_flag.so shared 라이브러리 내 함수이다.
마지막으로 “Goodbye!”를 출력하고 종료된다.
AFL을 사용해서 만족하는 입력을 fuzz할 수도 있지만, angr를 사용하면 더 명확하고 정확하게 constraint를 해결할 수 있다.
angr의 symbolic execution engine을 이용하여 프로그램 내 특정 부분을 도달하는데 필요한 입력을 계산할 수 있다.
프로그램 내 어느 지점에서 시작할 것인지(start), 찾는 경로(find), 피해야 할 경로(avoid)의 주소를 알아야 한다.
avoid는 constraint solving에 실패 시 실행이 이동하는 지점으로, jl loc_403A7E 에서 v410 < 76인 경우 0x403A7E로 이동하고, v410 ≥ 76인 경우 flag 출력하는 경로로 실행 흐름이 이동한다.
따라서 avoid를 0x403A7E로 find를 0x403A3B로, start 주소는 main 함수 시작 주소인 0x400B30으로 설정한다.
p = angr.Project('./FUck_binary', auto_load_libs=False)
# set initial state
state = p.factory.blank_state(addr=START)
angr.Project로 바이너리를 로드한 후, p.factory.blank_state(START) 를 통해 시작 지점을 start 주소로 설정한다.
# set initial state
state = p.factory.blank_state(addr=START)
# set initial flag
flag = state.solver.BVS('flag', BUF_LEN*8)
def char(state, c):
return state.solver.And(c <= '~', c >= ' ')
# add constraint to stdin
for c in flag.chop(8):
state.solver.add(char(state, c))
입력값 flag 초기값을 설장한다.
flag의 경우 127개 문자 중에서 값을 가지므로 stdin에 constraint를 추가한다.
# create simulation manager
sm = p.factory.simulation_manager(state)
# create state
sm.use_technique(angr.exploration_techniques.Explorer(find=FIND, avoid=AVOID))
# run Explorer
sm.run()
이후 smulation_manager의 Explorer를 사용하여 find, avoid 주소를 지정하고 constraint를 해결한다. 이후 found stash에 find 주소로 갈 수 있는 state가 저장되고, 이를 해결하기 위해 구한 flag 값을 구할 수 있다.
#!/usr/bin/python
import angr
START=0x400B30
FIND=0x403A3B
AVOID=[0x403A7E + i*60 for i in range(100)]
BUF_LEN=100
def char(state, c):
return state.solver.And(c <= '~', c >= ' ')
p = angr.Project('./FUck_binary', auto_load_libs=False)
# set initial state
state = p.factory.blank_state(addr=START)
# set initial flag
flag = state.solver.BVS('flag', BUF_LEN*8)
# add constraint to stdin
for c in flag.chop(8):
state.solver.add(char(state, c))
# create simulation manager
sm = p.factory.simulation_manager(state)
# create state
sm.use_technique(angr.exploration_techniques.Explorer(find=FIND, avoid=AVOID))
# run Explorer
sm.run()
# get solution
flag_input = sm.one_found.posix.dumps(0)
print(repr(flag_input))
[angr_ctf] 08_angr_constraints (0) | 2022.05.15 |
---|---|
[angr_ctf] 07_angr_symbolic_file (0) | 2022.05.14 |
[angr_ctf] 06_angr_symbolic_dynamic_memory (0) | 2022.05.14 |
[angr_ctf] 05_angr_symbolic_memory (0) | 2022.05.14 |
[angr_ctf] 04_angr_symbolic_stack (0) | 2022.05.14 |