这回我又被出题人逼成re手了
其他题型彻底翻车,啥也不会
个人成绩:3031pt/8kill/8名/1一血/1三血

Misc

拼图v2.0

WriteUp

原图是网页图标favicon.png

本题需要旋转拼图块,所以gaps似乎用不上,听说大多数人都是手拼的,俺也一样

file

flag

flag{17eb22e6-560f-404c-b008-a30af26cb500}

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

WriteUp

首先拿到二维码图片1.png,一扫发现是气人留言,然后发现文件尾还有个zip,那就解开拿到另一张二维码图片2.png,还是气人留言,再解压,得到3.png,再解压得到4.png,然后就死胡同了。

于是回头看,啥也没发现,第二天水群的时候终于在群大佬的提醒下发现2.png的最低位存在异常,把最低位的图存出来,然后xor一下,就得到了一张看着就很像猪圈密码的东西。然后解猪圈就能拿到flag。

file

file

file

flag

flag{dajiadoaidjb}

Crypto

RealSimpleAlgorithm

题目

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)))

WriteUp

这题的话分析一下会发现实际上q20210123 * p * p很接近,rp * q * q很接近,sp * q * r很接近(因为findPrime函数是++遍历过去的,下一个质数与原数相比大不了多少,与256bit这个量级比起来几乎可以忽略不计)

所以我们可以直接列出如下近似等式。
q = 20210123 * p * p
r = p * q * q
s = p * q * r

合并可得到n = p * q * r * s = p * (20210123 * p * p) * (p * (20210123 * p * p) * (20210123 * p * p)) * (p * (20210123 * p * p) * (p * (20210123 * p * p) * (20210123 * p * p))) = (p ** 16) * (20210123 ** 6)

所以直接就能求出p或者p的究极近似值,然后代进脚本把需要的参数算出来就能拿到flag。

那么先把long_to_bytesnc重新读成long,然后冲就完事了。

from Crypto.Util.number import *
import gmpy2

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

