NJCTF 官方出题思路
时间:2017-03-15 19:01:55   共有 2441 人围观    2441   0   2   0


Chall I

思路:

此题是有特定版本的nodejs当用一个字符串调用Buffer时,它将创建一个Buffer包含这些字节。但是如果它用一个数字调用,NodeJS将分配一个随机字节的大缓冲区。这是因为Buffernumber)不会使内存为零,并且它可能泄漏以前在堆上分配的数据。bodyParser.json(),发送包含数字的POST数据。返回一些从堆泄漏的内存。从而泄露出此题的第一个flag,但这中间加入了一些trick,就是输入的password是经过MD5,所以必须是纯数字的MD5,如下:

ximaz : 61529519452809720693702583126814 
aalbke : 55203129974456751211900188750366 
afnnsd : 49716523209578759475317816476053 
aooalg : 68619150135523129199070648991237 
bzbkme : 69805916917525281143075153085385


当然就有纯字符的md5:

cbaabcdljdac : cadbfdfecdcdcdacdbbbfadbcccefabd


Chall II

思路:

 Curl Post 数据,leak出的上一个flag,且给出部分源码,这里可以通过0lddrivergithub,填入leak出的flag作为config_session.keyshmac后的cookies提交,获取admin权限,得到flag

 

Blog

思路:

注册普通用户,在注册页面和修改密码页面可以提升权限为admin,在提升为admin后,可以在user页面看到flag,如果没有普通用户存在则无法查看flag。在注册页面和修改密码页面的表单中添加一个

<input type="text" name="user[admin]" value="1" />

提交后在删除用户的页面看到flag

 

Text wall

思路:漏洞是在cookie中,首先将对象序列化并且sha1加密,且与序列化后进行连接urlencode后设置为cookie,突破点是首先反序列化读取index.php, 得到flag位置,再反序列化读取flag

 

Get Flag

思路:

采用黑名单的方式过滤了一些危险符号,但是没有过滤&或者%0a,可以使用其绕过黑名单,实现命令注入。

pocflag=%0a+ls+../../9iZM2qTEmq67SOdJp%!oJm2%M4!nhS_thi5_flag&submit=


Pictures’ Wall

思路:

修改http包头中的host字段为127.0.0.1,成为root账户,登录页和注入无关,为了扰乱解思路,后台抓包修改上传图片为.phtml格式,执行.phtml文件读取flag,这里猥琐的点是大家可以相互删shell,互相搅屎。

 

Wallet

思路:将经过phpjm后的admin.php压缩为www.zip,设置密码为njctf2017,让选手爆破,得到源码解密后,审计源码,使cookie中的authmd5(auth)sha1(hsh)要相等,然后就是很普通SQLI(sqlite)了。

Sqlite injection trickshttps://github.com/unicornsasfuel/sqlite_sqli_cheat_sheet


Login

思路:注册admin50+个空格)1用户,在插入数据库时,由于varchar()的长度限制,和mysql的默认配置,只会插入admin加一些空格,当mysql再次把它带入=运算进行字符串比较时,将会忽略所有末尾的空格,从而以admin身份查看flag

 

Come on

思路:布尔型盲注。设定/**/andormidsubstrunion><、空白符、ascii等为敏感词,若检测到敏感词存在则重新跳转至index.php。参数使用mysql_real_escape_string()进行转义,但存在宽字节注入,可使用括号代替空格、使用left/right函数结合以代替substr()mid()的方式进行注入,且此查询默认不区分大小写,需要使用binary(),最终payload形如:

%df%27||binary((right(left((select(flag)from(flag)),1),1))=hex)

这样即可按位获取flag表中的flag内容。

 

Guess

思路:存在rfi漏洞可以直接读取题目的源码,通过设置自己的PHPSESSION为空,再解md5获取php_srand()的随机种子,即可预测出文件名,之后使用zip流来包含所上传的文件getshell,之后就可以获取flag

 

Be Admin

思路:存在bak源码泄露以提供白盒,使用sqli控制查询语句的返回值,从而可以控制被传递进login函数的参数,由于php==运算符的安全问题,登陆验证函数可以被作为Oracle来使用,从而可以编写脚本,在没有密钥的情况下,通过padding oracle解出明文,再通过异或构造iv,从而在密文不变的情况下使解密后的结果变为admin,进而获取flag

 

 

Be Logical

