librz/bin: add CaRT container format support (#5964)
* Add CaRT extractor plugin * Update cmd_list expected output for CaRT extractor plugin Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
7bac9f75fa
commit
5fab5584e2
9 changed files with 442 additions and 1 deletions
197
librz/bin/format/cart/cart.c
Normal file
197
librz/bin/format/cart/cart.c
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// SPDX-FileCopyrightText: 2025 Ayush Dwivedi <ayushd785@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include "cart.h"
|
||||
#include <rz_crypto/rz_rc4.h>
|
||||
#include <rz_util.h>
|
||||
|
||||
/**
|
||||
* Default RC4 key used by CaRT: first 8 digits of PI repeated twice.
|
||||
* Reference: https://github.com/CybercentreCanada/cart/blob/master/cart/cart.py#L34
|
||||
*/
|
||||
static const ut8 CART_DEFAULT_RC4_KEY[CART_ARC4_KEY_LENGTH] = {
|
||||
0x03, 0x01, 0x04, 0x01, 0x05, 0x09, 0x02, 0x06,
|
||||
0x03, 0x01, 0x04, 0x01, 0x05, 0x09, 0x02, 0x06
|
||||
};
|
||||
|
||||
static bool cart_key_is_null(const ut8 *key) {
|
||||
for (int i = 0; i < CART_ARC4_KEY_LENGTH; i++) {
|
||||
if (key[i] != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
RZ_API bool rz_bin_cart_check_buffer(RZ_NONNULL RzBuffer *buf) {
|
||||
rz_return_val_if_fail(buf, false);
|
||||
|
||||
ut64 sz = rz_buf_size(buf);
|
||||
if (sz < CART_MINIMUM_LENGTH) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ut8 header_magic[CART_HEADER_MAGIC_SIZE];
|
||||
if (rz_buf_read_at(buf, 0, header_magic, CART_HEADER_MAGIC_SIZE) != CART_HEADER_MAGIC_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(header_magic, CART_HEADER_MAGIC, CART_HEADER_MAGIC_SIZE) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ut8 footer_magic[CART_FOOTER_MAGIC_SIZE];
|
||||
ut64 footer_start = sz - CART_FOOTER_LENGTH;
|
||||
if (rz_buf_read_at(buf, footer_start, footer_magic, CART_FOOTER_MAGIC_SIZE) != CART_FOOTER_MAGIC_SIZE) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memcmp(footer_magic, CART_FOOTER_MAGIC, CART_FOOTER_MAGIC_SIZE) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RZ_API RZ_OWN CartObj *rz_bin_cart_new_from_buffer(RZ_NONNULL RzBuffer *buf) {
|
||||
rz_return_val_if_fail(buf, NULL);
|
||||
|
||||
if (!rz_bin_cart_check_buffer(buf)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ut64 sz = rz_buf_size(buf);
|
||||
|
||||
CartObj *obj = RZ_NEW0(CartObj);
|
||||
if (!obj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
obj->file_size = sz;
|
||||
|
||||
ut64 off = 0;
|
||||
if (!rz_buf_read_offset(buf, &off, (ut8 *)obj->header.magic, CART_HEADER_MAGIC_SIZE) ||
|
||||
!rz_buf_read_le16_offset(buf, &off, &obj->header.version) ||
|
||||
!rz_buf_read_le64_offset(buf, &off, &obj->header.reserved) ||
|
||||
!rz_buf_read_offset(buf, &off, obj->header.arc4_key, CART_ARC4_KEY_LENGTH) ||
|
||||
!rz_buf_read_le64_offset(buf, &off, &obj->header.opt_header_len)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (obj->header.version != CART_VERSION) {
|
||||
RZ_LOG_WARN("CaRT: Unsupported version %d (expected %d)\n", obj->header.version, CART_VERSION);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (obj->header.reserved != 0) {
|
||||
RZ_LOG_WARN("CaRT: Invalid header reserved value (expected 0)\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ut64 footer_start = sz - CART_FOOTER_LENGTH;
|
||||
off = footer_start;
|
||||
if (!rz_buf_read_offset(buf, &off, (ut8 *)obj->footer.magic, CART_FOOTER_MAGIC_SIZE) ||
|
||||
!rz_buf_read_le64_offset(buf, &off, &obj->footer.reserved) ||
|
||||
!rz_buf_read_le64_offset(buf, &off, &obj->footer.opt_footer_pos) ||
|
||||
!rz_buf_read_le64_offset(buf, &off, &obj->footer.opt_footer_len)) {
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (obj->footer.reserved != 0) {
|
||||
RZ_LOG_WARN("CaRT: Invalid footer reserved value (expected 0)\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (obj->footer.opt_footer_pos + obj->footer.opt_footer_len != footer_start) {
|
||||
RZ_LOG_WARN("CaRT: Invalid footer position/length\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
obj->data_start = CART_HEADER_LENGTH + obj->header.opt_header_len;
|
||||
obj->data_len = obj->footer.opt_footer_pos - obj->data_start;
|
||||
|
||||
if (obj->data_start > sz || obj->data_start + obj->data_len > sz) {
|
||||
RZ_LOG_WARN("CaRT: Invalid data block boundaries\n");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
return obj;
|
||||
|
||||
fail:
|
||||
free(obj);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RZ_API void rz_bin_cart_free(RZ_NULLABLE CartObj *obj) {
|
||||
free(obj);
|
||||
}
|
||||
|
||||
RZ_API RZ_OWN ut8 *rz_bin_cart_extract(RZ_NONNULL RzBuffer *buf, RZ_NONNULL CartObj *obj, RZ_NONNULL int *out_size) {
|
||||
rz_return_val_if_fail(buf && obj && out_size, NULL);
|
||||
|
||||
*out_size = 0;
|
||||
|
||||
if (obj->data_len == 0) {
|
||||
RZ_LOG_WARN("CaRT: Empty data block\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (obj->data_len > 0x10000000) {
|
||||
RZ_LOG_WARN("CaRT: Data block too large\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ut8 *encrypted = malloc(obj->data_len);
|
||||
if (!encrypted) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (rz_buf_read_at(buf, obj->data_start, encrypted, obj->data_len) != obj->data_len) {
|
||||
free(encrypted);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
const ut8 *key = obj->header.arc4_key;
|
||||
if (cart_key_is_null(key)) {
|
||||
key = CART_DEFAULT_RC4_KEY;
|
||||
}
|
||||
|
||||
ut8 *decrypted = malloc(obj->data_len);
|
||||
if (!decrypted) {
|
||||
free(encrypted);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rz_rc4_crypt(key, CART_ARC4_KEY_LENGTH, encrypted, decrypted, obj->data_len);
|
||||
free(encrypted);
|
||||
|
||||
int decompressed_len = 0;
|
||||
ut8 *decompressed = rz_inflate(decrypted, obj->data_len, NULL, &decompressed_len);
|
||||
free(decrypted);
|
||||
|
||||
if (!decompressed) {
|
||||
RZ_LOG_WARN("CaRT: Failed to decompress payload\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
*out_size = decompressed_len;
|
||||
return decompressed;
|
||||
}
|
||||
|
||||
RZ_API RZ_OWN RzBuffer *rz_bin_cart_extract_buf(RZ_NONNULL RzBuffer *buf, RZ_NONNULL CartObj *obj) {
|
||||
rz_return_val_if_fail(buf && obj, NULL);
|
||||
|
||||
int payload_size = 0;
|
||||
ut8 *payload = rz_bin_cart_extract(buf, obj, &payload_size);
|
||||
if (!payload) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RzBuffer *result = rz_buf_new_with_pointers(payload, payload_size, true);
|
||||
if (!result) {
|
||||
free(payload);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
66
librz/bin/format/cart/cart.h
Normal file
66
librz/bin/format/cart/cart.h
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// SPDX-FileCopyrightText: 2025 Ayush Dwivedi <ayushd785@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#ifndef RZ_BIN_CART_H
|
||||
#define RZ_BIN_CART_H
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <rz_util.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define CART_HEADER_MAGIC "CART"
|
||||
#define CART_FOOTER_MAGIC "TRAC"
|
||||
#define CART_HEADER_MAGIC_SIZE 4
|
||||
#define CART_FOOTER_MAGIC_SIZE 4
|
||||
|
||||
#define CART_VERSION 1
|
||||
|
||||
#define CART_HEADER_LENGTH 38
|
||||
#define CART_FOOTER_LENGTH 28
|
||||
#define CART_MINIMUM_LENGTH (CART_HEADER_LENGTH + CART_FOOTER_LENGTH)
|
||||
|
||||
#define CART_ARC4_KEY_LENGTH 16
|
||||
#define CART_BLOCK_SIZE (64 * 1024)
|
||||
|
||||
typedef struct cart_header_t {
|
||||
char magic[CART_HEADER_MAGIC_SIZE];
|
||||
ut16 version;
|
||||
ut64 reserved;
|
||||
ut8 arc4_key[CART_ARC4_KEY_LENGTH];
|
||||
ut64 opt_header_len;
|
||||
} CartHeader;
|
||||
|
||||
typedef struct cart_footer_t {
|
||||
char magic[CART_FOOTER_MAGIC_SIZE];
|
||||
ut64 reserved;
|
||||
ut64 opt_footer_pos;
|
||||
ut64 opt_footer_len;
|
||||
} CartFooter;
|
||||
|
||||
typedef struct cart_obj_t {
|
||||
CartHeader header;
|
||||
CartFooter footer;
|
||||
ut64 data_start;
|
||||
ut64 data_len;
|
||||
ut64 file_size;
|
||||
} CartObj;
|
||||
|
||||
/** Check if buffer contains valid CaRT magic (CART/TRAC) */
|
||||
RZ_API bool rz_bin_cart_check_buffer(RZ_NONNULL RzBuffer *buf);
|
||||
/** Parse CaRT header and footer from buffer */
|
||||
RZ_API RZ_OWN CartObj *rz_bin_cart_new_from_buffer(RZ_NONNULL RzBuffer *buf);
|
||||
/** Free CartObj */
|
||||
RZ_API void rz_bin_cart_free(RZ_NULLABLE CartObj *obj);
|
||||
/** Extract decrypted and decompressed payload */
|
||||
RZ_API RZ_OWN ut8 *rz_bin_cart_extract(RZ_NONNULL RzBuffer *buf, RZ_NONNULL CartObj *obj, RZ_NONNULL int *out_size);
|
||||
/** Extract payload as RzBuffer */
|
||||
RZ_API RZ_OWN RzBuffer *rz_bin_cart_extract_buf(RZ_NONNULL RzBuffer *buf, RZ_NONNULL CartObj *obj);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
|
@ -60,6 +60,7 @@ bin_plugins_list = [
|
|||
]
|
||||
|
||||
bin_xtr_plugins_list = [
|
||||
'cart',
|
||||
'fatmach0',
|
||||
'sep64',
|
||||
# this was never built.
|
||||
|
|
@ -157,6 +158,7 @@ rz_bin_sources = [
|
|||
'p/bin_wasm.c',
|
||||
'p/bin_xbe.c',
|
||||
'p/bin_xnu_kernelcache.c',
|
||||
'p/bin_xtr_cart.c',
|
||||
'p/bin_xtr_fatmach0.c',
|
||||
# this was never built.
|
||||
# 'p/bin_xtr_pemixed.c',
|
||||
|
|
@ -276,6 +278,7 @@ rz_bin_sources = [
|
|||
'format/pyc/marshal.c',
|
||||
'format/pyc/pyc.c',
|
||||
'format/pyc/pyc_magic.c',
|
||||
'format/cart/cart.c',
|
||||
'format/te/te.c',
|
||||
'format/wasm/wasm.c',
|
||||
|
||||
|
|
@ -328,6 +331,7 @@ rz_bin = library('rz_bin', rz_bin_sources,
|
|||
rz_search_dep,
|
||||
rz_cons_dep,
|
||||
rz_io_dep,
|
||||
rz_crypto_dep,
|
||||
rz_type_dep,
|
||||
libmspack_dep,
|
||||
],
|
||||
|
|
|
|||
110
librz/bin/p/bin_xtr_cart.c
Normal file
110
librz/bin/p/bin_xtr_cart.c
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// SPDX-FileCopyrightText: 2025 Ayush Dwivedi <ayushd785@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <rz_util.h>
|
||||
#include <rz_lib.h>
|
||||
#include <rz_bin.h>
|
||||
#include "../format/cart/cart.h"
|
||||
|
||||
static bool check_buffer(RzBuffer *buf) {
|
||||
rz_return_val_if_fail(buf, false);
|
||||
return rz_bin_cart_check_buffer(buf);
|
||||
}
|
||||
|
||||
static void free_xtr(void *xtr_obj) {
|
||||
rz_bin_cart_free((CartObj *)xtr_obj);
|
||||
}
|
||||
|
||||
static void destroy(RzBin *bin) {
|
||||
if (bin && bin->cur) {
|
||||
free_xtr(bin->cur->xtr_obj);
|
||||
bin->cur->xtr_obj = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static bool load(RzBin *bin) {
|
||||
rz_return_val_if_fail(bin && bin->cur, false);
|
||||
bin->cur->xtr_obj = rz_bin_cart_new_from_buffer(bin->cur->buf);
|
||||
return bin->cur->xtr_obj != NULL;
|
||||
}
|
||||
|
||||
static int size(RzBin *bin) {
|
||||
rz_return_val_if_fail(bin && bin->cur && bin->cur->xtr_obj, 0);
|
||||
CartObj *obj = (CartObj *)bin->cur->xtr_obj;
|
||||
return (int)obj->file_size;
|
||||
}
|
||||
|
||||
static RzBinXtrData *oneshot_buffer(RzBin *bin, RzBuffer *b, int idx) {
|
||||
rz_return_val_if_fail(bin && bin->cur && b, NULL);
|
||||
|
||||
if (idx != 0) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (!bin->cur->xtr_obj) {
|
||||
bin->cur->xtr_obj = rz_bin_cart_new_from_buffer(b);
|
||||
}
|
||||
if (!bin->cur->xtr_obj) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
CartObj *obj = (CartObj *)bin->cur->xtr_obj;
|
||||
RzBuffer *payload_buf = rz_bin_cart_extract_buf(b, obj);
|
||||
if (!payload_buf) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RzBinXtrMetadata *metadata = RZ_NEW0(RzBinXtrMetadata);
|
||||
if (!metadata) {
|
||||
rz_buf_free(payload_buf);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
metadata->xtr_type = "cart";
|
||||
metadata->libname = rz_str_dup("cart_payload");
|
||||
|
||||
ut64 payload_size = rz_buf_size(payload_buf);
|
||||
RzBinXtrData *res = rz_bin_xtrdata_new(payload_buf, 0, payload_size, 1, metadata);
|
||||
|
||||
rz_buf_free(payload_buf);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
static RzList /*<RzBinXtrData *>*/ *oneshotall_buffer(RzBin *bin, RzBuffer *b) {
|
||||
RzBinXtrData *data = oneshot_buffer(bin, b, 0);
|
||||
if (!data) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
RzList *res = rz_list_newf(rz_bin_xtrdata_free);
|
||||
if (!res) {
|
||||
rz_bin_xtrdata_free(data);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
rz_list_append(res, data);
|
||||
return res;
|
||||
}
|
||||
|
||||
RzBinXtrPlugin rz_bin_xtr_plugin_cart = {
|
||||
.name = "xtr.cart",
|
||||
.desc = "CaRT (Compressed and RC4 Transport) extractor",
|
||||
.license = "LGPL3",
|
||||
.check_buffer = check_buffer,
|
||||
.load = &load,
|
||||
.destroy = &destroy,
|
||||
.size = &size,
|
||||
.extract_from_buffer = &oneshot_buffer,
|
||||
.extractall_from_buffer = &oneshotall_buffer,
|
||||
.free_xtr = &free_xtr,
|
||||
};
|
||||
|
||||
#ifndef RZ_PLUGIN_INCORE
|
||||
RZ_API RzLibStruct rizin_plugin = {
|
||||
.type = RZ_LIB_TYPE_BIN_XTR,
|
||||
.data = &rz_bin_xtr_plugin_cart,
|
||||
.version = RZ_VERSION
|
||||
};
|
||||
#endif
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <rz_lib.h>
|
||||
#include <rz_crypto.h>
|
||||
#include <rz_crypto/rz_rc4.h>
|
||||
#include <rz_util.h>
|
||||
|
||||
struct rc4_state {
|
||||
|
|
@ -71,6 +72,21 @@ static void rc4_crypt(struct rc4_state *const state, const ut8 *inbuf, ut8 *outb
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* \brief One-shot RC4 encrypt/decrypt.
|
||||
*
|
||||
* Initializes the RC4 state with the given key and processes the input buffer.
|
||||
* Since RC4 is symmetric, the same function is used for both encryption and decryption.
|
||||
*/
|
||||
RZ_API void rz_rc4_crypt(RZ_NONNULL const ut8 *key, int keylen, RZ_NONNULL const ut8 *in, RZ_NONNULL ut8 *out, int len) {
|
||||
rz_return_if_fail(key && in && out);
|
||||
struct rc4_state state = { 0 };
|
||||
if (!rc4_init_state(&state, key, keylen)) {
|
||||
return;
|
||||
}
|
||||
rc4_crypt(&state, in, out, len);
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
|
||||
static bool rc4_set_key(RzCrypto *cry, const ut8 *key, int keylen, int mode, int direction) {
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ install_headers(rz_il_files, install_dir: join_paths(rizin_incdir, 'rz_il'))
|
|||
rz_crypto_files = [
|
||||
'rz_crypto/rz_des.h',
|
||||
'rz_crypto/rz_aes.h',
|
||||
'rz_crypto/rz_rc4.h',
|
||||
'rz_crypto/rz_sm4.h',
|
||||
]
|
||||
install_headers(rz_crypto_files, install_dir: join_paths(rizin_incdir, 'rz_crypto'))
|
||||
|
|
|
|||
19
librz/include/rz_crypto/rz_rc4.h
Normal file
19
librz/include/rz_crypto/rz_rc4.h
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
// SPDX-FileCopyrightText: 2025 Ayush Dwivedi <ayushd785@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#ifndef RZ_RC4_H
|
||||
#define RZ_RC4_H
|
||||
|
||||
#include <rz_types.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
RZ_API void rz_rc4_crypt(RZ_NONNULL const ut8 *key, int keylen, RZ_NONNULL const ut8 *in, RZ_NONNULL ut8 *out, int len);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // RZ_RC4_H
|
||||
File diff suppressed because one or more lines are too long
25
test/db/formats/cart
Normal file
25
test/db/formats/cart
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
NAME=CaRT: iI and extractor info
|
||||
FILE=bins/cart/sample.cart
|
||||
CMDS=<<EOF
|
||||
iI~binsz
|
||||
i~packet
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
binsz 0x00000039
|
||||
packet xtr.cart
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=CaRT: Extract payload with rz-bin -x
|
||||
FILE==
|
||||
CMDS=<<EOF
|
||||
!rm -rf sample.cart.fat
|
||||
!rz-bin -x bins/cart/sample.cart
|
||||
!cat sample.cart.fat/sample.cart.unknown.cart_payload_0.0
|
||||
!rm -rf sample.cart.fat
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
sample.cart.fat/sample.cart.unknown.cart_payload_0.0 created (57)
|
||||
This is a test payload for CaRT container format testing
|
||||
EOF
|
||||
RUN
|
||||
Loading…
Reference in a new issue