同济@复旦第一届网络安全竞赛 Write up

  • 选手

    1
    用户名:isseymour
  • 目录

    [TOC]


一、WEB

题目:游戏

  • 打开调试,查看源代码

  • 读懂代码逻辑,一般来说,游戏都是最后会有个 GameOver或者success之类的。

  • 本题发现是g._f_()里面会输出胜利的结果

  • 我直接把在g.state_GAMEOVER里加入这个函数执行,使得最后结束一定会输出成功的逻辑。

  • 至此,只需要直接进入一局游戏,直接死掉即可。

  • 得到 flag (见下图)

    1
    flag{java3cr1qtG3}
T1-1

题目:laohuji

  • 一看是个老虎机游戏,那就是猜数字了。

  • 查看源代码,发现在下面的代码是核心:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    <script>
    function numRand() {
    var x = 9999; //上限
    var y = 1111; //下限
    var rand = parseInt(Math.random() * (x - y + 1) + y);
    return rand;
    }


    function login1(result){
    $.ajax({
    url: "result.php",
    data: {number: result,sign:md5(result)},
    type: "POST",
    //dataType: "json",
    success: function(data) {
    data = jQuery.parseJSON(data);
    $('#res').text(data.flag);
    }
    });
    }
  • 也就是实际上,每次通过login1()向服务器发请求,收到回复来确认是否正确。

  • 而且数字只可能是四位数,所以那就直接爆就行。

    (注:不能爆太快,服务器会不响应,间隔10ms)

  • JS方式爆:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    function loginAll(){
    for (let i=0;i<=9999;i++)
    {
    setTimeout(() => {
    $.ajax({
    url: "result.php",
    data: {number: i,sign:md5(i)},
    type: "POST",
    //dataType: "json",
    success: function(data) {
    data = jQuery.parseJSON(data);
    $('#res').text(data.flag);
    console.log(data.flag);
    }
    });
    }, i*10)
    }
    }
T2-2
  • 看到flag

    1
    flag{rIhhOr}

二、REVERSE

题目:Just met

  • 打开IDA 64位反汇编看一下
  • 找到main函数处,查看到如下代码:
T1-1
  • 分析发现,虽然是两个 strcmp,但是dest的值是固定的,那么实际上输入的密码必须就是dest的值

  • dest 的值是由 src 复制而来,因此值就是 src的值。

  • 然而,我交上去,答案错误。。。。

    1
    flag{sedecrem}
  • 分析了好久,后面是看到有说大小端存储的问题,那么字符应该全部反过来读取

  • 重新提交,正确了。

    1
    flag{mercedes}

题目:jeb

  • 题目就是 jeb,下载是个安卓软件。
  • 用 JEB 打开,找到主要的逻辑判断的地方(看注释,有 ”错误“”恭喜你“之类的词)
T2-2
  • 发现是 和 edit_userName进行比较,那就找到 edit_userNameTenshine
T2-3
  • 后续代码逻辑在 checkSN 函数里
T2-4
  • checkSN逻辑:

    计算userName的md5 值,然后只取下标偶数的字符,包上 flag{}就是答案

    1
    2
    3
    4
    字符:Tenshine
    md5: b9c77224ff234f27ac6badf83b855c76
    取偶数下标:
    flag{bc72f242a6af3857}
  • 测试一下,正确。

    T2-6

三、PWN

题目:cat2

  • nc 连接上

  • 测试了一下

    1
    `ls`

    发现有输出

  • 那就直接获取了

    1
    `cat flag`

    成功获取

    1
    flag{d1ec102aa389e7c7aa5796af5fe6039a}
T1-1

四、CRYPTO

