CTFshow 大吉大利杯DJBCTF

比赛:CTFshow 大吉大利杯
简称:大吉杯 DJB
平台:https://ctf.show
开始:2021/1/23 9:00
结束:2021/1/24 22:00
规则:
1 比赛期间可以随意讨论,wp须在比赛结束后发布,wp统一发布地址:wp.ctf.show
2 公平竞技,独立比赛
3 服务器不要爆破,不要攻击服务器,不要扫描!!!
4 奖品:新春月饼一份,单项前三定制量子水杯一个
5 题目征集:https://shimo.im/docs/YP3tVqPJTxD6jhdt

出题:4 crypto + 1 reverse


WEB

veryphp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<?php
error_reporting(0);
highlight_file(__FILE__);
include("config.php");
class qwq
{
function __wakeup(){
die("Access Denied!");
}
static function oao(){
show_source("config.php");
}
}
$str = file_get_contents("php://input");
if(preg_match('/\`|\_|\.|%|\*|\~|\^|\'|\"|\;|\(|\)|\]|g|e|l|i|\//is',$str)){
die("I am sorry but you have to leave.");
}else{
extract($_POST);
}
if(isset($shaw_root)){
if(preg_match('/^\-[a-e][^a-zA-Z0-8]<b>(.*)>{4}\D*?(abc.*?)p(hp)*\@R(s|r).$/', $shaw_root)&& strlen($shaw_root)===29){
echo $hint;
}else{
echo "Almost there."."<br>";
}
}else{
echo "<br>"."Input correct parameters"."<br>";
die();
}
if($ans===$SecretNumber){
echo "<br>"."Congratulations!"."<br>";
call_user_func($my_ans);
}

extract($_POST)以POST方式传入变量。(第一个preg_match内的特殊字符完全无过滤作用,过滤的是$str变量。)

