
이 워크숍 세션에서는 EV 충전기에서 펌웨어를 추출하고, 펌웨어를 깊이 파고든 다음, 최종적으로 이를 에뮬레이션하여 실시간으로 서비스와 상호작용할 수 있게 됩니다.
유연한 펌웨어 추출기인 Unblob의 실습 데모에 참여하세요. 이 워크숍 세션에서는 EV 충전기에서 펌웨어를 추출하고, 펌웨어를 자세히 살펴본 후, 최종적으로 에뮬레이션하여 실시간으로 서비스와 상호작용할 수 있게 됩니다. Unblob은 하드웨어 및 다운로드 가능한 펌웨어 버전 모두에서 작동하므로 풍부한 대상 환경을 제공합니다. 사전 경험은 필요하지 않으며, 이 세션은 모든 수준의 기술에 적합합니다. 여러분을 그 자리에서 만나 뵙기를 기대합니다.
우리의 대상은 Phoenix Contact의 전기차 충전소 컨트롤러입니다. 자세한 내용은 여기에서 확인할 수 있습니다.
CHARX control modular, AC 충전 컨트롤러, 임베디드 Linux 시스템 탑재, IEC 61851-1, 운영 모드: Stand-Alone, Client, Server,
인터페이스:
- 이더넷 (2x)
- 셀룰러 통신 (4G/2G)
- CHARX control modular 시스템 버스
- MICRO-USB type C
통신 프로토콜:
- OCPP 1.6J
- Modbus/TCP
- MQTT
연결 가능한 주변 기기:
- 에너지 미터
- RFID
- DC 잔류 전류 감지
- DIN 레일 장착
이 워크숍에 필요한 몇 가지 도구가 있습니다. 다음과 같이
install-prerequisites 스크립트를 실행하여 설치할 수 있습니다:```sh
./install-prerequisites
## 펌웨어 얻기
펌웨어는 공급업체 웹사이트에서 얻을 수 있습니다. 이 저장소에는 `download-firmware`라는 스크립트가 있어서 브라우저를 열지 않고도 펌웨어를 내려받을 수 있습니다.
오늘 우리의 초점은 공급업체가 제공한 펌웨어에 맞춰져 있는데, 여기에는 필요한 모든 것이 포함되어 있기 때문입니다. 그러나 유사한 워크플로우는 실제 장치에서 추출한 메모리 덤프에도 적용할 수 있습니다. 흥미로운 점은 실제 장치가 없어도 추출하고, 탐색하고, 에뮬레이션할 수 있다는 것입니다.
## Unblob으로 추출하기
모든 의존성이 준비되어 있는지 확인하는 것부터 시작하겠습니다:```
unblob --show-external-dependencies
The following executables found installed, which are needed by unblob:
7z ✓
debugfs ✓
jefferson ✓
lz4 ✓
lziprecover ✓
lzop ✓
sasquatch ✓
sasquatch-v4be ✓
simg2img ✓
ubireader_extract_files ✓
ubireader_extract_images ✓
unar ✓
zstd ✓
이제 unblob으로 펌웨어를 추출할 수 있습니다:``` unblob CHARXSEC3XXXSoftwareBundleV190.raucb
성능이 괜찮은 노트북에서 추출에는 약 3분이 걸립니다. 진행률 표시줄이 올라가는 것을 볼 수 있습니다:

추출이 완료되면,
`CHARXSEC3XXXSoftwareBundleV190.raucb_extract`라는 디렉터리가 표시됩니다. 해당 디렉터리로 이동하여
내용을 나열할 수 있습니다.
### 청크, 알 수 없는 청크
Unblob은 파일 내의 데이터 청크를 식별하여 작동합니다. 청크가
압축 스트림이면 압축이 해제됩니다. 파일 시스템이나 아카이브이면
추출됩니다. 추출 또는 압축 해제가 성공하면, 디스크로 추출된 청크는
공간을 확보하기 위해 삭제됩니다.
여기서는 SquashFS 버전 4 리틀엔디언 청크가 디스크로 분리된 후, 추출되고,
삭제되었습니다. 파일(그리고 추출 디렉터리)은
`{start_offset}-{end_offset}.{type}` 명명 규칙으로 이름이 지정됩니다.
squashfs 파일 시스템 뒤에 11KB의 "unknown" 청크가 나타나는 것을 볼 수 있습니다.```
./0-132173824.squashfs_v4_le_extract
./132173824-132184833.unknown
binwalk를 실행하면 그 안에 무엇이 포함되어 있는지 확인할 수 있습니다:```
binwalk 132173824-132184833.unknown
0 0x0 Object signature in DER format (PKCS header length: 4, sequence length: 10997 58 0x3A Certificate in DER format (x509 v3), header length: 4, sequence length: 4372 4434 0x1152 Certificate in DER format (x509 v3), header length: 4, sequence length: 4387
openssl로 인증서를 확인할 수 있습니다:```
dd if=132173824-132184833.unknown bs=1 skip=58 | openssl x509 -in /dev/stdin -inform der -noout -text
dd if=132173824-132184833.unknown bs=1 skip=4434 | openssl x509 -in /dev/stdin -inform der -noout -text
이는 펌웨어가 공급업체의 개인 키로 서명되었을 가능성이 높으며, 기기는 펌웨어의 진위 여부를 확인할 수 있습니다.
이것이 unblob의 장점 중 하나입니다. 알 수 없는 미지(unknown unknowns)를 조사할 수 있는 알려진 미지(known unknowns)로 바꿔줍니다.
squashfs 파일시스템의 내용을 살펴보겠습니다:``` ls -al 0-132173824.squashfs_v4_le_extract total 460532 drwxrwxr-x 4 kali kali 4096 dec 5 09:51 . drwxrwxr-x 3 kali kali 4096 dec 5 09:51 .. -rw-rw-r-- 1 kali kali 20971520 sep 8 09:59 bootimg.vfat drwxrwxr-x 3 kali kali 4096 dec 5 09:51 bootimg.vfat_extract -rwxrwxr-x 1 kali kali 2654 sep 19 2022 hook -rw-rw-r-- 1 kali kali 442 sep 8 09:59 manifest.raucm -rw-rw-r-- 1 kali kali 450584576 sep 8 09:59 root.ext4 drwxrwxr-x 22 kali kali 4096 sep 8 09:57 root.ext4_extract
우리는 평문 매니페스트, 셸 스크립트, 하나의 MBR 및 EXT4 파일시스템을 볼 수 있습니다:```
find -maxdepth 1 -type f -exec file {} \;
./manifest.raucm: ASCII text
./hook: a /usr/bin/env sh script, ASCII text executable
./bootimg.vfat: DOS/MBR boot sector, code offset 0x3c+2, OEM-ID "mkfs.fat", sectors/cluster 4, reserved sectors 4, root entries 512, sectors 40960 (volumes <=32 MB), Media descriptor 0xf8, sectors/FAT 40, sectors/track 63, heads 255, hidden sectors 163158016, reserved 0x1, serial number 0xd09aad9c, label: "KERNEL ", FAT (16 bit)
./root.ext4: Linux rev 1.0 ext4 filesystem data, UUID=8ed19606-02c4-42e7-9cfb-1a2839f93ec4 (extents) (large files) (huge files)
bootimg.vfat 및 root.ext4는 모두 unblob에 의해 처리되어 추출되었습니다.
VFAT 파티션은 부팅 및 OS와 관련된 모든 것(Linux 커널, DTB,
TEE)을 포함합니다:```
oftree: Device Tree Blob version 17, size=27469, boot CPU=0, string block size=1657, DT structure block size=25756
tee.bin: data
zImage: Linux kernel ARM boot executable zImage (little-endian)
zImage-imx6ul-ksp0632.dtb: Device Tree Blob version 17, size=27469, boot CPU=0, string block size=1657, DT structure block size=25756
unblob이 조금 욕심이 많아서 Linux 커널(`zImage`)에서 ELF 파일과 CPIO
아카이브를 추출하는 것을 볼 수 있습니다. 이것들은 최소한의 커널과
램디스크에 해당합니다.
EXT4 파일시스템에는 부팅 시 Linux 커널이 마운트하는 루트
파일시스템이 포함되어 있습니다:```
ls -alh root.ext4_extract
total 88K
drwxrwxr-x 22 kali kali 4,0K sep 8 09:57 .
drwxrwxr-x 4 kali kali 4,0K dec 5 09:51 ..
drwxrwxr-x 2 kali kali 4,0K dec 5 09:51 bin
drwxrwxr-x 3 kali kali 4,0K dec 5 09:51 boot
drwxrwxr-x 16 kali kali 4,0K sep 8 09:56 data
drwxrwxr-x 2 kali kali 4,0K jul 18 00:09 dev
drwxrwxr-x 53 kali kali 4,0K dec 5 09:51 etc
drwxrwxr-x 18 kali kali 4,0K sep 8 09:56 home
drwxrwxr-x 2 kali kali 4,0K jul 18 03:13 identity
drwxrwxr-x 9 kali kali 4,0K dec 5 09:51 lib
drwxrwxr-x 2 kali kali 4,0K jul 18 03:14 log
drwxrwxr-x 2 kali kali 4,0K sep 8 09:57 lost+found
drwxrwxr-x 2 kali kali 4,0K jul 18 00:09 media
drwxrwxr-x 2 kali kali 4,0K jul 18 00:09 mnt
drwxrwxr-x 2 kali kali 4,0K jul 18 00:09 proc
drwxrwxr-x 2 kali kali 4,0K sep 8 09:57 run
drwxrwxr-x 2 kali kali 4,0K dec 5 09:51 sbin
drwxrwxr-x 2 kali kali 4,0K jul 18 03:14 sdcard
drwxrwxr-x 2 kali kali 4,0K jul 18 00:09 sys
drwxrwxrwx 2 kali kali 4,0K jul 18 00:09 tmp
drwxrwxr-x 11 kali kali 4,0K sep 8 09:56 usr
drwxrwxr-x 11 kali kali 4,0K dec 5 09:51 var
unblob의 다른 옵션 세트를 사용하여 약간의 탐색을 해보겠습니다:```
unblob -e /tmp/out -f -k -d 3 --report /tmp/report.json
--log /tmp/unblob.log CHARXSEC3XXXSoftwareBundleV190.raucb
여기서는 추출(`-e`)을 계속 `/tmp/out`으로 하지만, 덮어쓰기를 강제(`-f`)하면서 잘라낸(carved out) 청크를 유지(`-k`)하고 재귀 깊이(`-d`)를 3으로 제한합니다. 상세 보고서(`--report`)를 `/tmp/report.json`에 쓰고 로그 파일(`--log`)을 `/tmp/unblob.log`에 기록합니다.
로그 파일을 살펴보면 unblob의 내부 동작을 볼 수 있습니다.
보고서 파일에는 분석된 파일(크기, 파일 형식, 경로, magic, MIME 타입, MD5/SHA1/SHA256 해시), 청크(크기, 오프셋, 엔트로피 분포) 및 작업(추출, 압축 해제, 조각 추출)에 대한 자세한 정보가 포함되어 있습니다. 약간의 Python만 있으면 이 보고서 파일들로 멋진 시각화 자료를 생성할 수 있습니다.
이 저장소에 포함된 `diagram.py` 파이썬 스크립트로 직접 만들 수 있습니다:```
python3 diagram.py /tmp/report.json sunburst
python3 diagram.py /tmp/report.json treemap
이 명령들은 브라우저에서 아래와 같은 plotly 기반 시각화가 포함된 페이지를 엽니다.