题目:enemy_command

  • 打开发现 似乎只有三种可能:45/46/32

  • 猜测应该是 摩斯电码

    1
    45 45 45 45 45 32 45 46 46 45 32 45 45 46 46 46 32 46 46 46 45 45 32 45 45 46 46 46 32 45 45 45 45 46 32 45 46 46 46 46 32 46 32 45 45 46 46 46 32 46 46 46 46 45 32 45 45 46 46 46 32 45 46 46 46 32 45 46 46 46 46 32 46 45 32 46 46 46 46 45 32 45 45 45 46 46 32 46 46 46 46 45 32 45 45 45 46 46 32 46 46 46 45 45 32 46 46 46 45 45 32 45 46 46 46 46 32 45 46 46 46 32 46 46 46 46 45 32 46 32 46 46 46 46 46 32 45 46 46 46 46 32 46 46 46 46 45 32 45 46 46 32 46 46 46 46 45 32 45 46 46 32 46 46 46 46 46 32 45 45 46 46 46 32 46 46 46 45 45 32 46 46 46 45 45 32 45 46 46 46 46 32 45 46 46 46 46 32 46 46 46 45 45 32 45 45 45 45 45 32 46 46 46 46 45 32 46 46 45 45 45 32 45 46 46 46 46 32 46 46 46 45 45 32 45 46 46 46 46 32 46 45 45 45 45 32 45 45 46 46 46 32 45 46 46 
  • 同时,查了一下 ASCII 码,也证实了这一点:

T1-1
  • 写python脚本获取转换为摩斯电码的形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    allnum = '45 45 45 45 45 32 45 46 46 45 32 45 45 46 46 46 32 46 46 46 45 45 32 45 45 46 46 46 32 45 45 45 45 46 32 45 46 46 46 46 32 46 32 45 45 46 46 46 32 46 46 46 46 45 32 45 45 46 46 46 32 45 46 46 46 32 45 46 46 46 46 32 46 45 32 46 46 46 46 45 32 45 45 45 46 46 32 46 46 46 46 45 32 45 45 45 46 46 32 46 46 46 45 45 32 46 46 46 45 45 32 45 46 46 46 46 32 45 46 46 46 32 46 46 46 46 45 32 46 32 46 46 46 46 46 32 45 46 46 46 46 32 46 46 46 46 45 32 45 46 46 32 46 46 46 46 45 32 45 46 46 32 46 46 46 46 46 32 45 45 46 46 46 32 46 46 46 45 45 32 46 46 46 45 45 32 45 46 46 46 46 32 45 46 46 46 46 32 46 46 46 45 45 32 45 45 45 45 45 32 46 46 46 46 45 32 46 46 45 45 45 32 45 46 46 46 46 32 46 46 46 45 45 32 45 46 46 46 46 32 46 45 45 45 45 32 45 45 46 46 46 32 45 46 46'
    allnums = allnum.split()
    str = ''
    for c in allnums:
    if c == '45':
    str += '-'
    elif c == '46':
    str += '.'
    elif c == '32':
    str += '/'
    print(str)
  • 摩斯电码解码,仍然继续使用十六进制解码 ,得到如下:

    1
    synt{jHH3kNVMMW3f0Bca}
    T1-3 T1-4
  • 这还不是答案,因为不是flag{}形式,猜测可能是移位密码(凯撒密码)

  • 写个脚本看一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
cf = 'synt{jHH3kNVMMW3f0Bca}'
ai = ord('a')
Ai = ord('A')
for i in range(0,26):
str = ''
for c in cf:
if ord(c) >= ord('a') and ord(c) <= ord('z'):
str += chr((ord(c)-ai+i)%26 + ai)
elif ord(c) >= ord('A') and ord(c) <= ord('Z'):
str += chr((ord(c) - Ai + i) % 26 + Ai)
else:
str += c
print(str)
  • 如下图,看到有一个是 flag{}

    1
    flag{wUU3xAIZZJ3s0Opn}
T1-5

题目:rwx

  • Hash是 分段hash的
  • 因此可以分段爆破,最终复原明文。
  • python脚本如下:
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
f=open("output","rb").readlines()
print(len(f)/3)
random1=[]
for i in range(43):
random1.append(f[i].strip().decode("hex"))
targethash=[]
for i in range(43,86):
targethash.append(f[i].strip())
lasthash=[]
for i in range(86,129):
lasthash.append(f[i].strip())
import hashlib
def b1s(hash):
for i1 in range(0xff):
for i2 in range(0xff):
for xx in range(32):
if hashlib.sha256(str(xx)+chr(i1)+chr(i2)).hexdigest()==hash:
return xx

def brutehash(lasthash):
e=[]
for i in lasthash:
print(i)
e.append(b1s(i))
return e