思路:给出一个业务系统,选手需要用里面的积分去换取金钱并购买增值服务,在退款的地方,选手可以通过输入point1e3这样的数据来绕过检查,从而在退款中得到更多的分数。或者本题也可以获取.Sign.php.swp文件,能够发现签名算法存在缺陷,可以爆破secret_key长度并使用哈希长度扩展攻击伪造签名来进行刷钱;刷到相应的金额后可购买增值服务,即一个图片处理功能,其中存在ImageMagic RCE,通过恶意图片反弹shell后扫描内网,发现内网中存在phpmailer的邮件站点,并发现存在uploads目录,且很可能可写,直接用curl提交payload,写上去一个shell,在phpmailer主机上面获得flag

 

Echo server

思路: 程序很短就是输入一个字符串然后返回该字符串的ascii编码. Strings分析后发现输入”F1@gA”后会有hint. 但代码打了一些花指令, 会使反汇编器混淆. 需要解题人对照opcode区分出 .text 中的数据和指令, patch掉垃圾代码. 还原出程序后可以看到flag就是”1@gA”MD5.

 

On the fly

思路: 该题在编译的过程中就对flag加了密, 需要通过逆向加密逻辑反推出解密.

 

first

思路:

将输入分为6份,每份由一个线程运行计算md5,比较相同加入mid[],线程运行顺序是随机的所以经过重新的排序。需要爆破找到满足条件的结果即flag,可以自己写程序爆破也可以把输入喂到程序里(不过这样成功率不高)。

 

vsvs

思路:系统本来可以执行system命令,但是被过滤了可以命令注入的字符。但是在read的时候存在栈溢出可以控制后面system将要执行的字符从而实现命令执行。

 

Pingme

思路:不提供源程序,blind formatstring攻击。

 

Random

