Fix undeclared next_section_virtual_address usage#429
Fix undeclared next_section_virtual_address usage#429nightlark merged 2 commits intoerocarrera:masterfrom
Conversation
|
Another version of aksdf.sys and aksfridge.sys are also causing this issue. |
|
@erocarrera, I was wondering if you had an opinion on this matter, or if you would need me to add a test for it? |
|
My initial take looking at this is it shouldn't break things. @gdesmar I don't think I have a VT subscription that allows downloading the linked samples. Is there another way you can send a copy of them to be included as test cases? |
|
Here is a password protected zip file containing this specific one (as it was the smallest of the ones I linked). |
| self.sections.sort(key=lambda a: a.VirtualAddress) | ||
| for idx, section in enumerate(self.sections): | ||
| if idx == len(self.sections) - 1: | ||
| section.next_section_virtual_address = None | ||
| else: | ||
| section.next_section_virtual_address = self.sections[ | ||
| idx + 1 | ||
| ].VirtualAddress |
There was a problem hiding this comment.
Not a change for this PR, but was looking at this block and thinking it could perhaps be rewritten in a way that makes it more readable.
for section, next_section in zip(self.sections, self.sections[1:]):
section.next_section_virtual_address = next_section.VirtualAddress
if self.sections:
self.sections[-1].next_section_virtual_address = Nonefor index, section in enumerate(self.sections):
is_last = index == len(self.sections) - 1
section.next_section_virtual_address = (
None if is_last else self.sections[index + 1].VirtualAddress
)
pefile.py
Outdated
| for section in self.sections: | ||
| if section.__dict__.get( | ||
| "IMAGE_SCN_MEM_WRITE", False | ||
| ) and section.__dict__.get("IMAGE_SCN_MEM_EXECUTE", False): | ||
| if section.Name.rstrip(b"\x00") == b"PAGE" and self.is_driver(): | ||
| # Drivers can have a PAGE section with those flags set without | ||
| # implying that it is malicious | ||
| pass | ||
| else: | ||
| self.__warnings.append( | ||
| f"Suspicious flags set for section {i}. " |
There was a problem hiding this comment.
It looks like i is stuck at self.FILE_HEADER.NumberOfSections. My initial impression is that updating the for loop to use enumerate would give an index that could be used in the warning message, and could be enough to address the issue for this PR.
However, I realize that self.sections gets sorted after that initial loop... which I think raises some questions as to what index the warning/error messages should be referring to. If it says there's an error in section 3 but then sorting moves what was at index 3 in the file to index 5 in the sorted list, inspecting sections with pefile would result in looking at the wrong section for the cause of the warning.
Essentially, which is better for users: error messages referring to the section number as it appears in the file, or after sorting by VirtualAddress?
There was a problem hiding this comment.
The solution I'm leaning towards for the {i} numbering issue is that when the SectionStructure gets created in the first loop (start of this function), a field in the newly created section object (index_in_file?) could get set to i as a way to help users to find the section in the sorted list that corresponds to the warning messages they get.
And then in this warning message {section.index_in_file} can be used. Open to alternatives though if you've got ideas for how to handle the shifting index/section number issue.
There was a problem hiding this comment.
Great catch on the {i}. I completed the rebase and added the small fix/improvement.
Looking forward to any other correction to do!
nightlark
left a comment
There was a problem hiding this comment.
Aside from the issue with the section number in the warning message, can you also rebase the PR on the latest master branch?
pefile.py
Outdated
| for section in self.sections: | ||
| if section.__dict__.get( | ||
| "IMAGE_SCN_MEM_WRITE", False | ||
| ) and section.__dict__.get("IMAGE_SCN_MEM_EXECUTE", False): | ||
| if section.Name.rstrip(b"\x00") == b"PAGE" and self.is_driver(): | ||
| # Drivers can have a PAGE section with those flags set without | ||
| # implying that it is malicious | ||
| pass | ||
| else: | ||
| self.__warnings.append( | ||
| f"Suspicious flags set for section {i}. " |
There was a problem hiding this comment.
The solution I'm leaning towards for the {i} numbering issue is that when the SectionStructure gets created in the first loop (start of this function), a field in the newly created section object (index_in_file?) could get set to i as a way to help users to find the section in the sorted list that corresponds to the warning messages they get.
And then in this warning message {section.index_in_file} can be used. Open to alternatives though if you've got ideas for how to handle the shifting index/section number issue.
b086f2e to
9366cec
Compare
| idx + 1 | ||
| ].VirtualAddress | ||
|
|
||
| for section in self.sections: |
There was a problem hiding this comment.
We may be able to use getattr(object, name[, default]) here and throughout the file?
getattr(section, "IMAGE_SCN_MEM_WRITE", False) and getattr(section, "IMAGE_SCN_MEM_EXECUTE", False)
Better outside of this PR as is not just in this function.
| "This might indicate a packed executable." | ||
| ) | ||
|
|
||
| # Set the section's original index as it may change after being sorted by VirtualAddress |
There was a problem hiding this comment.
From the Microsoft official documentation:
The number of entries in the section table is given by the NumberOfSections field in the file header. Entries in the section table are numbered starting from one (1).
Due to us not doing, this is fine for this PR. After this PR could look into whether we need to change to one indexing or just add a comment to explain we use zero indexing. Check what PE-bear or similar does?
There was a problem hiding this comment.
A comment saying we use zero indexing would be good. The output has been zero indexed for long enough that I don't think we want to change it at this time (it would also be a change that would be good to get thoughts from @erocarrera)
|
I updated the regression tests with the reordered output lines due to the check being moved to after sorting has occurred instead of at the same time as the other checks for a given section. It would be nice if they were still grouped together by section, but I don't think there is a clean way of doing that without introducing complexity with a temporary data structure to store warnings for each section before adding them to the _warnings list. |
The following two files are making pefile crash with the next stacktrace:
aksdf.sys
hardlock.sys
From my understanding, the
parse_sectionsfunction may end up callingself.is_driver()as it's parsing the sections, but before theparse_sectionsfunction goes over the newly parsed sections to configure thenext_section_virtual_addressvariable. Sinceis_driver()ends up relying on it, it is crashing.I believe the code that needs the
is_driver()call is only adding warnings, so it would not cause any functional change to put it after the code assigning the value tonext_section_virtual_address.