什么是docker镜像?

首先,docker的英文含义是码头工人,搬运工人,百科里的定义它是一个开源的应用容器引擎,它能让linux开发者快速打包应用和依赖包到一个容器里,方便移植。所以这里的理解应该是它专门用于应用的跨平台搬运。它基于64为linux系统使用,不支持32位,它的跨平台实际上并不是说它可适用所有应用场景,而是只能虚拟基于Linux的服务运行。

docker分为四个部分:

  1. DockerClient客户端(docker是C/S架构,Daemon作为服务端接收用户请求并处理,客户端服务端既可运行到一个机器上,也可以运行到多个上用网络通信)
  2. Docker Daemon守护进程
  3. Docker Image镜像
  4. DockerContainer容器

docker镜像(image)相当于ubuntu里的root文件系统,镜像和容器的区别是,镜像相当于是静态的,而容器是镜像的运行实例,容器可以被创建,启动、停止、删除、暂停等。

仓库(Repository):仓库可看成一个代码控制中心,用来保存镜像。

docker命令导图

镜像分层的概念 镜像分析包括什么_调用函数

zsh,bash是什么?

有时候在运行linux命令的时候会遇到如下情况,zsh:command not found:某命令。其实zsh都是(shell shell相当于包裹于系统内核的一层壳,用于输入命令行运行内核调用内存等,shell就是命令解释器) 命令解释器的一种,命令解释器是一个单独的软件程序,它可在用户和操作系统之间提供直接的通讯。命令行解释器是解释器的一种,用于对命令行进行解释执行。

shell与基础命令行:

相同点:

①基础命令行和Shell都可以操作Linux系统
不同点:
①基础命令行(ls、cd等),是一种单一的操作。
②Shell可以比基础命令行更复杂,是一种组合型的操作。相比基础命令拥有了面向过程的概念。

常见的命令解释器有

Sh

Bash

Zsh

Csh

Ash

Fish

 函数调用栈:

程序运行时内存一段连续的区域,用来保存函数运行时的状态信息,调用函数时被调用函数压入栈顶,栈地址由高地址向低地址生长。函数状态主要涉及三个寄存器--esp,ebp,eip。esp 用来存储函数调用栈的栈顶地址,在压栈和退栈时发生变化。ebp 用来存储当前函数状态的基地址,在函数运行时不变,可以用来索引确定函数参数或局部变量的位置。eip 用来存储即将执行的程序指令的地址,cpu 依照 eip 的存储内容读取指令并执行,eip 随之指向相邻的下一条指令,如此反复,程序就得以连续执行指令。

参考:https://zhuanlan.zhihu.com/p/25816426

1.函数调用时,首先将参数逆序压入栈中,但参数仍旧保存在调用函数的状态内,之后压入栈中的数据都会当作被调用函数的函数状态处理。

镜像分层的概念 镜像分析包括什么_调用函数_02

 

                                                                      被调用函数的参数压入栈中。

2.参数压完之后压入返回地址,return address这里的返回地址实际上是调用函数调用完被调用函数之后,要执行的下一条指令地址,这样做使得调用函数eip(指令)信息得以保存。

3.将当前的ebp寄存器内值(值为调用函数的基地址)压入栈内,并将其更新为当前栈顶的地址。这样调用函数的基地址得以保存,且ebp也被改变成被调用函数的基地址。

镜像分层的概念 镜像分析包括什么_命令行_03

 

4.被调用函数局部变量等数据压入栈中,esp内地址下移,变低地址。

调用参数以外的数据共同构成了被调用函数(callee)的状态。

 

 发生调用时,被调用函数的指令地址存到了eip寄存器中,这样程序就可以依次执行被调用函数的指令了。这就是函数的调用,理解这个,那么调用状态的恢复也就不难理解了。

函数调用状态的变化,给了攻击者可乘之机,只要让eip寄存器指向攻击指令的地址,eip执行攻击指令,就可以达到目的。

具体操作是:

在调用函数的时候,存入的返回地址实际上是调用完成之后要执行的下一条指令的地址,它是要传给eip寄存器的,所以我们需要让溢出数据用攻击地址的地址覆盖返回地址即可攻击。

我们可以在溢出数据内包含一段攻击指令,也可以在内存其他位置寻找可用的攻击指令。

关于eip覆盖技术