이제 내부에 무엇이 있는지 더 잘 파악했으니, 장치를 제대로 에뮬레이션하기 위해 필요한 것들을 나열할 차례입니다.
이상적으로는 다음 정보를 수집해야 합니다.
플랫폼, CPU, 아키텍처에 대한 세부 정보를 얻기 위해 펌웨어에 포함된 디바이스 트리 블롭을 살펴볼 수 있습니다.
VFAT 파티션에는 다음과 같은 DTB가 있습니다.``` oftree: Device Tree Blob version 17, size=27469, boot CPU=0, string block size=1657, DT structure block size=25756 zImage-imx6ul-ksp0632.dtb: Device Tree Blob version 17, size=27469, boot CPU=0, string block size=1657, DT structure block size=25756
이 파일들은 디바이스 트리의 이진 표현입니다. `device-tree-compiler`를 사용하여
이들의 일반 텍스트 소스를 복구할 수 있습니다:```
dtc -I dtb -o zImage-imx6ul-ksp0632.dts zImage-imx6ul-ksp0632.dtb
dtc -I dtb -o oftree.dts oftree
DTS를 살펴보면 곧 model과 같은 흥미로운 세부 사항을 발견하게 될 것입니다:``` /dts-v1/;
/ { #address-cells = <0x01>; #size-cells = <0x01>; model = "Phytec phyBOARD-Segin i.MX6 UltraLite"; <----- right here compatible = "phytec,imx6ul-pbacd-10\0phytec,imx6ul-pcl063\0fsl,imx6ul";
CPU:```
cpus {
#address-cells = <0x01>;
#size-cells = <0x00>;
cpu@0 {
compatible = "arm,cortex-a7"; <---- here
device_type = "cpu";
reg = <0x00>;
clock-latency = <0xee6c>;
operating-points = <0xa9ec0 0x137478 0x80e80 0x11edd8 0x60ae0 0xfa3e8 0x30570 0xe7ef0>;
fsl,soc-operating-points = <0xa9ec0 0x137478 0x80e80 0x11edd8 0x60ae0 0x11edd8 0x30570 0x11edd8>;
clocks = <0x01 0x5d 0x01 0x1a 0x01 0x26 0x01 0xdb 0x01 0x38 0x01 0x39 0x01 0x19>;
clock-names = "arm\0pll2_bus\0pll2_pfd2_396m\0secondary_sel\0step\0pll1_sw\0pll1_sys";
arm-supply = <0x02>;
soc-supply = <0x03>;
dc-supply = <0x04>;
};
};
또는 심지어 주변기기까지:``` --snip-- ethernet@2188000 { compatible = "fsl,imx6ul-fec\0fsl,imx6q-fec"; reg = <0x2188000 0x4000>; interrupts = <0x00 0x76 0x04 0x00 0x77 0x04>; clocks = <0x01 0x90 0x01 0x91 0x01 0x30 0x01 0x2c 0x01 0x2c>; clock-names = "ipg\0ahb\0ptp\0enet_clk_ref\0enet_out"; fsl,num-tx-queues = <0x01>; fsl,num-rx-queues = <0x01>; status = "okay"; pinctrl-names = "default"; pinctrl-0 = <0x23>; phy-mode = "rmii"; phy-handle = <0x24>;
mdio {
#address-cells = <0x01>;
#size-cells = <0x00>;
ethernet-phy@1 {
reg = <0x01>;
interrupt-parent = <0x0b>;
interrupts = <0x02 0x08>;
micrel,led-mode = <0x01>;
clocks = <0x01 0x2c>;
clock-names = "rmii-ref";
status = "okay";
linux,phandle = <0x24>;
phandle = <0x24>;
};
ethernet-phy@2 {
reg = <0x03>;
micrel,led-mode = <0x01>;
clocks = <0x01 0x2d>;
clock-names = "rmii-ref";
status = "okay";
linux,phandle = <0x13>;
phandle = <0x13>;
};
};
};
--snip-- flexcan@2090000 { compatible = "fsl,imx6ul-flexcan\0fsl,imx6q-flexcan"; reg = <0x2090000 0x4000>; interrupts = <0x00 0x6e 0x04>; clocks = <0x01 0x94 0x01 0x95>; clock-names = "ipg\0per"; status = "okay"; pinctrl-names = "default"; pinctrl-0 = <0x0f>; xceiver-supply = <0x10>; };
flexcan@2094000 { compatible = "fsl,imx6ul-flexcan\0fsl,imx6q-flexcan"; reg = <0x2094000 0x4000>; interrupts = <0x00 0x6f 0x04>; clocks = <0x01 0x96 0x01 0x97>; clock-names = "ipg\0per"; status = "disabled"; };
### Linux 커널 버전 정보
압축된 커널이 들어 있는 VFAT 파티션 내에서 grep하여
Linux 커널 버전을 식별할 수 있습니다:```
grep 'Linux version' . -ra
./zImage_extract/7296-6371487.lzo_extract/7296-6371487:
Linux version 5.15.195 (oe-user@oe-host) (gcc version 8.3.0 (GCC)) #1 SMP PREEMPT
./zImage_extract/7296-6371487.lzo_extract/7296-6371487:
Linux version %s (%s)Bluetooth subsystem version %u.%uHCI socket registration failed
또 다른 방법은 EXT4 루트 파일시스템 내의 커널 모듈을 살펴보는 것입니다:```
modinfo ./CHARXSEC3XXXSoftwareBundleV190.raucb_extract/0-138014720.squashfs_v4_le_extract/root.ext4_extract/lib/modules/5.15.195/kernel/net/bluetooth/bnep/bnep.ko.xz
filename: ./CHARXSEC3XXXSoftwareBundleV190.raucb_extract/0-138014720.squashfs_v4_le_extract/root.ext4_extract/lib/modules/5.15.195/kernel/net/bluetooth/bnep/bnep.ko.xz
alias: bt-proto-4
license: GPL
version: 1.3
description: Bluetooth BNEP ver 1.3
author: Marcel Holtmann [email protected]
srcversion: 86266D360814678CE0C94E9
depends:
intree: Y
name: bnep
vermagic: 5.15.195 preempt mod_unload modversions ARMv7 thumb2 p2v8
parm: compress_src:Compress sources headers (bool)
parm: compress_dst:Compress destination headers (bool)
## 에뮬레이션
펌웨어 패키지 안에 이미 컴파일된 커널이 있을 수 있지만, 이 커널은 특정
머신(Phytec phyBOARD-Segin i.MX6 UltraLite)용으로만 컴파일된 것입니다.
이 커널을 제대로 사용하려면, 이 커널이 컴파일된 바로 그 머신의 에뮬레이션
위에서 이 커널을 실행할 수 있어야 합니다. QEMU 머신 목록에 이미 없다면 이는
시간이 많이 걸리는 작업입니다. 참고: 평가 보드 머신이 QEMU
(`mcimx6ul-evk - Freescale i.MX6UL Evaluation Kit (Cortex-A7)`)에 있지만
정확히 일치하지는 않습니다.
대신, 이론적으로는 QEMU 머신을 고르고, 펌웨어 이미지에서 찾은 다양한
메타데이터를 사용하여 해당 머신용 커널을 구성하고 빌드할 수 있습니다.
그렇게 하면 우리 파일시스템의 모든 것을 원본에 최대한 가깝게 실행할 수도
있습니다.
여기서 몇 가지 옵션이 있습니다:
- *사용자 공간 에뮬레이션*: 자체 커스텀 ramfs/rootfs로 부팅한 다음, 대상
펌웨어 rootfs를 로드하고 그 안으로 chroot하여 에뮬레이션할 수 있습니다.
- *전체 시스템 에뮬레이션*: 대상 펌웨어 rootfs를 직접 부팅하려고 시도할 수
있습니다(QEMU 머신에 직접 전달).
사용자 공간 에뮬레이션은 보통 제한적이지만 특정 바이너리를 조사하고,
디버깅하고, 악용하는 데 아주 좋은 방법입니다. 사용자 공간 에뮬레이션이
필요하다면 Saumil Shah의 [EMUX](https://github.com/therealsaumil/emux)를
꼭 살펴보시길 강력히 권장합니다.
어쨌든, 우리는 여전히 QEMU 머신 안에서 부팅할 수 있는 커널이 필요하며,
그 커널은 대상 펌웨어가 기대하는 것과 최대한 가깝게 일치해야 합니다.
### QEMU 머신
유연성을 제공하는 범용 머신으로는 후보가 몇 개뿐입니다. 꽤 많은 실험 끝에,
`virt`(https://qemu.readthedocs.io/en/latest/system/arm/virt.html)가
우리의 목적에 적합한 유연성/성능 비율을 가진 것으로 보입니다.
특정 QEMU 머신용 커널을 빌드할 때는 QEMU가 제공하는 일반 virtio 장치를
사용할 수 있도록 하는 것이 아마 가장 좋을 것입니다. 구성에서 이는 다음 중
하나 이상을 의미할 수 있습니다.```
CONFIG_VIRTIO=y
CONFIG_SCSI_VIRTIO=y
CONFIG_VIRTIO_PCI=y
CONFIG_VIRTIO_BLK=y
그리고 더 있습니다! 이 목록은 불완전합니다.
동일한(또는 최소한 매우 유사한) vermagic 문자열을 가진 커널을 사용하는 것이 유리합니다. 시스템 내의 모든 종류의 구성 요소가 커널 vermagic을 검사하고, 기대값과 다르면 실패할 수 있습니다. 이는 커널 모듈에서 가장 흔합니다. 커널은 일반적으로 자신과 일치하는 vermagic이 없는 모듈의 로드를 거부합니다. 커널 수준에서 이 검사를 무시하도록 커널을 패치할 수는 있지만, 문자열의 초기 “kernel release” 부분은 계속해서 커널 모듈의 릴리스 문자열과 일치해야 합니다. 그 값은 modules.dep의 검색 경로로 사용되기 때문입니다.
Phoenix Contact 펌웨어에서 vermagic은 5.15.195 preempt mod_unload modversions ARMv7 thumb2 p2v8입니다.
vermagic 문자열은 (include/linux/vermagic.h에서) 다음과 같은 형식입니다:```c
#define VERMAGIC_STRING
UTS_RELEASE " "
MODULE_VERMAGIC_SMP MODULE_VERMAGIC_PREEMPT
MODULE_VERMAGIC_MODULE_UNLOAD MODULE_VERMAGIC_MODVERSIONS
MODULE_ARCH_VERMAGIC
- 커널 릴리즈 = 커널 버전 + `CONFIG_LOCALVERSION` 문자열.
- SMP = `CONFIG_SMP`
- PREEMPT = `CONFIG_PREEMPT`
- mod_unload = `CONFIG_MODULE_UNLOAD`
- modversions = `CONFIG_MODVERSIONS`
- ARMv7 thumb2 p2v8 = `MODULE_ARCH_VERMAGIC` (커널이 컴파일되는 아키텍처와 관련되며, 따라서 전달되는 컴파일러/아키텍처 옵션과도 관련됩니다.)
추출된 커널 소스를 확보했다면, vermagic 문자열을 비교할 때 너무 엄격하지 않도록 패치할 수 있습니다. kernel/module.c에서 `return -ENOEXEC`를 주석 처리하면 됩니다:```diff
} else if (!same_magic(modmagic, vermagic, info->index.vers)) {
pr_err("%s: version magic '%s' should be '%s'\n", info->name, modmagic, vermagic);
- return -ENOEXEC;
+ // return -ENOEXEC;
}
if (!get_modinfo(info, "intree")) {
장기적으로 반드시 유용한 것은 아니지만, 커널 vermagic과 모듈 vermagic 사이에 약간의 차이가 있는 한 테스트에 유용합니다. 모듈은 (대부분) 여전히 로드됩니다.
참고: 여기서는 대상 요구 사항에 맞는 이미 빌드된 커널을 제공하므로 직접 컴파일할 필요가 없습니다. 이 커널은 사내 스크립트로 빌드되었으며, 해당 스크립트는 다음을 수행합니다:
multi_v7_defconfig를 호출합니다kernel/module.c의 vermagic 검사 코드를 패치합니다VIRTIO 옵션과 USB 또는 CANBUS 같은 특정 하위 시스템을 활성화하도록 구성을 패치합니다qemu-system-arm 명령의 다양한 매개변수를 살펴보겠습니다. 커널을 다음과 같이 제공합니다:```sh
qemu-system-arm -M virt,highmem=off
-m ${AVAILABLE_MEM_GB}G -smp ${CPU_CORES} -cpu cortex-a15
-kernel zImage
EXT4 파일시스템 이미지는 SCSI 디스크로 연결되어 있습니다:```sh
-device virtio-scsi-device \
-device scsi-hd,drive=SystemDisk \
-drive file=${FIRMWARE_IMAGE},format=raw,if=none,id=SystemDisk
다음과 같이 적절한 콘솔 출력을 설정합니다:```sh
-append "${BOOT_PARAMS}"
-no-reboot
-nographic
-serial mon:stdio \
`BOOT_PARAMS`는 `config.ini`에 설정되며 실제 커널 부트 커맨드에 해당합니다. 다만 콘솔 경로와 rootfs 경로라는 두 가지 작은 조정이 있습니다.
### 네트워킹
이제 동작하는 에뮬레이션 시스템이 준비되었으니 네트워킹을 설정할 차례입니다. 대상 장치는 다음과 같은 구성을 갖습니다:
- 두 개의 이더넷 인터페이스 (`eth0` 및 `eth1`)
- 하나의 CANbus 인터페이스 (`can0`)
- USB over Ethernet으로 동작할 수 있는 하나의 USB 인터페이스 (`usb0`)
- Qualcomm High Sierra 셀룰러 모뎀 (`ppp0`)
QEMU에서 셀룰러 모뎀과 USB 인터페이스를 에뮬레이션하는 것은 까다롭고, 해당 인터페이스는 "선택 사항"이므로 실제 장치 동작에는 반드시 필요하지 않습니다. CAN 인터페이스는 컨트롤러가 다른 컨트롤러나 충전 스테이션과 통신할 수 있도록 하기 위해 존재합니다. `eth0` 인터페이스는 기본적으로 DHCP 임대를 요청하여 업스트림 연결을 제공하고, `eth1`은 192.168.4.1로 설정되어 있으며 여러 컨트롤러를 데이지 체인 방식으로 연결하려는 경우에 사용됩니다.
이더넷 인터페이스는 TAP 인터페이스로 설정되며 다음과 같이 연결됩니다:```
-netdev tap,id=tap0net,ifname=${ETH_1},script=no,downscript=no \
-device virtio-net-device,netdev=tap0net,mac=${ETH_1_MAC} \
-netdev tap,id=tap1net,ifname=${ETH_0},script=no,downscript=no \
-device virtio-net-device,netdev=tap1net,mac=${ETH_0_MAC} \
CANBUS 인터페이스는 단순히 PCI 인터페이스로, 다음과 같이 연결됩니다:```
-object can-bus,id=canbus0
-device kvaser_pci,canbus=canbus0
-object can-host-socketcan,id=canhost0,if=can0,canbus=canbus0 \
아래 다이어그램은 네트워크 설정이 어떤 모습인지 보여줍니다:

Phoenix Contact가 권장하는 대로 컨트롤러들을 데이지 체인으로 연결하여 컨트롤러 간 통신을 시도하려면, 다음과 같이 구성할 수 있습니다:

## 준비? 셋. 발사!
이제 모든 것이 준비되었으니, 부팅할 시간입니다!
먼저, 모든 네트워크 인터페이스를 다음과 같이 설정해야 합니다:```
sudo ./ifup.sh
config.ini에 업스트림 인터페이스가 올바르게 설정되어 있는지 확인하십시오:```
OUT_IF="eth0"
그런 다음 에뮬레이터를 실행할 수 있습니다:```
sudo ./launch.sh
획득한 주소는 로그에서 확인할 수 있습니다:``` Dec 5 10:07:46 ev3000 daemon.info avahi-daemon[1047]: Registering new address record for 192.168.4.1 on eth1.IPv4. Dec 5 10:07:46 ev3000 daemon.info avahi-daemon[1047]: Registering new address record for 192.168.88.181 on eth0.IPv4.
### SSH를 통한 액세스
그런 다음 `user-app` 사용자를 사용하여 에뮬레이트된 장치에 SSH로 접속할 수 있습니다:```sh
ssh [email protected]
비밀번호는 "user"입니다. 첫 로그인 시 변경해야 합니다:```sh ssh [email protected] The authenticity of host '192.168.88.181 (192.168.88.181)' can't be established. ED25519 key fingerprint is SHA256:qMupzEehNFpx6eaGZ4d3AY5Jlms3Pmxh3/yg9OGA28o. This key is not known by any other names Are you sure you want to continue connecting (yes/no/[fingerprint])? yes Warning: Permanently added '192.168.88.181' (ED25519) to the list of known hosts. [email protected]'s password: WARNING: Your password has expired. You must change your password now and login again! Changing password for user-app Old password: Enter the new password (minimum of 5 characters) Please use a combination of upper and lower case letters and numbers. New password: Re-enter new password: passwd: password changed. Connection to 192.168.88.181 closed.
### 웹 인터페이스
웹 인터페이스는 http://192.168.4.1에서 접근할 수 있습니다. 기기가 부팅되는 동안 오류 메시지가 표시될 수 있습니다. 모든 것이 정상적으로 실행되면 다음 화면이 표시됩니다:

다음 계정으로 로그인할 수 있습니다:
| 사용자 이름 | 비밀번호 |
|----------|----------|
| manufacturer | manufacturer |
| operator | operator |
### CAN 트래픽
모든 것이 정상적으로 실행되면 컨트롤러에서 생성되는 CAN 트래픽을 캡처할 수 있습니다:```
candump -i can0
can0 701 [1] 00000101
can0 701 [1] 00000101
can0 701 [1] 00000101
can0 701 [1] 00000101
can0 701 [1] 00000101
can0 701 [1] 00000101
참고: QEMU 8.x 버전은 어떤 이유로 CAN 컨트롤러를 연결하는 데 어려움이 있는 것으로 보입니다.