import hashlib
from Crypto.Util.number import long_to_bytes,bytes_to_long
x=[]
w=[]
r=[]
for i in range(43):
for i1 in ["0","1"]:
for i2 in ["0","1"]:
for i3 in ["0","1"]:
if hashlib.sha256(long_to_bytes(int(bin(bytes_to_long(random1[i]))[2:]+i1+i2+i3,2))).hexdigest()==targethash[i]:
r.append(i1)
w.append(i2)
x.append(i3)
e=brutehash(lasthash)
print(r)
print(w)
print(x)
print(e)

for i in range(43):
print(chr(int(bin(e[i])[2:]+x[i]+w[i]+r[i],2)))

  • 运行得到:
1
flag{8uuuuu_dd_3yyyui_like_the_internet_g0}
T2-1

题目:easy_code

  • 打开看到是 三层加密,

  • 很简单,反过来三层解密即可。代码如下:

    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
    import base64

    def decode1(code):
    res = b''
    for i in code:
    x = i - 24
    x = x ^ 31
    res += bytes([x])

    return res


    def decode2(code):
    res = b''
    for i in code:
    x = i ^ 31
    x = x - 31
    res += bytes([x])

    return res


    def decode3(code):
    return base64.b32decode(code)


    encoded_flag = 'V622VMEEVWVEC7FMPV7265VLPN7X25T5V2XKW5VLVZBKY5TZPN4UA7CBIKX2U6NKVODA===='
    decoded_flag = decode1(decode2(decode3(encoded_flag)))
    print(decoded_flag.decode('utf-8'))
  • 运行得到 flag

    1
    flag{da83c46f-b264-4eeb-be9c-0207389fa0ab}
T3-1

题目:HASH

  • 缺失部分值而已,爆破即可,代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    import hashlib
    import itertools

    # 字符集
    dic = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_-/"
    passwd_template = "*ha*i*_go*d"
    sha_template = "2aa90dc*46aa5d0*baedc4a*13e4c0a6*91bf6*2"
    placeholders = [i for i, c in enumerate(passwd_template) if c == '*']

    # 尝试所有可能
    for combo in itertools.product(dic, repeat=len(placeholders)):
    # 构造候选
    candidate_passwd = list(passwd_template)
    for char, pos in zip(combo, placeholders):
    candidate_passwd[pos] = char
    candidate_passwd = ''.join(candidate_passwd)

    # 计算 SHA-1
    sha_candidate = hashlib.sha1(candidate_passwd.encode('ascii')).hexdigest()

    # 检查匹配
    if sha_template[:7].replace('*', '') == sha_candidate[:7]:
    print(f"SHA-1 = {sha_candidate}")
    print(f"Password = {candidate_passwd}")
  • 运行拿到值后,包上flag

    1
    flag{sha1is_good}
T4-1

五、MISC

题目:seeyouagain

  • 这题,我真的是想破脑袋了!!!!

    还好做出来了,要不然我要气死了。

  • 前面的流程很正常,一路顺风

    用 foremost 把 zip 压缩包分离出来;

    压缩包密码在图片中,修改图片高度 为 FF 就能看到密码是flag@abc

T1-1
  • 好,这里开始,开始我的痛苦了

    我一看 flag.txt 这不显然是 Base64 编码吗!(因为末尾有=故猜测)

    又是一通 Base 解码。

    结果是 《See You Again》的歌词。

    我就不懂了???题目就是答案???

    我试了很多遍,各种姿势,就是不对。。。。

    T1-3
  • 后面查询 Base64 编码原理,看到有个人提及“Base64隐写”

    Base64 不是编码原理吗?还能隐写?

    了解原理会发现, Base64 末尾为了能够使得位数为 6 的倍数,

    当不是 6 的倍数时,会自动填充0 进来

  • 但是!!!这表明这里填充的内容是不会影响解码和编码的,

    就是填充作用,仅此而已。

    也就是说,不论填0填1都可以

  • 好的,至此,隐写原理也就浮出水面了,就是利用这个填充位置来存储真实信息

  • 不得不说,确实挺好的一题。的的确确达到隐写效果了。

    无论我是怎么编码解码,看到的都不会是隐写的内容!!!

  • 下面的python脚本获取隐写内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
