반응형
음.. 이전 문제와 비슷한거 같은데.. scanf가 %u %u로 되어 있으니 시작 주소는 scanf 주소 이후로 맞춰준다.
start_address = 0x8048697
initial_state = project.factory.blank_state(
addr=start_address,
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
* 시작주소는 scanf 이 후 어떤 주소든 상관없다. complex_function0 위로 잡으면 된다.
이 문제는 전과 같이 scanf로 받은 값을 레지스터에 저장을 안하고 스택에서 사용을 한다.
이래서 symbolic stack인가 보다.
initial_state.regs.ebp = initial_state.regs.esp
password0 = claripy.BVS('password0', 32)
password1 = claripy.BVS('password1', 32)
우선 password는 두개로 만들어준다. (%u %u) 이기 때문!
또한, 스택 내부에서 이루어 진기 때문에 initial_state를 추가해준다.
스택의 크기는 0x18인데, 변수는 0x10, 0xC에 위치하고 있다.
변수는 크기가 4바이트 이므로 0xC에서 4를 빼면 0x8
스택의 시작부터 변수의 시작까지 0x8(패딩)이므로 빼준다.
padding_length_in_bytes = 0x8 # :integer
initial_state.regs.esp -= padding_length_in_bytes
스택에 변수들을 넣어 준다.
initial_state.stack_push(password0) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
initial_state.stack_push(password1) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
# This challenge will be more challenging than the previous challenges that you
# have encountered thus far. Since the goal of this CTF is to teach symbolic
# execution and not how to construct stack frames, these comments will work you
# through understanding what is on the stack.
# ! ! !
# IMPORTANT: Any addresses in this script aren't necessarily right! Dissassemble
# the binary yourself to determine the correct addresses!
# ! ! !
import angr
import claripy
import sys
def main(argv):
path_to_binary = '04_angr_symbolic_stack'
project = angr.Project(path_to_binary)
# For this challenge, we want to begin after the call to scanf. Note that this
# is in the middle of a function.
#
# This challenge requires dealing with the stack, so you have to pay extra
# careful attention to where you start, otherwise you will enter a condition
# where the stack is set up incorrectly. In order to determine where after
# scanf to start, we need to look at the dissassembly of the call and the
# instruction immediately following it:
# sub $0x4,%esp
# lea -0x10(%ebp),%eax
# push %eax
# lea -0xc(%ebp),%eax
# push %eax
# push $0x80489c3
# call 8048370 <__isoc99_scanf@plt>
# add $0x10,%esp
# Now, the question is: do we start on the instruction immediately following
# scanf (add $0x10,%esp), or the instruction following that (not shown)?
# Consider what the 'add $0x10,%esp' is doing. Hint: it has to do with the
# scanf parameters that are pushed to the stack before calling the function.
# Given that we are not calling scanf in our Angr simulation, where should we
# start?
# (!)
start_address = 0x8048697
initial_state = project.factory.blank_state(
addr=start_address,
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)
# We are jumping into the middle of a function! Therefore, we need to account
# for how the function constructs the stack. The second instruction of the
# function is:
# mov %esp,%ebp
# At which point it allocates the part of the stack frame we plan to target:
# sub $0x18,%esp
# Note the value of esp relative to ebp. The space between them is (usually)
# the stack space. Since esp was decreased by 0x18
#
# /-------- The stack --------\
# ebp -> | |
# |---------------------------|
# | |
# |---------------------------|
# . . . (total of 0x18 bytes)
# . . . Somewhere in here is
# . . . the data that stores
# . . . the result of scanf.
# esp -> | |
# \---------------------------/
#
# Since we are starting after scanf, we are skipping this stack construction
# step. To make up for this, we need to construct the stack ourselves. Let us
# start by initializing ebp in the exact same way the program does.
initial_state.regs.ebp = initial_state.regs.esp
# scanf("%u %u") needs to be replaced by injecting two bitvectors. The
# reason for this is that Angr does not (currently) automatically inject
# symbols if scanf has more than one input parameter. This means Angr can
# handle 'scanf("%u")', but not 'scanf("%u %u")'.
# You can either copy and paste the line below or use a Python list.
# (!)
password0 = claripy.BVS('password0', 32)
password1 = claripy.BVS('password1', 32)
# Here is the hard part. We need to figure out what the stack looks like, at
# least well enough to inject our symbols where we want them. In order to do
# that, let's figure out what the parameters of scanf are:
# sub $0x4,%esp
# lea -0x10(%ebp),%eax
# push %eax
# lea -0xc(%ebp),%eax
# push %eax
# push $0x80489c3
# call 8048370 <__isoc99_scanf@plt>
# add $0x10,%esp
# As you can see, the call to scanf looks like this:
# scanf( 0x80489c3, ebp - 0xc, ebp - 0x10 )
# format_string password0 password1
# From this, we can construct our new, more accurate stack diagram:
#
# /-------- The stack --------\
# ebp -> | padding |
# |---------------------------|
# ebp - 0x01 | more padding |
# |---------------------------|
# ebp - 0x02 | even more padding |
# |---------------------------|
# . . . <- How much padding? Hint: how
# |---------------------------| many bytes is password0?
# ebp - 0x0b | password0, second byte |
# |---------------------------|
# ebp - 0x0c | password0, first byte |
# |---------------------------|
# ebp - 0x0d | password1, last byte |
# |---------------------------|
# . . .
# |---------------------------|
# ebp - 0x10 | password1, first byte |
# |---------------------------|
# . . .
# |---------------------------|
# esp -> | |
# \---------------------------/
#
# Figure out how much space there is and allocate the necessary padding to
# the stack by decrementing esp before you push the password bitvectors.
padding_length_in_bytes = 0x8 # :integer
initial_state.regs.esp -= padding_length_in_bytes
# Push the variables to the stack. Make sure to push them in the right order!
# The syntax for the following function is:
#
# initial_state.stack_push(bitvector)
#
# This will push the bitvector on the stack, and increment esp the correct
# amount. You will need to push multiple bitvectors on the stack.
# (!)
initial_state.stack_push(password0) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
initial_state.stack_push(password1) # :bitvector (claripy.BVS, claripy.BVV, claripy.BV)
simulation = project.factory.simgr(initial_state)
def is_successful(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Good Job." in state.posix.dumps(sys.stdout.fileno())
def should_abort(state):
stdout_output = state.posix.dumps(sys.stdout.fileno())
return b"Try again." in state.posix.dumps(sys.stdout.fileno())
simulation.explore(find=is_successful, avoid=should_abort)
if simulation.found:
solution_state = simulation.found[0]
solution0 = solution_state.solver.eval(password0)
solution1 = solution_state.solver.eval(password1)
solution = str(solution0)+" "+str(solution1)
print(solution)
else:
raise Exception('Could not find the solution')
if __name__ == '__main__':
main(sys.argv)
반응형
'CTF. > Angr Tutorial For CTF' 카테고리의 다른 글
06_angr_symbolic_daynamic_memory #Angr Tutorial For CTF (0) | 2022.08.25 |
---|---|
05_angr_symbolic_memory #Angr Tutorial For CTF (0) | 2022.08.24 |
03_angr_symbolic_registers #Angr Tutorial For CTF (0) | 2022.08.22 |
02_angr_find_condition #Angr Tutorial For CTF (0) | 2022.08.21 |
01_angr_avoid #Angr Tutorial For CTF (0) | 2022.08.20 |
댓글