参考了好多文章才能大致理解shellcode

这题开始就有一个大坑

pwnable.tw-start-LMLPHPpwnable.tw-start-LMLPHP

可以看到不同的checksec查看start文件会有不同的结果,就结果而言,GDB-PEDA的checksec很不靠谱。。我根本没有s命名的文件。。

所以,start是一个32位没有开启NX的程序,NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不是去执行恶意指令。

关于各种程序保护可以参考https://www.jianshu.com/p/8a9ef7205632

既然start没有开启NX,那就可以使用shellcode了

先把程序放进IDA里

.text:08048060                 public _start
.text:08048060 _start          proc near               ; DATA XREF: LOAD:08048018↑o
.text:08048060                 push    esp
.text:08048061                 push    offset _exit
.text:08048066                 xor     eax, eax
.text:08048068                 xor     ebx, ebx
.text:0804806A                 xor     ecx, ecx
.text:0804806C                 xor     edx, edx
.text:0804806E                 push    3A465443h
.text:08048073                 push    20656874h
.text:08048078                 push    20747261h
.text:0804807D                 push    74732073h
.text:08048082                 push    2774654Ch
.text:08048087                 mov     ecx, esp        ; addr
.text:08048089                 mov     dl, 14h         ; len
.text:0804808B                 mov     bl, 1           ; fd
.text:0804808D                 mov     al, 4
.text:0804808F                 int     80h             ; LINUX - sys_write
.text:08048091                 xor     ebx, ebx
.text:08048093                 mov     dl, 3Ch
.text:08048095                 mov     al, 3
.text:08048097                 int     80h             ; LINUX -
.text:08048099                 add     esp, 14h
.text:0804809C                 retn
.text:0804809C _start          endp ; sp-analysis failed

五个连续PUSH是要准备输出到屏幕上的Let's start the CTF:
然后执行write(1,"Let's start the CTF:",20),mov al 4相当于write函数,而int 80h相当于call

int n是执行中断程序的指令,n是中断类型码,这里的int 80h就相当于执行call指令。al = 4是write,al=3是read

这个函数相当于执行
write(1,"Let's start the CTF:",20);

read(0,buf,60);

在retn之前看到 add esp,14h 这相当于栈顶距离栈底20字节,也就是这个栈只有20字节,然而我们的read能输入60字节,这就可以造成栈溢出了。

 

 

 

 

 

参考了众多write up 我始终无法理解,按别人的说法是通过第一次覆盖return地址(把栈内的offset _exit覆盖了)到mov eax esp地址,然后通过write()函数得到栈地址。

我姑且认为write()函数从eax寄存器内存储的地址处开始读取,可是函数结尾add esp 14h不是已经回收栈空间了吗,这时候esp内存的地址难道不是ret的位置吗,那write函数不是读出ret的栈地址吗,esp不是在ret栈地址下面吗?(一开始push esp,然后再push offset _exit,所以esp不是在offset _exit的下面吗?再之后push了20个字节的内容,所以一开始esp应该指向offset _exit所在栈空间往上20个字节啊)可为什么write读取的却是ret下面的esp地址呢?

接着是得到栈地址后再进行第二次跳转到esp下面20个字节处,在我的理解里这是为了避开输入的return地址,因为我们输入里准备用来覆盖return地址的地址会干扰我们shellcode的运行,但别人的输入却是‘a’*20 + (leakaddr+20)+shellcode,这里我完全不能理解,明明esp的地址在第一次跳转前就已经ADD 20了,那不是直接leakaddr+4就好了?还是说read函数输入的位置变成了从新的esp开始输入,但为了执行shell code也应该是leakaddr+24啊,20个'a'+leakaddr本身4个字节。

关于ecx,ebx对于write、read函数的影响不知道哪里可以查,甚至这个函数都没有我"熟悉"的ebp,没学过汇编语言的坏处就在这里。

 

 

接下来继续看王爽的汇编原理和0day安全软件漏洞分析。

 

10-05 10:24