思路: 程序每次猜单双的游戏连续取胜可以获得更长的数组访问,如果连续取胜数十次可以数组越界写造成栈溢出。猜单双的随机数是一个自己写的伪随机数生成器,可以通过计算初始状态预测之后的状态。(然而出题人留的栈长度不够。。。被爆破了Orz

 

地球是平的 

描述提示不是ECB,猜测是CBC加密,并且IVIrving这个单词。观察密文特征。




注意CBC加密方式中是要前一密文段异或一次明文再加密一次的,这里出现了几乎一样的密文段,说明不存在复杂机密模式中的雪崩效应。仔细观察,标记里的两段密文仅某一位进行了反转,其余位都一样,在明文存在重复的情况下密文仅出现几个bit的差异,加密可能是对bit进行的异或运算。

一直向量长度为6byte,所以key和分组长度为6,解密的下一步就是求出key,求出key必须知道明文,这里我们只能对明文进行猜测,猜测几百jb大小的密文是某种文件,文件的文件头是固定的。多次尝试后猜测是gif文件,文件头为GIF89a,进行已知明文攻击,解密脚本:


import sys

 

data = list(open(sys.argv[1], 'rb').read())

block_size = 6

iv = 'Irving'

magic = 'GIF89a'

key = []

 

# compute the key from the first block

for i in range(0, block_size):

    key.append(chr(ord(iv[i]) ^ ord(data[i]) ^ ord(magic[i])))

 

# decrypt it from end to start, easier to do since it's in place decryption

# make sure idx is block_size aligned

idx = len(data) - (len(data)%block_size) - block_size

while idx >= block_size:

    for i in range(0, block_size):

        data[idx+i] = chr(ord(data[idx+i])^ord(data[idx-block_size+i])^ord(key[i]))

    idx -= block_size

 

for i in range(0, block_size):

    data[i] = chr(ord(data[i])^ord(iv[i])^ord(key[i]))

 

open(sys.argv[2], 'wb').write(''.join(data))


得到解密后的gif文件仍然无法打开,查看gif16进制信息:



大部分信息成功得到解密,但是观察发现每个分组的第4个和第5个字节解密失败,文件尾的所有第4个字节和第5个字节分别为89的却解密成功,猜想89的关系,猜测加密时先将第5个字节-1再与第4个字节加密后调换再进行异或加密,至此,写出解密脚本:

import sys


data = list(open(sys.argv[1], 'rb').read())

block_size = 6

iv = 'Irving'

magic = 'GIF89a'

key = []


# compute the key from the first block

for i in range(0, block_size):

key.append(chr(ord(iv[i]) ^ ord(data[i]) ^ ord(magic[i])))

# decrypt it from end to start, easier to do since it's in place decryption

# make sure idx is block_size aligned


idx = len(data) - (len(data) % block_size) - block_size

while idx >= block_size: 

for i in range(0, block_size):     

data[idx+i] = chr(ord(data[idx+i])^ord(data[idx-block_size+i])^ord(key[i])) 

tmp = data[idx + 3] 

data[idx + 3] = chr((ord(data[idx + 4]) - 1) % 256) 

data[idx + 4] = chr((ord(tmp) + 1) % 256) 

idx -= block_size


# idx = 0

# tmp = data[idx + 3]

# data[idx + 3] = chr((ord(data[idx + 4]) - 1) % 256)

# data[idx + 4] = chr((ord(tmp) + 1) % 256)

for i in range(0, block_size):

data[i] = chr(ord(data[i])^ord(iv[i])^ord(key[i]))


open(sys.argv[2], 'wb').write(''.join(data))

得到解密后的gif图片:




messager

思路:栈溢出,有canaryBrute-force stack canary byte by byte。不使用socat,利用socket直接跟选手进行通信。题目中可以设置一个getflag函数,从而使得选手一旦控制流劫持就可以拿到flag

因为题目是直接用socket通信的,所以劫持控制流到/bin/sh是无法拿到shell的,而且不能通过stdio进行leak,要想拿shell只能开一个shell bind到一个端口。所以可以通过去掉getflag函数提高题目难度,从而考察选手是否会写bind shellexpif do,题目难度可以调整为中。

 

 

233

思路:

考察知识点:Vdso gadget SROP

#include<stdio.h>

void main(){

    char c[10];

    read(0,c,1024);

    atoi(c);

}

程序本身只有这么多,除了canary 所有安全机制全开。

CANARY    : disabled

FORTIFY   : ENABLED

NX        : ENABLED

PIE       : ENABLED

RELRO     : FULL

只能通过vdsogadgetrop来拿shell



syscallhelper

思路:传统pwn+chroot 沙箱逃逸。

Pwn部分:

可以越界向堆中写堆地址,考虑改写vtable为内容为0x0c0c0c0c的堆地址,然后堆喷拿控制流之后逃逸沙箱

沙箱逃逸:chroot后的根目录不可写。只能在劫持控制流之后通过ptrace父进程来逃逸chroot沙箱

 

Easy_crypto 

根据已有的加密源代码和已知明文及对应的密文,写出脚本对加密用的密钥key按位进行穷举。

得到key后根据加密脚本写出对应的解密脚本即可对flag进行解密

Key求解程序:

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

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

    if (argc != 3) {

        printf("USAGE: %s plain_file cipher_file\n", argv[0]);

        return 0;

    }

    FILE* plain_file  = fopen(argv[1], "rb");

    FILE* cipher_file = fopen(argv[2], "rb");

    if (!plain_file || !cipher_file) {

        printf("Error\n");

        return 0;

    }

    char key[] = "XXXXXXXXXXXX";

    char p, t, c, ch;

    unsigned int j = 0;

    int i = 0;

    while ((p = fgetc(plain_file)) != EOF) {

        c = fgetc(cipher_file);

        // printf("read %d", p);

        for (j=31;j<125;j++) {

            ch = ((j ^ t) + (p-t) + i*i ) & 0xff;

            if (ch == c) {

                printf("%c\n",j);

                t = p;

                i++;

                break;

            }

        }

    }

    return 0;

}


解密程序:

#include <stdlib.h>

#include <stdio.h>

#include <string.h>

 

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

    if (argc != 3) {

        printf("USAGE: %s input_file output_file\n", argv[0]);

        return 0;

    }

    FILE* input_file  = fopen(argv[1], "rb");

    FILE* output_file = fopen(argv[2], "wb");

    if (!input_file || !output_file) {

        printf("Error\n");

        return 0;

    }

    char key[] = "OKIWILLLETYOUKNOWWHATTHEKEYIS";

    char p, t, c = 0;

    int i = 0;

    while ((c = fgetc(input_file)) != EOF) {

        p = ( (c - (key[i % strlen(key)] ^ t) - i*i ) + t) & 0xff;

        t = p;

        i++;

        fputc(p, output_file);

    }

    return 0;

}


knock Writeup

Knock.txt文件是用来将text.txt字母分割的文件,下划线代表空格,.代表字母,分割后的密文是一个单字母替换加密密文。

zjqz hexjz mo oqrs sai daiyn lebn zjo vos ltah zjer horrqxo e iron lobdo za voou zjo vos qfqs ltah mqn qrr joto er zjo horrqxo ebooqydrztyqqojolx

