SEED Labs 2.0: Shellcode Development Lab

SEED Labs 2.0: Shellcode Development Lab
JohnnyShellcode Development Lab
Task 1.a: The Entire Process
Compiling to object code.
将汇编语言编译成为机器码:
Linking to generate final binary.
将编译后的文件进行链接,就生成了可执行文件
运行之后可以看到是子进程运行的shell命令
Getting the machine code
可以通过objdump -Mintel --disassemble mysh.o
查看编译之后机器码
也可以通过 xxd -p -c 20 mysh.o
查看没有被高亮的机器码
Using the shellcode in attacking code.
可以使用convert.py
将机器码转换一个格式
Task 1.b. Eliminating Zeros from the Code
先看看/
的指令是:2f
,然后68当作结尾符
由于不能使用\
,就使用***
将h填满到4个字节,因为是小端存储所以需要先左移,再右移的方式将***
消除掉。
查看反汇编指令:并没有00
成功运行
Task 1.c. Providing Arguments for System Calls
1 | section .text |
我们一行一行进行解释:
1 | xor eax,eax |
这一行先使用异或,将eax清零,然后将eax压入栈,用于字符串终止标志
1 | push "//sh" |
将两个字符串压入栈中,因为寄存器是32位,所以都是正好可以压入的,不用另外处理,然后把这个字符串,即/bin/sh
的地址赋值给ebx
1 | push eax |
将空的eax压入栈中,也是当作字符串的结尾使用。
接下来的两条指令都是为了将空格给创造出来
然后将字符串压入栈中,同时清空eax供之后直接使用
同时将字符串的地址赋值给esp
1 | mov eax,"##la" |
也是同样的将 la
指令创造出来压入栈中,同时把ls -
指令压入栈中,这样的字符串就是ls -la
,然后将字符串地址赋值给edx
1 | push eax ; argv[1] = 0 |
这些寄存器都存储好了字符串的地址,之后将这些寄存器按照参数先后顺序压入栈中就完成了
Task 1.d. Providing Environment Variables for execve()
对于每个字符串,都像前一个实验那样,将字符串压入栈中,并且存储对应字符串的值
按照文档顺序都压入栈中,但是最后一个压入的一定是edx,如果选用其他的寄存器会导致段错误
编译执行:
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”的地址
运行结果为:
Task3
注意到64位和32位的不同:
- 64位系统,调用系统调用是通过 syscall 指令完成的
- 系统调用的前三个参数分别存储在 rdx、 rsi 和 rdi 寄存器中
修改之后的文件为:
成功执行
Environment Variable and Set-UID Program Lab
Task 1: Manipulating Environment Variables
使用printenv
或者env
打印环境变量
使用env | grep PWD
或者 printenv PWD 查看特定的环境变量
使用export
命令添加环境变量,使用unset
删除环境变量
Task 2: Passing Environment Variables from Parent Process to Child Process
myprintenv.c
比较简单
Step1
编译文件,并且将输出重定向到file文件中
Step2
将父进程的环境变量打印,并输出到另一个文件file2
中
Step3
比较两个文件
并没有输出,说明两个文件没有差异,即子进程完全继承了父进程的环境变量
Task 3: Environment Variables and execve()
在文档的描述中说到,execve()
并不会返回,其运行的进程直接使用调用execve()
的进程的地址空间,而且是覆盖了原来进程的地址空间。
提出一个问题新的进程是否自动继承了调用进程的环境变量?
Step1
编译myenv.c
文件
运行它,发现并没有输出
Step2
更改第十二行代码,编译运行
发现打印出了环境变量
Step3
发现在原来的程序中定义了一个外部变量environ
,并且在调用exevce时,传入了这个值,他就是父进程中的环境变量
在man中也有说明
Task 4: Environment Variables and system()
不同于execve()
, system()
系统调用使用的其实是/bin/bash
运行指定的命令
编写代码,并且运行,发现它打印出了环境变量
Task 5: Environment Variable and Set-UID Programs
Step1 编写程序
Step2
编译程序,并且更改程序执行权限
Step3
设置三个环境变量
这些环境变量是设置在用户空间的,从$
就可看出来
之后运行上面写好的程序
我们发现在父进程中设置的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
先编写一个程序
这个程序运行了ls指令,并且使用的是相对路径,我们编译运行,结果如下
发现他确实运行了ls
指令
更改为Set-UID程序
为了关闭保护程序,我们将/bin/sh
重链接到另外一个程序上:
sudo ln -sf /bin/zsh /bin/sh
运行任意编写的代码
由于是使用的相对路径,所以我们写一个名叫ls的程序放在当前目录下,使用Set-UID程序就能执行这个恶意程序
编译这个程序
将/bin/bash
的运行根目录更改为我们当前的目录
再运行一次task6
命令,发现已经成功运行了我们的恶意程序
getshell
因为是直接运行目录下的ls
,所以我们将/bin/bash
复制到当前目录下,并且命名为ls
,则运行task6
后就可以getshell
Task 7: The LD PRELOAD Environment Variable and Set-UID Programs
Step1
- 编写
mylib.c
文件 - 编译
mylib.c
文件
- 将
LD_PRELOAD
环境变量的值改为当前目录下编译好的libmylib.so.1.0.1
文件
1 | export LD_PRELOAD=./libmylib.so.1.0.1 |
- 编译
myprog.c
文件
Step2
运行myprog
文件
- Make myprog a regular program, and run it as a normal user.
程序运行了我们在当前目录的sleep
文件,因为sleep
被链接到了当前目录下的sleep
函数
- Make myprog a Set-UID root program, and run it as a normal user.
程序正常睡眠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权限
添加环境变量
运行程序
发现运行了我们写的程序,因为调用和执行的两个用户是同一个所以可以使用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
运行程序
也是正常的运行了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程序
使用root用户创建一个文件, 并且使用seed用户不能删除这个文件
可以正常运行catall
程序
我们执行命令/home/super.c;rm /home/super.c
发现成功删除
Step2
更改程序
编译之后赋予Set-UID权限
继续尝试Step1中的方法,发现并不能删除
是因为system会创建一个子进程,然后子进程会调用一个新的shell程序,而且因为catall
是一个Set-UID根程序,所以在执行时会以root权限执行删除文件的命令,可以成功删除。
使用execve()不可以成功删除不可写文件,因为execve会执行一个新程序,而不会调用新的shell程序,所以将我们输入的参数仅仅当成一个字符串,不会执行命令,所以不能删除不可写文件。
Task 9: Capability Leaking
给程序赋予权限
使用root权限创建zzz
文件
运行代码,写入zzz
该程序是root有效用户,并是Set_UID程序,有权利打开/etc/zzz文件,在执行了setuid()撤销权限后,由于文件并没有关闭,出现了权限泄露的问题,该文件仍然具有root权限。因此能够继续执行写入hello内容