import io
import logging
import struct
from abc import ABC
from typing import List, Optional
from credsweeper.common.constants import UTF_8
from credsweeper.credentials.candidate import Candidate
from credsweeper.deep_scanner.abstract_scanner import AbstractScanner
from credsweeper.file_handler.data_content_provider import DataContentProvider
from credsweeper.file_handler.struct_content_provider import StructContentProvider
logger = logging.getLogger(__name__)
[docs]
class JclassScanner(AbstractScanner, ABC):
"""Implements java .class scanning"""
[docs]
@staticmethod
def match(data: bytes | bytearray) -> bool:
"""According https://en.wikipedia.org/wiki/List_of_file_signatures - java class"""
if data.startswith(b"\xCA\xFE\xBA\xBE"):
return True
return False
[docs]
@staticmethod
def u2(stream: io.BytesIO) -> int:
"""Extracts unsigned 16 bit big-endian"""
return int(struct.unpack(">H", stream.read(2))[0])
[docs]
@staticmethod
def get_utf8_constants(stream: io.BytesIO) -> List[str]:
"""Extracts only Utf8 constants from java ClassFile"""
result = []
# actual number of items is one less!
items_counter = JclassScanner.u2(stream) - 1
while 0 < items_counter:
items_counter -= 1
# uint8
tag = int(stream.read(1)[0])
if 1 == tag:
# UTF-8 string in bytes may be bigger than in characters
length = JclassScanner.u2(stream)
data = stream.read(int(length))
value = data.decode(encoding=UTF_8, errors="replace")
result.append(value)
elif tag in (3, 4, 9, 10, 11, 12, 18):
_ = stream.read(4)
elif tag in (7, 8, 16):
_ = stream.read(2)
elif tag in (5, 6):
_ = stream.read(8)
# long and double types use two indexes
items_counter -= 1
elif 15 == tag:
_ = stream.read(3)
else:
logger.warning("Unknown tag %s", tag)
break
return result
[docs]
def data_scan(
self, #
data_provider: DataContentProvider, #
depth: int, #
recursive_limit_size: int) -> Optional[List[Candidate]]:
"""Extracts data from binary"""
try:
stream = io.BytesIO(data_provider.data)
stream.read(4) # magic
minor = JclassScanner.u2(stream)
major = JclassScanner.u2(stream)
constants = JclassScanner.get_utf8_constants(stream)
struct_content_provider = StructContentProvider(struct=constants,
file_path=data_provider.file_path,
file_type=data_provider.file_type,
info=f"{data_provider.info}|Java.{major}.{minor}")
new_limit = recursive_limit_size - sum(len(x) for x in constants)
candidates = self.structure_scan(struct_content_provider, depth, new_limit)
return candidates
except Exception as jclass_exc:
logger.warning("%s:%s", data_provider.file_path, jclass_exc)
return None