首页 > 其他分享 >angr-ctf

angr-ctf

时间:2024-01-25 19:12:15浏览次数:39  
标签:password0 stdout state initial solution ctf angr

title: angr_ctf
date: 2023-11-17 14:00:37
tags: CTF

angr 的项目地址

https://github.com/jakespringer/angr_ctf

angr实战

00

拖到IDA

image-20231117140415445

就是输入正确的指令才能通关

这次试一下用angr来解题

goahead@DESKTOP-8KORQ75:/mnt/d/CTF/angr/angr_ctf-master/dist$ workon angr
(angr) goahead@DESKTOP-8KORQ75:/mnt/d/CTF/angr/angr_ctf-master/dist$ python
Python 3.6.9 (default, Mar 10 2023, 16:46:00)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import angr
>>> p = angr.Project("./00_angr_find")		#创建一个工程名
>>> init_state = p.factory.entry_state()	#给它初始化状态为从入口点开始
>>> sm = p.factory.simulation_manager(init_state)	#让angr执行
>>> sm.explore(find = 0x08048678)			# 给它探索的目的地,也就是IDA中分析到的“good job”
WARNING | 2023-11-17 14:12:29,194 | angr.storage.memory_mixins.default_filler_mixin | The program is accessing register with an unspecified value. This could indicate unwanted behavior.
WARNING | 2023-11-17 14:12:29,194 | angr.storage.memory_mixins.default_filler_mixin | angr will cope with this by generating an unconstrained symbolic variable and continuing. You can resolve this by:
WARNING | 2023-11-17 14:12:29,195 | angr.storage.memory_mixins.default_filler_mixin | 1) setting a value to the initial state
WARNING | 2023-11-17 14:12:29,195 | angr.storage.memory_mixins.default_filler_mixin | 2) adding the state option ZERO_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to make unknown regions hold null
WARNING | 2023-11-17 14:12:29,195 | angr.storage.memory_mixins.default_filler_mixin | 3) adding the state option SYMBOL_FILL_UNCONSTRAINED_{MEMORY,REGISTERS}, to suppress these messages.
WARNING | 2023-11-17 14:12:29,195 | angr.storage.memory_mixins.default_filler_mixin | Filling register edi with 4 unconstrained bytes referenced from 0x80486b1 (__libc_csu_init+0x1 in 00_angr_find (0x80486b1))
WARNING | 2023-11-17 14:12:29,196 | angr.storage.memory_mixins.default_filler_mixin | Filling register ebx with 4 unconstrained bytes referenced from 0x80486b3 (__libc_csu_init+0x3 in 00_angr_find (0x80486b3))
WARNING | 2023-11-17 14:12:30,087 | angr.storage.memory_mixins.default_filler_mixin | Filling memory at 0x7ffeff60 with 4 unconstrained bytes referenced from 0x817e690 (strcmp+0x0 in libc.so.6 (0x7e690))
<SimulationManager with 1 active, 16 deadended, 1 found>
>>> sm.found[0]
<SimState @ 0x8048678>
>>> found_state = sm.found[0]
>>> found_state.posix.dumps(0)
b'JXWVXRKX'
>>> found_state.posix.dumps(1)
b'Enter the password: '
##  state.posix.dumps (0) 代表该状态程序的所有输入, state.posix.dumps (1) 代表该状态程序的所有输出。

所以输入JXWVXRKX就可以通关

goahead@DESKTOP-8KORQ75:/mnt/d/CTF/angr/angr_ctf-master/dist$ ./00_angr_find
Enter the password: JXWVXRKX
Good Job.

常规解法

letter_list = ['J', 'A', 'C', 'E', 'J', 'G', 'C', 'S']

def reverse_operation(value, i):
    return chr((value - 65 - 3 * i) % 26 + 65)

# 反向计算得到字母数组 s[8]
s = [reverse_operation(ord(letter), i) for i, letter in enumerate(letter_list)]

print("原始字母数组:", letter_list)
print("通过反向运算得到的字母数组:", ''.join(s))

01

拖到IDA,看到一个函数maybe_good,打开

image-20231117150539723

很显然,我们想要的就是0x080485E0

执行程序

image-20231117150731058

发现和00类似

直接使用angr

