lldb ค้นหาจุดออกจากแอป

ฉันกำลังดีบั๊กแอปที่น่าจะมีมาตรการป้องกันการดีบั๊ก การตั้งค่าเบรกพอยต์และสัญญาณหยุดสำหรับการออกจากแอปไม่ได้หยุดแอปไม่ให้ออก

$ lldb App 
(lldb) target create "App"
error: Invalid fde/cie next entry offset of 0x43029a18 found in cie/fde at 0x1404
Current executable set to 'App' (x86_64).
(lldb) br s -n exit
Breakpoint 1: 3 locations.
(lldb) br s -n _exit
Breakpoint 2: where = libsystem_kernel.dylib`__exit, address = 0x00000000000167a8
(lldb) br s -n _Exit
Breakpoint 3: where = libsystem_c.dylib`_Exit, address = 0x000000000005ed8b
(lldb) process launch -stop-at-entry
Process 17849 stopped
* thread #1: tid = 0xb9ebc, 0x00007fff5fc01000 dyld`_dyld_start, stop reason = signal SIGSTOP
    frame #0: 0x00007fff5fc01000 dyld`_dyld_start
dyld`_dyld_start:
->  0x7fff5fc01000 <+0>: popq   %rdi
    0x7fff5fc01001 <+1>: pushq  $0x0
    0x7fff5fc01003 <+3>: movq   %rsp, %rbp
    0x7fff5fc01006 <+6>: andq   $-0x10, %rsp
Process 17849 launched: '/Users/admin/Downloads/App.app/Contents/MacOS/App' (x86_64)
(lldb) process handle -p false -s true
Do you really want to update all the signals?: [y/N] y
NAME         PASS   STOP   NOTIFY
===========  =====  =====  ======
SIGHUP       false  true   true 
... [removed for brevity]
(lldb) c
Process 17849 resuming
Process 17849 exited with status = 45 (0x0000002d) 
(lldb)

แอปสามารถออกโดยไม่ทำให้เกิดสัญญาณ ทางออก _ออก หรือ _ออก ได้อย่างไร

มีวิธีใดใน lldb ที่จะรันกระบวนการและเมื่อออกแล้วให้ 'ย้อนกลับ' เพื่อดูว่ามันออกจากที่ใด

มีวิธีใดที่ lldb จะบันทึกคำสั่งแอสเซมบลีแต่ละคำสั่ง ฯลฯ (เช่นเมื่อมันพัง) เพื่อให้คุณสามารถติดตามกลับเมื่อออกได้หรือไม่?


person Zimm3r    schedule 26.03.2016    source แหล่งที่มา
comment
สามารถเรียกใช้ exit syscall ได้โดยตรง   -  person Jester    schedule 26.03.2016
comment
@Jester จุดดี; และดูเหมือนว่า lldb (ต่างจาก gdb) ไม่สามารถรับ syscall ได้ (ดู stackoverflow.com/questions/26253347/ )   -  person Zimm3r    schedule 26.03.2016
comment
อย่างน้อยที่สุดสำหรับการดูการโทร sys ฉันรัน sudo dtruss -p [pid] ในเทอร์มินัลอื่นหลังจากรันกระบวนการ launch -stop-at-entry ใน lldb เพื่อดูการโทร sys น่าเสียดายที่ไม่มีการเรียก sys ออก   -  person Zimm3r    schedule 28.03.2016
comment
lldb และ dtruss อาจขัดแย้งกันเป็นอย่างดีหากคุณพยายามใช้พร้อมกัน   -  person Jester    schedule 28.03.2016
comment
@Jester ดีใจที่ได้รู้ ฉันจะลองใช้ dtruss เพียงอย่างเดียวและดูว่ามันเปลี่ยนแปลงหรือไม่ ขอบคุณสำหรับข้อมูลใหม่ทั้งหมด! การเรียนรู้มากมาย   -  person Zimm3r    schedule 28.03.2016


คำตอบ (1)


สำหรับผู้ที่สนใจ สามารถดูคำตอบที่แตกต่างออกไปได้ ที่นี่


เกิดอะไรขึ้นที่นี่?

เป็นไปได้มากว่าคุณกำลังเผชิญกับเทคนิคต่อต้านการแก้ไขข้อบกพร่องเช่นนี้:

ptrace(PT_DENY_ATTACH, 0, NULL, 0);

แนวคิดพื้นฐานคือมีเพียงกระบวนการเดียวเท่านั้นที่สามารถ ptrace อีกกระบวนการหนึ่งในเวลาเดียวกัน โดยเฉพาะอย่างยิ่งตัวเลือก PT_DENY_ATTACH จะทำให้แน่ใจว่า tracee ออกด้วยสถานะ ENOTSUP (45) ดู man ptrace เกี่ยวกับ PT_DENY_ATTACH:

คำร้องขอนี้เป็นการดำเนินการอื่นที่ใช้โดยกระบวนการติดตาม อนุญาตให้กระบวนการที่ไม่ถูกติดตามในปัจจุบันปฏิเสธการติดตามในอนาคตโดยพาเรนต์ อาร์กิวเมนต์อื่นๆ ทั้งหมดจะถูกละเว้น หากกระบวนการกำลังถูกติดตามอยู่ กระบวนการจะออกพร้อมกับสถานะการออกของ ENOTSUP มิฉะนั้นจะตั้งค่าสถานะที่ปฏิเสธร่องรอยในอนาคต ความพยายามโดยผู้ปกครองในการติดตามกระบวนการที่ได้ตั้งค่าสถานะนี้จะส่งผลให้เกิดการละเมิดการแบ่งส่วนในผู้ปกครอง

สำหรับสิ่งที่เกี่ยวข้องกับ 45 โปรดดูที่ /System/Library/Frameworks/Kernel.framework/Versions/A/Headers/sys/errno.h:

#define ENOTSUP     45      /* Operation not supported */

จะทำซ้ำสิ่งนี้ได้อย่างไร?

การเขียนโปรแกรมที่แสดงพฤติกรรมเดียวกันนั้นเป็นเรื่องเล็กน้อย:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ptrace.h>

int main() {
    printf("--- before ptrace()\n");
    ptrace(PT_DENY_ATTACH, 0, NULL, 0);
    perror("--- ptrace()");
    printf("--- after ptrace()\n");
    return 0;
}

คอมไพล์ด้วย:

clang -Wall -pedantic ptrace.c -o ptrace

เพียงเรียกใช้ก็จะออกได้สำเร็จ แต่การพยายามแก้ไขข้อบกพร่องจะให้ผลลัพธ์ดังต่อไปนี้:

(lldb) r
Process 4188 launched: './ptrace' (x86_64)
--- before ptrace()
Process 4188 exited with status = 45 (0x0000002d)

เนื่องจากตัวอย่างนี้ค่อนข้างเล็ก คุณจึงสามารถดำเนินการไปจนถึงคำสั่ง syscall:

(lldb) disassemble
libsystem_kernel.dylib`__ptrace:
    0x7fff6ea1900c <+0>:  xorq   %rax, %rax
    0x7fff6ea1900f <+3>:  leaq   0x394f12f2(%rip), %r11    ; errno
    0x7fff6ea19016 <+10>: movl   %eax, (%r11)
    0x7fff6ea19019 <+13>: movl   $0x200001a, %eax          ; imm = 0x200001A
    0x7fff6ea1901e <+18>: movq   %rcx, %r10
