How to calculate PowerPC specific analyzer options?

hi

Im analyzing a PlayStation 3 elf (decrypted EBOOT.BIN) file. because the TOC address is not set, strings are not referenced correctly.
i know what string should be accessed in a certain function, so i can test if a calculated TOC address is correct.
whatever i do, i cant seem to find the correct value.
this is the main function:

.globl ._start
._start:

.set back_chain, -0x70

li        r2, dword_16C0000@l
oris      r2, r2, dword_16C0000@h
ori       r2, r2, 0xA2D0
lwz       r2, (off_16CA2D4 - 0x16CA2D0)(r2)
stdu      r1, back_chain(r1)
li        r14, 0
std       r14, 0x70+back_chain(r1)
bl        ._initialize

also, in the elf file, in the “section headers” there is a section named “.toc1” with “sh_addr” field set to 0x16a2178

how can i calculate TOC using this info? how can i find SDA and MMIO addresses as well?

update:
I seem to have found the TOC address. my target string is a char** (ptr to [TOC - 0x123] to char*) now, in the decompilation window:
myFunc(a1, s: off_123456); // "__strBlah" // off_123456 is actually [TOC - 0x123]

i should not change the myFunc’s type. tries several things but couldnt make the decompilation window to show actual string literal, like:
myFunc(a1, "__strBlah");

Hi,

On the TOC address - IDA actually tries to resolve the TOC automatically for PS3 ELF files. If IDA manages to resolve it, it will be visible in Options → General → Processor specific analysis options.

If you’re not already on the latest IDA version (9.4), could you try it first? If it’s still not picking it up correctly, please open a support ticket and attach your sample file so we can take a closer look.

On the second issue (string literal not showing up): as a first thing worth checking, could you try changing the type of off_123456 to char* const?

I tried it with IDA 9.3 and 9.4. after analysis is finished, all fields in Options > General > Processor specific analysis options are -1 aka 0xfff…
im new in unix and ppc systems, could it be that the pe stub is damaged?
i calculated the TOC from _start function, it worked but not all xrefs get solved. i’m guessing due to 64kb limit of ppc PIC, the r2 register gets updated somewhere, i don’t know how to pinpoint that.

lets say if i do so, i’m supposed to do:
Edit > Segments > Change segment register value
for all functions? or IDA will fix everything onwards?

as for string literal, char* const solved the issue! thanks. i already tried many types and hoped the constness is ignored when i unchecked the “Print only constant string literals” in decompiler options.

update:
Sent the file to help center in SUPPORT-9734

It seems Hex-Rays will want to fix the automatic TOC detection if it’s a fixable thing but I hope this helps in the short term.

Speculating: I know 0xffffffff /0xffffffffffffffff is the value for BADADDR. If one tried to lookup a section by name in an ELF file where the section headers are stripped, that is the value I would expect it to return. Perhaps the IDA logic depends upon named .got, .got2, .opd or .toc section headers for TOC detection.

Disclosures: I haven’t worked directly with PS3 ELFs, but I do have experience with PowerPC, MIPS, and ELF binaries in general. I also read through a few PS3 reversing documents before replying, so apologies if I’ve misunderstood any PS3-specific details. Also, an LLM rewrote what I provided it.

Trying to be helpful:

MIPS has a similar concept called the Global Pointer (GP), and in IDA, changing its value manually after auto-analysis causes references to be re-evaluated automatically if I remember correctly. I don’t know whether the PS3 plugin behaves identically, but I would expect it to. If not, forcing a reanalysis would be worth trying.

If you’re new to Linux or FreeBSD, you may also be new to ELF binaries. A couple of readelf commands may be useful. You can install readelf easily on Linux, on Windows it might be easier to pip install pyelftools and use the readelf.py script it includes. It accepts the same arguments.

readelf -a <file>

This displays most of the ELF metadata, including program headers, section headers (if present), symbols, relocations, and the dynamic section.

On PS3, the TOC is not necessarily stored in a dedicated .toc section. Depending on how the binary was linked, you may find sections I mentioned earlier: .got, .got2, .opd, or .toc.

To list the sections:

readelf -SW <file>

The .opd section is particularly interesting on PowerPC64 because it contains function descriptors, including the TOC value used for each function.

If you don’t see those sections, it doesn’t necessarily mean the TOC is missing. According to what I read on it, retail PS3 executables are often distributed with their section headers stripped. In that case, readelf won’t show section names even though the underlying data is still present.

