From a8ffae5202fa409f221cd6aafeeb0cd137b97f77 Mon Sep 17 00:00:00 2001 From: nganhkhoa Date: Wed, 10 Jan 2024 14:50:19 +0700 Subject: [PATCH] move everything objc to objc.go --- macho-go/pkg/ios/macho/edit.go | 267 -------------------------------- macho-go/pkg/ios/macho/objc.go | 270 +++++++++++++++++++++++++++++++++ 2 files changed, 270 insertions(+), 267 deletions(-) diff --git a/macho-go/pkg/ios/macho/edit.go b/macho-go/pkg/ios/macho/edit.go index cc727bb..67a2d8a 100644 --- a/macho-go/pkg/ios/macho/edit.go +++ b/macho-go/pkg/ios/macho/edit.go @@ -360,273 +360,6 @@ func (mc *MachoContext) removeBindSymbolsLegacy() { mc.file.WriteAt(make([]byte, size), int64(start)) } -func (mc *MachoContext) ReworkForObjc() { - text_start := 0 - data_end := 0 - lc_main_offset := int64(0) - - ptr := int64(0) - if mc.Is64bit() { - ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart) - } else { - ptr, _ = mc.file.Seek(int64(Header_size), io.SeekStart) - } - - for _, cmd := range mc.commands { - if cmd.Cmd() == LC_MAIN { - lc_main_offset = ptr + 8 - ptr += int64(cmd.Cmdsize()) - continue - } - if cmd.Cmd() != LC_SEGMENT_64 { - ptr += int64(cmd.Cmdsize()) - continue - } - var segment = cmd.(*Segment64) - - if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 { - section_ptr := ptr + 0x40 + 8 - for _, section := range segment.Sections() { - if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__text")) == 0 { - text_start = int(section.Offset()) - } - if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__init_offsets")) == 0 { - // mc.file.WriteAt([]byte("__init_offsetx"), section_ptr) - // edit flags to not S_MOD_INIT_FUNC - mc.file.WriteAt([]byte{0, 0, 0, 0}, section_ptr+0x40) - } - - // erases all objc method names - // this should still works because the cache inserts the pointer value not string - // but some symbols relies on pre-defined implementations, such as **load** method - // load method is the same across all classes and so objc define an implementation - // selector should points to this load selector to make objc thinks that it's "load" - if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 { - // mc.file.WriteAt([]byte("__objc_methbruh"), section_ptr) - mc.file.WriteAt(make([]byte, section.Size()), int64(section.Offset())) - } - section_ptr += 16*2 + 8*2 + 4*8 - } - } - if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA_CONST")) == 0 { - section_ptr := ptr + 0x40 + 8 - for _, section := range segment.Sections() { - if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classlist")) == 0 { - mc.file.WriteAt([]byte("__objc_classbruh"), section_ptr) - } - if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_nlclslist")) == 0 { - mc.file.WriteAt([]byte("__objc_nlclsbruh"), section_ptr) - } - section_ptr += 16*2 + 8*2 + 4*8 - } - } - if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 { - // end of __DATA segment, should have enough space for a pointer - - // __bss section is dynamically allocated at the end to or something, hmmge - // assume that it order correctly, which it should if compiled and not modified - // each section has their addr field which we can use that with segment virtual address - // to calculate the offset of the last section from segment starts - // then use the size of section to calculate the end of segment in file - sections := segment.Sections() - last := sections[len(sections)-1] - data_end = int(last.Addr() - segment.Vmaddr() + segment.Fileoff() + last.Size()) - - // do not register selector and see what happens - section_ptr := ptr + 0x40 + 8 - for _, section := range segment.Sections() { - if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_selrefs")) == 0 { - // mc.file.WriteAt([]byte("__objc_selbruh"), section_ptr) - } - section_ptr += 16*2 + 8*2 + 4*8 - } - } - ptr += int64(cmd.Cmdsize()) - } - mc.file.Seek(0, io.SeekStart) - - // dummy value past the end of __DATA segment (logical size), - // its physical size is still a page - // mc.file.WriteAt([]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, int64(0x81d8)) - - // we use 2 registers, x8 x9 - // stack values: - // [ return address, header, argc, argv, env, apple ] - // we need to store the return address, and parameters passed to main - // we also store our header address to not calculate many times - - // must expand stack to store arguments passed - // must use newly allocated stack region - // must save return address - // must recover stack to same value before calling main - // must recover link register before calling main - - // we use shorthand store/load multiple - // arm also has different indexing instruction, so be careful - // https://developer.arm.com/documentation/102374/0101/Loads-and-stores---addressing - /* - adr x8, 0 - # x9 = (offset end of __DATA) - (offset shellcode) - movz x9, #0x9999 - add x8, x8, x9 - - stp x30, x8, [sp], #-0x10 - stp x3, x2, [sp], #-0x10 - stp x1, x0, [sp], #-0x10 - - # custom intializer - ldr x9, [x8] - blr x9 - - ldp x1, x0, [sp, #0x10]! - ldp x3, x2, [sp, #0x10]! - ldp x30, x8, [sp, #0x10]! - - # original main - # link register is set so jump only - ldr x9, [x8, #8] - br x9 - */ - shellcode := []uint32{} - ins_size_byte := 4 - main_offset := int(mc.entryoff) - var shellcode_offset int - - isArm := (mc.header.cputype & 0xff) == 12 - if isArm { - shellcode = []uint32{ - 0x10000008, - 0, // x9 = (offset end of __DATA) - (offset shellcode) - 0x8B090108, - 0xA8BF23FE, - 0xA8BF0BE3, - 0xA8BF03E1, - 0xF9400109, - 0xD63F0120, - 0xA9C103E1, - 0xA9C10BE3, - 0xA9C123FE, - 0xF9400509, - 0xD61F0120, - } - - shellcode_offset = text_start - (ins_size_byte * len(shellcode)) - - encode_movz := func(v int) uint32 { - return uint32(uint32(v)<<5 | uint32(0x694)<<21 | uint32(0x09)) - } - - // movz_shellcode_offset := encode_movz(shellcode_offset) - // movz_main_offset := encode_movz(main_offset) - // movz_data_end_offset := encode_movz(data_end) - movz_calculate_offset := encode_movz(data_end - shellcode_offset) - - shellcode[1] = movz_calculate_offset - // shellcode[10] = movz_data_end_offset - // shellcode[19] = movz_main_offset - - fmt.Printf("// shellcode_offset=%x\n", shellcode_offset) - fmt.Printf("// main_offset=%x\n", main_offset) - fmt.Printf("// data_end=%x\n", data_end) - fmt.Printf("// movz_calculate_offset=%x\n", movz_calculate_offset) - // fmt.Printf("// movz_shellcode_offset=%x\n", movz_shellcode_offset) - // fmt.Printf("// movz_main_offset=%x\n", movz_main_offset) - // fmt.Printf("// movz_data_end_offset=%x\n", movz_data_end_offset) - fmt.Printf("// lc_main_offset=%x\n", lc_main_offset) - } else { - shellcode_start := []uint8{ - 0x4c, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00, - 0x49, 0xC7, 0xC1, - } - - shellcode_end := []uint8{ - 0x4d, 0x01, 0xc8, - 0x57, - 0x56, - 0x52, - 0x51, - 0x41, 0x50, - 0x4d, 0x8b, 0x08, - 0x41, 0xff, 0xd1, - 0x41, - 0x58, - 0x59, - 0x5a, - 0x5e, - 0x5f, 0x4d, 0x8b, 0x48, 0x08, - 0x41, 0xff, 0xe1, - // pad to %4 - 0x00, 0x00, - } - - offset := []uint8{0x00, 0x00, 0x00, 0x00} // offset - shellcode_size := len(shellcode_start) + len(offset) + len(shellcode_end) - - // could use buffer encoding, but for correctness, - // we do this by hand - encode_movz := func(v int) { - for i := 0; i < 4; i++ { - offset[i] = uint8(v >> (i * 8)) - } - } - - -// ┌─────────────────┐ -// │ │ -// shellcode starts ────┼─────────────────┼───── │ │instruction -// │ │ │ │fetch RIP size -// RIP returns ────┼─────────────────┼───── ▲ │ │ -// │ │ │ │ -// │ │ │ │ shellcode length -// shellcode ends │ │ │ offset │ -// __text ────┼─────────────────┼───── │ range │ -// │ │ │ │ __DATA ends - __text -// │ │ │ │ -// __DATA ends ────┼─────────────────┼───── ▼ │ -// │ │ -// │ │ -// │ │ -// │ │ -// │ │ -// └─────────────────┘ - encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start))) - - shellcode_offset = text_start - shellcode_size - shellcode_bytes := append(shellcode_start, offset...) - shellcode_bytes = append(shellcode_bytes, shellcode_end...) - - for i := 0; i < len(shellcode_bytes); i += 4 { - val := 0 - // little endian - val |= int(shellcode_bytes[i+0]) << 0 - val |= int(shellcode_bytes[i+1]) << 8 - val |= int(shellcode_bytes[i+2]) << 16 - val |= int(shellcode_bytes[i+3]) << 24 - shellcode = append(shellcode, uint32(val)) - } - - fmt.Printf("// shellcode_offset=%x\n", shellcode_offset) - fmt.Printf("// main_offset=%x\n", main_offset) - fmt.Printf("// data_end=%x\n", data_end) - fmt.Printf("// lc_main_offset=%x\n", lc_main_offset) - } - - offset := int64(shellcode_offset) - { - // fix main to point to our newly created shellcode - bs := make([]byte, 8) - mc.byteorder.PutUint64(bs, uint64(offset)) - mc.file.WriteAt(bs, int64(lc_main_offset)) - } - - bs := make([]byte, 4) - for _, ins := range shellcode { - mc.byteorder.PutUint32(bs, ins) - mc.file.WriteAt(bs, offset) - offset += 4 - } -} - func (mc *MachoContext) RewriteImportsTable(keepSymbols []string) { allSymbols := mc.CollectBindSymbols() fixups, fixupsOffset := mc.Fixups() diff --git a/macho-go/pkg/ios/macho/objc.go b/macho-go/pkg/ios/macho/objc.go index 9e2683e..ec01d8d 100644 --- a/macho-go/pkg/ios/macho/objc.go +++ b/macho-go/pkg/ios/macho/objc.go @@ -1,8 +1,10 @@ package macho import ( + "fmt" "bytes" "encoding/binary" + "io" "strings" . "ios-wrapper/pkg/ios" @@ -93,3 +95,271 @@ func (mc *MachoContext) CollectSpecialSelectors() []*SpecialSelector { return special_selectors } +func (mc *MachoContext) ReworkForObjc() { + text_start := 0 + data_end := 0 + lc_main_offset := int64(0) + + ptr := int64(0) + if mc.Is64bit() { + ptr, _ = mc.file.Seek(int64(Header_size_64), io.SeekStart) + } else { + ptr, _ = mc.file.Seek(int64(Header_size), io.SeekStart) + } + + for _, cmd := range mc.commands { + if cmd.Cmd() == LC_MAIN { + lc_main_offset = ptr + 8 + ptr += int64(cmd.Cmdsize()) + continue + } + if cmd.Cmd() != LC_SEGMENT_64 { + ptr += int64(cmd.Cmdsize()) + continue + } + var segment = cmd.(*Segment64) + + if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__TEXT")) == 0 { + section_ptr := ptr + 0x40 + 8 + for _, section := range segment.Sections() { + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__text")) == 0 { + text_start = int(section.Offset()) + } + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__init_offsets")) == 0 { + // mc.file.WriteAt([]byte("__init_offsetx"), section_ptr) + // edit flags to not S_MOD_INIT_FUNC + mc.file.WriteAt([]byte{0, 0, 0, 0}, section_ptr+0x40) + } + + // erases all objc method names + // this should still works because the cache inserts the pointer value not string + // but some symbols relies on pre-defined implementations, such as **load** method + // load method is the same across all classes and so objc define an implementation + // selector should points to this load selector to make objc thinks that it's "load" + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_methname")) == 0 { + // mc.file.WriteAt([]byte("__objc_methbruh"), section_ptr) + mc.file.WriteAt(make([]byte, section.Size()), int64(section.Offset())) + } + section_ptr += 16*2 + 8*2 + 4*8 + } + } + if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA_CONST")) == 0 { + section_ptr := ptr + 0x40 + 8 + for _, section := range segment.Sections() { + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_classlist")) == 0 { + mc.file.WriteAt([]byte("__objc_classbruh"), section_ptr) + } + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_nlclslist")) == 0 { + mc.file.WriteAt([]byte("__objc_nlclsbruh"), section_ptr) + } + section_ptr += 16*2 + 8*2 + 4*8 + } + } + if bytes.Compare(bytes.Trim(segment.SegName(), "\x00"), []byte("__DATA")) == 0 { + // end of __DATA segment, should have enough space for a pointer + + // __bss section is dynamically allocated at the end to or something, hmmge + // assume that it order correctly, which it should if compiled and not modified + // each section has their addr field which we can use that with segment virtual address + // to calculate the offset of the last section from segment starts + // then use the size of section to calculate the end of segment in file + sections := segment.Sections() + last := sections[len(sections)-1] + data_end = int(last.Addr() - segment.Vmaddr() + segment.Fileoff() + last.Size()) + + // do not register selector and see what happens + section_ptr := ptr + 0x40 + 8 + for _, section := range segment.Sections() { + if bytes.Compare(bytes.Trim(section.SectName(), "\x00"), []byte("__objc_selrefs")) == 0 { + // mc.file.WriteAt([]byte("__objc_selbruh"), section_ptr) + } + section_ptr += 16*2 + 8*2 + 4*8 + } + } + ptr += int64(cmd.Cmdsize()) + } + mc.file.Seek(0, io.SeekStart) + + // dummy value past the end of __DATA segment (logical size), + // its physical size is still a page + // mc.file.WriteAt([]byte{0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7}, int64(0x81d8)) + + // we use 2 registers, x8 x9 + // stack values: + // [ return address, header, argc, argv, env, apple ] + // we need to store the return address, and parameters passed to main + // we also store our header address to not calculate many times + + // must expand stack to store arguments passed + // must use newly allocated stack region + // must save return address + // must recover stack to same value before calling main + // must recover link register before calling main + + // ┌─────────────────┐ + // │ │ + // shellcode starts ────┼─────────────────┼───── │ │instruction + // │ │ │ │fetch RIP size + // RIP returns ────┼─────────────────┼───── ▲ │ │ + // │ │ │ │ + // │ │ │ │ shellcode length + // shellcode ends │ │ │ offset │ + // __text ────┼─────────────────┼───── │ range │ + // │ │ │ │ __DATA ends - __text + // │ │ │ │ + // __DATA ends ────┼─────────────────┼───── ▼ │ + // │ │ + // │ │ + // │ │ + // │ │ + // │ │ + // └─────────────────┘ + + shellcode := []uint32{} + ins_size_byte := 4 + main_offset := int(mc.entryoff) + var shellcode_offset int + + isArm := (mc.header.cputype & 0xff) == 12 + if isArm { + + // we use shorthand store/load multiple + // arm also has different indexing instruction, so be careful + // https://developer.arm.com/documentation/102374/0101/Loads-and-stores---addressing + /* + adr x8, 0 + # x9 = (offset end of __DATA) - (offset shellcode) + movz x9, #0x9999 + add x8, x8, x9 + + stp x30, x8, [sp], #-0x10 + stp x3, x2, [sp], #-0x10 + stp x1, x0, [sp], #-0x10 + + # custom intializer + ldr x9, [x8] + blr x9 + + ldp x1, x0, [sp, #0x10]! + ldp x3, x2, [sp, #0x10]! + ldp x30, x8, [sp, #0x10]! + + # original main + # link register is set so jump only + ldr x9, [x8, #8] + br x9 + */ + shellcode = []uint32{ + 0x10000008, + 0, // x9 = (offset end of __DATA) - (offset shellcode) + 0x8B090108, + 0xA8BF23FE, + 0xA8BF0BE3, + 0xA8BF03E1, + 0xF9400109, + 0xD63F0120, + 0xA9C103E1, + 0xA9C10BE3, + 0xA9C123FE, + 0xF9400509, + 0xD61F0120, + } + + shellcode_offset = text_start - (ins_size_byte * len(shellcode)) + + encode_movz := func(v int) uint32 { + return uint32(uint32(v)<<5 | uint32(0x694)<<21 | uint32(0x09)) + } + + // movz_shellcode_offset := encode_movz(shellcode_offset) + // movz_main_offset := encode_movz(main_offset) + // movz_data_end_offset := encode_movz(data_end) + movz_calculate_offset := encode_movz(data_end - shellcode_offset) + + shellcode[1] = movz_calculate_offset + // shellcode[10] = movz_data_end_offset + // shellcode[19] = movz_main_offset + + fmt.Printf("// shellcode_offset=%x\n", shellcode_offset) + fmt.Printf("// main_offset=%x\n", main_offset) + fmt.Printf("// data_end=%x\n", data_end) + fmt.Printf("// movz_calculate_offset=%x\n", movz_calculate_offset) + // fmt.Printf("// movz_shellcode_offset=%x\n", movz_shellcode_offset) + // fmt.Printf("// movz_main_offset=%x\n", movz_main_offset) + // fmt.Printf("// movz_data_end_offset=%x\n", movz_data_end_offset) + fmt.Printf("// lc_main_offset=%x\n", lc_main_offset) + } else { + shellcode_start := []uint8{ + 0x4c, 0x8d, 0x05, 0x00, 0x00, 0x00, 0x00, + 0x49, 0xC7, 0xC1, + } + + shellcode_end := []uint8{ + 0x4d, 0x01, 0xc8, + 0x57, + 0x56, + 0x52, + 0x51, + 0x41, 0x50, + 0x4d, 0x8b, 0x08, + 0x41, 0xff, 0xd1, + 0x41, + 0x58, + 0x59, + 0x5a, + 0x5e, + 0x5f, 0x4d, 0x8b, 0x48, 0x08, + 0x41, 0xff, 0xe1, + // pad to %4 + 0x00, 0x00, + } + + offset := []uint8{0x00, 0x00, 0x00, 0x00} // offset + shellcode_size := len(shellcode_start) + len(offset) + len(shellcode_end) + + // could use buffer encoding, but for correctness, + // we do this by hand + encode_movz := func(v int) { + for i := 0; i < 4; i++ { + offset[i] = uint8(v >> (i * 8)) + } + } + + encode_movz((data_end - text_start) + (shellcode_size - len(shellcode_start))) + + shellcode_offset = text_start - shellcode_size + shellcode_bytes := append(shellcode_start, offset...) + shellcode_bytes = append(shellcode_bytes, shellcode_end...) + + for i := 0; i < len(shellcode_bytes); i += 4 { + val := 0 + // little endian + val |= int(shellcode_bytes[i+0]) << 0 + val |= int(shellcode_bytes[i+1]) << 8 + val |= int(shellcode_bytes[i+2]) << 16 + val |= int(shellcode_bytes[i+3]) << 24 + shellcode = append(shellcode, uint32(val)) + } + + fmt.Printf("// shellcode_offset=%x\n", shellcode_offset) + fmt.Printf("// main_offset=%x\n", main_offset) + fmt.Printf("// data_end=%x\n", data_end) + fmt.Printf("// lc_main_offset=%x\n", lc_main_offset) + } + + offset := int64(shellcode_offset) + { + // fix main to point to our newly created shellcode + bs := make([]byte, 8) + mc.byteorder.PutUint64(bs, uint64(offset)) + mc.file.WriteAt(bs, int64(lc_main_offset)) + } + + bs := make([]byte, 4) + for _, ins := range shellcode { + mc.byteorder.PutUint32(bs, ins) + mc.file.WriteAt(bs, offset) + offset += 4 + } +} +