Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python 3.11 compatibility because of enum module #108

Closed
exploide opened this issue Nov 28, 2022 · 11 comments
Closed

Python 3.11 compatibility because of enum module #108

exploide opened this issue Nov 28, 2022 · 11 comments

Comments

@exploide
Copy link

At the moment, Certipy is not compatible with Python 3.11.

This is because it uses the undocumented _decompose function of the enum module, which apparently got removed in Python 3.11. When running under this version of Python, the following error is shown:

AttributeError: module 'enum' has no attribute '_decompose'

The problematic function calls are in certipy/lib/structs.py and certipy/lib/constants.py.

@ly4k
Copy link
Owner

ly4k commented Dec 27, 2022

Thank you for reporting. I will try to support 3.11 in a future release. I'll keep the issue open until then

@f3rn0s
Copy link
Contributor

f3rn0s commented Feb 8, 2023

I did some looking into this, turns out that IntFlag enums are now iterable, so:

list(ACTIVE_DIRECTORY_RIGHTS(65536 | 64)) # Returns [Delete, DeleteTree]

So in this case calls like this:

def to_list(self):
        cls = self.__class__
        members, _ = enum._decompose(cls, self._value_)
        filtered_members = []
        for member in members:
            found = False
            for n in members:
                if n & member and n != member:
                    found = True

            if not found:
                filtered_members.append(member)
        return members

We should just be able to do:

def to_list(self):
    return list(self._value_)

Obviously, legacy supporting python <= 3.11 is a bit of a hassle :/ maybe something like this is fine:

    def to_list(self):
        try:
            return list(self._value_)
        except TypeError:
            # Can't use the new python3.11 syntax, so we have to do it the old way
            pass

        cls = self.__class__
        members, _ = enum._decompose(cls, self._value_)
        filtered_members = []
        for member in members:
            found = False
            for n in members:
                if n & member and n != member:
                    found = True

            if not found:
                filtered_members.append(member)
        return members

@0xGreen
Copy link

0xGreen commented Feb 28, 2023

Hi,

Facing the same issue as well
AttributeError: module 'enum' has no attribute '_decompose'

Thanks!

@OfekShushan
Copy link

Hi!
A solution to this can be to add the code of the function _decompose back to the code!
On my machine, the path for the file was /usr/lib/python3.11/enum.py
The code itself:

def _decompose(flag, value):
    """
    Extract all members from the value.
    """
    # _decompose is only called if the value is not named
    not_covered = value
    negative = value < 0
    members = []
    for member in flag:
        member_value = member.value
        if member_value and member_value & value == member_value:
            members.append(member)
            not_covered &= ~member_value
    if not negative:
        tmp = not_covered
        while tmp:
            flag_value = 2 ** _high_bit(tmp)
            if flag_value in flag._value2member_map_:
                members.append(flag._value2member_map_[flag_value])
                not_covered &= ~flag_value
            tmp &= ~flag_value
    if not members and value in flag._value2member_map_:
        members.append(flag._value2member_map_[value])
    members.sort(key=lambda m: m._value_, reverse=True)
    if len(members) > 1 and members[0].value == value:
        # we have the breakdown, don't need the value member itself
        members.pop(0)
    return members, not_covered

@f3rn0s
Copy link
Contributor

f3rn0s commented Mar 18, 2023

@OfekShushan You shouldn't really be touching package owned source code. If you need to use python3.11 now most people have been having no issues with the version in my PR.

@7MinSec
Copy link

7MinSec commented Mar 19, 2023

Hey pardon the newb question, but in a Kali environment where Python3.11 comes as default, how do I get Certipy running as cleanly as possible while leaving 3.11 as the "dominant" installed version? I've tried going into a venv and compiling python3.10 and reinstalling Certipy with 3.10 but I still always get the decompose error at the end of Certipy running.

@f3rn0s
Copy link
Contributor

f3rn0s commented Mar 19, 2023

@7MinSec

Atm you can use the fork until ly4k reviews and merges changes:

git clone https://github.com/f3rn0s/Certipy
cd Certipy
pip install .

If you want a venv it would go something like

git clone https://github.com/f3rn0s/Certipy
cd Certipy
python -m venv venv
source venv/bin/activate
pip install .

@7MinSec
Copy link

7MinSec commented Mar 20, 2023

That worked thanks!

@digitalohm
Copy link

+1

@exploide
Copy link
Author

Fixed since the patch by @f3rn0s got merged. Closing.

@sagisar1
Copy link

sagisar1 commented Aug 3, 2023

Hi! A solution to this can be to add the code of the function _decompose back to the code! On my machine, the path for the file was /usr/lib/python3.11/enum.py The code itself:

def _decompose(flag, value):
    """
    Extract all members from the value.
    """
    # _decompose is only called if the value is not named
    not_covered = value
    negative = value < 0
    members = []
    for member in flag:
        member_value = member.value
        if member_value and member_value & value == member_value:
            members.append(member)
            not_covered &= ~member_value
    if not negative:
        tmp = not_covered
        while tmp:
            flag_value = 2 ** _high_bit(tmp)
            if flag_value in flag._value2member_map_:
                members.append(flag._value2member_map_[flag_value])
                not_covered &= ~flag_value
            tmp &= ~flag_value
    if not members and value in flag._value2member_map_:
        members.append(flag._value2member_map_[value])
    members.sort(key=lambda m: m._value_, reverse=True)
    if len(members) > 1 and members[0].value == value:
        # we have the breakdown, don't need the value member itself
        members.pop(0)
    return members, not_covered

that's great worked for me also

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

8 participants