오늘은 마찬가지로 angr CTF문제를 이어서 풀어보도록 하겠습니다.
12_angr_veritesting
veritesting은 검증적 테스팅 으로 angr가 지원하는 강력한 기능 중 하나라고 합니다.
문제 보면 scanf에서 인자를 엄청나게 많이 받는데, 저 인자들에 대해 if문과 같은 분기에 들어가면 경로폭발이 발생할 수 있을 것 같습니다.
문제 풀이 파일을 봤는데
# When you construct a simulation manager, you will want to enable Veritesting:
# project.factory.simgr(initial_state, veritesting=True)
# Hint: use one of the first few levels' solutions as a reference.
이거 3줄밖에 없더라고요 .. ??
처음 몇단계의 솔루션을 가져오고, simgr에서 veritesting 모드를 활성화 하라고 합니다.
일단 잘 모르겠어서 기존 02_angr_find_condition의 solve를 가져와서 veritesting=True만 켜주었습니다.
solve 전문입니다.
# When you construct a simulation manager, you will want to enable Veritesting:
# project.factory.simgr(initial_state, veritesting=True)
# Hint: use one of the first few levels' solutions as a reference.
import sys
import angr
def main ( argv ):
project = angr.Project( "./12_angr_veritesting" )
initial_state = project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS})
simulation = project.factory.simgr(initial_state, veritesting = True )
def is_successful ( state ):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b "Good Job." in stdout_output
def should_abort ( state ):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b "Try again." in stdout_output
simulation.explore( find = is_successful, avoid = should_abort)
if simulation.found:
solution_state = simulation.found[ 0 ]
print (solution_state.posix.dumps(sys.stdin.fileno()).decode())
else :
raise Exception ( 'Could not find the solution' )
if __name__ == '__main__' :
main(sys.argv)
실행 결과입니다.
veritesting이라는 개념 자체가 좀 어려운 개념인 것 같은데, 해당 기능을 angr라는 툴에서는 쉽게 지원한다! 라는 취지의 문제인 듯 싶습니다. 해당 개념에 관해서는 논문이 한두편 있던데 이건 지금 읽어보고 있는 논문만 읽고 읽어보고 올려드리겠습니다.
13_angr_static_binary
이번 문제는 제목처럼 정적 링크 문제입니다.
문제를 보면 굉장히 평범해 보이지만
정적 링킹은 프로그램을 만들 때 필요한 모든 라이브러리 코드를 실행 파일 안에 복사해서 넣는 방식입니다.
실제로 import칸엔 아무 함수도 없습니다.
저번에 scanf를 sim으로 대체한거랑 비슷한 느낌으로 풀면 될 것 같은데, 선언되어 있는 함수가 너무 많아 일일히 연결하기엔 무리가 있습니다.
문제 파일을 보니까
# 이 도전은 첫 번째 도전과 정확히 동일하지만
# 정적 이진 파일로 컴파일됩니다. 일반적으로 Angr은 표준을 자동으로 대체합니다
# 라이브러리는 훨씬 더 빠르게 작동하는 SimProcedures와 함께 작동합니다.
#
# 문제를 해결하려면 다음과 같은 표준 라이브러리 c 함수를 수동으로 연결합니다
# 를 사용합니다. 그런 다음, 실행을 시작할 때
# 주 함수. entry_state를 사용하지 않습니다.
#
# 다음은 Angr이 이미 작성한 몇 가지 SimProcedures입니다. 그들은 다음을 구현합니다
# 표준 라이브러리 함수. 모든 것이 필요하지 않을 것입니다:
# angr.SIM_PRODUESS ['libc']['malloc']
# angr.SIM_PRODUESS ['libc']['열림']
# angr.SIM_PRODUESS ['libc']['fclose']
# angr.SIM_PRODUESS ['libc']['작성']
# angr.SIM_PRODUESS ['libc']['getchar']
# angr.SIM_PRODUESS ['libc']['strncmp']
# angr.SIM_PRODUESS ['libc']['strcmp']
# angr.SIM_PRODUESS ['libc']['scanf']
# angr.SIM_PRODUESS ['libc']['printf']
# angr.SIM_PRODUESS ['libc']['풋스]
# angr.SIM_PRODUESS ['libc']['출구']
#
# 참고로, 함수를 다음과 유사한 것으로 연결할 수 있습니다:
# project.hook(malloc_address, anggr.SIM_PROCEDUES['libc']['malloc']( )
#
# 더 많은 것들이 있습니다, 보세요:
# https://github.com/angr/angr/tree/master/angr/procedures/libc
#
# 또한 바이너리가 실행될 때 주 함수는 다음과 같습니다
# 첫 번째 코드 조각이 호출되었습니다. _시작 함수에서 __libc_start_main은 다음과 같습니다
# 프로그램을 시작하기 위해 호출됩니다. 이 함수에서 발생하는 초기화
# Angr을 사용하면 시간이 오래 걸릴 수 있으므로 SimProcessure로 교체해야 합니다.
# angr.SIM_PRODUESS ['glibc']['_libc_start_main']
# 'libc' 대신 'glibc'를 주목하세요.
라는 힌트가 있는데 github.com/링크로 들어가보겠습니다.
음 슥 보니 저번 문제 풀때는 scanf를 대신하는 클래스를 저희가 만들었었는데, 이번에는 angr가 이미 기능으로 만들어뒀기에 저희는 편하게
# As a reminder, you can hook functions with something similar to:
# project.hook(malloc_address, angr.SIM_PROCEDURES['libc']['malloc']())
이런식으로 연결만 하면 될 것 같습니다.
문제 설명에서는 main함수도 glibc __libc_start_main으로 교체하라고 하네요.
위 코드를 말하는 듯 싶습니다.
일단 코드를 먼저 짜보겠습니다.
import angr
import claripy
import sys
def main ( argv ):
project = angr.Project( "./13_angr_static_binary" )
initial_state = project.factory.entry_state()
project.hook( 0x 804AF00 , angr.SIM_PROCEDURES[ 'glibc' ][ '__libc_start_main' ]())
project.hook( 0x 804CBC0 , angr.SIM_PROCEDURES[ 'libc' ][ 'scanf' ]())
project.hook( 0x 804CBF0 , angr.SIM_PROCEDURES[ 'libc' ][ 'printf' ]())
project.hook( 0x 8057E90 , angr.SIM_PROCEDURES[ 'libc' ][ 'puts' ]())
project.hook( 0x 8064400 , angr.SIM_PROCEDURES[ 'libc' ][ 'strcmp' ]())
simulation = project.factory.simgr(initial_state)
def is_successful ( state ):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b "Good Job." in stdout_output
def should_abort ( state ):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b "Try again." in stdout_output
simulation.explore( find = is_successful, avoid = should_abort)
if simulation.found:
solution_state = simulation.found[ 0 ]
print (solution_state.posix.dumps(sys.stdin.fileno()))
else :
raise Exception ( 'Could not find the solution' )
if __name__ == '__main__' :
main(sys.argv)
문제 코드는 비교적 간단하게 나왔습니다.
실행결과입니다.
14_angr_shared_library
크게 어려워 보일 것 없는 바이너리지만 제목에서 알 수 있듯이 공유 라이브러리를 사용합니다.
입력값을 검사하는 함수인 validate가 14_angr_shared_library.so에 따로 포함되어 있습니다.
다행이 angr는 공유 라이브러리를 같이 분석할 수 있는 기능이 있습니다.
# The shared library is compiled with position-independent code. You will need
# to specify the base address. All addresses in the shared library will be
# base + offset, where offset is their address in the file.
# (!)
base = ???
project = angr.Project(path_to_binary, load_options = {
'main_opts' : {
'base_addr' : base
}
})
진짜 모르겠어서 제미나이한테 이것 저것 물어봤는데,
project = angr.Project(path_to_binary, auto_load_libs = True )
auto_load_libs 옵션을 켜주면 공유 라이브러리도 angr가 알아서 분석해주는 기능이 있더라고요.
import angr
import sys
def main ( argv ):
path_to_binary = "./14_angr_shared_library"
project = angr.Project(path_to_binary, auto_load_libs = True )
initial_state = project.factory.entry_state()
simulation = project.factory.simulation_manager(initial_state)
def is_successful ( state ):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b "Good Job" in stdout_output
def should_abort ( state ):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b "Try again" in stdout_output
simulation.explore( find = is_successful, avoid = should_abort)
if simulation.found:
solution_state = simulation.found[ 0 ]
print ( "[!] 정답 발견:" , solution_state.posix.dumps(sys.stdin.fileno()))
else :
print ( "[-] 정답을 찾지 못했습니다." )
if __name__ == '__main__' :
main(sys.argv)
이렇게 간단한 코드만으로도 정답이 찾아집니다.
하지만 저희는 출제자의 의도를 찾아봐야하기 때문에 다시 처음부터 해보겠습니다 ..
base에는 파일의 base주소를 찾아서 넣었습니다
# Initialize any symbolic values here; you will need at least one to pass to
# the validate function.
# (!)
buffer_pointer = claripy.BVV( ??? , ??? )
여기에는 가짜 비밀번호를 저장할 메모리 주소이고 아무 값이나 넣으면 됩니다.
initial_state = project.factory.call_state(
validate_function_address,
buffer_pointer,
claripy.BVV( 8 , 32 )
)
해당 코드는 validate를 실행하기 직전으로 이동해서 함수 인자 규약을 맞추는 것입니다.
password = claripy.BVS( ??? , ??? )
initial_state.memory.store( ??? , ??? )
password은 8바이트니까 설정해주고,
해당 값을 아까 만들어둔 메모리주소에 저장합니다.
check_constraint_address = base + ???
simulation.explore( find = check_constraint_address)
이건 공유 라이브러리에서 ret주소를 찾아서 넣는 과정입니다. odjdump로 봤을때
마지막 ret이 들어있는 주소를 확인하면 됩니다.
# Determine where the program places the return value, and constrain it so
# that it is true. Then, solve for the solution and print it.
# (!)
solution_state.add_constraints( ??? )
solution = ???
여기서 solution은 함수의 반환값입니다.
함수의 리턴값은 항상 eax 레지스터에 저장되므로 eax 레지스터가 1이 되어야 한다는 제약을 걸면 됩니다.
solution_state.add_constraints(solution_state.regs.eax == 1 )
안되서 중간중간 주소값들을 변경했습니다.
일단 path_to_binary에서 대상이 .so파일이고, base는 0x0
function 주소도 실행파일에서가 아닌 .so파일에서의 주소인 1234를 넣어줬습니다.
전체 코드는 다음과 같습니다.
import angr
import claripy
import sys
def main ( argv ):
path_to_binary = "./14_angr_shared_library.so"
base = 0x 0
project = angr.Project(path_to_binary, load_options = {
'main_opts' : {
'base_addr' : base
}
})
buffer_pointer = claripy.BVV( 0x 77777777 , 32 )
validate_function_address = 0x 1234
initial_state = project.factory.call_state(
validate_function_address,
buffer_pointer,
claripy.BVV( 8 , 32 )
)
password = claripy.BVS( 'password' , 8 * 8 )
initial_state.memory.store( buffer_pointer , password )
simulation = project.factory.simgr(initial_state)
check_constraint_address = base + 0x 12e0
simulation.explore( find = int (check_constraint_address))
if simulation.found:
solution_state = simulation.found[ 0 ]
solution_state.add_constraints(solution_state.regs.eax == 1 )
solution = solution_state.solver.eval(password, cast_to = bytes )
print (solution)
else :
raise Exception ( 'Could not find the solution' )
if __name__ == '__main__' :
main(sys.argv)
실행 결과입니다.
이상입니다.
이제 3문제 남았는데, arbitrary_read, write, jump입니다.
일단 3문제로 포스팅을 마무리 하도록 하겠습니다.
아마 다음편이 angr 마지막편이 될 것 같은데 단기간에 참 열심히한 것 같네요.
이 ctf 시리즈가 끝나고 나면 angr를 이용해서 다른 무언가를 해보고 싶습니다.
뭘 해야될진 생각해봐야겠네요.
읽어주셔서 감사합니다~