(angr) goahead@DESKTOP-8KORQ75:/mnt/d/CTF/angr/angr_ctf-master/dist$ workon angr
(angr) goahead@DESKTOP-8KORQ75:/mnt/d/CTF/angr/angr_ctf-master/dist$ python
Python 3.6.9 (default, Mar 10 2023, 16:46:00)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import angr
>>> p = p.angr.Project("./01_angr_avoid")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'p' is not defined
>>> p = angr.Project("./01_angr_avoid")
>>> init_state=p.factory.entry_state()
>>> sm = p.factory.simulation_manager(init_state)
>>> sm.explore(find=0x080485E0,avoid=0x080485F2) #find的值就是要让angr到达的地址值,而avoid的值是不让angr到达的地址值

>>> found_state = sm.found[0]
>>> found_state.posix.dumps(0)
b'HUJOZMYS'
>>> found_state.posix.dumps(1)
b'Enter the password: '

故输入HUJOZMYS即可通关

image-20231117153253889

02

观察有多个地方输出“Good job”

使用脚本:

# It is very useful to be able to search for a state that reaches a certain
# instruction. However, in some cases, you may not know the address of the
# specific instruction you want to reach (or perhaps there is no single
# instruction goal.) In this challenge, you don't know which instruction
# grants you success. Instead, you just know that you want to find a state where
# the binary prints "Good Job."
#
# Angr is powerful in that it allows you to search for a states that meets an
# arbitrary condition that you specify in Python, using a predicate you define
# as a function that takes a state and returns True if you have found what you
# are looking for, and False otherwise.

import angr
import sys

def main(argv):
  path_to_binary = "./02_angr_find_condition"
  project = angr.Project(path_to_binary)
  initial_state = project.factory.entry_state()
  simulation = project.factory.simgr(initial_state)

  def is_successful(state):		#判断当前状态能否使程序输出 Good Job,然后返回 True or False,
    stdout_output = state.posix.dumps(sys.stdout.fileno()) #把标准输出赋值给 stdout_output ,那不是字符串而是一个bytes 对象
    if b'Good Job.' in stdout_output:  #  要使用 b'Good Job.' 替代 Good Job. 检查是否输出了字符串 Good Job.
      return True  # (3)
    else:
      return False

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    if b'Try again.' in stdout_output:
      return True
    else:
      return False

  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)

输出结果为:b'HETOBRCU'

03 寄存器符号化

发现要输入多个参数(3个)

image-20231122131953858

则我们直接跳过输入,让angr直接从0x8048980 处开始执行

start_address = 0x08048980 # :integer (probably hexadecimal)
initial_state = project.factory.blank_state(addr=start_address)
#这次使用 blank_state() 方法替代了 entry_state() 。通过把 addr=start_address 传递给 blank_state() 

观察反汇编,使用eax,ebx,edx,进行传参,故位数为32位

claripy 通过 BVS() 方法生成三个位向量。这个方法需要两个参数:第一个参数表示符号名,第二个参数表示这个符号的长度 单位bit。因为符号值都保存在寄存器里,并且寄存器都是32位的,所以位向量的大小也需要是32位的。

password0_size_in_bits = 32 # :integer
password0 = claripy.BVS('password0', password0_size_in_bits)
password1 = claripy.BVS('password1', password0_size_in_bits)
password2 = claripy.BVS('password2', password0_size_in_bits)

现在我们已经创建了三个符号位向量,现在就把他们赋值给 eax,ebx,edx。我准备修改先前创建的状态 initial_state,并更新寄存器的内容,幸运的是,angr提供了一个非常智能的方法:

initial_state.regs.eax = password0
initial_state.regs.ebx = password1
initial_state.regs.edx = password2

现在我们准备跟以前一样定义 find , avoid 状态。

simulation = project.factory.simgr(initial_state) 

