6

I am trying to unpack android 11 image / get info from the raw .img for selinux info, symlinks etc.

I am using this wonderful tool: https://github.com/cubinator/ext4/blob/master/ext4.py35.py

and my code looks like this:

#!/usr/bin/env python3

import argparse
import sys
import os
import ext4

parser = argparse.ArgumentParser(description='Read <modes, symlinks, contexts and capabilities> from an ext4 image')
parser.add_argument('ext4_image', help='Path to ext4 image to process')
args = parser.parse_args()
exists = os.path.isfile(args.ext4_image)
if not exists:
    print("Error: input file " f"[{args.ext4_image}]" " was not found")
    sys.exit(1)

file = open(args.ext4_image, "rb")
volume = ext4.Volume(file)

def scan_dir (root_inode, root_path = ""):
    for entry_name, entry_inode_idx, entry_type in root_inode.open_dir():
        if entry_name == "." or entry_name == "..":
            continue
        entry_inode = root_inode.volume.get_inode(entry_inode_idx)
        entry_inode_path = root_path + "/" + entry_name
        if entry_inode.is_dir:
            scan_dir(entry_inode, entry_inode_path)
        if entry_inode_path[-1] == '/':
            continue
        xattrs_perms = list(entry_inode.xattrs())
        found_cap = False
        found_con = False
        if "security.capability" in f"{xattrs_perms}": found_cap = True
        if "security.selinux" in f"{xattrs_perms}": found_con = True
        contexts = ""
        capability = ", \"capabilities\", 0x0"
        if found_cap:
            if found_con:
                capability = f"{xattrs_perms[1:2]}"
            else:
                capability = f"{xattrs_perms[0:1]}"
            capability = capability.split(" ")[1][:-3][+2:].encode('utf-8').decode('unicode-escape').encode('ISO-8859-1')
            capability = hex(int.from_bytes(capability[4:8] + capability[14:18], "little"))
            capability = ", \"capabilities\", " f"{capability}"
            capability = f"{capability}"
        if found_con:
            contexts = f"{xattrs_perms[0:1]}"
            contexts = f"{contexts.split( )[1].split('x00')[0][:-1][+2:]}"
            contexts = f"{contexts}"
        filefolder = ''.join(entry_inode_path.split('/', 1))
        print("set_metadata(\""f"{filefolder}" "\", \"uid\", " f"{str(entry_inode.inode.i_uid)}" ", \"gid\", " f"{str(entry_inode.inode.i_gid)}"  ", \"mode\", " f"{entry_inode.inode.i_mode & 0x1FF:0>4o}" f"{capability}" ", \"selabel\", \"" f"{contexts}" "\");")

scan_dir(volume.root)
file.close()

then I just have to do ./read.py vendor.img and it works.

Untill recently I tried this weird vendor.img from android 11 and got this weird issue.

Traceback (most recent call last):
  File "./tools/metadata.py", line 53, in <module>
    scan_dir(volume.root)
  File "./tools/metadata.py", line 26, in scan_dir
    scan_dir(entry_inode, entry_inode_path)
  File "./tools/metadata.py", line 26, in scan_dir
    scan_dir(entry_inode, entry_inode_path)
  File "./tools/metadata.py", line 29, in scan_dir
    xattrs_perms = list(entry_inode.xattrs())
  File "/home/semaphore/unpacker/tools/ext4.py", line 976, in xattrs
    for xattr_name, xattr_value in self._parse_xattrs(inline_data[offset:], 0, prefix_override = prefix_override):
  File "/home/semaphore/unpacker/tools/ext4.py", line 724, in _parse_xattrs
    xattr_inode = self.volume.get_inode(xattr.e_value_inum, InodeType.FILE)
NameError: name 'xattr' is not defined

I have tried removing the if and keeping code after else only here: https://github.com/cubinator/ext4/blob/master/ext4.py35.py#L722

Sadly no luck. It looks like the tool is not finished? But there are no other alternatives.

Any help is welcome :)

Thank you.

EDIT: someone suggested replace xattr with xattr_entry So i did and i got this error: takes 2 positional arguments but 3 were given

I tried fixing that and got:

File "/home/semaphore/unpacker/tools/ext4.py", line 724, in _parse_xattrs
  xattr_inode = self.volume.get_inode(xattr_entry.e_value_inum)
File "/home/semaphore/unpacker/tools/ext4.py", line 595, in get_inode
  inode_table_offset = self.group_descriptors[group_idx].bg_inode_table * self.block_size
IndexError: list index out of range

And I could not fix this error :(

Maybe theres an alternative to getting selinux info, capabilities, uid, gid, permissions from raw ext4 image?

2 Answers2

3

I read that you had tried to fix the issue yourself but you never posted a snippet of the code you're currently using.

I am not sure but it seems to me you modified the signature of get_inode instead of modifying which parameters get passed to it.

E.g. did you try:

xattr_inode = self.volume.get_inode(xattr_entry.e_value_inum)
alexisdevarennes
  • 5,437
  • 4
  • 24
  • 38
  • Please don't post only code as answer, but also provide an explanation what your code does and how it solves the problem of the question. Answers with an explanation are usually more helpful and of better quality, and are more likely to attract upvotes. – Dima Kozhevin Jul 29 '20 at 10:33
  • @DimaKozhevin you're right ! I usually always do, updated :) – alexisdevarennes Jul 29 '20 at 10:37
  • actually yes i have tried that. i get index out of error. i posted that in the question before... – Margarita Weiß Jul 29 '20 at 13:25
  • Yes, I know but I wasn't sure how you fixed it, e.g. if you just added a private argument to the method or removed the second passed argument. – alexisdevarennes Jul 29 '20 at 13:30
  • @alexisdevarennes you mean the `takes 2 positional arguments but 3 were given` I removed `InodeType.FILE` here https://github.com/cubinator/ext4/blob/master/ext4.py35.py#L724 as recent commit by author was Removed reliance on the dirents`s file_type and he removed it there on ext4.py file but not ext4.py35.py so i guess a mistake.but i get out of range on both ext4.py and ext4.py35.py – Margarita Weiß Jul 29 '20 at 14:14
  • Will keep looking into it :) – alexisdevarennes Jul 29 '20 at 14:39
  • @alexisdevarennes if you want i can also send the image file (2gb) so you could try to see whats wrong. – Margarita Weiß Jul 30 '20 at 06:56
  • I actually think that'd make the most sense, let's start a chat :) – alexisdevarennes Jul 30 '20 at 10:16
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/218886/discussion-between-margarita-weiss-and-alexisdevarennes). – Margarita Weiß Jul 30 '20 at 11:20
1

I figured out how to do it in an alternative way.

First mount the image (needs root access):

os.system("sudo mount -t ext4 -o loop vendor.img vendor")

Then use: os.lstat and os.getxattr on each file. It gives all the information:

stat_info = os.lstat(file)
try:
    cap = hex(int.from_bytes(os.getxattr(file, "security.capability")[4:8] + os.getxattr(file, "security.capability")[14:18], "little"))
except:
    cap = "0x0"
try:
    selabel = os.getxattr(file, b"security.selinux", follow_symlinks=False).decode().strip('\n\0')
except:
    selabel = "u:object_r:unlabeled:s0"
metadata.append("set_metadata(\"/" + file + "\", \"uid\", " + str(stat_info.st_uid) + ", \"gid\", " + str(stat_info.st_gid) + ", \"mode\", " + oct(stat_info.st_mode)[-4:] + ", \"capabilities\", " + cap + ", \"selabel\", \"" + selabel + "\");")

Like so. This is the only solution I could find