data = open("output", "rb")
a = data.read(1062)
data.close()
a = a.split(b"\n\n")
n = bytes_to_long(a[0])
c = bytes_to_long(a[1])
p = gmpy2.iroot(n // pow(20210123, 6), 16)[0]
q = findPrime(20210123 * p * p)
r = findPrime(p * q * q)
s = findPrime(p * q * r)
assert n == p * q * r * s
phi = (p-1)*(q-1)*(r-1)*(s-1)
e = 0x10001
d = gmpy2.invert(e, phi)
m = pow(c, d, n)
print long_to_bytes(m)

flag

flag{we11_y0U_aRe_n07_AFra1d_0F_nA1VE_R5A_NOw~}

Reverse

A-Maze-In

WriteUp

打开附件,得到一个exe,运行游戏发现摸不着头脑,只说输入个way

于是ida看看,根据经验找到mainsub_4011B0(没经验的话动态跟一下或者一个一个点进去都行)。

file

发现一堆判断输入合法性的东西,那挺好,有目标了,首先是长度34,然后挨个判断字符是否是UDLR的其中一个,是的话对比自身数据,数据是1就没事,不是1直接over。然后纵向U-D+横向L-R+计算类似坐标的东西,最后需要落在横4纵7的位置上才算合法,不然通通over。

刚开始我以为这题是拿深搜递归做的,跑了半天愣是没一个结果,后来等不下去了就来手动做了。

那就继续看,最后有个关键一判sub_401090,具体干啥的我也没看懂。

emm还是看上面对数据的判定吧,下标4 * (横 + 8 * 纵)看着就很暗示,然后结合题目的maze猜测应当就是走路题没跑了,于是可以推测出应当是8*n的迷宫,然后这个4 * 的话,可以配合byte_404018byte_404019byte_40401Abyte_40401B这种连号的data来理解,可以猜测数据格式应当是4字节1组,分别是上 下 左 右,那么咱感觉去康康数据是不是和猜的一样。

0 1 0 1  0 1 1 1  0 0 1 1  1 0 1 1  0 1 1 0  0 1 0 1  0 1 1 0  0 1 0 0
1 0 0 0  1 0 0 1  0 0 1 1  0 1 1 0  1 1 0 0  1 0 0 0  1 1 0 0  1 1 0 0
0 1 0 1  0 0 1 1  0 0 1 1  1 0 1 0  1 0 0 1  0 1 1 0  1 0 0 1  1 1 1 0
1 0 0 1  0 1 1 0  0 1 0 1  0 0 1 1  0 1 1 0  1 1 0 0  0 1 0 1  1 0 1 0
0 1 0 1  1 0 1 0  1 0 0 1  0 1 1 0  1 1 0 0  1 1 0 0  1 0 0 1  0 1 1 0
1 1 0 0  0 1 0 1  0 0 1 1  1 0 1 0  1 1 0 0  1 1 0 0  0 1 0 1  1 0 1 0
1 1 0 0  1 0 0 1  0 1 1 0  0 1 0 1  1 0 1 0  1 1 0 0  1 0 0 1  0 1 1 0
1 0 0 1  0 0 1 1  1 0 1 0  1 0 0 1  0 1 1 0  1 0 0 1  0 0 1 1  1 0 1 0

似乎确实可以按刚才猜的来分割数据,分割出了个8*8的地图,再加以验证一下,最左8个点位的路全是0,所以确实是不通的,最右8个点位的路全是0,所以也确实是不通的,最后在验证最上8个点位的路和最下8个点位的路的时候,发现有几个点会溢出去,地图没做封闭,所以这似乎可能就是之前我用递归脚本跑不出来的原因?

那么既然地图已经完整了,那咱就把地图的路给画出来呗,于是得到如下草图。

file

根据之前源码里看到的起始点位横3纵0,发现确实可以把出入口都给对应上,而且路就一条,那么冲就完事了,得到输入路径LLDRRDLLLDRDLDDDRRULURRULURRDDDLDR,于是去连上服务器,交互一下得到flag。(事实证明那个sub_401090的判断就是虚晃一枪,让服务器去判就完事了,咱不care)

flag

flag{6b5d489e-c461-4512-8254-6016a92be9be}

Matara Okina[一血]

WriteUp

刚开始拿到题,我是懵逼的,这题是的main咋这么短,难道又是分析so的题?我tm直接放弃。

但是还是去看了一下另一个叫flagactivity,似乎就是交互flag相关的代码,于是看看,发现根本看不懂,就发现有个需要异或一下@lgvjocWzihodmXov[EWO的验证,然后顺手百度了一下scheme是个啥,因为感觉有点眼熟,于是嘛,好家伙,SchemeUrl啊,那这题思路有了,跟web一样构造出SchemeUrl,然后调用就完事了。

于是去查了一下SchemeUrl的相关参数是怎么设定的,因为没在源码里看到,就一个?secret=是已经知道的。

于是就找到了在AndroidManifest.xmlSchemeUrl参数定义。

file

n那么拼一下就能得到sh0w://p4th/70/1nput?secret=这么一个SchemeUrl,然后把上面的@lgvjocWzihodmXov[EWO给再按特定方式xor一下得到Android_scheme_is_FUN拼上去,一个崭新的payload就构造好了。

byte = bytearray(b'@lgvjocWzihodmXov[EWO')

i = 0
while i < (len(byte) + 1) // 2:
    i2 = i + 1
    byte[i] = byte[i] ^ i2
    length = len(byte) - 1 - i
    byte[length] = byte[length] ^ i2
    i = i2
print(byte)

接着随便找个能调SchemeUrl的东西就行了,我用的是Anywhere-

file

调一遍,直接得到flag。

file

flag

flag{sh0w://p4th/70/1nput?secret=Android_scheme_is_FUN_1635b71e036d}

UnrealFlag[三血]

WriteUp

拿到附件,解压发现是个拿UE4做的3D程序,但是并没有显示flag。

file

不过既然提示了flag在pak里,那么就去解包呗,这不是UEUnitySpine这类游戏引擎的基本操作嘛。

但是拿知名UE解包工具umodel解包一下,发现存在加密,需要提供key。

于是...上谷歌呗,找到了Reverse Engineering AES Keys From Unreal Engine 4 Projects这个文章,虽然差了好几个版本,但是大版本都是UE4,应该差不多,于是就开始抄作业呗。

先是x64dbg调试.\FindFlag\Binaries\Win64\FindFlag-Win64-Shipping.exe这个文件,直接搜字符串Corrupted index offset in pak file.,大概会出现俩结果,俩个都进去在相关跳转的外部下个断(因为下在跳内部的根本到不了断点,咱这是报错文本,前提是报错才进去)然后哪个断住了哪个就是咱需要的地方。

file

然后往下找call点,找到和下图差不多的(周围的汇编代码都差不多)就是关键的和key有关的函数入口了。

file

跟进去,然后在这个函数的结尾处找到传参寄存器是r8edxecx的call点。

file

然后找到r8指向的内存,就能拿到key的16进制形式B9 91 67 8A C1 A6 F4 01 5D 43 68 46 04 C4 4A 9B 7F 3E 2C 04 A1 82 46 C4 30 93 A7 F1 BB FF 6A B7,然后把hex转成base64形式就得到了uZFnisGm9AFdQ2hGBMRKm38+LAShgkbEMJOn8bv/arc=

file

之后试了一下umodelUnrealPakViewer解包,一个说key不对一个直接闪退,搞得我一度怀疑是我找错了key。

于是就跑epic官网下了个虚幻引擎,用安装目录里面的.\Engine\Binaries\Win64\UnrealPak.exe去解包。

需要把key放在一个json里。

{
  "$types": {
    "UnrealBuildTool.EncryptionAndSigning+CryptoSettings, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null": "1",
    "UnrealBuildTool.EncryptionAndSigning+EncryptionKey, UnrealBuildTool, Version=4.0.0.0, Culture=neutral, PublicKeyToken=null": "2"
  },
  "$type": "1",
  "EncryptionKey": {
    "$type": "2",
    "Name": null,
    "Guid": null,
    "Key": "uZFnisGm9AFdQ2hGBMRKm38+LAShgkbEMJOn8bv/arc="
  },
  "SigningKey": null,
  "bEnablePakSigning": false,
  "bEnablePakIndexEncryption": true,
  "bEnablePakIniEncryption": true,
  "bEnablePakUAssetEncryption": false,
  "bEnablePakFullAssetEncryption": false,
  "bDataCryptoRequired": true,
  "SecondaryEncryptionKeys": []
}

UnrealPak FindFlag-WindowsNoEditor.pak -Extract ExtractDir -cryptokeys=crypto.jsonExtractDircrypto.json需要填绝对路径,不然如果异目录执行还是会跑到UnrealPak目录下去,应该是bug)

成功解包出东西来了,有EngineFindFlag俩文件夹,直觉告诉我flag在后面这个文件夹。

FindFlag
│  AssetRegistry.bin
│  FindFlag.uproject
│
├─Config
│      DefaultEngine.ini
│      DefaultGame.ini
│      DefaultGameUserSettings.ini
│
├─Content
│      flag.uasset
│      flag.uexp
│      ShaderArchive-FindFlag-PCD3D_SM5.ushaderbytecode
│      ShaderArchive-Global-PCD3D_SM5.ushaderbytecode
│      Untitled.uexp
│      Untitled.umap
│
└─Plugins
        FindFlag.upluginmanifest

然后直觉又告诉我flag.uassetflag.uexp应该就是最后的抵抗了,冲!

JohnWickParse继续二次解包,先看一下里面有啥。

john-wick-parse serialize flag

得到个json文件打表。

[{"export_type":"Texture2D","ImportedSize":{"x":1440,"y":128},"LightingGuid":"3e2af41544ba5ffc9e66b6b7d4f60cf5","NeverStream":true}]

一看,哦吼,就一个文件,而且是Texture2D也就是图片格式,那么冲!

john-wick-parse texture flag

拿到flag.png

file

flag

flag{tHe_sKiLl_of_UnpAck1n9_UNrea1}

anniu

WriteUp

file

简单题,直接用灰色按钮克星就行。

flag

flag{huiseanniuyeyoukexing}

warmup

WriteUp

file

乍一看,好像挺简单的,输入flag内容,判长==48,sub_11F5就是个判断内容合法的,只要全是/[0:9a:f]/范围内的就行。

然后sub_125E看一下的话,其实用python总结一下就是下面这个功能。

t = 0
for i in range(16):
    for j in range(16):
        if byte_40A0[16 * i + j] == 0xff:
            byte_40A0[16 * i + j] = sub_1224(flag[t])
            t += 1

也就是把byte_40A0里的0xff按顺序替换成flag的内容,那么可以猜测有48个0xff

于是去看一下byte_40A0是啥。

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

emm,一眼看感觉是单区域的16*16数独...然后flag就是要填的数,好像很有道理,那就这么干呗(懒得接着看验证flag的代码了),先把能直接根据行和列缺少的数推测出来地方直接填上,为了区别本来就在的数,我把高位写成X。然后再把每行和每列的缺数统计出来。

file

为了方便看哪行哪列缺啥,我在下面写了一行00~0F的亮灯用的(就是双击能高亮,我都佩服我这个操作)

然后在每个空处取一下行列的缺数交集,如果交集就一个数,那就能得出该填啥,如果有多个数,那就先把其他空填了,最后回来再看一遍交集。

如此往复几次,就把数独填好了。

file

然后用正则把填的数提取出来就完事。

import re
result = """08 0E X7 0C 09 0D X6 01 0A 0F 03 0B 00 02 X5 04
01 06 03 02 05 0A 07 00 08 09 XC 04 0F 0E 0B 0D
0A 00 X9 0D 04 0F 03 0B 07 05 0E 02 06 08 0C 01
04 0B 05 0F X8 02 XE 0C 06 0D 01 00 X7 0A 03 09
02 0A X8 03 0D 00 0B 05 0C X6 09 01 X4 0F 07 0E
0D 07 0C 0B 0F 0E 0A 08 00 X4 05 03 09 06 01 02
X5 01 0F X0 0C 09 04 06 02 0E 0D X7 XB 03 0A X8
09 04 06 0E 02 07 01 03 0B 08 0A 0F 05 XD 00 0C
XF 03 0A 07 0E 08 0C 04 09 XB 00 0D 02 X1 06 X5
0C 09 01 X5 0B 03 0F 0D 0E 0A X2 X6 08 00 04 07
06 0D 00 08 0A 01 02 X9 X3 07 04 05 0C 0B XE 0F
0B 02 0E X4 00 X6 05 X7 0F 01 X8 0C 0A 09 0D 03
X7 0F 0B X1 03 0C X0 0E 05 X2 X6 09 XD 04 08 0A
0E 08 X2 X6 07 05 0D 0F 04 03 XB XA 01 0C 09 00
X0 05 0D 09 06 04 08 0A 01 0C 0F 0E X3 07 02 0B
03 XC 04 0A X1 0B 09 02 0D 00 X7 08 0E X5 0F 06"""
print("".join([i for i in re.findall("X([0-9A-F])", result)]).lower())

就能拿到flag的内容,俩头套一下格式就完事。

flag

flag{765c98e78644507b8dfb1552693e467871026d26ba03c175}


The End