可分为四类:

  • 修改返回地址,让其指向溢出数据中的一段指令(shellcode
  • 修改返回地址,让其指向内存中已有的某个函数(return2libc
  • 修改返回地址,让其指向内存中已有的一段指令(ROP
  • 修改某个被调用函数的地址,让其指向另一个函数(hijack GOT

1.修改返回地址,让其指向溢出数据中的一段指令(shellcode

溢出数据组成

镜像分层的概念 镜像分析包括什么_命令行_04

 

                                                                      shellcode所用溢出数据的构造

address of shellcode 是后面 shellcode 起始处的地址,用来覆盖返回地址。

 溢出数据不要包含”\x00“,否则会造成截断。\x00 == 0x00。对应于ASCII码的NULL。它在字符串中作为结束标志使用。

根据上边的构造,需要去解决两个问题,padding1有多长,shellcode起始地址在哪里?

padding1长度可以运行程序试探或者用调试工具从汇编查看。

shellcode起始地址我们有时候只能得到大概位置,所以可以在padding2填充若干长度“\x90”,意思是NOP指令,不进行任何操作进入下一条。只要返回地址命中这一段任意位置,都能到shellcode起始地址。

这个方案执行的前提是对方关闭地址随机化,否则每次程序运行地址都不同,无法确定地址,另外shellcode也要有可执行的权限。所以就引出了调用程序内部函数和指令的方法:return2libc和ROP

2.修改返回地址,让其指向内存中已有的某个函数(return2libc

 在内存中确认某个函数的地址,并用其覆盖返回地址。之所以叫return2libc,是因为libc动态链接库中的函数大概率会被用到并且在内存中找到,此外该库还包含了一些系统级函数,可用以取得当前进程的控制权。要执行的函数可能需要参数,所以要把需用到的参数写入溢出数据中。

镜像分层的概念 镜像分析包括什么_镜像分层的概念_05

 

同一理,这个构造中需要解决的问题为padding1的长度,以及system()函数的起始地址在哪?这里同理,只有在关闭地址随机化的前提下才能用调试工具找出system()函数起始地址(也可找到动态库的起始地址,加上动态库内的相对偏移量就是system函数绝对地址)。padding1任意填充,填充长度可用调试工具或运行程序不断试探输入长度得到。此外还需搜索字符串“/bin/sh”在链接库中的位置,如果这个位置在动态库中找不到,可以将其添加到环境变量里,再通过getenv() 等函数来确定地址。如果对方打开了地址随机化,以上两种方法都会失效,下面介绍两种不需此前提的方法。

3.修改返回地址,让其指向内存中已有的一段指令(ROP

镜像分层的概念 镜像分析包括什么_镜像分层的概念_06

 

包含多个gadget的溢出数据,gadget为某段指令地址,这里只是这样命名为英文小工具。

现在任务可以分解为:针对程序栈溢出所要实现的效果,找到若干段以 ret 作为结束的指令片段,按照上述的构造将它们的地址填充到溢出数据中。

要解决问题如下:

栈溢出要实现的效果---------------常见的效果是实现一次系统调用

如何寻找对应的指令片段---------有许多可借助的开源工具可实现搜索以ret为结尾的指令片段

如何传入系统调用参数------------使用pop指令写入后再弹出相对简单一点,对于单个 gadget,pop 所传输的数据应该在 gadget 地址之后

镜像分层的概念 镜像分析包括什么_镜像分层的概念_07

 

4.修改某个被调用函数的地址,让其指向另一个函数(hijack GOT

这个方法是要在内存中修改某个函数的地址,使其指向另一个函数。 比如原来要调用printf函数,现在通过修改,变成了执行system函数。

 关于程序如何实现外部函数调用:静态链接和动态链接。动态链接在发送调用的时候去链接库定位所需函数,这个过程需要用到全局偏移量表GOT和程序链接表PLT。

GOT存储外部函数在内存的确切地址,存储于数据段中,在程序运行的时候可修改。

PLT存储外部函数的入口点,程序运行时会来这里寻找外部函数地址。它存在于代码段,运行前确定不可被修改,它的入口点就是GOT表中对应的地址。

之所以不直接使用GOT表其实是为了效率考虑。从PLT表找寻GOT表中函数地址第一次如果没有,则会回到PLT运行地址解析函数,并写入GOT表,如果已经有了,会直接从GOT表跳转到代码段。

这个过程不是一次全部实现的,而是在调用的时候才会发生,这就给了我们函数伪装的可能。修改GOT表中函数A的地址变为函数B,这样每次调用函数A都会变成调用B。

以上可分为三步,确定A在GOT表中位置,找到B函数位置,替换

1.找A:可通过PLT的入口点找到GOT表中具体条目。如call 0x08048430 <printf@plt>,说明 printf 在 PLT 表中的入口点是在 0x08048430,所以 0x08048430 处存储的就是 GOT 表中 printf 的条目地址。

 2.找函数B:如果关闭地址随机化,那方法与前面一样,但这还不够,为了能够在打开地址随机化的情况下使用,可以借助链接库中相对位置不变这一特点,知道函数A的地址,借助其与函数B在链接库内的相对位置可找到函数B运行时地址。

3.替换:对方肯定不会用提供合适的函数,所以可借助ROP。只要找到若干条gadget,就不难改写GOT表中数据。

pop eax; ret; 		# printf@plt -> eax
mov ebx [eax]; ret;	# printf@got -> ebx
pop ecx; ret; 		# addr_diff = system - printf -> ecx
add [ebx] ecx; ret; 	# printf@got += addr_diff

 学好ROP还是很重要的,它可在一定程度上绕开地址随机化。