密码
mmmd5
直接在网上找一个md5解密的网站现场来解就可以了。每一段都拼接起来就可以了。
但是的话,也可以直接用脚本,但是要用到MD5的字典。
它给了一个附件,可以借助此来解答。
那么我们主要来学习一下hash函数,
哈希函数,可以给任何数据生成一个全局标识符。我们一般用它检测文件的完整性。
可以接受任意长度的输入并产生固定长度的输出。对于同一个哈希函数,相同的输入会产生相同的摘要和哈希值,
hash函数的安全特性:
抗第一原像性:无法根据哈希函数的输出恢复其对于的输入。(当输入空间不能太小且不可预测)
抗第二原像性,简单来说就是没有攻击者可以将原文件替换成不同的文件。
抗碰撞性:保证不能产生哈希值相同的两个不同的输入。
但是,哈希函数满足这些安全特性的前提是:有正确的方式使用哈希函数,它的输出摘要大小(一般我们要求输出长度不能低于256比特)
为什么是256比特?可以参考以下:
生日界限:生日界限源于概率论,典型问题:一个房间内至少需要多少人,才能使得两个人生日相同的概率至少达到50%,随机抽查23个人,他们中两个人生日相同的概率就足以达到50%。这个现象称为生日悖论,实际上,在2^N种可能性的空间中随机生成字符串,在已经生成大约2^N/2个后,发生碰撞的概率已经达到了50%
那么我们学习一下MD5,
MD5(信息摘要算法),一种被广泛使用的密码散列函数,1992年公开,取代MD4算法,主要作用:对信息管理系统加密,计算机、数据安全传输,数字签名认证。
加密原理:
MD5以512位的分组处理信息,又将每一个分组划分为16个32位的子分组,经过一系列出来后,输出由四个32位的分组结合后将生成一个128位的散列值 。
关键的步骤:
1、填充(为了提高安全性、保证数据的完整性,满足内部的算法处理机制)
将信息进行填充,使其位长对512求余后的结果等于448,即使符合也必须填充,为了使不同长度的都能产生难以预测的哈希值保证安全性。
填充后位长=N*512+448,N非负整数。
计算原始的消息长度(不含填充的部分),附加到填充位与消息之后,该长度值为64位的二进制数表示的填充前消息的长度。
2、信息分组
将每512位为一组进行分组,再把每组里面分成16个32位的数据。
3、初始化
初始化四个链接变量A、B、C、D,它们都是32位的数字,这些链接变量的初始十六进制数值如下所示,低字节在前:
当设置好这四个链接变量后,就开始进入算法的四轮循环运算。将上面四个链接变量复制到另外四个变量中:A到a,B到b,C到c,D到d。
主循环有四轮,每轮循环都很相似,每一轮进行16次操作。每次操作对a、b、c和d的其中三个进行一次非线性函数运算,然后将所得结果加上第四个变量、信息的一个子分组和一个常数,再将所得结果左移一个不确定的数,并加上a、b、c、d之一。
以下四轮循环中用到的四个非线性函数:
操作完成之后,将A、B、C、D分别加上a、b、c、d,然后用下一分组的数据继续运行算法,最后MD5算法产生128位的输出是A、B、C、D的级联,其中低字节始于A,高字节终于D。至此,整个MD5算法处理结束。
用Python实现:
from hashlib import md5
def encrypt_md5(s):
new_md5 = md5() # 创建md5对象
new_md5.update(s.encode(encoding='utf-8')) # 更新md5对象
return new_md5.hexdigest() # 返回16进制的md5字符串
if __name__ == '__main__':
print(encrypt_md5('ms08067.com'))
XOR操作(异或)
XOR使一种按位操作的运算。操作对象一般是2个比特,除两个操作数都是1的情况外,XOR运算与OR运算的结果完全一样,满足“有1出1,相同出0”
这里有一个实现异或的脚本:
#ecc1
p = 146808027458411567
A = 46056180
B = 2316783294673
E = EllipticCurve(GF(p),[A,B])
P = E(119851377153561800, 50725039619018388)
Q = E(22306318711744209, 111808951703508717)
P.discrete_log(Q)
#ecc2
p = 1256438680873352167711863680253958927079458741172412327087203
A = 377999945830334462584412960368612
B = 604811648267717218711247799143415167229480
E = EllipticCurve(GF(p),[A,B])
P = E(550637390822762334900354060650869238926454800955557622817950,
700751312208881169841494663466728684704743091638451132521079)
Q = E(1152079922659509908913443110457333432642379532625238229329830,
819973744403969324837069647827669815566569448190043645544592)
factors, exponents = zip(*factor(E.order()))
primes = [factors[i] ^ exponents[i] for i in range(len(factors))][:-1]
print(primes)
dlogs = []
for fac in primes:
t = int(int(P.order()) // int(fac))
dlog = discrete_log(t*Q,t*P,operation="+")
dlogs += [dlog]
print("factor: "+str(fac)+", Discrete Log: "+str(dlog)) #calculates discrete logarithm for each prime order
print(crt(dlogs,primes))
#ecc3
p =0xd3ceec4c84af8fa5f3e9af91e00cabacaaaecec3da619400e29a25abececfdc9bd678e2708a58acb1bd15370acc39c596807dab6229dca11fd3a217510258d1b
A =0x95fc77eb3119991a0022168c83eee7178e6c3eeaf75e0fdf1853b8ef4cb97a9058c271ee193b8b27938a07052f918c35eccb027b0b168b4e2566b247b91dc07
B =0x926b0e42376d112ca971569a8d3b3eda12172dfb4929aea13da7f10fb81f3b96bf1e28b4a396a1fcf38d80b463582e45d06a548e0dc0d567fc668bd119c346b2
E = EllipticCurve(GF(p),[A,B])
P =E(10121571443191913072732572831490534620810835306892634555532657696255506898960536955568544782337611042739846570602400973952350443413585203452769205144937861,8425218582467077730409837945083571362745388328043930511865174847436798990397124804357982565055918658197831123970115905304092351218676660067914209199149610)
Q =E(964864009142237137341389653756165935542611153576641370639729304570649749004810980672415306977194223081235401355646820597987366171212332294914445469010927,5162185780511783278449342529269970453734248460302908455520831950343371147566682530583160574217543701164101226640565768860451999819324219344705421407572537)
def SmartAttack(P,Q,p):
E = P.curve()
Eqp = EllipticCurve(Qp(p, 2), [ ZZ(t) + randint(0,p)*p for t in E.a_invariants() ])
P_Qps = Eqp.lift_x(ZZ(P.xy()[0]), all=True)
for P_Qp in P_Qps:
if GF(p)(P_Qp.xy()[1]) == P.xy()[1]:
break
Q_Qps = Eqp.lift_x(ZZ(Q.xy()[0]), all=True)
for Q_Qp in Q_Qps:
if GF(p)(Q_Qp.xy()[1]) == Q.xy()[1]:
break
p_times_P = p*P_Qp
p_times_Q = p*Q_Qp
x_P,y_P = p_times_P.xy()
x_Q,y_Q = p_times_Q.xy()
phi_P = -(x_P/y_P)
phi_Q = -(x_Q/y_Q)
k = phi_Q/phi_P
return ZZ(k)
n = SmartAttack(P, Q, p)
n
baby crypto
根据代码就可以知道有几个关键,
一开始将flag转化成长整型最后转化成二进制,补0使其有514位。
swap_bits ():将二进制进行位反转,最后一位与第一位进行调换。
custom_add() :它将每个位与其索引(0开始)加1并取模10。这里实际上不符合它的常规用法,custom_add()这里实际上是对二进制进行操作得到的还是二进制,而现在将二进制转化为了十进制。官方术语:自定义加法
解答的思路很清晰了,直接进行逆过程就可以了,
现在看我当时写的脚本,
def reverse_custom_add(input_str):
input_list = list(input_str)
length = len(input_list)
for i in range(length):
value = int(input_list[i])
new_value = (value - i - 1) % 10
if new_value < 0:
new_value += 10 # 处理负结果
input_list[i] = str(new_value)
return ''.join(input_list)
def reverse_bits(input_str):
input_list = list(input_str)
length = len(input_list)
for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp
return ''.join(input_list)
#
b = "1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345779012334577801133466899113356779911345578801233467789112355779912234577990233556780113"
# 逆自定义加法
reversed_custom_add = reverse_custom_add(b)
# 反转位
reversed_bits = reverse_bits(reversed_custom_add)
# 移除填充的0(原始长度为514位,因为flag是514位长的数字)
flag = reversed_bits[:514]
# 打印结果
print("flag=", flag)
我已经将整个过程基本实现了,但是最后可以看到了,陷入了一种思维的困境,其实我们不知道flag的有效长度是多少,盲目的根据以下,就定义了它的有效长度514位,但是flag的有效长度一定小于等于514位,要用一种动态的方法来解决它,即利用爆破的方法。
还有一个问题就是变换的逻辑错误,我写得的代码中reverse_custom_add()
就是简单的进行了基于位置的数学运算,而根本的目的是找到一个满足条件的数。有些本末倒置了。
修改后,在 input_list
中的每个字符(即数字),它尝试找到一个数字 x
(从0到9),使得 (x + i + 1 - int(input_list[i])) % 10 == 0
成立。这个变换与 reverse_custom_add
中的变换不同,它试图找到一个特定的 x
来满足条件,而不是简单地基于位置进行变换
正确的代码:
from Crypto.Util.number import *
b = 12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567891134567799023445779912234577900124456899023346779001344577801223566780112445788912335667990234457780122355788001244568990133566780013445778902335578800133467889112356679902344567991223557880013455788902335667990234457799023355788001235568990133566789113445679912235577801123457889112456678911245578801233467789112355779912234577990233556780113
input_list = list(str(b))
length = len(str(b))
for i in range(length):
for x in range(10):
if (x + i + 1 - int(input_list[i])) % 10 == 0:
input_list[i] = str(x)
break
leak = ''.join(input_list)
print(leak)
input_list = list(leak)
length = len(input_list)
for i in range(length // 2):
temp = input_list[i]
input_list[i] = input_list[length - 1 - i]
input_list[length - 1 - i] = temp
leak = ''.join(input_list)
# 由于不知道有效长度是多少,所以就把可能的长度都遍历了一遍
for i in range(352, 514):
list = leak[:i]
# print(leak)
flag = int(list, 2)
# print(flag)
flag = long_to_bytes(flag)
print(flag)
最终得到了flag。
这个题目主要就是考你的编程能力了,想到后并实现它,那我们就可以学学这点了,
里面涉及的编程思维:
字符串处理:
- 二进制字符串的生成和填充:通过将
flag
转换为二进制形式,并填充0
以达到514位长度,这涉及到了字符串的生成和修改。 - 字符串的分割和组合:在
swap_bits
函数中,分割字符串(去掉前缀 '0b')并重新组合来实现位反转,这也是字符串处理的一部分
位操作:
swap_bits
函数通过交换二进制字符串中的字符位置来模拟了位反转的效果。这实际上是一种高级的位操作,因为它在逻辑上反转了二进制数的位。
自定义数学运算:
custom_add
函数执行了一个非标准的加法操作,其中每个二进制位(实际上是字符串中的字符)都根据其索引增加了一个偏移量(索引+1),然后模10以避免进位。这不是传统意义上的数学加法,但它通过字符串操作和字符到整数的转换实现了自定义的运算逻辑。
函数重用:
swap_bits
和custom_add
函数的重复使用:在代码中两次定义了swap_bits
和custom_add
函数,并分别对其进行了调用。然而,这并不是函数重用的典型例子,因为您实际上是在不同的上下文中重复了相同的代码。真正的函数重用应该涉及在多个地方调用同一个函数,而不是重新定义它。- 潜在的函数重用:如果您将
swap_bits
和custom_add
函数的定义放在代码的开始部分,并在需要时调用它们,那么这就是函数重用的正确方式。例如,您可以先定义这两个函数,然后在处理leak
字符串时只调用它们一次,而不是重复定义和调用。
web
ezezssrf
根据代码就可以知道了,主要考察php的md5弱比较,强比较,ssrf
想到了数组绕过
接下来MD5强比较,相同的MD5不同的16进制
yunxi=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&wlgf=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
最后的ssrf,GLG必须为http://blog.csdn.net
ssrf:找到目标应用程序中URL验证和处理的弱点,并利用这些弱点来构造恶意请求
这个url是发送HTTP请求的,但因为
但是需要加个@127.0.0.1
添加@,这个绕过方式针对的是php的一个内置的,用来解析url的一个函数
最后要用tee命令或者>写入文件
访问1.txt得到了flag{}
但是还有一个点要注意,
小小py
看到这个的话,肯定想到用bp了,来试试吧!!!
简单抓包看看的话,
可以了解一下什么是session?
session是一种服务器端技术,用于保存用户状态信息,以便多种用户之间保存用户的会话信息,主要识别用户身份,并在用户与服务器之间的多个请求中保持用户的状态信息。
一看就是被加密了,找了个解密的脚本。
import sys
import zlib
from base64 import b64decode
from flask.sessions import session_json_serializer
from itsdangerous import base64_decode
def decryption(payload):
payload, sig = payload.rsplit(b'.', 1)
payload, timestamp = payload.rsplit(b'.', 1)
decompress = False
if payload.startswith(b'.'):
payload = payload[1:]
decompress = True
try:
payload = base64_decode(payload)
except Exception as e:
raise Exception('Could not base64 decode the payload because of '
'an exception')
if decompress:
try:
payload = zlib.decompress(payload)
except Exception as e:
raise Exception('Could not zlib decompress the payload before '
'decoding the payload')
return session_json_serializer.loads(payload)
if __name__ == '__main__':
print(decryption(sys.argv[1].encode()))
运行得到了: {'balance': 1336, 'purchases': []}
我们接下来就是伪造session了
观察源代码,找到了一张图片下载路径,尝试用该路径查看文件但是有问题,没有明显的回显,我们使用目录穿越/etc/passwd是否会有回显,
刚好看到了回显,
接着查看当前的环境变量,/proc/self/environ
最后得到了flag
学习高数
这个没有什么特别的地方,根据提示有10000个页面,index1.html
那么就想到利用bp来看看不同之处,终于找到了cvFXZohgjf.php
之后访问一下
这里考的是payload长度限制小于60且过滤了很多的符号
大体的思路:绕过长度限制和黑名单过滤构造合法的payload;分析后端存在的执行点,重点解决$content的问题,环境有问题的话,那我们就学习一下这个payload如何构造。
访问数学连接的,看到了这些,看懂了
你能够跟上我的速度吗?
上传图片马之后,获得了一张图片,
bp进行抓包之后得到了,
允许一大堆东西上传,我们找到的缺陷就是它将文件上传到服务器中并且没有重命名上传的文件,如果知道了文件上传的位置,我们就可以设置burp一直上传,一直访问。
这里的null payload使用时,需要设定一个变量,而且该变量最后是一个空格否则的话,它回会将变量变为空。
然后必须要知道文件名和地址。(自己命名为hu,在uploads目录下)
抓包文件的路径
两边同时爆破,选择
最后再333页面的时候停止,最后发送到重发器,更改hu.php为shell.php,然后蚁剑连接就可以了得到flag了。
这里我们继续学习bp的使用技巧。
杂项
misc1
简单的流量分析
规律有些难找,仔细观察它的长度就可以发现了,一短两长
将短的最后一位字母拼接在一起,就可以找到了flag了。
misc大杂烩
第一部分是一个盲水印,
我们利用Python来解开,可以同时解开几个盲水印
先下载opencv
pip install opencv-python
pip3 install opencv-python
然后安装matplotlib
python -m pip install matplotlib
然后使用以下的命令:
python2 bwm.py decode day1.png day2.png flag.png
python bwmforpy3.py decode day1.png day2.png flag.png --oldseed
主要Python2还是3
解释一下,
对于Python2
python2
:在系统中运行Python 2脚本。随着Python 2在2020年初官方停止支持bwm.py
:脚本的文件名,包含执行BWM技术的代码。decode
:传递给bwm.py
脚本的一个参数,指示脚本执行解码操作。day1.png
和day2.png
:两个输入文件,可能包含了通过某种方式(如BWM技术)隐藏的信息。flag.png
:脚本将从day1.png
和day2.png
中解码出的信息保存到这个文件中。
python
:在很多系统中,python
命令默认指向Python 3,但在一些旧系统或配置了特定环境变量的系统中可能需要使用python3
。bwmforpy3.py
:Python脚本文件名,可decode
、day1.png
、day2.png
、flag.png
:这些参数与第一个命令中的相同,分别表示操作类型、输入和输出。--oldseed
:额外的命令行参数,可能用于指定解码过程中使用的旧版随机种子(seed)或其他与加密/解密过程相关的特定设置。它可能用于确保向后兼容性,以便能够解码使用旧版本BWM技术加密的数据。