学逆向论坛

找回密码
立即注册

只需一步,快速开始

发新帖

116

积分

0

好友

25

主题
发表于 2020-6-15 00:45:59 | 查看: 2595| 回复: 0

相关题目:


前言
  我们时常会遇到各种平台下的程序,有时我们为了搭一个动态调试的环境需要耗费不少的时间,更有甚者无功而返,那么这时不妨用angr试试,毕竟多一分尝试,多一分希望。
如何跨平台使用angr解题  通常来说,我们会在linux下安装angr框架,那么如果遇到不是linux的程序该怎么办呢?程序无法运行还能使用angr解决么!答案当然是可以。因为angr是符号执行框架,不是真正的在执行程序。我们只需要进行正确的条件设置就可以了。
ARM程序  这里以 android_arm_license_validation为例

深入浅出angr(五)

深入浅出angr(五)
  最关键的是设置合理的初始状态,从IDA的分析来看,最后的比较部分在sub_1760函数,因此我们以此作为初始状态,同时看到该函数有一个v6参数,经过分析,可以知道该参数经过加密变换,最终用来校验的字符串,也就是说最后的校验过程我们可以不用了解,只需要使用angr将其爆破出来即可。
  我们需要设置一个空的状态
state = b.factory.blank_state(addr=0x401760)
  而后设置需要传入的参数,选取任意一个地址,用来存放变量。
>>> concrete_addr = 0xfff00000
>>> code = claripy.BVS('code', 10*8)
>>> state.memory.store(concrete_addr, code, endness='Iend_BE')

深入浅出angr(五)

深入浅出angr(五)
  注意ARM程序是大端对齐。
  为了正确的传入参数,我们需要了解arm程序传参方式。
  参考文章
  因此我们设置r0为输入的地址
  state.regs.r0 = concrete_addr
  后面就是类似的。

深入浅出angr(五)

深入浅出angr(五)
  当然如果,加密过程也同样非常复杂,那么我们就需要从加密函数开始,设置好参数,利用angr符号执行。
windows dll  这里以mma_howtouse为例
  程序是windows的dll,如果采用正常的方式解题,那么搭建动态调试的环境势必要花费一番功夫。
  IDA载入,首先我们应该注意的是dll程序的基址是0x10000000,而不是0x400000。

深入浅出angr(五)

深入浅出angr(五)
  通过导出表,我们可以定位到fnhowtouse函数,进行了handler的绑定,然后通过(*(&v2 + a1))();进行函数调用。

深入浅出angr(五)

深入浅出angr(五)
  为了调用fnhowtouse函数,可以使用callable方法

深入浅出angr(五)

深入浅出angr(五)
  首先加载howtouse.dll
p = angr.Project('howtouse.dll', load_options={'main_opts': {'base_addr': 0x10000000}})
howtouse = p.factory.callable(0x10001130)
  通过callable方法将fnhowtouse转化为python可以调用的函数

深入浅出angr(五)

深入浅出angr(五)
  然后我们便可以通过howtouse(i)进行函数调用,而无需依赖环境。通过题目,我们可以知道howtouse(i)结果就是flag,因此我们可以通过
claripy.backends.concrete.convert(howtouse(i))
  将结果转换为BVV类型,并通过.value将值取出。
因此代码可以如下进行组织:
getch = lambda i: chr(claripy.backends.concrete.convert(howtouse(i)).value)

# Let's call this 45 times, and that's the result!
return ''.join(getch(i) for i in range(45))
windows驱动  这里以flareon2015_10为例
  程序是windows驱动,2.5M还是蛮大的,IDA载入分析。

深入浅出angr(五)

深入浅出angr(五)
  What!这次又是个什么鬼图形,感觉出题人都要玩出花了。当然啦,angr最喜欢解决这种线性的题目了。
  首先程序的入口。

深入浅出angr(五)

深入浅出angr(五)
  可以知道sub_29cd20是有用的函数

深入浅出angr(五)

深入浅出angr(五)
  好吧。

深入浅出angr(五)

深入浅出angr(五)
  挨个查看一下,发现一个可疑的case

深入浅出angr(五)

深入浅出angr(五)
  话说这程序导致我的IDA都有点不正常了。

深入浅出angr(五)

深入浅出angr(五)
  接下来查看sub_2D2E0函数

深入浅出angr(五)

深入浅出angr(五)
  真漂亮,还好不是在比赛的时候遇到这种题目。。
  我仔细盘了一下代码。还真有趣。
  看到sub_13590函数

深入浅出angr(五)

深入浅出angr(五)
  一百多行代码,F5之后就两行。

深入浅出angr(五)

深入浅出angr(五)
  sub_2D2E0函数的前半部分都在为0x29F210地址的数据赋值

深入浅出angr(五)

深入浅出angr(五)
  只有在最后几行,调用了sub_110F0函数,并传入了byte_29F210和edx,ecx作为参数

深入浅出angr(五)

深入浅出angr(五)
  查一下edx对应[ebp+var_3C]的交叉引用,可以知道长度为40

深入浅出angr(五)

