2023年第一届长城杯信息安全铁人三项赛决赛WP(CTF部分题解)
这次长城杯的线下决赛的CTF几题质量还行,这里保存了几题赛后复现一下
1 aesweblog
题目提供了一个访问日志 access_access.log
和 数据库插入的数据的生成方式(也就是 flag
的加密方式)代码 initsecretandflagdata.py
#initsecretandflagdata.py
import sqlite3
import os
import random
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import hashlib
def md5_hash(input_rawstring):
# 创建一个md5 hash对象
m = hashlib.md5()
# 更新hash对象
m.update(input_rawstring)
# 获取散列值的16进制字符串表示(32个字节长度)
hexdigest = m.hexdigest()
#将16进制字符串转换为16字节的字节串
byte_digest = bytes.fromhex(hexdigest)
return byte_digest
#return (hex_dig)
def genpassphrase(username,number):
passphrase = ""
accvalue = 0
# 这里省略了计算随机数种子的部分代码。其余代码都是okay的。
random.seed(accvalue)
total = random.randint(4,7)
for i in range(total):
str4bytes = base64.b64encode(os.urandom(3))
passphrase = passphrase + str4bytes.decode("utf-8")
return passphrase
def genflagstr(username,number):
flagstr = f"flag{number}" + "{"
accvalue = 0
# 这里省略了计算随机数种子的部分代码。其余代码都是okay的。
random.seed(accvalue)
total = random.randint(7,15)
base64str = base64.b64encode(os.urandom(total))
return base64str
# 迭代次数,增加这个值会提高安全性,但也会降低性能
iterations = 10000
# 密钥长度(可以是16、24或32字节,对应AES-128、AES-192或AES-256)
key_length = 16
# 简化版的PBKDF2实现,这里仅使用HMAC-SHA256作为伪随机函数
def pbkdf2(passphrase, salt, iterations, key_length):
h = hashlib.sha256()
u = passphrase.encode("utf-8") + salt
for _ in range(iterations):
h.update(u)
u = h.digest()
return h.digest()[:key_length]
conn = sqlite3.connect('customers.db')
cursor = conn.cursor()
# 先清除现有数据。
cursor.execute("delete from secrets")
conn.commit()
cursor.execute("delete from user_flag")
conn.commit()
sqlcmd = "SELECT username FROM customers"
cursor.execute(sqlcmd)
rows = cursor.fetchall()
usernames = [row[0] for row in rows]
count = len(usernames)
for username in usernames:
passphrase = genpassphrase(username, count)
insertsecretcmd = f"insert into secrets(username,passphrase) values('{username}','{passphrase}')"
print(insertsecretcmd)
flagname = f"flag{count}"
# AES加密用的salt从passphrase派生
salt = md5_hash(passphrase.encode("utf-8") + b"saltseed")
# 使用passphrase和salt通过PBKDF2派生密钥
key = pbkdf2(passphrase, salt, iterations, key_length)
# 初始化向量也从passphrase派生
iv = md5_hash(passphrase.encode("utf-8") + b"IVseed")
# 创建Cipher对象
cipher = AES.new(key, AES.MODE_CBC, iv)
# 需要加密的数据
original_flagdata = genflagstr(username,count)
# 对数据进行填充,以符合AES的块大小(128位/16字节)
padded_flagdata = pad(original_flagdata, AES.block_size)
# 加密数据
encrypted_flagdata = cipher.encrypt(padded_flagdata)
# 使用相同的salt和passphrase通过PBKDF2派生密钥
key_decrypt = pbkdf2(passphrase, salt, iterations, key_length)
# 创建解密器对象
decipher = AES.new(key_decrypt, AES.MODE_CBC, iv)
# 解密数据
decrypted_padded_data = decipher.decrypt(encrypted_flagdata)
# 去除填充
decrypted_flagdata = unpad(decrypted_padded_data, AES.block_size)
#decrypted_flagdatastr = decrypted_flagdata.decode("utf-8")
if original_flagdata == decrypted_flagdata:
print(f"\r\n congrutulations: {original_flagdata} matchs {decrypted_flagdata}\r\n")
flagvalue = base64.b64encode(encrypted_flagdata)
base64flagdata = flagvalue.decode("utf-8")
notestr = "encrypted with aes128. associated key, iv & salt are derived from passphrase."
insertuser_flagcmd = f"insert into user_flag(username,flagname,flagvalue,note) values('{username}','{flagname}','{base64flagdata}','{notestr}')"
print(insertuser_flagcmd)
print("")
count = count - 1
cursor.execute(insertsecretcmd)
cursor.execute(insertuser_flagcmd)
conn.commit()
cursor.close()
conn.commit()
conn.close()
访问日志太大,就不贴了,上面是题目的脚本
首先第一步肯定是将日志文件进行清洗,观察下日志,其中包含了 400
、403
、404
等错误响应码,我们只需要 200
的日志即可,清洗脚本如下
import urllib.parse
import re
accesslogs = open('access_access.log','r').readlines()
washed = open('washed.log','a+');
errorline = re.compile('172.21.0.2:80 172.21.0.1 - - \[.*\].* (40[0348]|500)',re.I);
for line in accesslogs:
if len(errorline.findall(line)) > 0:
continue;
washed.write(urllib.parse.unquote(line))
这里正则写复杂了,直接匹配 200
的响应码就可以了应该
最后能得到一个干净的日志
在日志中,其中 cookie
参数传入了一串 base64
编码数据
IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwppbXBvcnQgc3FsaXRlMwoKY29ubiA9IHNxbGl0ZTMuY29ubmVjdCgnL3Zhci93d3cvZGIvY3VzdG9tZXJzLmRiJykKY3Vyc29yID0gY29ubi5jdXJzb3IoKQoKc3FsY21kID0gIiIiCkNSRUFURSBUQUJMRSBzZWNyZXRzCigKICAgaWQgIElOVEVHRVIgUFJJTUFSWSBLRVkgQVVUT0lOQ1JFTUVOVCwKICAgdXNlcm5hbWUgdmFyY2hhcigyMCkgICAgTk9UIE5VTEwsCiAgIHBhc3NwaHJhc2UgdmFyY2hhcigyMCkgTk9UIE5VTEwsCiAgIGRhdGV0aW1lIFRJTUVTVEFNUCBERUZBVUxUIENVUlJFTlRfVElNRVNUQU1QLAogICBGT1JFSUdOIEtFWSAodXNlcm5hbWUpIFJFRkVSRU5DRVMgY3VzdG9tZXJzKHVzZXJuYW1lKQopOyIiIgoKY3Vyc29yLmV4ZWN1dGUoc3FsY21kKQoKc3FsY21kID0gIiIiCkNSRUFURSBUQUJMRSB1c2VyX2ZsYWcKKAogICBpZCAgSU5URUdFUiBQUklNQVJZIEtFWSBBVVRPSU5DUkVNRU5ULAogICB1c2VybmFtZSAgIHZhcmNoYXIoMjApICAgICAgIE5PVCBOVUxMLAogICBmbGFnbmFtZSAgICB2YXJjaGFyKDIwKSAgICAgICBOT1QgTlVMTCwKICAgZmxhZ3ZhbHVlICAgICB2YXJjaGFyKDIwKSAgICAgICBOT1QgTlVMTCwKICAgZW5jcnlwdGVkICAgaW50IERFRkFVTFQoMSksCiAgIG5vdGUgdmFyY2hhcigyMCkgREVGQVVMVCgnJyksCiAgIGRhdGV0aW1lIFRJTUVTVEFNUCBERUZBVUxUIENVUlJFTlRfVElNRVNUQU1QLAogICBGT1JFSUdOIEtFWSAodXNlcm5hbWUpIFJFRkVSRU5DRVMgY3VzdG9tZXJzKHVzZXJuYW1lKQopOyIiIgoKY3Vyc29yLmV4ZWN1dGUoc3FsY21kKQoKY3Vyc29yLmNsb3NlKCkKY29ubi5jb21taXQoKQpjb25uLmNsb3NlKCkK
解密后是一段 python
代码,用来为数据库创建表
#!/usr/bin/env python3
import sqlite3
conn = sqlite3.connect('customers.db')
cursor = conn.cursor()
sqlcmd = """
CREATE TABLE secrets
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
username varchar(20) NOT NULL,
passphrase varchar(20) NOT NULL,
datetime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (username) REFERENCES customers(username)
);"""
cursor.execute(sqlcmd)
sqlcmd = """
CREATE TABLE user_flag
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
username varchar(20) NOT NULL,
flagname varchar(20) NOT NULL,
flagvalue varchar(20) NOT NULL,
encrypted int DEFAULT(1),
note varchar(20) DEFAULT(''),
datetime TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (username) REFERENCES customers(username)
);"""
cursor.execute(sqlcmd)
cursor.close()
conn.commit()
conn.close()
这个文件的主要目的应该是告诉你表的字段,简化了做题流程,可有可无吧
下一步,也就是要提取这些流量包中的数据了,仔细观察流量,其实很明显是 sql注入
流量,且是用 sqlmap
跑出来的,每条流量后面携带的 User-Agant
头就能看出来
布尔盲注,写个脚本提取数据即可
import re
TABLENAME = 'user_flag'
FIELD = 'encrypted'
datas = open('washed.log','r').readlines()
passphrase = re.compile('.*COALESCE\(%s,CHAR\(32\)\) FROM %s LIMIT ([0-9]{1,2})\,1\),([0-9]{1,2}),1\)\>CHAR\(([0-9]{1,3})\) AND .* 200 (196|341).*' % (FIELD,TABLENAME),re.I)
passphraseData = {
}
for line in datas:
matchs = passphrase.match(line)
if matchs:
find = passphrase.findall(line)[0]
if passphraseData.get(find[0]) == None:
passphraseData[find[0]] = {
}
if passphraseData[find[0]].get(find[1]) == None:
passphraseData[find[0]][find[1]] = []
passphraseData[find[0]][find[1]].append(
[
find[2],
find[3]
]
)
baseformat = "{:<4}{:<32}"
print(baseformat.format('id',FIELD))
for id in passphraseData:
password = "";
for e in passphraseData[id]:
rightkey = -1;
errorkey = -1;
for i in passphraseData[id][e][::-1]:
if i[1] == '341' and rightkey == -1:
rightkey = int(i[0])
if i[1] == '196' and errorkey == -1:
errorkey = int(i[0])
if rightkey == -1 or errorkey == -1:
continue;
if rightkey+1 == errorkey:
password += chr(rightkey+1);
print(baseformat.format(id,password));
通过修改脚本中的 TABLENAME
和 FIELD
变量可以提取不同的盲注数据
那么需要提取哪些数据我们可以先放一放,回到前面他提供的生成加密后 flag
数据的文件中,看一下代码,可以锁定这几行
insertsecretcmd = f"insert into secrets(username,passphrase) values('{username}','{passphrase}')"
insertuser_flagcmd = f"insert into user_flag(username,flagname,flagvalue,note) values('{username}','{flagname}','{base64flagdata}','{notestr}')"
cursor.execute(insertsecretcmd)
cursor.execute(insertuser_flagcmd)
而通过下面的这些代码,可以知道这些数据字段之间的关系
flagname = f"flag{count}"
# AES加密用的salt从passphrase派生
salt = md5_hash(passphrase.encode("utf-8") + b"saltseed")
# 使用passphrase和salt通过PBKDF2派生密钥
key = pbkdf2(passphrase, salt, iterations, key_length)
# 初始化向量也从passphrase派生
iv = md5_hash(passphrase.encode("utf-8") + b"IVseed")
# 创建Cipher对象
cipher = AES.new(key, AES.MODE_CBC, iv)
# 需要加密的数据
original_flagdata = genflagstr(username,count)
# 对数据进行填充,以符合AES的块大小(128位/16字节)
padded_flagdata = pad(original_flagdata, AES.block_size)
# 加密数据
encrypted_flagdata = cipher.encrypt(padded_flagdata)
# 使用相同的salt和passphrase通过PBKDF2派生密钥
key_decrypt = pbkdf2(passphrase, salt, iterations, key_length)
# 创建解密器对象
decipher = AES.new(key_decrypt, AES.MODE_CBC, iv)
# 解密数据
decrypted_padded_data = decipher.decrypt(encrypted_flagdata)
# 去除填充
decrypted_flagdata = unpad(decrypted_padded_data, AES.block_size)
#decrypted_flagdatastr = decrypted_flagdata.decode("utf-8")
if original_flagdata == decrypted_flagdata:
print(f"\r\n congrutulations: {original_flagdata} matchs {decrypted_flagdata}\r\n")
flagvalue = base64.b64encode(encrypted_flagdata)
base64flagdata = flagvalue.decode("utf-8")
表 secrets
中的 username
用于生成了目标 flag
, passphrase
字段派生了加密 flag
所使用的 AES
中的 key
、 iv
变量
而 AES
的 key
又通过函数 pbkdf2
生成,且携带了由 passphrase
派生的 salt
所以我们先通过上面的盲注提取脚本获取 username
、passphrase
、flagvalue
最后得到的结果,经过整理后,如下
{0: {'username': 'Abraham', 'passphrase': 'lX2cl2FX5rWAvaF1', 'flagvalue': 'vsuhBrXLbl6z08OuMTpsjr14aBmip1RYcYV5mT6m3jY='}, 1: {'username': 'Akiko', 'passphrase': 'li23N1voIkniAwk5T7fTm0w1', 'flagvalue': 'ciz4vj/Knj7yR7ZqHDMtdbjrR1bIff7HgLdAHTGHC6mYOn1LJnYN0at7gA9m0+iW'}, 2: {'username': 'Albert', 'passphrase': '+ETF9yU36zgSy1/1u1Rq', 'flagvalue': 'zkkGriQEyEqj9FXYT2z/uynbkKBTrjfmZkjD0YxEp30='}, 3: {'username': 'Ann', 'passphrase': 'ki4qt4e6ymRedpvS5QjVqzaddLzU', 'flagvalue': 'bNnBB1c4Lce2DmdRkcN5U2R7t4oyqkfs1McVr5B9/xjalk2ZKUYpvdc1aglm6QbB'}, 4: {'username': 'Anne', 'passphrase': 'gY8w4pdXh0oW6++AzeVHveGRWkdP', 'flagvalue': 'BeBtptc/VAKsH+xYYy7Ppq3uRu7u2B57Pckz60AlWpDfIMo7myZiviQqjvGhkUTh'}, 5: {'username': 'Burt', 'passphrase': 'wuImkcqj1kVIrUC1+QkL9ET7wGys', 'flagvalue': 'hAMyf9Twj1bkmsmby21uujAIExv0atQ/gX6zdnO5RGv+XW/wA6tZn78j0LCG45u9'}, 6: {'username': 'Charlene', 'passphrase': 'U4D4HEXHkbX9jX8ykv8C', 'flagvalue': '5NZ+GDYsWjftT6ja8jH1IoD02F09KA7nTafuyhuhsEc='}, 7: {'username': 'Cheryl', 'passphrase': 'AxG7MVCVNtffr4ZYVErlEYLU', 'flagvalue': '2RGYYX451isKyXykksliKdqfqfZGBx9bKrnsMfJJynpoUvXHpWgpTYTV6W1hX1Rn'}, 8: {'username': 'Dean', 'passphrase': '+kZla/aX4bQy+Bgl+FqjiAIkel9R', 'flagvalue': 'O6kg8FJc3aOoOPsTz3WeW17p8nt8gMzHkqCe1kG1V6lkseMb9I5JMVF3PrBC067I'}, 9: {'username': 'Dirk', 'passphrase': 'Zglu/lEZPG1q9/PC6N8yVEJR3hiT', 'flagvalue': 'kiz9+8DwaJIwp74CwgiCRKAsJH8Icuo9qYaTnjS975hM3vBmWQZEoRfIV2Ht0+47'}, 10: {'username': 'Heather', 'passphrase': '+ROXoWcfiG1etDzI5Ce+IIma', 'flagvalue': 'be+4cGpopf2smgbGk7BsJtXbAVHnVEQmIEJaks5LS+o='}, 11: {'username': 'Innes', 'passphrase': 's/e8gc9O9tLVgnSu', 'flagvalue': 'TZp3Wngcls23O4doukmnTH0W3uZTRIQs3ih3c3G5hHU='}, 12: {'username': 'Johnson', 'passphrase': 'p4qnEU26lRL3rWihaeTltPWP', 'flagvalue': 'SmxLETbfO+BEeg3R1j+UWkc0f0X+faJm6Rv0ox4Covw='}, 13: {'username': 'Livia', 'passphrase': 'GHIPf43AUssFp9Pf', 'flagvalue': 'QNAsEwp7zeWlCjZQgbYoYCDE4DcIECke99cCW/mQgqM='}, 14: {'username': 'Marjorie', 'passphrase': 'EHpYh9ZwmvwDZNWWIObA', 'flagvalue': 'rMjnPlIf024hJuSct08fR6EerfM0YZI4JthJO4iWed4='}, 15: {'username': 'Meander', 'passphrase': 'KSOfdQPrmLXd+L/J4meO6zE1', 'flagvalue': 'zjPLgsu/riGxovEFhNdXSgbJeLI24hFsouajIwYiLuJm6STZi823EAUMGVM2T3TA'}, 16: {'username': 'Michael', 'passphrase': '5z4puHJAOUr30v5kUNKx/Q/s', 'flagvalue': 'n+m/7/d9w8bhF6J+vgwA7PeyDP0vKVsuXwV6fIkXOHM='}, 17: {'username': 'Michel', 'passphrase': 'Apwibg5LKhOOu4394eCo', 'flagvalue': 'Mg5d8cqX7ESyudDEujG8xFL0Gg7fcfGh76MMB++ij2g='}, 18: {'username': 'Morningstar', 'passphrase': 'TJOC6+L+vTajLOGqfJMweoLFZqvJ', 'flagvalue': 'PgH8ySWqsB1IJEradvOouNWEP5mpHZnu5e9ShkiS1LWLb81hRUjAZLD4zzzxO5Tn'}, 19: {'username': 'Reginald', 'passphrase': '8oO3XRquAlw69cTS', 'flagvalue': 'QnuOV76w2slE2KH1frVRQLYyXv1eVu1R8OQj/tJ0KwI='}, 20: {'username': 'Sheryl', 'passphrase': '3v4uHprHE/6+OzwiuDeGQ84z', 'flagvalue': '8jeIVEKbTQvSTt1mgWLbQ2BAcvSXXwdAcNSh6G7d9qQ='}, 21: {'username': 'Stearns', 'passphrase': 'l9+Lb9lMCHVDFc0Vg7b3', 'flagvalue': 'XBWdUHCfIDSQ8KETGkYo5gjuUFOKi4PYE4ugv7FwdPM='}, 22: {'username': 'Sylvia', 'passphrase': 'M9/QApJbT4aMHMrfVWv+', 'flagvalue': 'vuXmNsrtO6Jhk3rhLpFpgvX9JJd8VDlyav1OGnjbZl4='}, 23: {'username': 'admin', 'passphrase': '7xjo0DHFsoF1Jrus', 'flagvalue': 'lTr01ZNX8R9xotVanrPtKiBjCfQD5U5dD+KLRPgjnQ4='}}
编写解密脚本如下,其中一些迭代加密的函数题目已经给出,复制过来用即可
import hashlib
from base64 import b64decode
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
datalist = {0: {'username': 'Abraham', 'passphrase': 'lX2cl2FX5rWAvaF1', 'flagvalue': 'vsuhBrXLbl6z08OuMTpsjr14aBmip1RYcYV5mT6m3jY='}, 1: {'username': 'Akiko', 'passphrase': 'li23N1voIkniAwk5T7fTm0w1', 'flagvalue': 'ciz4vj/Knj7yR7ZqHDMtdbjrR1bIff7HgLdAHTGHC6mYOn1LJnYN0at7gA9m0+iW'}, 2: {'username': 'Albert', 'passphrase': '+ETF9yU36zgSy1/1u1Rq', 'flagvalue': 'zkkGriQEyEqj9FXYT2z/uynbkKBTrjfmZkjD0YxEp30='}, 3: {'username': 'Ann', 'passphrase': 'ki4qt4e6ymRedpvS5QjVqzaddLzU', 'flagvalue': 'bNnBB1c4Lce2DmdRkcN5U2R7t4oyqkfs1McVr5B9/xjalk2ZKUYpvdc1aglm6QbB'}, 4: {'username': 'Anne', 'passphrase': 'gY8w4pdXh0oW6++AzeVHveGRWkdP', 'flagvalue': 'BeBtptc/VAKsH+xYYy7Ppq3uRu7u2B57Pckz60AlWpDfIMo7myZiviQqjvGhkUTh'}, 5: {'username': 'Burt', 'passphrase': 'wuImkcqj1kVIrUC1+QkL9ET7wGys', 'flagvalue': 'hAMyf9Twj1bkmsmby21uujAIExv0atQ/gX6zdnO5RGv+XW/wA6tZn78j0LCG45u9'}, 6: {'username': 'Charlene', 'passphrase': 'U4D4HEXHkbX9jX8ykv8C', 'flagvalue': '5NZ+GDYsWjftT6ja8jH1IoD02F09KA7nTafuyhuhsEc='}, 7: {'username': 'Cheryl', 'passphrase': 'AxG7MVCVNtffr4ZYVErlEYLU', 'flagvalue': '2RGYYX451isKyXykksliKdqfqfZGBx9bKrnsMfJJynpoUvXHpWgpTYTV6W1hX1Rn'}, 8: {'username': 'Dean', 'passphrase': '+kZla/aX4bQy+Bgl+FqjiAIkel9R', 'flagvalue': 'O6kg8FJc3aOoOPsTz3WeW17p8nt8gMzHkqCe1kG1V6lkseMb9I5JMVF3PrBC067I'}, 9: {'username': 'Dirk', 'passphrase': 'Zglu/lEZPG1q9/PC6N8yVEJR3hiT', 'flagvalue': 'kiz9+8DwaJIwp74CwgiCRKAsJH8Icuo9qYaTnjS975hM3vBmWQZEoRfIV2Ht0+47'}, 10: {'username': 'Heather', 'passphrase': '+ROXoWcfiG1etDzI5Ce+IIma', 'flagvalue': 'be+4cGpopf2smgbGk7BsJtXbAVHnVEQmIEJaks5LS+o='}, 11: {'username': 'Innes', 'passphrase': 's/e8gc9O9tLVgnSu', 'flagvalue': 'TZp3Wngcls23O4doukmnTH0W3uZTRIQs3ih3c3G5hHU='}, 12: {'username': 'Johnson', 'passphrase': 'p4qnEU26lRL3rWihaeTltPWP', 'flagvalue': 'SmxLETbfO+BEeg3R1j+UWkc0f0X+faJm6Rv0ox4Covw='}, 13: {'username': 'Livia', 'passphrase': 'GHIPf43AUssFp9Pf', 'flagvalue': 'QNAsEwp7zeWlCjZQgbYoYCDE4DcIECke99cCW/mQgqM='}, 14: {'username': 'Marjorie', 'passphrase': 'EHpYh9ZwmvwDZNWWIObA', 'flagvalue': 'rMjnPlIf024hJuSct08fR6EerfM0YZI4JthJO4iWed4='}, 15: {'username': 'Meander', 'passphrase': 'KSOfdQPrmLXd+L/J4meO6zE1', 'flagvalue': 'zjPLgsu/riGxovEFhNdXSgbJeLI24hFsouajIwYiLuJm6STZi823EAUMGVM2T3TA'}, 16: {'username': 'Michael', 'passphrase': '5z4puHJAOUr30v5kUNKx/Q/s', 'flagvalue': 'n+m/7/d9w8bhF6J+vgwA7PeyDP0vKVsuXwV6fIkXOHM='}, 17: {'username': 'Michel', 'passphrase': 'Apwibg5LKhOOu4394eCo', 'flagvalue': 'Mg5d8cqX7ESyudDEujG8xFL0Gg7fcfGh76MMB++ij2g='}, 18: {'username': 'Morningstar', 'passphrase': 'TJOC6+L+vTajLOGqfJMweoLFZqvJ', 'flagvalue': 'PgH8ySWqsB1IJEradvOouNWEP5mpHZnu5e9ShkiS1LWLb81hRUjAZLD4zzzxO5Tn'}, 19: {'username': 'Reginald', 'passphrase': '8oO3XRquAlw69cTS', 'flagvalue': 'QnuOV76w2slE2KH1frVRQLYyXv1eVu1R8OQj/tJ0KwI='}, 20: {'username': 'Sheryl', 'passphrase': '3v4uHprHE/6+OzwiuDeGQ84z', 'flagvalue': '8jeIVEKbTQvSTt1mgWLbQ2BAcvSXXwdAcNSh6G7d9qQ='}, 21: {'username': 'Stearns', 'passphrase': 'l9+Lb9lMCHVDFc0Vg7b3', 'flagvalue': 'XBWdUHCfIDSQ8KETGkYo5gjuUFOKi4PYE4ugv7FwdPM='}, 22: {'username': 'Sylvia', 'passphrase': 'M9/QApJbT4aMHMrfVWv+', 'flagvalue': 'vuXmNsrtO6Jhk3rhLpFpgvX9JJd8VDlyav1OGnjbZl4='}, 23: {'username': 'admin', 'passphrase': '7xjo0DHFsoF1Jrus', 'flagvalue': 'lTr01ZNX8R9xotVanrPtKiBjCfQD5U5dD+KLRPgjnQ4='}}
def md5_hash(input_rawstring):
# 创建一个md5 hash对象
m = hashlib.md5()
# 更新hash对象
m.update(input_rawstring)
# 获取散列值的16进制字符串表示(32个字节长度)
hexdigest = m.hexdigest()
#将16进制字符串转换为16字节的字节串
byte_digest = bytes.fromhex(hexdigest)
return byte_digest
#return (hex_dig)
def pbkdf2(passphrase, salt, iterations, key_length):
h = hashlib.sha256()
u = passphrase.encode("utf-8") + salt
for _ in range(iterations):
h.update(u)
u = h.digest()
return h.digest()[:key_length]
iterations = 10000
key_length = 16
flag1 = ""
flag6 = ""
for index in datalist:
e = datalist[index]
username = e['username']
passphrase = e['passphrase']
flagvalue = e['flagvalue']
salt = md5_hash(passphrase.encode("utf-8") + b"saltseed")
key = pbkdf2(passphrase, salt, iterations, key_length)
iv = md5_hash(passphrase.encode("utf-8") + b"IVseed")
cipher = AES.new(key, AES.MODE_CBC, iv)
flag = unpad(
cipher.decrypt(
b64decode(flagvalue)
),
AES.block_size
).decode()
print(flag)
"""
flag24{5be1fd0324f5d4c0}
flag23{f70a26e0c802b318191590d5}
flag22{6e3cf0e098f7891c51e0}
flag21{c793acc91e433349bcb413fde0}
flag20{a401647f22f5754229f8c737fc}
flag19{919fc8765f7dbebbfc9bb8cd1608}
flag18{79f3a122e01e2ce73c9a}
flag17{4ff7944842b0441fa6785264}
flag16{468bd9239567b536f194b7bdca9b}
flag15{ac07329898b13e965fc70adb06fcd8}
flag14{340f915dd1d175cb441d5a}
flag13{7244036fd7cf9c89}
flag12{5102b8cbd3a38ca73d2749}
flag11{1089f5382dc193}
flag10{bf6bd8307a5b5885402f}
flag9{34cab1ff059a3a1858fb1db96a598f}
flag8{f6daa6ea9a299e80d7285444}
flag7{6f28d6f16daad6440a39}
flag6{a85bcfc646ff161c71e520e7cba3}
flag5{f22e5ce320a95c02}
flag4{16bf4b02a8a5ff58e6b69d0c}
flag3{a70f5b89829e5ac8b9f5}
flag2{852e01448420410c26}
flag1{e6f1573b9496a7f3}
"""
然后题目说只关注 flag1
和 flag6
,那么按照题目拼接提交就好了
flag{e6f1573b9496a7f3-a85bcfc646ff161c71e520e7cba3}
2 crazyaes
本身对加密不是特别擅长,看这个看了挺久,又要去看AES的原理,终于搞清楚了改了哪里。。。
拿到的 exe
文件加了 UPX
的壳,而且修改了一些信息,需要手动改回来
共四处地方,改完后就能用 UPX
工具成功脱壳
脱壳后发现花指令
main
函数中的花指令
将这里的调用指令 nop
掉就能正常识别 main
函数
int __cdecl main_0(int argc, const char **argv, const char **envp)
{
unsigned int j; // [esp+D0h] [ebp-60h]
int i; // [esp+DCh] [ebp-54h]
int v6; // [esp+E8h] [ebp-48h]
char v7; // [esp+F7h] [ebp-39h]
char v8[24]; // [esp+100h] [ebp-30h] BYREF
char v9[20]; // [esp+118h] [ebp-18h]
v9[0] = -76;
v9[1] = 56;
v9[2] = 54;
v9[3] = 48;
v9[4] = 30;
v9[5] = 104;
v9[6] = 72;
v9[7] = 87;
v9[8] = 81;
v9[9] = 1;
v9[10] = -73;
v9[11] = 3;
v9[12] = -101;
v9[13] = -104;
v9[14] = -29;
v9[15] = 126;
memset(v8, 17, 15);
v8[15] = 1;
v7 = 0;
v6 = 0;
sub_433B4F(&unk_4A2114);
while ( v7 != 10 )
{
v7 = sub_433451();
byte_4AEF38[v6++] = v7;
}
for ( i = 0; i < 16; ++i )
v8[i] = byte_4AEF38[i];
sub_43615B(0, v8, 16, &byte_4AE000);
for ( j = 0; j < 0x10; ++j )
{
if ( v8[j] != v9[j] )
{
sub_433B4F("wrong!!!");
Sleep(0x1388u);
j___loaddll(0);
}
}
sub_433B4F("WOW!!!");
sub_4358A0("pause");
return sub_433B4F("\n");
}
逻辑很简单,用户的输入被赋值给 v8
数据,然后传入到 sub_43615B
函数中,应该是进行加密,而同步传入的 byte_4AE000
是一串字符串,应该是密钥
函数 sub_43615B
调用的加密函数 loc_439F00
也被花指令干扰,nop
掉即可
// positive sp value has been detected, the output may be wrong!
int __cdecl sub_439F00(int a1, int a2, int a3, int a4)
{
int v5; // [esp-4h] [ebp-220h]
size_t v6; // [esp+0h] [ebp-21Ch]
int j; // [esp+D0h] [ebp-14Ch]
int m; // [esp+D0h] [ebp-14Ch]
int n; // [esp+D0h] [ebp-14Ch]
int i; // [esp+DCh] [ebp-140h]
int k; // [esp+E8h] [ebp-134h]
_BYTE v12[24]; // [esp+F4h] [ebp-128h]
_DWORD v13[6]; // [esp+10Ch] [ebp-110h] BYREF
_BYTE v14[244]; // [esp+124h] [ebp-F8h] BYREF
j__memset(v14, v5, v6);
memset(v13, 0, 16);
v12[0] = 0;
v12[1] = 1;
v12[2] = 2;
v12[3] = 3;
v12[4] = 1;
v12[5] = 0;
v12[6] = 3;
v12[7] = 2;
v12[8] = 2;
v12[9] = 3;
v12[10] = 0;
v12[11] = 1;
v12[12] = 3;
v12[13] = 2;
v12[14] = 1;
v12[15] = 0;
sub_4354FE(a1, a4, (int)v14);
for ( i = 0; i < a3; i += 4 * dword_4AE01C[a1] )
{
for ( j = 0; j < 4 * dword_4AE01C[a1]; ++j )
*((_BYTE *)v13 + j) = *(_BYTE *)(a2 + j + i);
for ( k = 0; k <= dword_4AE014[a1]; ++k )
{
if ( k > 0 )
{
sub_4365C0(a1, (int)v13);
sub_434860(a1, (int)v13);
if ( k < dword_4AE014[a1] )
((void (__cdecl *)(int, _DWORD *))sub_434D51)(a1, v13);
}
sub_435C01(a1, (int)v13, (int)v14, k);
for ( m = 0; m < 4 * dword_4AE01C[a1]; ++m )
*((_BYTE *)v13 + m) ^= v12[m];
}
for ( n = 0; n < 4 * dword_4AE01C[a1]; ++n )
*(_BYTE *)(a2 + n + i) = *((_BYTE *)v13 + n);
}
return 0;
}
通过加密函数可以判断出来是 AES
加密,但是对加密进行了魔改
-
字节替换
函数sub_4365C0
中,在字节替换最后还进行了异或0xA1
-
列混合
函数sub_434D51
中,在每次的伽罗华域加法和乘法
结束后进行了异或0x54
,这个函数里面也有花指令,要去花的话把jz
去掉,然后其他转为数据即可 -
每轮运算结束前都会进行一个异或处理
还有就是在 TLS
的回调函数里面修改了 key[2]
的值为 0x68
脚本比较长,网上找个 AES
的模板改了一下,下面就贴魔改的部分
- 字节替换
void inv_sub_bytes(uint8_t *state) {
for (int i = 0; i < 16; i++) {
state[i] ^= 0xA1u;
state[i] = inv_sbox[state[i]];
}
}
- 列混合
uint8_t GMul(uint8_t v ,uint8_t u) {
uint8_t p = 0;
for (int i = 0; i < 8; ++i) {
if (u & 0x01) { //
p ^= v;
}
int flag = (v & 0x80);
v <<= 1;
if (flag) {
v ^= 0x1B; /* x^8 + x^4 + x^3 + x + 1 */
}
u >>= 1;
}
return p;
}
void inv_mix_columns(uint8_t *state) {
uint8_t a, b, c, d;
for (int i = 0; i < 4; i++) {
a = state[i*4+0] ^ 0x54;
b = state[i*4+1] ^ 0x54;
c = state[i*4+2] ^ 0x54;
d = state[i*4+3] ^ 0x54;
state[i*4+0] = GMul(0x0e , a) ^ GMul(0x0b , b) ^ GMul(0x0d , c) ^ GMul(0x09 , d);
state[i*4+1] = GMul(0x09 , a) ^ GMul(0x0e , b) ^ GMul(0x0b , c) ^ GMul(0x0d , d);
state[i*4+2] = GMul(0x0d , a) ^ GMul(0x09 , b) ^ GMul(0x0e , c) ^ GMul(0x0b , d);
state[i*4+3] = GMul(0x0b , a) ^ GMul(0x0d , b) ^ GMul(0x09 , c) ^ GMul(0x0e , d);
}
}
- 解密主体
void aes_decrypt(uint8_t *ciphertext, uint8_t *key, uint8_t *plaintext) {
uint8_t state[16];
uint8_t expanded_key[176];
uint8_t key2[16] = {0,1,2,3,1,0,3,2,2,3,0,1,3,2,1,0};
memcpy(state, ciphertext, 16);
key_expansion(key, expanded_key);
add_round_key(state, expanded_key + 160);
for(int i=0;i<16;++i){
((unsigned char*)state)[i] ^= key2[i];
}
for (int round = 9; ;round--) {
inv_shift_rows(state);
inv_sub_bytes(state);
add_round_key(state, expanded_key + round * 16);
for(int i=0;i<16;++i){
((unsigned char*)state)[i] ^= key2[i];
}
if ( round == 0){
break;
}else{
inv_mix_columns(state);
}
}
memcpy(plaintext, state, 16);
}
int main() {
uint8_t key[] = { 0x67,0x61,0x21,0x34,0x33,0x6A,0x4A,0x4B,0x67,0x66,0x6A,0x47,0x4D,0x65,0x41,0x52 };
key[2] = 0x68;
unsigned char flag[] = { 0xb4,0x38,0x36,0x30,0x1e,0x68,0x48,0x57,0x51,0x01,0xb7,0x03,0x9b,0x98,0xe3,0x7e };
unsigned char plaintext[16];
aes_decrypt(flag, key, plaintext);
printf("flag: ");
for (int i = 0; i < 16; i++) {
printf("%c", plaintext[i]);
}
printf("\n");
return 0;
}
最后 flag
flag{Re_1ts_f0n}
3 坚守要地
反编译 apk
后,查看具体逻辑,其中在初始化的时候,调用了 init
函数
private void init() {
File file = new File(getDataDir().getPath() + File.separator + DATABASE);
if (!file.exists()) {
file.mkdir();
file.getPath();
FileUtil.copyRawFileToDirectory(this, R.raw.information, "information.db", file);
}
File file2 = new File(getDataDir().getPath() + File.separator + CODE);
if (file2.exists()) {
return;
}
file2.mkdir();
file2.getPath();
FileUtil.copyRawFileToDirectory(this, R.raw.wcmm5ukk, "wcmm5ukk", file2);
}
其目的是将本地 apk
中的资源 information.db
和 wcmm5ukk
文件复制到安装的路径
题目还给了一个模拟器的镜像,应该是将镜像中的 information.db
文件提取,然后里面就是密文了,对密文进行解密即可
其实镜像没什么用,都是从资源文件里面复制出来了,我们提取 apk
中的 db
文件就可以了,但是里面是假的 flag
在 apk
代码中,还存在三个变量
private static final String CODE = "code";
private static final String DATABASE = "databases";
private static final String KEY = "VWt1OEhOMkpnbU1VSnhzQw==";
其中这个 KEY
变量是 base64
编码,且没有地方调用,联想到 information.db
文件是假的数据库文件,所以猜测 wcmm5ukk
文件是加密的 sqlite
文件,而这个 KEY
便是解密密钥
将密钥解密后得到 Uku8HN2JgmMUJxsC
作为密码进行解密
能够得到一串加密的 flag
而在 apk
代码中,虽然 onClick
函数是直接显示提示,并未调用解密函数,但是我们可以通过 hook
进行代替调用
private void decrypt() {
this.mEtFlag.setText(Encryption.decrypt(this.mEtFlag.getText().toString(), "FCAr62VFBF9SRw8Z"));
}
通过 hook
调用 Encryption.decrypt
函数即可
Java.perform(function(){
let MainActivity = Java.use("com.information.app1.MainActivity"),
Encryption = Java.use("com.information.ndk.Encryption");
MainActivity["showToast"].implementation = function () {
console.log(`clicked`);
console.log(Encryption['decrypt']("a9f3718960505f6bba12e85b550db764922b4b97","FCAr62VFBF9SRw8Z"))
};
})
flag{119.21803,2604085}