The TOC and function descriptor table are part of the loaded image, not the section headers themselves. If the section headers are gone, the TOC is typically recovered from the function descriptor table (.opd data), the entry point, or relocation information rather than from a section literally named .toc or .got.

Out of curiosity, what does the output of this say:

readelf -SW <file>

Does it list .got, .got2, .opd, or .toc? Or does it report that there are no section headers? That would help narrow down whether the loader simply failed to identify the TOC automatically.

If you don’t have readelf, you can install it on Linux with any package manager OR you can use the Python-based readelf clone readelf.py that is included with the pyelftools package (pip install pyelftools) and/or see pyelftools github project

First off, thank you very much for the time and effort you invested in this topic.
I have prior experience of reporting bugs, Hex-Rays does fix them but I’m afraid even if this is a bug on their end, I’ll have to wait till version 9.5 or 9.6.

As you suspect, I have little experience with elf and any arch other than x86, so all my attempts to walk sections for TOC have failed so far. i calculated it via _start function.
The executable is debug build. I don’t have a ps3 device, I’m studying it as a hobby. From what I know, it crashes upon launch on a ps3 device; even the original encrypted executable.

I’m using MSYS2 for Linux utilities. readelf -SW <file> yields:

Section Headers:
  [Nr] Name              Type       
  [ 0]                   NULL       
  [ 1] .init             PROGBITS   
  [ 2] .text             PROGBITS   
  [ 3] .fini             PROGBITS   
  [ 4] .sceStub.text     PROGBITS   
  [ 5] .eh_frame         PROGBITS   
  [ 6] .gcc_except_table PROGBITS   
  [ 7] .rodata.sceResident PROGBITS 
  [ 8] .rodata.sceFNID   PROGBITS   
  [ 9] .lib.ent.top      PROGBITS   
  [10] .lib.ent.btm      PROGBITS   
  [11] .lib.stub.top     PROGBITS   
  [12] .lib.stub         PROGBITS   
  [13] .lib.stub.btm     PROGBITS   
  [14] .rodata           PROGBITS   
  [15] .spu_image        PROGBITS   
  [16] .sys_proc_param   PROGBITS   
  [17] .sys_proc_prx_param PROGBITS 
  [18] .ctors            PROGBITS   
  [19] .dtors            PROGBITS   
  [20] .jcr              PROGBITS   
  [21] .data.rel.ro      PROGBITS   
  [22] .data.sceFStub    PROGBITS   
  [23] .toc1             PROGBITS   
  [24] .opd              PROGBITS   
  [25] .got              PROGBITS   
  [26] .tdata            PROGBITS   
  [27] .tbss             NOBITS     
  [28] .data             PROGBITS   
  [29] .bin_objs.job.bin PROGBITS   
  [30] .bss              NOBITS     
  [31] .debug_aranges    PROGBITS   
  [32] .debug_pubnames   PROGBITS   
  [33] .debug_info       PROGBITS   
  [34] .debug_abbrev     PROGBITS   
  [35] .debug_line       PROGBITS   
  [36] .debug_frame      PROGBITS   
  [37] .debug_str        PROGBITS   
  [38] .debug_loc        PROGBITS   
  [39] .debug_ranges     PROGBITS   
  [40] .debug_libgenfunc PROGBITS   
  [41] .sceversion       PROGBITS   
  [42] .shstrtab         STRTAB     
  [43] .symtab           SYMTAB     
  [44] .strtab           STRTAB

Any further suggestions and guidance is much appreciated.

Hi @alex ,
Thanks for the details and for providing us with the sample. Glad to hear the string literal issue is solved.
We’ll take a closer look at this with the file you already sent via support before drawing any conclusions, so we’ll follow up once we’ve had a chance to investigate it.

if anyone else encounters this problem, this may help:

first take a look at THIS, especially section permissions.

then all char** addresses must be manually casted to offset for IDA to create xrefs. i wrote a crude python snippet to automate this task:

import idautils
import ida_segment
import ida_bytes
import ida_offset
import ida_idaapi
import ida_ida 
import ida_nalt


