1
0
mirror of https://github.com/FFmpeg/FFmpeg.git synced 2024-12-23 12:43:46 +02:00

avformat/hca: add support for decryption

This commit is contained in:
Paul B Mahol 2023-09-21 23:20:20 +02:00
parent 63371cde9d
commit 035d187c4d
2 changed files with 159 additions and 12 deletions

View File

@ -28,6 +28,7 @@
#include "get_bits.h" #include "get_bits.h"
#include "hca_data.h" #include "hca_data.h"
#define HCA_MASK 0x7f7f7f7f
#define MAX_CHANNELS 16 #define MAX_CHANNELS 16
typedef struct ChannelContext { typedef struct ChannelContext {
@ -50,8 +51,12 @@ typedef struct HCAContext {
ChannelContext ch[MAX_CHANNELS]; ChannelContext ch[MAX_CHANNELS];
uint8_t ath[128]; uint8_t ath[128];
uint8_t cipher[256];
uint64_t key;
uint16_t subkey;
int ath_type; int ath_type;
int ciph_type;
unsigned hfr_group_count; unsigned hfr_group_count;
uint8_t track_count; uint8_t track_count;
uint8_t channel_config; uint8_t channel_config;
@ -65,6 +70,93 @@ typedef struct HCAContext {
AVFloatDSPContext *fdsp; AVFloatDSPContext *fdsp;
} HCAContext; } HCAContext;
static void cipher_init56_create_table(uint8_t *r, uint8_t key)
{
const int mul = ((key & 1) << 3) | 5;
const int add = (key & 0xE) | 1;
key >>= 4;
for (int i = 0; i < 16; i++) {
key = (key * mul + add) & 0xF;
r[i] = key;
}
}
static void cipher_init56(uint8_t *cipher, uint64_t keycode)
{
uint8_t base[256], base_r[16], base_c[16], kc[8], seed[16];
/* 56bit keycode encryption (given as a uint64_t number, but upper 8b aren't used) */
/* keycode = keycode - 1 */
if (keycode != 0)
keycode--;
/* init keycode table */
for (int r = 0; r < (8-1); r++) {
kc[r] = keycode & 0xFF;
keycode = keycode >> 8;
}
/* init seed table */
seed[ 0] = kc[1];
seed[ 1] = kc[1] ^ kc[6];
seed[ 2] = kc[2] ^ kc[3];
seed[ 3] = kc[2];
seed[ 4] = kc[2] ^ kc[1];
seed[ 5] = kc[3] ^ kc[4];
seed[ 6] = kc[3];
seed[ 7] = kc[3] ^ kc[2];
seed[ 8] = kc[4] ^ kc[5];
seed[ 9] = kc[4];
seed[10] = kc[4] ^ kc[3];
seed[11] = kc[5] ^ kc[6];
seed[12] = kc[5];
seed[13] = kc[5] ^ kc[4];
seed[14] = kc[6] ^ kc[1];
seed[15] = kc[6];
/* init base table */
cipher_init56_create_table(base_r, kc[0]);
for (int r = 0; r < 16; r++) {
uint8_t nb;
cipher_init56_create_table(base_c, seed[r]);
nb = base_r[r] << 4;
for (int c = 0; c < 16; c++)
base[r*16 + c] = nb | base_c[c]; /* combine nibbles */
}
/* final shuffle table */
{
unsigned x = 0;
unsigned pos = 1;
for (int i = 0; i < 256; i++) {
x = (x + 17) & 0xFF;
if (base[x] != 0 && base[x] != 0xFF)
cipher[pos++] = base[x];
}
cipher[0] = 0;
cipher[0xFF] = 0xFF;
}
}
static void cipher_init(uint8_t *cipher, int type, uint64_t keycode, uint16_t subkey)
{
switch (type) {
case 56:
if (keycode) {
if (subkey)
keycode = keycode * (((uint64_t)subkey<<16u)|((uint16_t)~subkey+2u));
cipher_init56(cipher, keycode);
}
break;
case 0:
for (int i = 0; i < 256; i++)
cipher[i] = i;
break;
}
}
static void ath_init1(uint8_t *ath, int sample_rate) static void ath_init1(uint8_t *ath, int sample_rate)
{ {
unsigned int index; unsigned int index;
@ -124,13 +216,13 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
c->ath_type = version >= 0x200 ? 0 : 1; c->ath_type = version >= 0x200 ? 0 : 1;
if (bytestream2_get_be32u(gb) != MKBETAG('f', 'm', 't', 0)) if ((bytestream2_get_be32u(gb) & HCA_MASK) != MKBETAG('f', 'm', 't', 0))
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
bytestream2_skipu(gb, 4); bytestream2_skipu(gb, 4);
bytestream2_skipu(gb, 4); bytestream2_skipu(gb, 4);
bytestream2_skipu(gb, 4); bytestream2_skipu(gb, 4);
chunk = bytestream2_get_be32u(gb); chunk = bytestream2_get_be32u(gb) & HCA_MASK;
if (chunk == MKBETAG('c', 'o', 'm', 'p')) { if (chunk == MKBETAG('c', 'o', 'm', 'p')) {
bytestream2_skipu(gb, 2); bytestream2_skipu(gb, 2);
bytestream2_skipu(gb, 1); bytestream2_skipu(gb, 1);
@ -160,7 +252,7 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
while (bytestream2_get_bytes_left(gb) >= 4) { while (bytestream2_get_bytes_left(gb) >= 4) {
chunk = bytestream2_get_be32u(gb); chunk = bytestream2_get_be32u(gb) & HCA_MASK;
if (chunk == MKBETAG('v', 'b', 'r', 0)) { if (chunk == MKBETAG('v', 'b', 'r', 0)) {
bytestream2_skip(gb, 2 + 2); bytestream2_skip(gb, 2 + 2);
} else if (chunk == MKBETAG('a', 't', 'h', 0)) { } else if (chunk == MKBETAG('a', 't', 'h', 0)) {
@ -170,7 +262,7 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
} else if (chunk == MKBETAG('c', 'o', 'm', 'm')) { } else if (chunk == MKBETAG('c', 'o', 'm', 'm')) {
bytestream2_skip(gb, bytestream2_get_byte(gb) * 8); bytestream2_skip(gb, bytestream2_get_byte(gb) * 8);
} else if (chunk == MKBETAG('c', 'i', 'p', 'h')) { } else if (chunk == MKBETAG('c', 'i', 'p', 'h')) {
bytestream2_skip(gb, 2); c->ciph_type = bytestream2_get_be16(gb);
} else if (chunk == MKBETAG('l', 'o', 'o', 'p')) { } else if (chunk == MKBETAG('l', 'o', 'o', 'p')) {
bytestream2_skip(gb, 4 + 4 + 2 + 2); bytestream2_skip(gb, 4 + 4 + 2 + 2);
} else if (chunk == MKBETAG('p', 'a', 'd', 0)) { } else if (chunk == MKBETAG('p', 'a', 'd', 0)) {
@ -180,6 +272,14 @@ static int init_hca(AVCodecContext *avctx, const uint8_t *extradata,
} }
} }
if (bytestream2_get_bytes_left(gb) >= 10) {
bytestream2_skip(gb, bytestream2_get_bytes_left(gb) - 10);
c->key = bytestream2_get_be64u(gb);
c->subkey = bytestream2_get_be16u(gb);
}
cipher_init(c->cipher, c->ciph_type, c->key, c->subkey);
ret = ath_init(c->ath, c->ath_type, avctx->sample_rate); ret = ath_init(c->ath, c->ath_type, avctx->sample_rate);
if (ret < 0) if (ret < 0)
return ret; return ret;
@ -420,7 +520,7 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
if (AV_RN16(avpkt->data) != 0xFFFF) { if (AV_RN16(avpkt->data) != 0xFFFF) {
if (AV_RL32(avpkt->data) != MKTAG('H','C','A',0)) { if ((AV_RL32(avpkt->data)) != MKTAG('H','C','A',0)) {
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
} else if (AV_RB16(avpkt->data + 6) <= avpkt->size) { } else if (AV_RB16(avpkt->data + 6) <= avpkt->size) {
ret = init_hca(avctx, avpkt->data, AV_RB16(avpkt->data + 6)); ret = init_hca(avctx, avpkt->data, AV_RB16(avpkt->data + 6));
@ -434,6 +534,16 @@ static int decode_frame(AVCodecContext *avctx, AVFrame *frame,
} }
} }
if (c->key || c->subkey) {
uint8_t *data, *cipher = c->cipher;
if ((ret = av_packet_make_writable(avpkt)) < 0)
return ret;
data = avpkt->data;
for (int n = 0; n < avpkt->size; n++)
data[n] = cipher[data[n]];
}
if (avctx->err_recognition & AV_EF_CRCCHECK) { if (avctx->err_recognition & AV_EF_CRCCHECK) {
if (av_crc(c->crc_table, 0, avpkt->data + offset, avpkt->size - offset)) if (av_crc(c->crc_table, 0, avpkt->data + offset, avpkt->size - offset))
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;

View File

@ -19,18 +19,28 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/ */
#include "libavutil/opt.h"
#include "libavutil/intreadwrite.h" #include "libavutil/intreadwrite.h"
#include "libavcodec/bytestream.h" #include "libavcodec/bytestream.h"
#include "avformat.h" #include "avformat.h"
#include "internal.h" #include "internal.h"
#define HCA_MASK 0x7f7f7f7f
typedef struct HCADemuxContext {
AVClass *class;
int64_t keyl;
int64_t keyh;
int subkey;
} HCADemuxContext;
static int hca_probe(const AVProbeData *p) static int hca_probe(const AVProbeData *p)
{ {
if (AV_RL32(p->buf) != MKTAG('H', 'C', 'A', 0)) if ((AV_RL32(p->buf) & HCA_MASK) != MKTAG('H', 'C', 'A', 0))
return 0; return 0;
if (AV_RL32(p->buf + 8) != MKTAG('f', 'm', 't', 0)) if ((AV_RL32(p->buf + 8) & HCA_MASK) != MKTAG('f', 'm', 't', 0))
return 0; return 0;
return AVPROBE_SCORE_MAX / 3; return AVPROBE_SCORE_MAX / 3;
@ -38,6 +48,7 @@ static int hca_probe(const AVProbeData *p)
static int hca_read_header(AVFormatContext *s) static int hca_read_header(AVFormatContext *s)
{ {
HCADemuxContext *hca = s->priv_data;
AVCodecParameters *par; AVCodecParameters *par;
GetByteContext gb; GetByteContext gb;
AVIOContext *pb = s->pb; AVIOContext *pb = s->pb;
@ -60,20 +71,23 @@ static int hca_read_header(AVFormatContext *s)
return AVERROR(ENOMEM); return AVERROR(ENOMEM);
par = st->codecpar; par = st->codecpar;
ret = ff_alloc_extradata(par, data_offset); ret = ff_alloc_extradata(par, data_offset + 10);
if (ret < 0) if (ret < 0)
return ret; return ret;
ret = avio_read(pb, par->extradata + 8, par->extradata_size - 8); ret = avio_read(pb, par->extradata + 8, par->extradata_size - 8 - 10);
if (ret < par->extradata_size - 8) if (ret < par->extradata_size - 8 - 10)
return AVERROR(EIO); return AVERROR(EIO);
AV_WL32(par->extradata, MKTAG('H', 'C', 'A', 0)); AV_WL32(par->extradata, MKTAG('H', 'C', 'A', 0));
AV_WB16(par->extradata + 4, version); AV_WB16(par->extradata + 4, version);
AV_WB16(par->extradata + 6, data_offset); AV_WB16(par->extradata + 6, data_offset);
AV_WB32(par->extradata + par->extradata_size - 10, hca->keyh);
AV_WB32(par->extradata + par->extradata_size - 6, hca->keyl);
AV_WB16(par->extradata + par->extradata_size - 2, hca->subkey);
bytestream2_init(&gb, par->extradata + 8, par->extradata_size - 8); bytestream2_init(&gb, par->extradata + 8, par->extradata_size - 8);
if (bytestream2_get_le32(&gb) != MKTAG('f', 'm', 't', 0)) if ((bytestream2_get_le32(&gb) & HCA_MASK) != MKTAG('f', 'm', 't', 0))
return AVERROR_INVALIDDATA; return AVERROR_INVALIDDATA;
par->codec_type = AVMEDIA_TYPE_AUDIO; par->codec_type = AVMEDIA_TYPE_AUDIO;
@ -83,7 +97,7 @@ static int hca_read_header(AVFormatContext *s)
par->sample_rate = bytestream2_get_be24(&gb); par->sample_rate = bytestream2_get_be24(&gb);
block_count = bytestream2_get_be32(&gb); block_count = bytestream2_get_be32(&gb);
bytestream2_skip(&gb, 4); bytestream2_skip(&gb, 4);
chunk = bytestream2_get_le32(&gb); chunk = bytestream2_get_le32(&gb) & HCA_MASK;
if (chunk == MKTAG('c', 'o', 'm', 'p')) { if (chunk == MKTAG('c', 'o', 'm', 'p')) {
block_size = bytestream2_get_be16(&gb); block_size = bytestream2_get_be16(&gb);
} else if (chunk == MKTAG('d', 'e', 'c', 0)) { } else if (chunk == MKTAG('d', 'e', 'c', 0)) {
@ -113,9 +127,32 @@ static int hca_read_packet(AVFormatContext *s, AVPacket *pkt)
return ret; return ret;
} }
#define OFFSET(x) offsetof(HCADemuxContext, x)
static const AVOption hca_options[] = {
{ "hca_lowkey",
"Low key used for handling CRI HCA files", OFFSET(keyl),
AV_OPT_TYPE_INT64, {.i64=0}, .min = 0, .max = UINT32_MAX, .flags = AV_OPT_FLAG_DECODING_PARAM, },
{ "hca_highkey",
"High key used for handling CRI HCA files", OFFSET(keyh),
AV_OPT_TYPE_INT64, {.i64=0}, .min = 0, .max = UINT32_MAX, .flags = AV_OPT_FLAG_DECODING_PARAM, },
{ "hca_subkey",
"Subkey used for handling CRI HCA files", OFFSET(subkey),
AV_OPT_TYPE_INT, {.i64=0}, .min = 0, .max = UINT16_MAX, .flags = AV_OPT_FLAG_DECODING_PARAM },
{ NULL },
};
static const AVClass hca_class = {
.class_name = "hca",
.item_name = av_default_item_name,
.option = hca_options,
.version = LIBAVUTIL_VERSION_INT,
};
const AVInputFormat ff_hca_demuxer = { const AVInputFormat ff_hca_demuxer = {
.name = "hca", .name = "hca",
.long_name = NULL_IF_CONFIG_SMALL("CRI HCA"), .long_name = NULL_IF_CONFIG_SMALL("CRI HCA"),
.priv_class = &hca_class,
.priv_data_size = sizeof(HCADemuxContext),
.read_probe = hca_probe, .read_probe = hca_probe,
.read_header = hca_read_header, .read_header = hca_read_header,
.read_packet = hca_read_packet, .read_packet = hca_read_packet,