patch elf文件 - 使用lief

lief詳細介紹

import lief
# ELF
binary = lief.parse("/usr/bin/ls")
print(binary)

# PE
binary = lief.parse("C:\\Windows\\explorer.exe")
print(binary)

# Mach-O
binary = lief.parse("/usr/bin/ls")
print(binary)

# OAT
binary = lief.parse("android.odex")
print(binary)

# DEX
dex = lief.DEX.parse("classes.dex")
print(dex)

# VDEX
vdex = lief.VDEX.parse("classes.vdex")
print(vdex)

# ART
art = lief.ART.parse("boot.art")
print(art)

可打印出文件各個區(qū)段,頭,節(jié)區(qū)等信息

header = binary.header

圖片.png

更改入口點和目標(biāo)體系結(jié)構(gòu)[ARCH]

header.entrypoint = 0x123
header.machine_type = lief.ELF.ARCH.AARCH64
binary.write("ls.modified")  //重建寫入新文件
圖片.png
圖片.png

我們還可以迭代輸出二進制[節(jié)區(qū)]

for section in binary.sections:
  print(section.name) # section's name
  print(section.size) # section's size
  print(len(section.content)) # Should match the previous print

要修改該.text部分的內(nèi)容

text = binary.get_section(".text")
text.content = bytes([0x33] * text.size)
圖片.png

使用lief創(chuàng)建簡單PE

創(chuàng)建文件完整腳本

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# Description:
# Create a PE which pop a MessageBox
# with the message "Hello World"

from lief import PE

title   = "LIEF is awesome\0"
message = "Hello World\0"

data =  list(map(ord, title))
data += list(map(ord, message))
code = [
        0x6a, 0x00,                         # push 0x00 uType
        0x68, 0x00, 0x20, 0x40, 0x00,       # push VA(title)
        0x68, 0x10, 0x20, 0x40, 0x00,       # push VA(message)
        0x6a, 0x00,                         # push 0 hWnd
        0xFF, 0x15, 0x54, 0x30, 0x40, 0x00, # call MessageBoxA
        0x6A, 0x00,                         # push 0 uExitCode
        0xFF, 0x15, 0x4C, 0x30, 0x40, 0x00  # call ExitProcess
        ]

binary32 = PE.Binary("pe_from_scratch", PE.PE_TYPE.PE32)

section_text                 = PE.Section(".text")
section_text.content         = code
section_text.virtual_address = 0x1000

section_data                 = PE.Section(".data")
section_data.content         = data
section_data.virtual_address = 0x2000

section_text = binary32.add_section(section_text, PE.SECTION_TYPES.TEXT)
section_data = binary32.add_section(section_data, PE.SECTION_TYPES.DATA)

print(section_text)
print(section_data)

binary32.optional_header.addressof_entrypoint = section_text.virtual_address

kernel32 = binary32.add_library("kernel32.dll")
kernel32.add_entry("ExitProcess")

user32 = binary32.add_library("user32.dll")
user32.add_entry("MessageBoxA")


ExitProcess_addr = binary32.predict_function_rva("kernel32.dll", "ExitProcess")
MessageBoxA_addr = binary32.predict_function_rva("user32.dll", "MessageBoxA")
print("Address of 'ExitProcess': 0x{:06x} ".format(ExitProcess_addr))
print("Address of 'MessageBoxA': 0x{:06x} ".format(MessageBoxA_addr))

builder = PE.Builder(binary32)
builder.build_imports(True)
builder.build()
builder.write("pe_from_scratch.exe")

step - 1

先創(chuàng)建一個對象

from lief import PE
 
binary32 = PE.Binary("pe_from_scratch", PE.PE_TYPE.PE32)

第一個參數(shù)是二進制文件的名字,第二個參數(shù)是PE文件的類型:PE32或是PE64(PE_TYPE)。Binary的構(gòu)造器可以自動創(chuàng)建DosHeader,Header,OptionalHeader以及一個空的DataDirectory

step - 2

創(chuàng)建區(qū)段

section_text                 = PE.Section(".text")
section_text.content         = code
section_text.virtual_address = 0x1000
 
section_data                 = PE.Section(".data")
section_data.content         = data
section_data.virtual_address = 0x2000

step - 3
MessageBoxA由title和message組成。這兩個字符串將存儲在.data段中:

title   = "LIEF is awesome\0"
message = "Hello World\0"
 
data =  list(map(ord, title))
data += list(map(ord, message))

step - 4

創(chuàng)建text段asm代碼

push 0x00              ; uType
push "LIEF is awesome" ; Title
push "Hello World"     ; Message
push 0                 ; hWnd
call MessageBoxA       ;
push 0                 ; uExitCode
call ExitProcess       ;

我們push的不是ascii,而是字符串所在的虛擬地址。在PE格式中,虛擬地址表示的是相對虛擬地址(如果ASLR不開啟的話,是相對于Optional.imagebase)。Binary構(gòu)造器會默認把imagebase設(shè)為0x400000

字符串的虛擬地址計算如下的如下:

title:imagebase+virtual_address+0=0x402000
message:imagebase+virtual_address+len(title)=0x402010

