angr爆破Android注册码

发布于 2019-05-15  48 次阅读


题目地址

https://github.com/pcy190/CTF_Train/blob/master/iscc01_Android/iscc01.apk

分析

解包,查看java层核心逻辑

if ((MainActivity.this.checkFirst(paramAnonymousView)) && (MainActivity.this.checkSecond(paramAnonymousView))) {
          Toast.makeText(MainActivity.this, "注册成功!", 0).show();
        } else {
          Toast.makeText(MainActivity.this, "注册失败!", 0).show();
        }

其中checkSecond函数为so层里的。

public native boolean checkSecond(String paramString);

checkFirst函数限制了注册码为16位,且为1-8的数字

private boolean checkFirst(String paramString)
  {
    if (paramString.length() != 16) {
      return false;
    }
    int i = 0;
    while (i < paramString.length()) {
      if ((paramString.charAt(i) <= '8') && (paramString.charAt(i) >= '1')) {
        i++;
      } else {
        return false;
      }
    }
    return true;
  }

看到so层的校验函数有两个

if ( checkfirst((__int64)&v7) & 1 )
  {
    std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::basic_string(&v6, &v8);
    v5 = 1;
    v4 = checkAgain((__int64)&v6);
  }

其中checkfirst要求注册码以12345678开头,checkAgain要求注册码为12345678数字的某种排列,注册码总共16位。

最终EXP

# libnative-lib.so
import angr
import claripy
import binascii

base = 0x400000
p = angr.Project("./libnative-lib.so")
key = claripy.BVS("key", 16 * 8)

state = p.factory.blank_state(addr=0xECC0+base)
# 0xF19C + base :first_out_call
# 0xF198 check_first_wrapper
# 0xE3C0 check_first
# 0xECC0 check_again

# for i in range(0, 8):
#     state.add_constraints(key.get_byte(i) == claripy.BVV(i + ord('1'), 4))

# state.se.add(key.get_byte(8) == int(binascii.hexlify(b"12345678"), 16))

state.se.add(key.get_byte(0) == int(binascii.hexlify(b"1"), 16))
state.se.add(key.get_byte(1) == int(binascii.hexlify(b"2"), 16))
state.se.add(key.get_byte(2) == int(binascii.hexlify(b"3"), 16))
state.se.add(key.get_byte(3) == int(binascii.hexlify(b"4"), 16))
state.se.add(key.get_byte(4) == int(binascii.hexlify(b"5"), 16))
state.se.add(key.get_byte(5) == int(binascii.hexlify(b"6"), 16))
state.se.add(key.get_byte(6) == int(binascii.hexlify(b"7"), 16))
state.se.add(key.get_byte(7) == int(binascii.hexlify(b"8"), 16))

sm = p.factory.simulation_manager(state)
sm.explore(find=[  # 0xE607 + base,  # first_check
    # 0xE487 + base,  # char+1
    # 0xE4E4 + base,  # cahr+1
    0xF0F5 + base,
    #0xF223 + base  # out of check_second , in check_main
],
    avoid=[0xE5E2 + base,  # check_first
           0xF00F + base, 0xF037 + base, 0xF0B7 + base, 0xF077 + base, 0xEFB4 + base, 0xEF9C + base,
           0xEF81 + base,
            0xF22C+base # behind calculation
           ])  #
found = sm.found[0]

for i in range(8, 16):
    cond_0 = key.get_byte(i) >= ord('1')
    cond_1 = key.get_byte(i) <= ord('8')
    found.add_constraints(found.solver.And(cond_0, cond_1))

    for j in range(i + 1, 16):
        if i == j or j >= 16:
            break
        # found.add_constraints(found.solver.And(key.get_byte(i) != key.get_byte(j), cond_1))
        # print("NOW ADD index(%d) !=index(%d)", i, j)
        cond2 = key.get_byte(i) != key.get_byte(j)
        found.add_constraints(found.solver.And(cond_0, cond2))

    # if i + 1 <= 17:
    #     cond_2 = key.get_byte(i) != key.get_byte(i + 1)
    #     found.add_constraints(found.solver.And(cond_0, cond_2))


tmp_addr = 0x38460 + base
state.regs.rdi = tmp_addr
flag_addr = state.regs.rdi
# found.add_constraints(found.memory.load(flag_addr, 5) == int(binascii.hexlify(b"12345678"), 16))

found.memory.store(flag_addr, key)

if found.solver.satisfiable():
    print("FIND IT")
else:
    print("CANNOT FIND")
    exit(0)

flag_str = found.solver.eval(key, cast_to=bytes)

print(bytes.decode(flag_str).strip('\x00'))

跑了几分钟后的结果

FIND IT
1234567853267418

注意点(PS)

基址

这里so文件的加载基址在angr刚装载的时候有提示

WARNING |  cle.loader | The main binary is a position-independent executable. It is being loaded with a base address of 0x400000.

所以我们后面程序的地址要加上基址偏移。

angr用法注意点

  • 内存读入与写入
found.memory.store(flag_addr, key) #key是要写入的数值
res=found.memory.load(flag_addr, YourWantedLength) # res是一个int类型的数字,判断比较的时候常用int(binascii.hexlify(b"TargetStr"), 16)
  • 符号条件
cond_0 = key.get_byte(i) >= ord('1')
cond_1 = key.get_byte(i) <= ord('8')
found.add_constraints(found.solver.And(cond_0, cond_1)) #其实found.solver.And(cond_0, cond_1)也是一个条件cond,可以作为子类再次参与逻辑运算
state.se.add(key.get_byte(0) == int(binascii.hexlify(b"1"), 16))
  • 处理结果

判断是否有解

if found.solver.satisfiable():

结果的输出

flag_str = found.solver.eval(key, cast_to=bytes)
print(bytes.decode(flag_str).strip('\x00'))