with open("flag.txt") as f:
all = f.read()
list = all.splitlines()
binstr = ""
base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
for i in list:
if i.find("==")>0:
# 取倒数第3个字符,在base64找到对应的索引数(就是编码数),取低4位,再转换为二进制字符
temp = bin((base64.find(i[-3])&15))[2:]
binstr = binstr + "0"*(4-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
print(i, temp)
elif i.find("=")>0:
# 取倒数第2个字符,在base64找到对应的索引数(就是编码数),取低2位,再转换为二进制字符
temp = bin((base64.find(i[-2])&3))[2:]
binstr = binstr + "0"*(2-len(temp))+temp #二进制字符补高位0后,连接字符到binstr
print(i, temp)

str=""
for i in range(0,len(binstr),8):
str = str + chr(int(binstr[i:i+8],2)) #从左到右,每取8位转换为ascii字符,连接字符到字符串
print(binstr)
print(str)
  • 运行得到flag

    1
    flag{da6ac101b05b6974}
T1-2

题目:Pirate king steganography

  • 解压得到很多图片,只有一个是无法显示的,很简单,就是压缩包(也可以二进制打开查看文件头就知道是什么了)

  • 修改后缀名,解压打开即可里面有一个flag.txt 就是答案

    1
    flag{7e8eee62-508e-4421-a28b-baa12d0b6aa9}
T3-1

题目:password

  • 题目提示可以通过现有信息获取到什么?
    1. CRC值
    2. 字节长度
  • 可以看到里面的三个 password.txt文件很短,只有 4 大小。
  • 直接 CRC 碰撞即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import zipfile
import string
import binascii

def CrackCrc(crc):
dic = string.ascii_letters + string.digits + '_+/='
for i in dic:
for j in dic:
for p in dic:
for q in dic:
s = i + j + p + q
s_bytes = s.encode('utf-8') # 将字符串转换为字节串
if crc == (binascii.crc32(s_bytes) & 0xffffffff):
print(s)
return

def CrackZip():
crcs = [int("0x49629A97", 16), int("0xCE70D424", 16), int("0xC3F17511", 16)]
for crc in crcs:
print(hex(crc))
CrackCrc(crc)

CrackZip()

  • 运行得到字符串,连起来,就是压缩包密码。

  • 输入密码打开压缩包,得到

    1
    flag{fb39358b-6c2e-ddb4-56e0-0f026663058d}
T2-2

题目:中国菜刀

  • 打开流量分析

  • 过滤只看 HTTP/TCP

  • 所有数据都看了下,最后是这个 209 bytes 的包比较可能。

    下载下来。

  • 打开看到有 X@Y ,把头尾的这个数据去掉

    使用压缩包方式打开,得到flag

    1
    key{8769fe393f2b998fa6a11afe2bfcd65e}
T4-2

题目:Basic type

  • 打开,一眼就是像素点
  • 写个 python 脚本恢复为文件。
  • 但是图片长宽不知道,长 * 宽 = 像素点个数(135000)
  • 挨个猜,试一下即可。
  • 发现在 900 * 150 是对的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from PIL import Image

# 乘积为 135000
width = 900
height = 150

img = Image.new('RGB', (width, height))
pixel_index = 0 # 计数器,用于跟踪当前像素的索引

with open('basic/basic.txt', 'r') as file:
for line in file:
rgb = line.strip().strip('()').split(',')
rgb = tuple(int(color) for color in rgb)
x = pixel_index % width
y = pixel_index // width
img.putpixel((x, y), rgb)
pixel_index += 1

img.show()
img.save('output.png')
  • 运行打开图片如下,得到flag

    1
    flag{RGB_1s_e4sY}
    output

题目:Only one picture

  • 常规操作
  • 放入 notepad++ 发现有 flag.txt ,说明肯定有藏文本或者压缩包
  • 使用 foremost 分离出来,有一个压缩包
T6-1
  • 解压密码在 图片属性的作者 有写4UEXBD0Q
T6-3
  • 解压得到flag

    1
    flag{d349bd48-5e00-4159-8b4a-008bb48502c5}
T6-2