step - 5

載入dll文件

MessageBoxA,我們需要將user32.dll放進Imports表中,并將MessageBoxA放進ImportEntry中;
使用add_library()和add_entry()來實現(xiàn)

user32 = binary32.add_library("user32.dll")
user32.add_entry("MessageBoxA")

ExitProcess(kernel32.dll)的導(dǎo)入也是:

kernel32 = binary32.add_library("kernel32.dll")
kernel32.add_entry("ExitProcess")

step - 6

確定導(dǎo)入庫的地址

使用predict_funciton_rva()方法,它會返回由Builder設(shè)置的IAT地址:

ExitProcess_addr = binary32.predict_function_rva("kernel32.dll", "ExitProcess")
MessageBoxA_addr = binary32.predict_function_rva("user32.dll", 
"MessageBoxA")
print("Address of 'ExitProcess': 0x{:06x} ".format(ExitProcess_addr))
print("Address of 'MessageBoxA': 0x{:06x} ".format(MessageBoxA_addr))
Address of 'ExitProcess': 0x00304c
Address of 'MessageBoxA': 0x003054

MessageBoxA和ExitProcess的絕對虛擬地址是:

MessageBoxA: imagebase + 0x306a = 0x40306a
ExitProcess: imagebase + 0x305c = 0x40305c

匯編代碼

push 0x00              ; uType
push 0x402000          ; Title
push 0x402010          ; Message
push 0                 ; hWnd
call 0x40306a          ;
push 0                 ; uExitCode
call 0x40305c          ;

step - 7
將Binary對象轉(zhuǎn)化為可執(zhí)行文件的操作是由Builder類來實現(xiàn)的;導(dǎo)入表不會被重建,所以我們需要手動配置

builder = lief.PE.Builder(binary32)
builder.build_imports(True)
builder.build()
builder.write("pe_from_scratch.exe")

修改ELF符號
使用exported_functions和imported_functions對funtion進行枚舉

import lief
binary  = lief.parse("/usr/bin/ls")
library = lief.parse("/usr/lib/libc.so.6")
 
print(binary.imported_functions)
print(library.exported_functions)

使用lief將下面函數(shù)名進行更換

gcc  jing.c -lm
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
 
double hashme(double input) {
  return pow(input, 4) + log(input + 3);
}
 
int main(int argc, char** argv) {
  if (argc != 2) {
    printf("Usage: %s N\n", argv[0]);
    return EXIT_FAILURE;
  }
 
  double N = (double)atoi(argv[1]);
  double hash = hashme(N);
  printf("%f\n", hash);
 
  return EXIT_SUCCESS;
}
圖片.png

將pow和log函數(shù)用LIEF把當(dāng)前的函數(shù)名和另一個函數(shù)名進行互換

first - 導(dǎo)入文件和庫

import lief
 
hasme = lief.parse("a.out")
libm  = lief.parse("/libm.so.6")

second - 改變binary中兩個導(dǎo)入函數(shù)的函數(shù)名:

hashme_pow_sym = next(filter(lambda e : e.name == "pow", my_binary.imported_symbols))
hashme_log_sym = next(filter(lambda e : e.name == "log", my_binary.imported_symbols))
 
hashme_pow_sym.name = "cos"
hashme_log_sym.name = "sin"

in the end
把log替換為sin,把pow替換為cos,并重建這兩個對象

!/usr/bin/env python3

import lief
 
 
hasme = lief.parse("hasme")
libm  = lief.parse("/usr/lib/libm.so.6")
 
 
def swap(obj, a, b):
    symbol_a = next(filter(lambda e : e.name == a, obj.dynamic_symbols))
    symbol_b = next(filter(lambda e : e.name == b, obj.dynamic_symbols))
    b_name = symbol_b.name
    symbol_b.name = symbol_a.name
    symbol_a.name = b_name
 
hashme_pow_sym = next(filter(lambda e : e.name == "pow", my_binary.imported_symbols))
hashme_log_sym = next(filter(lambda e : e.name == "log", my_binary.imported_symbols))
 
hashme_pow_sym.name = "cos"
hashme_log_sym.name = "sin"
 
 
swap(libm, "log", "sin")
swap(libm, "pow", "cos")
 
hashme.write("hashme.obf")
libm.write("libm.so.6")

我們在當(dāng)前目錄下構(gòu)建了一個修改過后的libm,接下來需要在執(zhí)行binary.obf的時候強制讓Linux加載器加載我們修改過后的這個庫。要實現(xiàn)這個功能,我們需要把LD_LIBRARY_PATH導(dǎo)出到當(dāng)前目錄:

$ LD_LIBRARY_PATH=. hashme.obf 123
228886645.836282

修改過后的動態(tài)庫中cos對應(yīng)的地址是pow函數(shù)的地址,sin的對應(yīng)地址是log函數(shù)的地址,而修改后的binary中調(diào)用的分別是cos和sin函數(shù)。調(diào)用的是cos函數(shù),而實際執(zhí)行的是pow函數(shù)的功能,這就會對分析人員造成困擾


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容