->  0x7fff6ea19021 <+21>: syscall
    0x7fff6ea19023 <+23>: jae    0x7fff6ea1902d            ; <+33>
    0x7fff6ea19025 <+25>: movq   %rax, %rdi
    0x7fff6ea19028 <+28>: jmp    0x7fff6ea10791            ; cerror
    0x7fff6ea1902d <+33>: retq
    0x7fff6ea1902e <+34>: nop
    0x7fff6ea1902f <+35>: nop
(lldb) s
Process 3170 exited with status = 45 (0x0000002d)

ดังนั้นจึงเป็นโค้ดเคอร์เนลที่ ฆ่า กระบวนการ แต่ไม่มีสัญญาณหรือ exit syscall ที่เหมาะสม (จนถึงสิ่งนี้และมันยังคงพัดใจของฉัน)

syscall ใดที่ถูกดำเนินการจะถูกกำหนดโดยค่าของการลงทะเบียน EAX ในกรณีนี้ 0x200001A ซึ่งอาจดูแปลกเพราะหมายเลข ptrace syscall เป็นเพียง 26 (0x1a) โปรดดูที่ syscalls.master:

26  AUE_PTRACE  ALL { int ptrace(int req, pid_t pid, caddr_t addr, int data); }

หลังจากขุดมาสักพักฉันก็พบ syscall_sw.h:

