Open2

angr

mojashimojashi

SECCON 2022 qual eguite

  • ghidraで観察すると、eguite::Crackme::onclick で入力した文字列の処理を行なっていそう
  • gdbで見てみると、$rdi+0x90 に長さが入っていそうで、これが 0x2b
  • angrのターン。素直にやると、パスの数が爆発する。追っていくと、爆発している原因は from_str_radix らしい
  • ここだけhookしてみる。atoiとかは既にangrのproceduresにあるだろうから、これを使いたい

https://github.com/angr/angr/blob/74dcdb309bf976d3a70e4599c56beb3b0bb6d1b1/angr/procedures/libc/atoi.py#L8-L14

  • strtol.strtol_innerを使って書いてみる。引数、戻り値はgdbで追って把握。多分戻ってくるのは(エラーコード, 実際の値) のタプルっぽい($rdi, $rdi + 8)が、エラーは踏み潰しても処理的には問題なさそうなのでそうしている。
class SimProcForStrToInt(angr.SimProcedure):
    def run(self):
        destAddr = self.state.regs.rdi
        inputStrAddr = self.state.regs.rsi
        inputLength = self.state.regs.rdx
        base = self.state.regs.rcx
        print(destAddr, inputStrAddr, inputLength, base)
        
        strtol = angr.SIM_PROCEDURES["libc"]["strtol"]
        val = strtol.strtol_inner(inputStrAddr, self.state, self.state.memory, base.concrete_value, False, read_length=inputLength.concrete_value)[1]
        self.state.mem[0x7ffffffa0000].uint64_t = 0
        self.state.mem[destAddr].uint64_t = 0x7ffffffa0000
        self.state.mem[destAddr+8].uint64_t = val
        print(val)
        return claripy.BVV(0x7ffffffa0000, 64)
  • パスがほぼ一本道になって、完了・・・
  • findにラムダ式を渡すと、なぜかそこを超えてしまうことがあるので、制約は後でつけることにして頑張って回避
  • たまたま inputLength が BVV だったためにうまく回っている

全体

# %%
import angr
p = angr.Project("eguite.elf", load_options={'auto_load_libs': True}, main_opts={'base_addr': 0x400000})


# %%
[n.name for n in list(p.loader.symbols) if "radix" in n.name]


# %%
import logging

class SimProcForStrToInt(angr.SimProcedure):
    def run(self):
        destAddr = self.state.regs.rdi
        inputStrAddr = self.state.regs.rsi
        inputLength = self.state.regs.rdx
        base = self.state.regs.rcx
        print(destAddr, inputStrAddr, inputLength, base)
        
        strtol = angr.SIM_PROCEDURES["libc"]["strtol"]
        val = strtol.strtol_inner(inputStrAddr, self.state, self.state.memory, base.concrete_value, False, read_length=inputLength.concrete_value)[1]
        self.state.mem[0x7ffffffa0000].uint64_t = 0
        self.state.mem[destAddr].uint64_t = 0x7ffffffa0000
        self.state.mem[destAddr+8].uint64_t = val
        print(val)
        return claripy.BVV(0x7ffffffa0000, 64)

p.hook_symbol("_ZN4core3num14from_str_radix17h6e12dd4ab354d568E", SimProcForStrToInt())
p.hook_symbol("_ZN4core3num14from_str_radix17h893e9e49eefa3035E", SimProcForStrToInt())
p.hook_symbol("_ZN4core3num14from_str_radix17hd224106a99abd59eE", SimProcForStrToInt())


logging.getLogger("angr.sim_manager").setLevel(logging.INFO)
oncF = p.loader.find_symbol("_ZN6eguite7Crackme7onclick17ha26112793d42c9d8E")

st = p.factory.blank_state(
    addr=0x460170,
    add_options=angr.options.unicorn | set([angr.sim_options.ZERO_FILL_UNCONSTRAINED_MEMORY]),
)

import claripy
flag_len=0x2b - len("SECCON{}")
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(flag_len)]
flag = claripy.Concat(*[b"SECCON{"] + flag_chars + [claripy.BVV(b'}\0')])
st.memory.store(0x7fffffffe000, flag)
st.mem[0x7fffffff0000 + 0x80].uint64_t = claripy.BVV(0x7fffffffe000, 8*8)
st.mem[0x7fffffff0000 + 0x90].uint64_t = claripy.BVV(0x2b, 8*8)

st.regs.rdi = 0x7fffffff0000
for k in flag_chars:
    st.solver.add(k < 0x7f)
    st.solver.add(k > 0x20)

def find(s: angr.SimState):
    return s.addr == 0x0046068e #and (st.regs.rbp ^ st.regs.r13 ^ st.regs.r14) == st.regs.rcx

sim = p.factory.simgr(st)#, techniques=[angr.exploration_techniques.DFS()])
o = sim.explore(avoid=[0x004604db, 0x00460472, 0x0046027f,0x0046046d,0x0046036f, ], find=0x0046068e)
o


# %%
f = o.one_found
f.solver.add(
    f.regs.r13 ^ f.regs.r14 ^ f.regs.rbp == f.regs.rcx
)
f.solver.eval(flag, cast_to=bytes)
mojashimojashi

CakeCTF 2023 qual gaming_vm

  • flag.qvmはconcreteなので複雑性は普通のバイナリと変わらない気がして、angrチャレンジ
  • 実際のところやっぱりpathの数は結構多くなってしまい、普通のsimulationだとダメだった
  • DFSにしたら解決(うーん)
    • たまたま探索順が良かっただけかも?
import angr
p = angr.Project("./gaming_vm/q3vm")
p.loader.shared_objects
import logging
import claripy

logging.getLogger('angr.sim_manager').setLevel('INFO')

# 60は適当にデカく取った
input_len = 60 - len(b"CakeCTF{}")
flag_chars = [claripy.BVS('flag_%d' % i, 8) for i in range(input_len)]
flag = claripy.Concat(*([claripy.BVV(b'CakeCTF{')] + flag_chars))

es = p.factory.entry_state(
    fs={
        "flag.qvm": angr.SimFile(name="flag.qvm", content=open("./gaming_vm/flag.qvm", "rb").read(), has_end=True, concrete=True),
    },
    argc=2,
    args=["./q3vm", "flag.qvm"],
    stdin=flag,
    add_options=angr.options.unicorn | set([angr.sim_options.ZERO_FILL_UNCONSTRAINED_MEMORY]),
)

for k in flag_chars:
    es.solver.add(k < 0x7f)
    
sim = p.factory.simgr(es, techniques=[angr.exploration_techniques.DFS()])
def find(s: angr.SimState):
    return b"Correct!" in s.posix.dumps(1)
out = sim.explore(find=find)
out.one_found.posix.dumps(0)