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

readpe crashed when I use -e #166

Closed
i0gan opened this issue May 17, 2021 · 6 comments
Closed

readpe crashed when I use -e #166

i0gan opened this issue May 17, 2021 · 6 comments
Milestone

Comments

@i0gan
Copy link

i0gan commented May 17, 2021

readpe crashed when I use -e

Describe the bug*
In readpe newst version, Analysing abnormal test.exe will crash this program, And the version 0.70 can modify the RIP regester.

The test.exe download: https://github.com/I0gan/files/raw/main/pev/test.exe

version 0.70 test:

[i0gan@arch build]$ /bin/readpe -e ./test.exe 

Exported functions
Segmentation fault (core dumped)

I use gdb to debugger this program,the rip regester can be set as 0

pwndbg> set args -e test.exe
pwndbg> start
Temporary breakpoint 1 at 0x403966
pwndbg> [Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".

Temporary breakpoint 1, 0x0000000000403966 in main ()
c
Continuing.
Exported functions

Program received signal SIGSEGV, Segmentation fault.
0x0000000000000000 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
────────────────────────────────────────[ REGISTERS ]─────────────────────────────────────────
*RAX  0x0
 RBX  0x403dd0 (__libc_csu_init) ◂— mov    qword ptr [rsp - 0x28], rbp
*RCX  0x0
*RDX  0x5
*RDI  0x7fffffffde60 —▸ 0x403dd0 (__libc_csu_init) ◂— mov    qword ptr [rsp - 0x28], rbp
*RSI  0xa00
*R8   0x608480 ◂— 0x608
*R9   0x7ffff7f0c0c0 (step3a_jumps) ◂— 0x0
*R10  0xfffffffffffffb87
*R11  0x206
 R12  0x401070 (_start) ◂— xor    ebp, ebp
 R13  0x0
 R14  0x0
 R15  0x0
*RBP  0x0
*RSP  0x7fffffffde40 ◂— 0x0
*RIP  0x0
──────────────────────────────────────────[ DISASM ]──────────────────────────────────────────
Invalid address 0x0

In newst verion:

test

[i0gan@arch build]$ ./readpe -e ./test.exe 
Exported functions
    Library
        Name:                            MZ�ִ�Ǿ
        Functions
Segmentation fault (core dumped)
@jweyrich
Copy link
Contributor

I haven't checked the code, but I suspect this is related to merces/libpe@5737a97 and merces/libpe#34.

@saullocarvalho
Copy link
Contributor

I suspect this segmentation fault is happening because:

  1. calloc is called using a huge value (exp->NumberOfFunctions) as first argument at pe_exports function @ libpe which causes exports->functions being set to NULL;
─────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────
 ► 0x7ffff7f83b62 <pe_exports+386>    call   calloc@plt <calloc@plt>
        nmemb: 0x69703afb
        size: 0x20
 
   0x7ffff7f83b67 <pe_exports+391>    mov    qword ptr [r13 + 0x18], rax
   0x7ffff7f83b6b <pe_exports+395>    test   rax, rax
   0x7ffff7f83b6e <pe_exports+398>    je     pe_exports+1133 <pe_exports+1133>

https://github.com/merces/libpe/blob/5f44724e8fcdebf8a6b9fd009543c9dcfae4ea32/exports.c#L93-L98

Then, according to the above snippet, exports->err is set to LIBPE_E_ALLOCATION_FAILURE (the error is catched) and the function pe_exports returns exports.

  1. The main (actually print_exports) function does not handle the catched error from pe_exports and tries to dereference an invalid pointer, read the dword at 0x18 address.
────────────────────────────────────────────[ REGISTERS ]────────────────────────────────────────────
 RAX  0x69703afb
 RBX  0x0
 RCX  0x55555555f360 —▸ 0x555555560470 ◂— 0x0
 RDX  0x69703afb
 RDI  0x55555555f010 ◂— 0x1
 RSI  0x4
 R8   0x0
 R9   0x12
 R10  0x7ffff7fc1005 ◂— 0x3a7325732a25000a /* '\n' */
 R11  0x246
 R12  0x7fffffffdbd0 —▸ 0x55555555f310 ◂— '/usr/local/lib/pev/plugins'
 R13  0x555555560980 ◂— 0xffffffe9
 R14  0x7fffffffdcd0 ◂— 0x0
 R15  0x0
 RBP  0x55555555f410 ◂— 0x1000000000000
 RSP  0x7fffffffdba0 —▸ 0x7ffff7949460 (__fork_generation) ◂— 0x0
*RIP  0x555555558410 (main+7168) ◂— cmp    dword ptr [rbx + 0x18], 0
─────────────────────────────────────────────[ DISASM ]──────────────────────────────────────────────
   0x55555555840c <main+7164>    add    rbx, qword ptr [r13 + 0x18]
 ► 0x555555558410 <main+7168>    cmp    dword ptr [rbx + 0x18], 0
   0x555555558414 <main+7172>    je     main+7142 <main+7142>

https://github.com/merces/pev/blob/072085d5e656915f3e6c3107189233b4052a988b/src/readpe.c#L792-L794

The segmentation fault occurs when readpe tries to get the func->address value. As func is NULL, the code tries to dereference an invalid pointer (0x18).

@saullocarvalho
Copy link
Contributor

saullocarvalho commented Oct 16, 2021

How should the error be handled?
As print_export does not return any value, how the main fuction would be aware of an error in it?

Should errors in the print_* functions be ignored, they only return to main, or cause main to return EXIT_FAILURE as occurs at the following snippet?

https://github.com/merces/pev/blob/072085d5e656915f3e6c3107189233b4052a988b/src/readpe.c#L878-L891

@merces
Copy link
Collaborator

merces commented Nov 3, 2021

Hi @saullocarvalho and @i0gan . Thanks for raising this issue. I believe we should print a warn to the user. This could be done in the print_export function itself. Does it sound coherent?

Thanks.

@merces
Copy link
Collaborator

merces commented Nov 3, 2021

Another thing came to mind here.. we can also set a limit for, let's say 65535 exported functions. I can't imagine a valid PE with such a huge number of functions to be honest.

@GoGoOtaku
Copy link
Collaborator

The issue here is in libpe.
This executable has no sections (as everything is overlapping inside the header. It's more art than software; this exe does not run in wine btw).
The RVA for the export table is 0x55555555 which would be well outside the image. The issue is in pe_rva2ofs

        if (rva == 0 || ctx->pe.sections == NULL)
                return 0;

[...]
        // This is impossible to reach
        return rva; // PE with no sections, return RVA

pe.sections is always NULL when there are no sections as this is the code that allocates it:

        if (ctx->pe.num_sections > 0) {
                ctx->pe.sections = malloc(ctx->pe.num_sections
                        * sizeof(IMAGE_SECTION_HEADER *));

This causes pe_rva2ofs to always return 0 which is then used to check if the data directory can be reached which it obviously always can when the VA starts at 0 as well.

This doesn't really happen outside of artificial executables like this since virtually every executable will at least have a .text or similar section so code can be run.
Remember how I said that Wine doesn't run this? While I don't want to debug this exe in wine deeper, the exe (not wine) segfaulted instantly. My assumption is that the wine loader refuses to run code in the header "section"; as it frankly should and I would be shocked if a modern Windows would run this. Especially since the header is also used like .bss/.data which just begs for arbitrary code execution bugs.
I will let pe_rva2ofs return the RVA directly if pe.sections or pe.num_sections is zero, do some testing on it and submit a merge request within a couple of days. It should at least not segfault.

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

5 participants