替换表:


得到keyineealcstrlaaehefg

使用栅栏密码六个一组解密后得到

flagica nse eth ere alf lag



shooter Writeup

查看图片hex信息,发现在jpg后在0x24ef9处跟了一个去了头和尾的png文件,提取出来加上文件头后是一个二维码,扫出来“key 是‘boomboom’”,用outguess提取jpg图片的隐写信息:



traffic Writeup

查看pcap流量后,发现存在小部分IRC数据报文,把这些包过滤出来后对最后一个包进行tcp流追踪,得到完整的IRC会话。





会话中一方发送了一串奇怪的字符串并得到了一个get的回应,开始进行不断的base64字符串的发送,对前几行进行base64解码后发现是济慈的海伯利安的诗句,尝试解密最后一行并对解密后的明文自行再base64加密,发现同一明文的base64密文间存在区别:



存在base64隐写,并且只隐写了倒数第二个字节的最后两个bit

解题脚本:

def get_base64_diff_value(s1, s2):

    base64chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

    res = 0

    for i in xrange(len(s2)):

        if s1[i] != s2[i]:

            return abs(base64chars.index(s1[i]) - base64chars.index(s2[i]))

    return res

 

 

def solve_stego():

    with open('text.txt', 'rb') as f:

        file_lines = f.readlines()

    bin_str = ''

    for line in file_lines:

        steg_line = line.replace('\n', '')

        norm_line = line.replace('\n', '').decode('base64').encode('base64').replace('\n','')

        diff = get_base64_diff_value(steg_line, norm_line)

        pads_num = steg_line.count('=')

        if pads_num == 2:

            pads_num = 1

        if diff:

            bin_str += bin(diff)[2:].zfill(pads_num * 2)

        else:

            bin_str += '0' * pads_num * 2

    bin_str = bin_str[2:]

    res_str = ''

    for i in xrange(0, len(bin_str), 8):

        res_str += chr(int(bin_str[i:i + 8], 2))

    print res_str

 

 

solve_stego()

得到flagNJCTF{Th4res_Ev1l_iN_1rC}


Ransom Writeup

首先使用DiskGeniusvmdk文件进行读取,



xp-000001.vmdk的桌面存在4个文件,其中3个被加密,打开html文件



提示文件是被256AES加密,并且提到如果30天内没有付款就会删除所有密钥,而日期是13日,已经过了30天,已经有文件被删除了。尝试使用DiskGenius进行文件恢复。



在桌面下成功恢复一个.key.zip压缩文件,打开后是三个aes密钥,但显然被加密了,加密前应该是32位。再看vmem文件,文件修改日期就是13日,当天内存是否会留存有密钥信息?尝试去分析vmem文件对密钥进行解密。

使用volatility查看vmem进程信息:



存在一个Winrar的进程,这个进程是否打开了某个带有密钥信息的文件?使用volatilityprocdump功能将winrar进程信息dump出来。

对提取出来的文件进行strings提取。



仔细搜索提取出来的strings,找到了一个download.zip中的文件,recieve.txt,出现了一个32位的奇怪字符串,下面提到了private.jpgpublic.jpg,这两个jpg就是rsa的私钥和公钥,再去vmdk中找(应该也被删除,需要恢复)。(这个txt文件真的是怕坑到人才放上去的,好像多虑了,导致降低了一些复杂度)



在用户根目录下找到这两个文件,打开后发现被加密过,根据勒索软件的行为,要么是rsa加密或者aes加密或者其他,想起当时那个32位的字符串,将其作为密钥尝试AES256解密文件,解密成功!在用rsa私钥解密三个AES密钥,再AES解密三个文本文件,拼接起来即可得到flag

NJCTF{L3t_Vs_G0ooo0000_g000000_9o}


 

easycrack

Java层和native层的逆向与调试,题中有三个地方涉及到加密。没有加壳和混淆。

A.逆向的做法。看起来2处需要逆向,1处需要分析,其中1处加密使用原函数即可解密。所以总体来说需要逆向的只有一个简单的异或加密。

B.HOOK。因加密输出不受输入的前面字节影响,所以可以逐位暴破,但需要HOOK入输入的入口。

 

 

 SafeBox

要逆向看出来一个隐藏的activity,需要am startactivity调出来。然后逆向出逻辑,得到正确的输入,输入正确解密数字之后,得到flag

 

 

Littlerotategame

逆向混淆之后的arm代码。

 

 

已有  0  条评论
加载中...