#define SYSCALL_CONSTRUCT_UNIX(syscall_number) \
            ((SYSCALL_CLASS_UNIX << SYSCALL_CLASS_SHIFT) | \
             (SYSCALL_NUMBER_MASK & (syscall_number)))

เมื่อคำนวณผลลัพธ์คือ 0x200001A

เหตุใด dtruss ไม่ติดตาม ptrace syscall

การใช้ dtruss ดูเหมือนเป็นความคิดที่ดี แต่น่าเสียดายที่มันไม่ได้รายงาน ptrace syscall (ความเข้าใจของฉันคือ ไม่สามารถทำเช่นนั้นได้ เนื่องจาก ptrace syscall จะไม่ส่งคืนในกรณีนี้)

โชคดีที่คุณสามารถเขียนสคริปต์ DTrace เพื่อบันทึก syscall เมื่อป้อนเข้าไปแล้ว (กล่าวคือ ไม่ใช่หลังจากที่ส่งคืนแล้ว) เพื่อทริกเกอร์พฤติกรรมนี้ โปรแกรมจะต้องเริ่มต้นจาก lldb:

$ lldb ./ptrace
(lldb) process launch --stop-at-entry

สังเกต PID แล้ว:

sudo dtrace -q -n 'syscall:::entry /pid == $target/ { printf("syscall> %s\n", probefunc); }' -p $PID

สุดท้าย continue ใน lldb ผลลัพธ์ควรเป็น:

[...]
syscall> sysctl
syscall> csops
syscall> getrlimit
syscall> fstat64
syscall> ioctl
syscall> write_nocancel
syscall> ptrace

การแก้ปัญหาที่เป็นไปได้

ตอนนี้ เป็นการดีที่จะพังก่อน ptrace syscall และค้นหาโค้ดโปรแกรมที่เรียกใช้หรือข้ามไปสำหรับเซสชันการดีบักปัจจุบัน (LLDB: thread jump -a ADDRESS)

แน่นอนว่าใครๆ ก็พยายามหยุดการเรียกไลบรารี ptrace ได้ แต่หากนี่เป็นจริงและมีโอกาสพยายามป้องกันการดีบักที่การเรียกจริงจะดำเนินการในบล็อก asm ดังนั้นเบรกพอยต์ข้างต้นจะไม่ทริกเกอร์

วิธีแก้ปัญหาที่เป็นไปได้คือใช้ DTrace เพื่อวางเบรกพอยต์ก่อน syscall แต่จำเป็นต้องปิดการใช้งาน System Integrity Protection ดังนั้นฉันจึงไม่ได้ลอง

อีกทางหนึ่งสามารถพิมพ์ userland stacktrace ด้วยฟังก์ชัน ustack():

sudo dtrace -q -n 'syscall:::entry /pid == $target && probefunc == "ptrace"/ { ustack(); }' -p $PID
person cYrus    schedule 11.12.2017
comment
ว้าว ค่อนข้างจะเขียนได้นะ ฉันจะดูอย่างแน่นอนว่านี่คือสิ่งที่ฉันประสบอยู่หรือไม่และหวังว่าจะรายงานกลับมา - person Zimm3r; 24.12.2017