def get_segment(segment_name):
    if segment_name == "*": print("[*] Printing all segments ...")
    
    for seg_ea in idautils.Segments():
        # for quick seg name
        seg_name = ida_segment.get_segment_name(seg_ea)
        
        seg_info = ida_segment.segment_info_t()
        flags = ida_segment.GSI_NAME | ida_segment.GSI_SCLASS
        success = ida_segment.get_segment_info(seg_info, seg_ea, flags)
        
        if success and segment_name != None and segment_name != "*":
            if seg_info.get_name() == segment_name:
                return seg_info
        
        if success and segment_name == "*":
            name = seg_info.get_name()
            class_ = seg_info.get_sclass() 
            start = hex(seg_info.start_ea)
            end = hex(seg_info.end_ea)
            bit = seg_info.get_bitness() # 0=16bit, 1=32bit, 2=64bit
            perm = seg_info.get_perm()
            print(f"Segment: {name} | Class: {class_}"
            f" | Start: {start} | End: {end} | Bit: {bit} | Perm: {perm}")
    if segment_name == "*": print("[*] All segments printed")       


def get_actual_pointer_size():
    if hasattr(ida_ida, 'inf_get_ptr_size'):
        return ida_ida.inf_get_ptr_size()
    if hasattr(ida_ida, 'inf_is_64bit') and hasattr(ida_ida, 'inf_is_32bit_exactly'):
        if ida_ida.inf_is_64bit():
            pass

    # 3. Ultimate fallback:
    return 4
    
    
def force_resolve_string_pointers(segment_name):
    seg_info = get_segment(segment_name)
    if not seg_info:
        print(f"[!] Segment '{segment_name}' not found.")
        return

    print(f"[*] Scanning segment {seg_info.get_name()} " 
    f"({hex(seg_info.start_ea)} - {hex(seg_info.end_ea)})...")
    
    converted_count = 0
    ptr_size = get_actual_pointer_size() # get pointer size
    ref_type = ida_nalt.REF_OFF64 if ptr_size == 8 else ida_nalt.REF_OFF32

    # Walk through every defined or undefined head item in the segment
    for head in idautils.Heads(seg_info.start_ea, seg_info.end_ea):
        # DEBUG: manually probing a pointer to char*
        if head == 0x12345678:
            pointer_val = ida_bytes.get_dword(head) # assuming 4byte ptr sz
            string_len = ida_bytes.get_max_strlit_length(pointer_val, STRTYPE_C)
            if string_len > 0:
                # 2. Extract the contents directly using the discovered length
                raw_bytes = ida_bytes.get_strlit_contents(pointer_val, string_len, STRTYPE_C)
                if raw_bytes:
                    try:
                        decoded_str = raw_bytes.decode('ascii')
                        # Force-define the array of bytes as a native IDA string literal
                        #ida_bytes.create_strlit(pointer_val, str_len + 1, ida_nalt.STRTYPE_TERMCHR)
                        success = ida_offset.op_offset(head, 0, ref_type)
                        # return True, decoded_str, string_len
                    except UnicodeDecodeError:
                        pass
        # DEBUG: end
         
        
        if ptr_size == 8:
            pointer_val = ida_bytes.get_qword(head)
        else:
            pointer_val = ida_bytes.get_dword(head)
        
        if not pointer_val or pointer_val == 0 or pointer_val == ida_idaapi.BADADDR:
            continue
        
        string_len = ida_bytes.get_max_strlit_length(pointer_val, STRTYPE_C)
        if string_len > 0:
            # 2. Extract the contents directly using the discovered length
            raw_bytes = ida_bytes.get_strlit_contents(pointer_val, string_len, STRTYPE_C)
            if raw_bytes:
                try:
                    decoded_str = raw_bytes.decode('ascii')
                    # Force-define the array of bytes as a native IDA string literal
                    #ida_bytes.create_strlit(pointer_val, str_len + 1, ida_nalt.STRTYPE_TERMCHR)
                    success = ida_offset.op_offset(head, 0, ref_type)
                    if success:
                        print(f"[+] Found & Linked: {hex(head)} -> {hex(pointer_val)}: \"{decoded_str}\"")
                        converted_count += 1
                        # return True, decoded_str, string_len
                except UnicodeDecodeError:
                    print(f"[!] Exception occured for ptr: {hex(head)}")
                    pass

    print(f"[*] Done. linked {converted_count} hidden string pointers.")


# ///////////////////////////////////////////////

# print all segments
get_segment("*")

force_resolve_string_pointers(".toc1")

this issue is planned to be resolved in the next release.