第一层,传入$shaw_root(如果上面preg_match过滤正常,则不能使用_,利用PHP特性,当参数名中含+/[/./空格这些字符时会被解析为_,传入shaw[root=1);接着匹配preg_match内正则表达式,利用 https://regex101.com/ 可试出匹配的字符串,这里使用-a9<b>xxxxxx>>>>zzabcdphp@Rsx,得到hint:

>Here is a hint : md5("shaw".($SecretNumber)."root")==166b47a5cb1ca2431a0edfcef200684f && strlen($SecretNumber)===5

第二层,完全无需理会hint内容,照样利用extract($_POST)以POST方式传入参数ans=1SecretNumber=1,覆盖原始变量值,再给call_user_func传入my_ans变量执行自定义函数,结合给出的类静态方法,传my_ans=qwq:oao得flag。

payload: shaw_root=-a9<b>xxxxxx>>>>zzabcdphp@Rsx&ans=1&SecretNumber=1&my_ans=qwq::oao

spaceman

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
<?php
error_reporting(0);
highlight_file(__FILE__);
class spaceman
{
public $username;
public $password;
public function __construct($username,$password)
{
$this->username = $username;
$this->password = $password;
}
public function __wakeup()
{
if($this->password==='ctfshowvip')
{
include("flag.php");
echo $flag;
}
else
{
echo 'wrong password';
}
}
}
function filter($string){
return str_replace('ctfshowup','ctfshow',$string);
}
$str = file_get_contents("php://input");
if(preg_match('/\_|\.|\]|\[/is',$str)){
die("I am sorry but you have to leave.");
}else{
extract($_POST);
}
$ser = filter(serialize(new spaceman($user_name,$pass_word)));
$test = unserialize($ser);
?>

应该是出题失误,本想考反序列化字符逃逸,结果变成很简单的非预期。

同样,extract($_POST)以POST方式传入变量。(第一个preg_match内的特殊字符完全无过滤作用,过滤的是$str变量。)

传入user_name=1&pass_word=ctfshowvip即得flag。

MISC

十八般兵器

刀、枪、剑、戟、斧、钺、钩、叉、鞭、锏、锤、戈、镋、棍、槊、棒、矛、耙

hint1: JPHS

hint2: 用Notepad++打开试试?

hint3: 前十种兵器对应10进制,后八种对应8进制

18张图,根据hint1,用jphs05隐写工具分别从18张图分别提取出txt文件(空密码),

再根据hint2分别把每个txt文件最后一行的数字都摘出,前十个连接后十进制转字符串,后八个连接后八进制转字符串,字符串连接起来为flag。

请问大吉杯的签到是在这里签吗

flag为全部小写字母,没有空格

一张二维码,按照扫码内容能分离出提取出4张二维码,内容有提示意义,

第2张图的内容为还要往前走......是不是在这个路口转弯呢?,有问题,上stegsolve查看,在Random colour map就能看出端倪:

solved

猪圈密码对照解密得flag。

牛年大吉

题目下载 蓝奏云下载地址:https://wws.lanzous.com/i1Ac0jybrvc

百度云下载地址: https://pan.baidu.com/s/14EXw7U4w0Am0oP_xRXfbqQ 提取码:ns2k

hint1: 不要格式化哟,看看引导扇区是不是丢东西了

hint2: 压缩包密码在图片文件头里

vhd磁盘文件,用DiskGenius装载,修复磁盘,能提取出!lag.7z牛年大吉.png两个文件,根据hint2,7z压缩包密码为png文件头89504E47,解压得flag。

拼图v2.0

有手就行,没手的可以拿眼睛去瞪

打开环境,带旋转的拼图,gaps不方便,纯手工上。

拼图2

AA86

在一台旧电脑上(大约在16位操作系统还能跑的年代)发现了这个文件,挖掘它的秘密

hint: 请仔细阅读题目描述(5毛一条,去掉括号)

根据提示关键是16位操作系统。

Google搜索DOS AA86,在第2条结果可发现AA86文件编码说明:

Aa86 is a .COM file encoder by Yosuke Hasegawa that encodes binaries using only symbols characters, with a decoder.

把文件加上后缀.COM,找个MSDOS在虚拟机安装,再运行AA86.COM得flag。

CRYPTO

easysignin

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from Crypto.Util.number import getPrime, isPrime, bytes_to_long
from random import getrandbits
from secret import flag

def genpq(k):
while True:
p = getPrime((k + 3) // 4)
q = getPrime((k + 3) // 4)
if ((p ** 2) * (q ** 2)).bit_length() == k:
return (p, q)

def genseq(t, k):
x = getrandbits(k)
y = getrandbits(k)
r = []
r += [pow(x * getrandbits(k)+y, pow(getrandbits(k), t - 1, t), t)]
for i in range(len(flag)):
r += [pow(x * r[i] +y, pow(getrandbits(k), t - 1, t), t)]
return r

(p, q) = genpq(2021)
e = getPrime(0x0123)
r = [genseq(p, p.bit_length() // 4), genseq(q, q.bit_length() // 4), genseq(e, e.bit_length() // 4)]
c = pow(bytes_to_long(flag), e, 2021 * p * q)

out = open('output.txt','w')
out.write(str(r) + "\n")
out.write(str(c) + "\n")
out.close()

套LCG壳的RSA。

首先解决LCG问题

观察 genseq() 函数,发现 pow(getrandbits(k), t - 1, t) 为混淆式,由于传入的 $t$ 为质数,可根据费马小定理化为1,再按照正常方式生成递归状态数组 $r_{i+1}=(x \cdot r_i+y) \pmod t$,其中乘数 $x$、增量$y$、模数$t$ 均未知。

三次调用genseq()函数,产生以 $p,q,e$ 为模数的递归状态数组 $r_p,r_q,r_e$,利用三种值未知情况下的攻击方式,求出 $p,q,e$ 值。

再解简单RSA即得flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from functools import reduce
from math import gcd

def egcd(a, b):
if a == 0:
return (b, 0, 1)
else:
g, y, x = egcd(b % a, a)
return (g, x - (b // a) * y, y)

def modinv(a, m):
g, x, y = egcd(a, m)
if g != 1:
raise Exception('modular inverse does not exist')
else:
return x % m

def crack_unknown_increment(states, modulus, multiplier):
increment = (states[1] - states[0]*multiplier) % modulus
return modulus, multiplier, increment

def crack_unknown_multiplier(states, modulus):
multiplier = (states[2] - states[1]) * modinv(states[1] - states[0], modulus) % modulus
return crack_unknown_increment(states, modulus, multiplier)

def crack_unknown_modulus(states):
diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
modulus = abs(reduce(gcd, zeroes))
return crack_unknown_multiplier(states, modulus)

rp = []
rq = []
re = []
xp, yp, p = crack_unknown_modulus(rp)
xq, yq, q = crack_unknown_modulus(rq)
xe, ye, e = crack_unknown_modulus(re)
print(p)
print(q)
print(e)

#RSA步骤略

luckybase

baseの试炼

hint: b64decode(‘452/4520’)=’㝿㝴’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3.8

from base64 import b64encode
from random import randint, random
from os import getcwd

print('\n\n')
print(open(getcwd() + '/' + __file__, 'r').read())
print('\n\n')

luck = randint(0, 2021) * random()
print(luck)

good = eval(b64encode(input().encode('utf-8')))
if abs(good - luck) < 1e-10:
print(open('/flag').read())
else:
print('Back luck 2021???')

其实算半个misc题。

代码逻辑为,输入内容,使得UTF-8字符以base64编码后结果与给定的float值近似($10^{-10}$误差内)。

base64编码的结果包含的字符有ABCDEFGHIJKLMNOPQRSVWXYZabcdeghiklmnopqrstuvxyz0123456789+/,那么可以使用数字0123456789和运算符号+/凑出计算式来表示0.1,以及用e+数字凑出科学计数法。

由于任意浮点数都可以表示为 $\sum\limits_{i,j<k}(d_i \cdot 0.1\cdot 10^{-j})$,其中 $d_i$ 表示第 $i$ 位的数字,$j$ 表示对应的指数值,如

$1.143=11 \cdot 0.1 \cdot 10^{0}+4 \cdot 0.1 \cdot 10^{-1}+3 \cdot 0.1 \cdot 10^{-2}$

根据hint,$0.1$可表示为452/4520,$10^{-j}$可以表示为e+数字,系数 $d_i$ 可以变为对应的项的叠加,以 $1.143$ 为例,即

452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e00+452/4520e01+452/4520e01+452/4520e01+452/4520e01+452/4520e02+452/4520e02+452/4520e02

接下来寻找满足base64特性(编码:每3字符→4字符,解码:每4字符→3字符)并且编解码一致的e+数字串,有:

e00+对应{M>e01+对应{M~e04+对应{N>e05+对应{N~e08+对应{O>e09+对应{O~e10+对应{]>e11+对应{]~e013对应{Mw

将生成值按上面公式拆解,替换为对应特征串,nc交互:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#coding=utf-8
#注意py2和py3关于float值的输出长度不同,py2输出值过短会导致误差不满足要求,此用py3

from pwn import *
from base64 import b64encode

r = remote('111.231.70.44', 28044)

r.recvuntil('Back luck 2021???')
r.recvline()
r.recvline()
r.recvline()
r.recvline()
num = r.recvline().strip(b'.')
print(num)
num = num.split(b'.')
a = int(num[0])
b = num[1].decode('utf-8')

table = {'0.1/': '㝿㝴', 'e00+': '{M>', 'e01+': '{M~', 'e04+': '{N>', 'e05+': '{N~', 'e08+': '{O>', 'e09+': '{O~', 'e10+': '{]>', 'e11+': '{]~', 'e013': '{Mw'}

payload = "0.1/e00+"*(a*10+int(b[0]))
payload += "0.1/e01+"*(int(b[1]))
payload += "0.1/e04+"*(int(b[2:5]))
payload += "0.1/e05+"*(int(b[5]))
payload += "0.1/e08+"*(int(b[6:9]))
payload += "0.1/e09+"*(int(b[9]))
payload += "0.1/e10+"*(int(b[10]))
payload += "0.1/e11+"*(int(b[11]))
payload += "0.1/e013"

data = ""
for i in range(0, len(payload), 4):
data+=table[payload[i:i+4]]

print(eval(b64encode(data.encode('utf-8'))))
r.sendline(data)
print(r.recvall())

出题思路来自TSG CTF 2020 - Beginner’s Misc,此题不止一种方法,欢迎分享其他做法。

eccsimiscce

初探ecc。Be patient!

hint: 注意看一下题目名字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
from random import getrandbits
from secret import flag

def gen(n):
g = []
while len(g) < 2:
r = getrandbits(128)
if r < n:
g += [r]
return g[0], g[1]

pt = b'\x00' * 6 + long_to_bytes(int(flag,2))
assert len(pt) % 8 == 0

o = open('output','w')

n = getPrime(64) * getPrime(64)
o.write(str(n) + '\n')
a, b = gen(n)

p = []
E = EllipticCurve(IntegerModRing(n), [a, b^2])
P = E((0, b))
p += [P.xy()]
for k in range(len(pt) // 8):
Q = bytes_to_long(pt[8 * k : 8 * k + 8]) * P
p += [Q.xy()]
P = Q
o.write(str(p))

简单ECDLP(椭圆曲线离散对数问题)。

将flag二进制值对应字符串在前面补充6个'\x00'后,以8个字节一组通过ECC倍乘加密,得到的点 $Q$ 再作为下一组的基点 $P$ 继续倍乘,以此类推。

依次对每一组求解ECDLP问题,即给定2个素数 $p,q$ 的乘积 $n=pq$,已知生成元 $P$ 和积 $Q=m_iP$,求 $m_i$。

$n$ 是一个合数,根据Elliptic Curve Discrete Log in a Composite Ring,可以将其分解成模 $p$ 和模 $q$ 上的两条曲线,然后再在这两条曲线上分别求解DLP,最后通过CRT算法即可得到模 $n$ 下的解。

由于DLP的运行时长取决于ECC的光滑度,且由输出的点列表知有211个8字节串,总运行时间会比较长(Be patient)。

解出所有8字节串,连接发现是很长的01串,结合hint发现题目eccsi[misc]ce里含有misc,且$(211 \cdot 8-6)\cdot8=13456=(2^2\cdot29)^2$ 为完全平方数,猜测为二维码,将01串转化为图片扫码得flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#sage

n=
point=[]

x1,y1=point[0]
x2,y2=point[1]
a=(((y2^2-y1^2)-(x2^3-x1^3))%n*inverse_mod(x2-x1,n))%n
b=y1

#factor(n)
p=12117702104890171579
q=16627969210850438723

flag01=''

for i in range(1,len(point)):
print(i-1,i)
G=point[i-1]
K=point[i]
Ep=EllipticCurve(GF(p),[a,b^2])
Eq=EllipticCurve(GF(q),[a,b^2])
xp=discrete_log(Ep(K),Ep(G),operation='+')
xq=discrete_log(Eq(K),Eq(G),operation='+')
x=crt([ZZ(xp),ZZ(xq)],[ZZ(Ep(K).order()),ZZ(Eq(K).order())])
m=bin(x)[2:].rjust(64,'0')
print(m)
flag01+=m

print(flag01)

#01串转图片略

大佬们帮我看看我这个Python脚本为什么运行不了啊

菜鸡 9:36:27
菜鸡上传了文件 新建文本文档.py

菜鸡 9:37:02
key1: Do you want a DaJiBei?

菜鸡 9:37:61
大佬们帮我看看我这个Python脚本为什么运行不了啊

hint1: 最终结果是自带flag格式的,可以据此判断结果是否正确,不必浪费时间尝试提交格式

hint2: 为什么运行结果里好好的3,也要写成大小写混乱的样子?

hint3: 如果某个方向已经找不到更多的线索,不妨回头看看来时的道路

hint4: 本题的加密方式来源于对以下问题的思考:如何在同一个载体上加密两段信息,且读取其中一种信息的过程会令另一种信息被破坏;并且,如果前一种信息的读取方式足够显而易见,是否可以在有限的短时间内尽可能转移注意力,减少非预期接收者发现另一段信息的可能性?
为了降低难度,本题在选择每一种信息的加密方式时,尽可能选择了复杂度较低的做法;同时,将其中一段信息(相信大家都已经找到这一段了)设计为另一段信息加密方式的提示。

hint5: 3对应...--

1
2
3
4
5
6
7
8
9
10
11
12
13
14
fROM CRYPTO.utIL.NuMBER IMPORT BYteS_TO_LoNG, long_TO_BYTES

A_Fake_FLaG = B'FLag{I_AM_the_TRUE_Flag_trUST_me}'
nuMBER = bYTEs_tO_long(a_FAKE_FLAG)

KeY1 = B'DO yOU WAnT A DAJIBEI?'
KEY1 = Bytes_to_lONG(KEY1)

KEY2 = 0XBCD2deE7E7114B5C856F8DAECeD0782BD891200B4D8264D854A13D53cF1F0c481b
iv = 10800
KEY3 = KeY2 * IV

IS_THIS_rEAL_FlAG = (NUmber + kEY3) // KEy1
print(long_tO_bytes(IS_THis_REAl_flag))

新颖的题目设计,将真正信息藏于易读信息里,视觉第一影响大脑,接收先入信息以减少其他信息被注意的可能,有种首因效应的意味。

根据题面知真正的key1='Do you want a DaJiBei?',修正除'FLag{I_AM_the_TRUE_Flag_trUST_me}'外的字母大小写,运行脚本得到输出结果为thrEE_means_3,结合hint5知应该是摩斯密码,赛时连蒙带猜猜中flag,下来询问出题者@cheyenne预期解为,将代码全文大小写分别转换为.-后解密得flag。

单表加密

替换式密码,又名取代加密法,是密码学中按规律将文字加密的一种方式。替换式密码仅对明文中字符组成的单元进行替换,但密文中单元的位置没有改变。如果每一个字符为一单元进行加密操作,就称之为“简易替换密码”或“单表加密”。一种单表加密的做法是事先约定一份文本作为密码本,并根据文本内容和特征对明文进行替换加密。由于密码本仅提供给非常重要的人士,在一定程度上增加了密码的安全性。据称,在我国古代,苏州一带的当铺曾经大量使用此类密码。注意:古代苏州当铺的伙计不认识拉丁字母、阿拉伯数字和标点符号,所以当时的密码本仅使用汉字

hint1: 做题时不要老是上外部网站。

hint2: 密码本可能会在2月10日进行一次较大的更新。

hint3: 想一想,那个把数字加密成汉字的替换密码的实质是什么?

hint4: 密码本:请点击页面最上方的VIP

苏州码子+ctfshow vip页密码本+当铺密码。

第一步,将word中苏州码子替换为数字。

第二步,到https://vip.ctf.show/,将上一步每行数字按照`模块数+行数+第几个字`方式取出对应的字。

第三步,数每个汉字有多少笔画出头,就是转化成16进制的数字几。

最后得到的16进制串转字符串即为flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
dic={'11':'什么是会员','12':'平台自开通以来凭借着众多优秀的原创题目与活跃和谐的社区环境受到了广大的一致好评但是近几','21':'会员有什么特权','22':'会员目前针对平台入门系列题目开通会员后可以解锁全部道题目同时由平台技术','41':'题目有哪些内容','42':'入门系列题目采用循序渐进的方式逐步开放题栈如下','61':'会员开通价格','62':'平台题目绝大部分都是原创题目出题师傅们付出了辛勤的劳动考虑到学习的大部分还是以学生为主','71':'会员价格元还是感觉贵了怎么办','72':'这个确实还是个问题所以笔者建议经济实力不够的同学可以联系两三个好友合买一个号目前支付宝支持分期和花呗','81':'如何支付开通会员'}

x=[613,613,613,225,613,2231,613,6239,6239,7235,4223,723,421,4223,613,6224,813,2222,4223,225,421,2231,813,2222,4223,4223,813,813,813,2222,4223,6239,6239,813,421,1219,813,2222,4223,6239,613,2231,4223,2222,6239,7249]

word=''

for k in x:
s=str(k)
key=s[:2]
index=int(s[2:])
print(index)
word+=dic[key][index-1]

print(word)
#开开开针开由开以以买下确入下开勤支解下针入由支解下下支支支解下以以支入目支解下以开由下解以花
#666c61677b48346e5f4c315f44555f4775305f47614f7d

RealSimpleAlgorithm

So real, so simple…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from Crypto.Util.number import *
from secret import flag

def findPrime(k):
return k if isPrime(k) else findPrime(k+1)

p = getPrime(256)
q = findPrime(20210123 * p * p)
r = findPrime(p * q * q)
s = findPrime(p * q * r)
n = p * q * r * s
e = 0x10001
m = bytes_to_long(flag)

w = open('output','wb')
w.write(long_to_bytes(n))
w.write(b'\n\n')
w.write(long_to_bytes(pow(m, e, n)))

RSA签到题,考察next_prime特性。

根据素数定理,素数的平均间隔为:$\cfrac{x}{\pi(x)} \approx \ln(x)$,因此常见的下一个素数比当前素数大一点,一般不会超过1500。

由于素数间隔不会超1500,故:

$q\approx 20210123 \cdot p^2$

$r\approx p \cdot q^2 \approx 20210123^2 \cdot p^5$

$s \approx p \cdot q \cdot r \approx 20210123^3 \cdot p^8$

$n \approx p \cdot q \cdot r \cdot s \approx 20210123^6 \cdot p^{16}$

以开方取整得到的值开始爆破 $p$ ,再分别求出对应的 $q,r,s$,基本RSA操作即可得到flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import gmpy2

n=
c=

def cal(p):
q=gmpy2.next_prime(20210123*p*p)
r=gmpy2.next_prime(p*q*q)
s=gmpy2.next_prime(p*q*r)
return p*q*r*s

def findp(p,n):
print(p)
while 1:
nx=cal(p)
if nx<n:
p=gmpy2.next_prime(p+1)
else:
return p

#n ~ (20210123**6)*(p**16)
approx=gmpy2.iroot(n//pow(20210123,6),16)[0]
p=findp(approx,n)
print(p)
q=gmpy2.next_prime(20210123*p*p)
r=gmpy2.next_prime(p*q*q)
s=gmpy2.next_prime(p*q*r)
phi=(p-1)*(q-1)*(r-1)*(s-1)
d=gmpy2.invert(0x10001,phi)
m=pow(c,d,n)
print(bytes.fromhex(hex(m)[2:]))

REVERSE

A-Maze-In

真·签到

迷宫题。

注意函数sub_4011B0()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
int sub_4011B0()
{
char x; // bl@1
unsigned __int8 j; // dl@1
signed int i; // esi@2
char c; // al@3
int len; // ecx@4
unsigned int v5; // esi@19
char flag[256]; // [sp+8h] [bp-230h]@1
char input[256]; // [sp+108h] [bp-130h]@1
int data; // [sp+208h] [bp-30h]@1
int v10; // [sp+20Ch] [bp-2Ch]@1
int v11; // [sp+210h] [bp-28h]@1
int v12; // [sp+214h] [bp-24h]@1
int v13; // [sp+218h] [bp-20h]@1
int v14; // [sp+21Ch] [bp-1Ch]@1
int v15; // [sp+220h] [bp-18h]@1
int v16; // [sp+224h] [bp-14h]@1
int v17; // [sp+228h] [bp-10h]@1
int v18; // [sp+22Ch] [bp-Ch]@1
int v19; // [sp+230h] [bp-8h]@1

data = -1341248919;
v10 = 1078449436;
v11 = -404433706;
v12 = 2107721006;
v13 = 310654741;
v14 = 466487083;
v15 = 244438942;
v16 = -1045521021;
v17 = -1205263960;
v18 = 136611182;
v19 = 31438528;
memset(flag, 0, 0x100u);
memset(input, 0, 0x100u);
printf_("Do you wanna play a game?\n");
printf_("Let's play escape game where you have to find a way out. Please enter your way:");
sub_401050("%s", input, 256);
x = 3;
j = 0;
if ( strlen(input) != 34 )
goto _Failed;
i = 0;
do
{
c = input[i];
switch ( c )
{
case 'U':
len = j;
if ( byte_404018[4 * (x + 8 * j)] != 1 )
goto _Failed;
--j;
break;
case 'D':
len = j;
if ( byte_404019[4 * (x + 8 * j)] != 1 )
goto _Failed;
++j;
break;
case 'L':
len = j;
if ( byte_40401A[4 * (x + 8 * j)] != 1 )
goto _Failed;
--x;
break;
default:
if ( c != 'R' )
goto _Failed;
len = j;
if ( byte_40401B[4 * (x + 8 * j)] != 1 )
goto _Failed;
++x;
break;
}
++i;
}
while ( i < 34 );
if ( x != 4 || j != 7 )
{
_Failed:
printf_("You're stuck!\n");
return 0;
}
if ( decrypt((int)flag, (int)&data, len, (int)input) == -1 )
return 0;
printf_("Escaped! You see the flag\n");
v5 = 0;
do
{
Sleep(0xC8u);
printf_("%c", flag[v5++]);
}
while ( v5 <= 44 );
return 0;
}

信息:

迷宫起点(3,0),终点(4,7),大小8*8,只允许四个方向键-上(U)下(D)左(L)右(R),每个格子由代表4个方向的4个字节构成,1表示对应方向通,0表示对应方向不通,最大步数为34,求路径。

0x404018处可以导出迷宫数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
00 01 00 01 00 01 01 01 00 00 01 01 01 00 01 01
00 01 01 00 00 01 00 01 00 01 01 00 00 01 00 00
01 00 00 00 01 00 00 01 00 00 01 01 00 01 01 00
01 01 00 00 01 00 00 00 01 01 00 00 01 01 00 00
00 01 00 01 00 00 01 01 00 00 01 01 01 00 01 00
01 00 00 01 00 01 01 00 01 00 00 01 01 01 01 00
01 00 00 01 00 01 01 00 00 01 00 01 00 00 01 01
00 01 01 00 01 01 00 00 00 01 00 01 01 00 01 00
00 01 00 01 01 00 01 00 01 00 00 01 00 01 01 00
01 01 00 00 01 01 00 00 01 00 00 01 00 01 01 00
01 01 00 00 00 01 00 01 00 00 01 01 01 00 01 00
01 01 00 00 01 01 00 00 00 01 00 01 01 00 01 00
01 01 00 00 01 00 00 01 00 01 01 00 00 01 00 01
01 00 01 00 01 01 00 00 01 00 00 01 00 01 01 00
01 00 00 01 00 00 01 01 01 00 01 00 01 00 00 01
00 01 01 00 01 00 00 01 00 00 01 01 01 00 01 00

可在纸上复现迷宫,正常走出路径,nc提交得flag。

Matara Okina

https://ctfshow.lanzous.com/i4PQEkn6vch

apk分析。

jadx逆向分析源码,找到FlagActivity,根据结果字符串@lgvjocWzihodmXov[EWO和算法逆出原始字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
ans=b'@lgvjocWzihodmXov[EWO'
ans=list(ans)
secret=[0]*21

i=0
while i<len(secret)//2:
j=i+1
secret[i]^=j
x=len(secret)-1-i
secret[x]^=j
i=j

for i in range(10):
secret[i]=ans[i]^(i+1)
secret[20-i]=ans[20-i]^(i+1)

secret[10]=ans[10]
print(bytes(secret))

#Android_scheme_is_FUN

得到的只是secret部分,需提交data

注意到paramBundle=getIntent().getData()paramBundle具有getScheme()getHost()方法,了解知为Android业务组件URL Scheme,到AndroidManifest.xml中查看,发现

<data android:host="p4th" android:path="/70/1nput android:scheme="sh0w">

构造出链接<a href="sh0w://p4th/70/1nput?secret=Android_scheme_is_FUN">打开APP</a>,调试出flag。

warmup

IDA分析,输入flag长度为48,再把byte_40A0数组中的0xFF依次替换为48个值,最后16×16矩阵检测每行、每列以及每个4×4块是否满足0-15共16个值。

原始byte_40A0数组:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
08 0E FF 0C 09 0D FF 01  0A 0F 03 0B 00 02 FF 04
01 06 03 02 05 0A 07 00 08 09 FF 04 0F 0E 0B 0D
0A 00 FF 0D 04 0F 03 0B 07 05 0E 02 06 08 0C 01
04 0B 05 0F FF 02 FF 0C 06 0D 01 00 FF 0A 03 09
02 0A FF 03 0D 00 0B 05 0C FF 09 01 FF 0F 07 0E
0D 07 0C 0B 0F 0E 0A 08 00 FF 05 03 09 06 01 02
FF 01 0F FF 0C 09 04 06 02 0E 0D FF FF 03 0A FF
09 04 06 0E 02 07 01 03 0B 08 0A 0F 05 FF 00 0C
FF 03 0A 07 0E 08 0C 04 09 FF 00 0D 02 FF 06 FF
0C 09 01 FF 0B 03 0F 0D 0E 0A FF FF 08 00 04 07
06 0D 00 08 0A 01 02 FF FF 07 04 05 0C 0B FF 0F
0B 02 0E FF 00 FF 05 FF 0F 01 FF 0C 0A 09 0D 03
FF 0F 0B FF 03 0C FF 0E 05 FF FF 09 FF 04 08 0A
0E 08 FF FF 07 05 0D 0F 04 03 FF FF 01 0C 09 00
FF 05 0D 09 06 04 08 0A 01 0C 0F 0E FF 07 02 0B
03 FF 04 0A FF 0B 09 02 0D 00 FF 08 0E FF 0F 06

从前面代码逻辑推测为十六宫格填充,0xFF为需要填充的数字,填入后逐个取出即为flag。

利用excel,填充结果:

image-20210125233625306

逐个取出:

1
2
3
4
5
6
7
8
9
10
11
12
13
x =[7, 6, 5, 12, 9, 8, 14, 7, 8, 6, 4, 4, 5, 0, 7, 11, 8, 13, 15, 11, 1, 5, 5, 2, 6, 9, 3, 14, 4, 6, 7, 8, 7, 1, 0, 2, 6, 13, 2, 6, 11, 10, 0, 3, 12, 1, 7, 5]

flag=''

for k in x:
if k+48>47 and k+48<=57:
flag+=chr(k+48)
elif k+87>96 and k+87<=102:
flag+=chr(k+87)
else:
flag+='?'

print(flag)