SEED Labs 2.0: Shellcode Development Lab

Shellcode Development Lab

Task 1.a: The Entire Process

Compiling to object code.

将汇编语言编译成为机器码:

image-20231023160052204

Linking to generate final binary.

将编译后的文件进行链接,就生成了可执行文件

image-20231023160232950

运行之后可以看到是子进程运行的shell命令

image-20231023160355881

Getting the machine code

可以通过objdump -Mintel --disassemble mysh.o查看编译之后机器码

image-20231023160537136

也可以通过 xxd -p -c 20 mysh.o查看没有被高亮的机器码

image-20231023160613800

Using the shellcode in attacking code.

可以使用convert.py将机器码转换一个格式

image-20231023160924699

image-20231023160938185

Task 1.b. Eliminating Zeros from the Code

先看看/的指令是:2f,然后68当作结尾符

image-20231016163304175

由于不能使用\,就使用***将h填满到4个字节,因为是小端存储所以需要先左移,再右移的方式将***消除掉。

image-20231016165603858

查看反汇编指令:并没有00

image-20231016165728013

成功运行

image-20231016165749981

Task 1.c. Providing Arguments for System Calls

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
section .text
global _start
_start:
; Store the argument string on stack
xor eax,eax
push eax ; Use 0 to terminate the string

push "//sh"
push "/bin"
mov ebx, esp ; Get the string address
; Construct the argument array argv[]
push eax
mov eax, "##-c"
shr eax,16
push eax
xor eax, eax
mov ecx, esp

mov eax,"##la"
shr eax,16
push eax
xor eax,eax
push "ls -"
mov edx, esp

xor eax,eax

push eax ; argv[1] = 0
push edx ; argv[0] points "/bin//sh"
push ecx
push ebx

mov ecx, esp ; Get the address of argv[]

; For environment variable
xor edx, edx ; No env variables

; Invoke execve()
xor eax, eax ; eax = 0x00000000
mov al, 0x0b ; eax = 0x0000000b
int 0x80

我们一行一行进行解释:

1
2
xor eax,eax
push eax

这一行先使用异或,将eax清零,然后将eax压入栈,用于字符串终止标志

1
2
3
push "//sh"
push "/bin"
mov ebx, esp

将两个字符串压入栈中,因为寄存器是32位,所以都是正好可以压入的,不用另外处理,然后把这个字符串,即/bin/sh的地址赋值给ebx

1
2
3
4
5
6
push eax
mov eax, "##-c"
shr eax,16
push eax
xor eax, eax
mov ecx, esp

将空的eax压入栈中,也是当作字符串的结尾使用。

接下来的两条指令都是为了将空格给创造出来

然后将字符串压入栈中,同时清空eax供之后直接使用

同时将字符串的地址赋值给esp

1
2
3
4
5
6
mov eax,"##la"
shr eax,16
push eax
xor eax,eax
push "ls -"
mov edx, esp

也是同样的将 la指令创造出来压入栈中,同时把ls -指令压入栈中,这样的字符串就是ls -la,然后将字符串地址赋值给edx

1
2
3
4
push eax          ; argv[1] = 0
push edx
push ecx
push ebx

这些寄存器都存储好了字符串的地址,之后将这些寄存器按照参数先后顺序压入栈中就完成了

image-20231017214828641

Task 1.d. Providing Environment Variables for execve()

对于每个字符串,都像前一个实验那样,将字符串压入栈中,并且存储对应字符串的值

image-20231017222332309

按照文档顺序都压入栈中,但是最后一个压入的一定是edx,如果选用其他的寄存器会导致段错误

image-20231017222508497

编译执行:

image-20231017222635210

Task 2: Using Code Segment

Task1

one:: 定义一个标签 one

pop ebx: 弹出堆栈顶部的值,将其存储到EBX寄存器中,这个值是标签 two 后面的字符串的地址。

xor eax, eax: 将EAX寄存器的值清零,通常用于清零寄存器中的临时数据。

mov [ebx+7], al: 将EAX中的0写入到EBX加7的地址处,这是为了将字符串 “/bin/sh” 的结尾标志设置为null终止符。

mov [ebx+8], ebx: 将EBX的值写入到EBX加8的地址处,实际上是将 “/bin/sh” 的地址存储在这个位置。

mov [ebx+12], eax: 将0写入到EBX加12的地址处,用于argv[2],表示命令参数列表的结束。

lea ecx, [ebx+8]: 计算 [ebx+8] 的地址,将结果存储在ECX中,这将成为execve系统调用的第二个参数,即参数数组 argv 的地址。