def is_successful(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  if b'Good Job.\n' in stdout_output:
    return True
  else: return False

def should_abort(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  if b'Try again.\n' in  stdout_output:
    return True
  else: return False 

simulation.explore(find=is_successful, avoid=should_abort)

下面就是打印解了。

if simulation.found:
    solution_state = simulation.found[0]

    # Solve for the symbolic values. If there are multiple solutions, we only
    # care about one, so we can use eval, which returns any (but only one)
    # solution. Pass eval the bitvector you want to solve for.
    # (!) NOTE: state.se is deprecated, use state.solver (it's exactly the same).
    solution0 = format(solution_state.solver.eval(password0), 'x') 
    # 我们根据注入的三个符号值调用求解引擎的 eval()方法; format() 方法格式化解并去掉16进制的 “0x”。
    solution1 = format(solution_state.solver.eval(password1), 'x')
    solution2 = format(solution_state.solver.eval(password2), 'x')

    # Aggregate and format the solutions you computed above, and then print
    # the full string. Pay attention to the order of the integers, and the
    # expected base (decimal, octal, hexadecimal, etc).
    
    #重组3个解,组合为一个字符串,然后打印出来。
    solution = solution0 + " " + solution1 + " " + solution2 # (2)
    print("[+] Success! Solution is: {}".format(solution))
  else:
    raise Exception('Could not find the solution')

if __name__ == '__main__':
  main(sys.argv)

04 符号化栈

手搓一下

F5反汇编进入 handle_user()函数

image-20231122142222044

发现只要将输入的两个数分别与另外两个数进行异或操作,再与两个数比较就能得到正确答案

因此,只需将最终要比较的数分别和那两个数异或就能得到输入(两次异或等于不操作)

使用angr

image-20231122193723533

因为我们要跳过scanf,故call scanf 下面的add esp,10h(平衡堆栈)我们也不用执行

所以,从下一行 0x08048697开始执行

import angr
import claripy
import sys

def main(argv):
    bin_path = "./04_angr_symbolic_stack"
    p = angr.Project(bin_path)
    
    start_addr = 0x08048697
    init_state = p.factory.blank_state(addr = start_addr)

claripy 通过 BVS() 方法生成两个个位向量。

    #ebp = esp
    init_state.regs.esp = init_state.reg.esp
    
    password0 = claripy.BVS('password0',32)
    password1= claripy.BVS('password0',32)
    
    #因为他push 两个参数为 [ebp-0Ch]和[ebp-10h],而栈的地址是向下增长的
 	#一个参数大小为4字节,所以padding 的位置为0x0C-4 = 0x08
    padding_length_in_bytes = 0x08
    #提升堆栈
    initial_state.regs.esp -= padding_length_in_bytes
    #push password0
  	initial_state.stack_push(password0)  
    #push password1
  	initial_state.stack_push(password1)
    
    sm = p.factory.simgr(init_state)
    
    def is_successful(state):
        stdout_output = state.posix.dumps(sys.stdout.fileno())
        if b'Good Job' in stdout_output:
          return True
        else:
          return False

    def should_abort(state):
        stdout_output = state.posix.dumps(sys.stdout.fileno())
        if b'Try again' in stdout_output:
          return True
        else:
          return False
    
    simulation.explore(find=is_successful, avoid=should_abort)
    
      if simulation.found:
      	solution_state = simulation.found[0]  
        solution0 = solution_state.se.eval(password0)
    	solution1 = solution_state.se.eval(password1)
        print("Solution is:{} {}".format(solution0,solution1))
     else:
        raise Exception"Solution not found"
    
if __name__ == '__main__':
    main(sys.argv)

答案输入:

1704280884 -1912626145

或者

1704280884 2382341151

都对!因为将-1912626145转化为无符号整型就是2382341151(python实现将有符号整型a转为无符号整型:print(a+2**32))

05 符号化内存

找到scanf附近,发现需要输入4个变量,并且这4个变量都是通过push内存直接传入的

image-20231123163800289

因此,本次得到目标是符号化内存

首先,看一下从哪个地址开始执行吧,因为无需调用scanf函数,故他的堆栈平衡也不用操作,

所以直接从0x08048601开始执行

start_address = 0x08048601
init_state = project.factory.blank_state(addr = start_address)

claripy 通过 BVS() 方法生成四个位向量。由于scanf("%8s %8s %8s %8s"),所以每个参数64位

password0 = claripy.BVS('password0',64)
password1 = claripy.BVS('password1',64)
password2 = claripy.BVS('password2',64)
password3 = claripy.BVS('password3',64)

把内存单元的地址值和变量之间进行一定关系的绑定

password0_address = 0x0A1BA1C0
password1_address = 0x0A1BA1C8
password2_address = 0x0A1BA1D0
password3_address = 0x0A1BA1D8

initial_state.memory.store(password0_address, password0)
initial_state.memory.store(password1_address, password1)
initial_state.memory.store(password2_address, password2)
initial_state.memory.store(password3_address, password3)

执行

simulation = project.factory.simgr(initial_state)
def is_successful(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  if b'Good Job' in stdout_output:
    return True
  else:
    return False

def should_abort(state):
  stdout_output = state.posix.dumps(sys.stdout.fileno())
  if b'Try again' in stdout_output:
    return True
  else:
    return False

simulation.explore(find=is_successful, avoid=should_abort)

if simulation.found:
  solution_state = simulation.found[0]

  solution0 = solution_state.se.eval(password0, cast_to=bytes)
  solution1 = solution_state.se.eval(password1, cast_to=bytes)
  solution2 = solution_state.se.eval(password2, cast_to=bytes)
  solution3 = solution_state.se.eval(password3, cast_to=bytes)
 #将b'NAXTHGNR' b'JVSFTPWE' b'LMGAUHWC' b'XMDCPALU' 转化为 NAXTHGNR JVSFTPWE LMGAUHWC XMDCPALU
  print("Solution is: {} {} {} {}".format(solution0.decode('utf-8'),solution1.decode('utf-8'),solution2.decode('utf-8'),solution3.decode('utf-8')))
  else:
    raise Exception('Could not find the solution')

结果:NAXTHGNR JVSFTPWE LMGAUHWC XMDCPALU

06符号化堆

观察反汇编

image-20231123175326510

程序使用了malloc动态分配内存,故本次我们要执行符号化堆

先看下从哪开始执行

image-20231123175457697

不调用scanf,所以我们从0x08048699处开始执行

start_address = 0x08048699
initial_state = project.factory.blank_state(addr=start_address)

需要两个参数,所以用 claripy 通过 BVS() 方法生成两个位向量

password0 = claripy.BVS('password0', 64)
password1 = claripy.BVS('password1', 64)

因为malloc是随机分配地址的,所以我们直接指定地址

addr_esp = initial_state.regs.esp
fake_heap_address0 = addr_esp - 0x100
fake_heap_address1 = addr_esp - 0x200

将地址和变量进行绑定,默认情况下,Angr 在内存中存储整数时采用大字节。要使用参数 endness=project.arch.memory_endness。在 x86 架构上,这是小端序。

pointer_to_malloc_memory_address0 = 0x0ABCC8A4
pointer_to_malloc_memory_address1 = 0x0ABCC8AC
initial_state.memory.store(pointer_to_malloc_memory_address0, fake_heap_address0, endness=project.arch.memory_endness)
initial_state.memory.store(pointer_to_malloc_memory_address1, fake_heap_address1, endness=project.arch.memory_endness)

在我们的 fake_heap_address 处存储我们的符号值。查看二进制文件,确定 scanf 从 fake_heap_address 写入的偏移量。

initial_state.memory.store(fake_heap_address0, password0)
initial_state.memory.store(fake_heap_address1, password1)

执行

simulation = project.factory.simgr(initial_state)

  def is_successful(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    if b'Good Job' in stdout_output:
      return True
    else:
      return False

  def should_abort(state):
    stdout_output = state.posix.dumps(sys.stdout.fileno())
    if b'Try again' in stdout_output:
      return True
    else:
      return False


  simulation.explore(find=is_successful, avoid=should_abort)

  if simulation.found:
    solution_state = simulation.found[0]

    solution0 = solution_state.se.eval(password0, cast_to=bytes)
    solution1 = solution_state.se.eval(password1, cast_to=bytes)
    #solution = ???
    print("Solution is: {} {}".format(solution0.decode('utf-8'), solution1.decode('utf-8')))
   # print(solution)
  else:
    raise Exception('Could not find the solution')

if __name__ == '__main__':
  main(sys.argv)

结果是:UBDKLMBV UNOERNYS

07 文件内容符号化

拖到IDA,F5一下

image-20231126160856679

看到文件操作,因此本次目标符号化文件内容

image-20231126161055053

起始地址我们需要在初始化文件之前(因为这里并没有符号化文件名),输入password之后,选择的是ignore_me之后,选的是0x80488D6

start_address = 0x080488D6
initial_state = project.factory.blank_state(addr=start_address)

给它文件名和大小

filename = "OJKSQYDP.txt"
file_size = 0x40

紧接着进行文件文本符号化

password = initial_state.solver.BVS("password",file_size*8)
sim_file = angr.storage.SimFile(filename,content=password,size=file_size)

然后进行相应的 插入操作

initial_state.fs.insert(filename,sim_file)

最后进行文本内容的求解:

sm = project.factory.simulation_manager(initial_state)

结果为 AZOMMMZM

image-20231126161624253

标签:password0,stdout,state,initial,solution,ctf,angr
From: https://www.cnblogs.com/h40fei/p/17987951

相关文章

  • BUUCTF Reverse easyre wp
    使用exeinfo工具查看文件信息使用IDA64位打开文件,再使用Shift+F12打开字符串窗口,发现flag字符串双击跳转到字符串在汇编代码中的存储地址点击字符串下方注释中的跳转链接,即可跳转至引用它的函数对应的汇编代码处按F5反汇编,生成对应汇编代码处的C语言伪代码分析代码。......
  • UofTCTF 2024 比赛记录
    这次的题目挺有意思,难度适中,*开头的代表未做出,简单记录一下解题笔记。IntroductionGeneralInformation题目TheflagformatforallchallengesisUofTCTF{...},caseinsensitive.Ifyouareexperiencingtechnicaldifficultieswithchallenges,supportisonour......
  • KnightCTF 2024 WEB做题记录
    WEBLeviAckerman题目信息LeviAckermanisarobot! N:B:Thereisnoneedtodobruteforce. Author:saifTarget:http://66.228.53.87:5000/我的解答:签到题,题目提示了robot!直接访问robots.txt得到路径Disallow:/l3v1_4ck3rm4n.html再次访问路径得到flagK......
  • CTF-秀-Web(1-3)
    write-up:web-1:题目描述:web签到题解题方法:打开靶机得到一个写着:wherisflag?的页面:先查看一下它的源码:得到一串类似base64的编码,然后把它放进base64里面进行解码一下得到我们的flag:ctfshow{c3a5c6fd-4bc3-45b5-ad68-afd54e4b99d6}web-2:题目描述:最简单的SQL注入解题......
  • CTFHUB-综合过滤练习
    前言靶场地址:www.ctfhub.com综合过滤练习点击查看代码<?php$res=FALSE;if(isset($_GET['ip'])&&$_GET['ip']){$ip=$_GET['ip'];$m=[];if(!preg_match_all("/(\||&|;||\/|cat|flag|ctfhub)/",$......
  • [ACTF2020 新生赛]Upload 1
    [ACTF2020新生赛]Upload1审题文件上传类题型和上一题一样。知识点一句话木马。解题一句话木马构建,在前面加上GIF89a,让其判断为图片,并使用BP抓包后缀改为php时无法上传,更改后缀为phtml时上传成功。打开上传成功的文件页面。看到GIF89a,表示成功上传。蚁剑连接......
  • CTF学习记录pwn篇
    作为一个CTF初学者,在这里记录自己学习刷题的过程,不定期更新。此为pwn篇,有关pwn方向的题目会放在这里,目前来说,这篇会主要更新,其他方向也许会有,敬请期待。目录一、formatstring1.[HUBUCTF2022新生赛]fmt-NSSCTF2.test_format-PolarD&N二、ROP1.03ret2syscall_32-PolarD&N......
  • BUUCTF—Crypto
    BUUCTF—Crypto1.一眼就解密考点:base64我的解答:字符串后面的等号,看来是base大家族,由字母和数字范围来看是base64,不管了,先扔CyberCher,仙女魔法棒变出flag。 2.MD5考点:MD5我的解答:打开文件出现一串由数字和字母拼出的字符串,MD5的特征:长度为32或16。由题目名提示MD5,得出......
  • [GFCTF 2021]web部分题解(更新中ing)
    [GFCTF2021]Baby_Web拿源码环节:打开环境(◡ᴗ◡✿)乍一看什么都没有,F12下没看到js文件,但是看到了出题师傅的提示:“源码藏在上层目录xxx.php.txt里面,但你怎么才能看到它呢?”这时候在思考文件在上层目录中,既然是目录下那就试一下dirsearch扫描先看看后台都有什么(这里就直接......
  • 【SCTF-Round#16】 Web和Crypto详细完整WP
    每天都要加油哦!   ------2024-01-18 11:16:55[NSSRound#16Basic]RCE但是没有完全RCE<?phperror_reporting(0);highlight_file(__file__);include('level2.php');if (isset($_GET['md5_1']) && isset($_GET['md5_2'])) {    if ((str......