深入浅出angr(五)
  查一下edx对应[ebp+var_30]的交叉引用可以知道key

深入浅出angr(五)

深入浅出angr(五)
  具体查看sub_110F0函数,这回比较正常

深入浅出angr(五)

深入浅出angr(五)
  查看sub_11010函数

深入浅出angr(五)

深入浅出angr(五)
  很明显是一个不太标准的Tea解密函数。
  在查sub_110F0的交叉引用时,发现还有其他几个地方引用了该函数,不过我仔细查看了一下,这三个地方引用sub_110F0函数都是对相同的数据进行解密,只是数据存放的位置不同罢了。所以也就先不去关心了。
  所以把数据dump下来,解密就可以了。
  代码如下:
void decrypt (uint32_t* v, uint32_t* k) {
uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i;  /* set up */
uint32_t delta=0x61C88647;          /* a key schedule constant */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3];   /* cache key */
for (i=0; i<32; i++) {                         /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum += delta;
}                                              /* end cycle */
v[0]=v0; v[1]=v1;
}

int main()
{
uint32_t k[4]={858927408,926299444,1111570744,1178944579};
uint32_t v0[] = {0xfadc7f56,0xc49927aa};
uint32_t v1[] = {0x92cf7c6c,0x1a476161};
uint32_t v2[] = {0xfd63b919,0x20b6f20c};
uint32_t v3[] = {0xfd5c2dc0,0x965471d9};
uint32_t v4[] = {0xfff7434f,0x315d4cbb};
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
setbuf(stdin,0);
setbuf(stdout,0);
setbuf(stderr,0);
decrypt(v0,k);
decrypt(v1,k);
decrypt(v2,k);
decrypt(v3,k);
decrypt(v4,k);
char *flag;
sprintf(flag,"%x%x%x%x%x%x%x%x%x%x",v0[0],v0[1],v1[0],v1[1],v2[0],v2[1],v3[0],v3[1],v4[0],v4[1]);

printf("%s",flag);

return 0;
}
  最后的结果需要转换一下。
  再让我们看看如何使用angr的hook来完成解密工作。
  大致思路,就是在程序调用解密函数前,将对应的数据存放到相应的位置即可。
  因此我们的Hook函数可以这么写
def before_tea_decrypt(state):
# Here we want to set the value of the byte array starting from 0x29f210
# I got those bytes by using cross-reference in IDA
all_bytes = bytes([0x56, 0x7f, 0xdc, 0xfa, 0xaa, 0x27, 0x99, 0xc4, 0x6c, 0x7c,
0xfc, 0x92, 0x61, 0x61, 0x47, 0x1a, 0x19, 0xb9, 0x63, 0xfd,
0xc, 0xf2, 0xb6, 0x20, 0xc0, 0x2d, 0x5c, 0xfd, 0xd9, 0x71,
0x54, 0x96, 0x4f, 0x43, 0xf7, 0xff, 0xbb, 0x4c, 0x5d, 0x31])
state.memory.store(ARRAY_ADDRESS, all_bytes)
  这时hook的长度应该为0
p.hook(0xadc31, before_tea_decrypt, length=0),有点类似于插桩
  我们不会运行该程序,而是选取有用的部分运行。
我们只需要运行2D2E0函数即可。
  这时需要用到callable方法:
  它是将程序的二进制函数,变成像python本地的函数一样调用。
  如下初始化
proc_big_68 = p.factory.callable(0x2D2E0, cc=p.factory.cc(func_ty=prototype), toc=None, concrete_only=True)
  其中prototype是函数原型的申明
  可以像这样声明:
  prototype = SimTypeFunction((SimTypeInt(False),), SimTypeInt(False))
  因此完整的callable调用如下
# Declare the prototype of the target function
prototype = SimTypeFunction((SimTypeInt(False),), SimTypeInt(False))
# Initialize the function instance
proc_big_68 = p.factory.callable(BIG_PROC, cc=p.factory.cc(func_ty=prototype), toc=None, concrete_only=True)
# Call the function and get the final state
proc_big_68.perform_call(0)
state = proc_big_68.result_state
  最后我们可以通过state.solver.eval(state.memory.load(ARRAY_ADDRESS, 40), cast_to=bytes)获取flag
  如果学会了,是不是非常简单呢
总结  对于跨平台的程序,最重要的了解该平台调用约定,需要对程序的运行有所了解。


温馨提示:
1.如果您喜欢这篇帖子,请给作者点赞评分,点赞会增加帖子的热度,评分会给作者加学币。(评分不会扣掉您的积分,系统每天都会重置您的评分额度)。
2.回复帖子不仅是对作者的认可,还可以获得学币奖励,请尊重他人的劳动成果,拒绝做伸手党!
3.发广告、灌水回复等违规行为一经发现直接禁言,如果本帖内容涉嫌违规,请点击论坛底部的举报反馈按钮,也可以在【投诉建议】板块发帖举报。

小黑屋|手机版|站务邮箱|学逆向论坛 ( 粤ICP备2021023307号 )|网站地图

GMT+8, 2024-4-24 17:23 , Processed in 0.099223 second(s), 42 queries .

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表