xor edx, edx: 将EDX寄存器的值清零,通常用于清零寄存器中的临时数据。

mov al, 0x0b: 将AL寄存器设置为0x0b,这是execve系统调用的系统调用号。

int 0x80: 触发中断0x80,从而调用execve系统调用,以执行 /bin/sh

two:: 定义标签 two

call one: 调用标签 one,实际上执行了前面定义的 one 子例程。

db '/bin/sh*AAAABBBB': 定义一个字符串,其中包含要执行的命令 /bin/sh,以及一些填充字符。这个字符串是 pop ebx 命令后面的内容,它将被用来设置EBX寄存器,以便执行 /bin/sh 命令。

通过修改内存中的字符串和寄存器来构造 execve 系统调用所需的参数,然后触发系统调用来执行指定的命令,从而成功执行 /bin/sh 程序。

他是将定义好的字符串,即’/bin/sh*AAAABBBB’,在one中弹出并且存储到ebx中,同时将其之下的位置当作参数位置进行构造,然后记录其起始位置,通过中断并且调用execve函数进行/bin/sh的执行

Task2

  • ‘/usr/bin/env#-i#a=11#b=22#AAAABBBBCCCCDDDDEEEE’是我们构造的字符串,通过call + pop指令可以获取该地址

    • #是占位符。为了防止0导致strcpy无法复制字符串,这里使用#作为占位符,后面会用al进行替换
    1
    /usr/bin/env -i a=11 b=22

    是我们要执行的命令(一定要注意到字符串最后有个\0)

    • ecx存储argv的地址,因此指向ebx+26
    • ebx存储“/usr/bin/env\0”的地址

运行结果为:

image-20231023165212011

Task3

注意到64位和32位的不同:

  • 64位系统,调用系统调用是通过 syscall 指令完成的
  • 系统调用的前三个参数分别存储在 rdx、 rsi 和 rdi 寄存器中

修改之后的文件为:

image-20231023165357052

成功执行

image-20231023165500580

Environment Variable and Set-UID Program Lab

Task 1: Manipulating Environment Variables

使用printenv或者env打印环境变量

image-20231023173945650

image-20231023173955477

使用env | grep PWD或者 printenv PWD 查看特定的环境变量

image-20231023174106529

使用export命令添加环境变量,使用unset删除环境变量

image-20231023174217076

Task 2: Passing Environment Variables from Parent Process to Child Process

myprintenv.c比较简单

image-20231023174550859

Step1

编译文件,并且将输出重定向到file文件中

image-20231023174415051

Step2

将父进程的环境变量打印,并输出到另一个文件file2

image-20231023174728509

image-20231023174742360

Step3

比较两个文件

image-20231023174824472

并没有输出,说明两个文件没有差异,即子进程完全继承了父进程的环境变量

Task 3: Environment Variables and execve()

在文档的描述中说到,execve()并不会返回,其运行的进程直接使用调用execve()的进程的地址空间,而且是覆盖了原来进程的地址空间。

提出一个问题新的进程是否自动继承了调用进程的环境变量?

Step1

编译myenv.c文件

image-20231023175233449

运行它,发现并没有输出

image-20231023175324768

Step2

更改第十二行代码,编译运行

image-20231023175410360

image-20231023175432534

发现打印出了环境变量

Step3

发现在原来的程序中定义了一个外部变量environ,并且在调用exevce时,传入了这个值,他就是父进程中的环境变量

image-20231023175754689

在man中也有说明

image-20231023175941334

Task 4: Environment Variables and system()

不同于execve(), system()系统调用使用的其实是/bin/bash运行指定的命令

编写代码,并且运行,发现它打印出了环境变量

image-20231023180437677

image-20231023180423426

Task 5: Environment Variable and Set-UID Programs

Step1 编写程序

image-20231023181051422

Step2

编译程序,并且更改程序执行权限

image-20231023181142941

image-20231023181151872

Step3

设置三个环境变量

image-20231023181410423

这些环境变量是设置在用户空间的,从$就可看出来

之后运行上面写好的程序

我们发现在父进程中设置的PATH、name环境变量都已经进入了子进程的环境变量,但是我们发现在子进程的环境变量中找不到LD_LIBRARY_PATH环境变量

LD_LIBRARY_PATH用于指定查找共享库(动态链接库)时除了默认路径以外的其他路径;LD_LIBRARY_PATH可以被修改,从而加载攻击者的恶意库;为了使Set-UID程序更加安全,不受LD_LIBRARY_PATH环境变量的影响,运行时的链接器或加载器(ld.so)会忽略LD*环境变量;或许可以理解为链接器所拥有的一个保护机制:当执行程序的进程ID与拥有者的进程ID不一致时,链接器就会忽略掉LD_LIBRARY_PATH环境变量,所以在子进程的环境变量中找不到此环境变量。

Task 6: The PATH Environment Variable and Set-UID Programs

先编写一个程序

image-20231023182511812

这个程序运行了ls指令,并且使用的是相对路径,我们编译运行,结果如下

image-20231023182555684

发现他确实运行了ls指令

更改为Set-UID程序

image-20231023182715697

为了关闭保护程序,我们将/bin/sh重链接到另外一个程序上:

sudo ln -sf /bin/zsh /bin/sh

运行任意编写的代码

由于是使用的相对路径,所以我们写一个名叫ls的程序放在当前目录下,使用Set-UID程序就能执行这个恶意程序

image-20231023183017767

编译这个程序

image-20231023183036390

/bin/bash的运行根目录更改为我们当前的目录

image-20231023183226743

再运行一次task6命令,发现已经成功运行了我们的恶意程序

image-20231023183256825

getshell

因为是直接运行目录下的ls,所以我们将/bin/bash复制到当前目录下,并且命名为ls,则运行task6后就可以getshell

image-20231023183735615

Task 7: The LD PRELOAD Environment Variable and Set-UID Programs

Step1

  • 编写mylib.c文件
  • 编译mylib.c文件

image-20231023185206029

  • LD_PRELOAD环境变量的值改为当前目录下编译好的libmylib.so.1.0.1文件
1
export LD_PRELOAD=./libmylib.so.1.0.1

image-20231023185335837

  • 编译myprog.c文件

image-20231023185519083

Step2

运行myprog文件

  • Make myprog a regular program, and run it as a normal user.

image-20231023185659861

程序运行了我们在当前目录的sleep文件,因为sleep被链接到了当前目录下的sleep函数

  • Make myprog a Set-UID root program, and run it as a normal user.

image-20231023190008227

image-20231023190028107

程序正常睡眠1s之后退出, 因为切换为另一个用户之后,动态链接器会忽略LD_PRELOAD环境变量

  • Make myprog a Set-UID root program, export the LD PRELOAD environment variable again in the root account and run it.

切换为root权限

image-20231023190408036

添加环境变量

image-20231023190505303

运行程序

image-20231023190520507

发现运行了我们写的程序,因为调用和执行的两个用户是同一个所以可以使用LD_PRELOAD环境变量

  • Make myprog a Set-UID user1 program (i.e., the owner is user1, which is another user account), export the LD PRELOAD environment variable again in a different user’s account (not-root user) and run it.

将文件所有者更改为user1

image-20231023191020282

运行程序

image-20231023191030371

也是正常的运行了sleep函数,

Step3

  • 真实用户ID与拥有者用户ID一致,子进程会继承seed用户下的LD*环境变量,并加入共享库,执行设置的sleep函数
  • 真实用户ID与拥有者用户ID不一致,子进程不会继承seed用户下的LD*环境变量,执行原有的sleep函数
  • 真实用户ID与拥有者用户ID一致,子进程会继承seed用户下的LD*环境变量,并加入共享库,执行设置的sleep函数
  • 还是因为运行的环境id和拥有者id不一致,导致LD_PRELOAD环境变量被忽略

Task 8: Invoking External Programs Using system() versus execve()

Step 1:

转为一个Set-UID程序

image-20231023193749483

使用root用户创建一个文件, 并且使用seed用户不能删除这个文件

image-20231023194246085

可以正常运行catall程序

我们执行命令/home/super.c;rm /home/super.c 发现成功删除

image-20231023194855537

Step2

更改程序

image-20231023195025466

编译之后赋予Set-UID权限

image-20231023195228388

继续尝试Step1中的方法,发现并不能删除

image-20231023195412743

是因为system会创建一个子进程,然后子进程会调用一个新的shell程序,而且因为catall是一个Set-UID根程序,所以在执行时会以root权限执行删除文件的命令,可以成功删除。

使用execve()不可以成功删除不可写文件,因为execve会执行一个新程序,而不会调用新的shell程序,所以将我们输入的参数仅仅当成一个字符串,不会执行命令,所以不能删除不可写文件。

Task 9: Capability Leaking

给程序赋予权限

image-20231023200029316

使用root权限创建zzz文件

image-20231023200334758

运行代码,写入zzz

image-20231023200526927

image-20231023201008669

该程序是root有效用户,并是Set_UID程序,有权利打开/etc/zzz文件,在执行了setuid()撤销权限后,由于文件并没有关闭,出现了权限泄露的问题,该文件仍然具有root权限。因此能够继续执行写入hello内容