The basic of developing iOS Tweak (Part 3/4)

Witsarut L.
INCOGNITO LAB
Published in
6 min readMay 9, 2019
ขั้นตอนการพัฒนา Tweak

ใน Part 2 เราได้วิเคราะห์ application จนถึงขั้นตอนที่ 2 static analysis แบบเบื้องต้นกันไปแล้ว โดยพบ class, method และ variable ที่น่าจะเกี่ยวข้องกับ exercise Personal Photo Storage ก็คือ class PersonalPhotoStorageVC , method — (id)thePw และ instance variable NSString *_pw ที่อยู่ในไฟล์ PersonalPhotoStorageVC.h

ข้อมูลภายในไฟล์ PersonalPhotoStorageVC.h

สำหรับขั้นตอนต่อไป คือขั้นตอนที่ 3 dynamic analysis จะเป็นการวิเคราะห์ application ขณะที่กำลังทำงานอยู่ โดยจะเน้นไปที่การดูการทำงานภายในของ application iGoat และพิสูจน์ว่า class PersonalPhotoStorageVC , method — (id)thePw และ instance variable NSString *_pw เป็นไปอย่างที่ตั้งสมมติฐานไว้ใน Part ที่แล้วหรือไม่

มาเริ่มกันเลยครับ

ขั้นตอนที่ 3 dynamic analysis

เข้าใจ Concept เพิ่มเติม

iOS application ถูกพัฒนาโดยใช้ design pattern แบบ MVC (Model-View-Controller) และแต่ละส่วนมีความสัมพันธ์กันตามรูป

MVC design pattern (ที่มา: https://developer.apple.com/)

View คือ ส่วนที่รับผิดชอบในการแสดงผลของ application เมื่อมี user action เข้ามาก็จะส่งไปให้ controller ประมวลผล

Model คือ ส่วนที่ทำหน้าที่ในการเก็บข้อมูลของ application

Controller คือ ส่วนที่ทำหน้าที่ในการประมวลผลหลักหรือเปรียบเสมือนส่วนที่เป็นสมองของ application อีกทั้งยังเป็นสื่อกลางระหว่าง view และ model

หากเราอยากจะรู้วิธีการทำงานของ application แน่นอนว่าจะต้องหา controller ซึ่งเป็นส่วนหลักในการประมวลผลให้เจอ เมื่อหา controller เจอแล้วเรายังจะสามารถหา model หรือ view ที่เกี่ยวข้องกับ controller ดังกล่าวต่อไปได้อีก

ในกรณีที่ยังไม่รู้ หรือไม่แน่ใจว่า controller ไหนกันแน่ที่ควบคุมส่วนที่เราสนใจอยู่ วิธีการในการหา controller ที่ง่ายที่สุดคือการหาจาก view object หรือส่วนของ user interface ที่เราเห็นบน application นั่นเอง

จากข้อมูลที่พบตั้งแต่ Part 2 เราสามารถเดาได้ว่า view controller PersonalPhotoStorageVC น่าจะเป็นส่วนที่ควบคุม view object ของ exercise Personal Photo Storage และใน Part 3 นี้จะเป็นการแสดงวิธีหา view controller จาก view object กันครับ เพื่อพิสูจน์ว่าสิ่งที่เราตั้งสมมติฐานไว้ ถูกต้องหรือไม่ แต่ก่อนที่จะเริ่มหาได้ เราต้องมาทำความรู้จักกับเครื่องมือที่จะใช้กันก่อน

ทำความรู้จักและติดตั้งเครื่องมือ

ในขั้นตอนนี้เราจะใช้เครื่องมือประเภท Dynamic Instrumental Toolkit มาวิเคราะห์การทำงานของ application กัน โดยเครื่องมือที่จะใช้คือ Cycript ซึ่งเป็นเครื่องมือที่พัฒนาโดย Jay Freeman (saurik) (คน ๆ เดียวกันกับที่พัฒนา Cydia) หรืออีกทางเลือกหนึ่งก็คือการใช้ Frida ก็สามารถทำได้เช่นเดียวกัน

ทั้ง Cycript และ Frida จะมีวิธีการทำงานคร่าว ๆ คือ เครื่องมือพวกนี้จะ inject dylib ของตัวเองเข้าไปยัง process ของ application เป้าหมาย ทำให้เราสามารถ interact กับ Objective-C runtime ของ process เป้าหมายได้ อีกทั้งยังสามารถที่จะอ่าน เขียน หรือแก้ไข memory ส่วนใดส่วนหนึ่งของ process ได้เช่นเดียวกัน (ถ้า memory ในส่วนนั้นอนุญาตให้ application สามารถทำได้)

ใน iOS version < 11 การติดตั้ง Cycript สามารถค้นหาและติดตั้งผ่าน Cydia ได้ทันที สำหรับ iOS 11 จะต้องติดตั้ง Cycript จาก repository ต่อไปนี้ จึงจะสามารถใช้งานได้ปกติ (Cycript ใน repository นี้จะเป็น version 0.9.520 ที่ถูกแก้ไขให้ทำงานบน iOS 11 ได้)

https://jakeashacks.ga/cydia/

หรือหากไม่อยากจะใช้ repository ข้างต้น ก็สามารถใช้วิธีการอื่นได้โดยดูรายละเอียดที่นี่

ใช้ Cycript ทำ dynamic analysis

อันดับแรกจะต้องเปิด iGoat ขึ้นมา จากนั้น SSH ไปยังอุปกรณ์ และใช้คำสั่ง ps เพื่อดูว่า iGoat มี process id และ executable name เป็นอะไร

ใช้คำสั่ง ps เพื่อหา process id และ executable name ของ iGoat

-a = แสดง process ของทุก user

-x = แสดง process ที่ไม่ได้ attach กับ terminal ด้วย

จากผลลัพธ์จะเห็นว่า iGoat มี process id = 3042 และ executable name = iGoat และการ inject Cycript ไปยัง process สามารถใช้ option -p และตามด้วย process id หรือ executable name ก็ได้

Inject Cycript ไปยัง process iGoat ได้สำเร็จ

ภาษาที่สามารถใช้ใน Cycript console ได้ คือ Javascript และ Objective-C โดยสามารถที่จะใช้ syntax ของทั้งสองภาษาผสมกันได้ในเวลาเดียวกัน

หา view controller จาก view

เมื่อ inject Cycript ไปที่ process ของ iGoat ได้แล้ว จากนั้นเข้าไปยังหน้าของ exercise PersonalPhotoStorageVC และใช้ code ต่อไปนี้ (ที่เป็น Objective-C ผสมกับ Javascript) เพื่อแสดง view hierarchy ของหน้าปัจจุบันออกมา

cy# [[UIApp keyWindow] recursiveDescription].toString()

UIApp = ย่อมาจาก [UIApplication sharedApplication] ซึ่งจะ return instance ของ UIApplicaiton ออกมา

keyWindow = return instance ของ UIWindow ที่ปรากฎอยู่บนหน้าจอปัจจุบัน

recursiveDescription = method ที่ใช้แสดง view hierarchy ของ window นั้น ๆ

ซ้ายคือ view hierarchy, ขวาคือ เมนูที่ปรากฎอยู่บนหน้าจออุปกรณ์

เลือก view object ที่เราเห็นได้มา 1 object โดยในตัวอย่างจะเลือก instance ของ UILabel ที่แสดงข้อความ “Enter Password to Access Private Photos” และมี address ใน memory อยู่ที่ 0x1080f6ef0

วิธีการในการระบุ instance เพื่อทำการ interact กับ instance นั้นสามารถระบุได้โดยใช้เครื่องหมาย # และตามด้วย address ของ instance เช่น #0x1080f6ef0 ซึ่งจะทำให้เราสามารถที่จะเรียกใช้ instance method ได้

Note: การเรียกใช้ class method สามารถทำได้โดยใช้ syntax [ClassName MethodName] หรือ ClassName.MethodName() ได้เลย

Responder

ใน iOS application จะมีสิ่งที่เรียกว่า Responder object ซึ่งมีหน้าที่ในการจัดการ event (เช่น touch, press เป็นต้น) ที่เข้ามา หาก Responder object นั้นไม่สามารถจัดการ event ดังกล่าวได้ ก็จะ forward ไปให้ Responder object ถัดไปที่ถูกกำหนดไว้ โดย Responder object จะต่อกันไปเรื่อย ๆ เป็น Responder Chain ซึ่ง instance ของ class UIResponder, UIView, UIController และ UIApplication จะมีลักษณะเป็น Responder object โดยธรรมชาติอยู่แล้ว

และโดยปกติ view controller ของ view object เป้าหมาย ก็จะอยู่ใน Responder Chain ที่ว่ามาด้วย ทำให้หากเราไล่ตาม chain ไปเรื่อย ๆ ก็จะพบกับ view controller ที่ควบคุม view object นั้นอยู่

Code ต่อไปนี้จะเป็นการเรียกใช้ instance method nextResponder เพื่อไล่ไปตาม Reponder Chain และเมื่อไล่ไปเรื่อย ๆ เราก็จะพบกับ view controller ที่ควบคุม exercise นี้อยู่ ซึ่งก็คือ PersonalPhotoStorageVC

หา Controller โดยใช้ method nextResponder

Note:

1.instance method isKindOfClass เอาไว้ใช้ตรวจสอบว่า instance นั้น ๆ เป็น class อะไร

2.ส่วนใหญ่แล้ว view controller อันแรกที่พบเมื่อไล่ไปตาม Responder Chain คือ controller ที่ควบคุม view object เป้าหมายของเราอยู่

หรือทางที่ง่ายกว่า คือ การใช้ instance method _printHierarchy (ซึ่งเป็น method ที่มีอยู่ใน class UIViewController อยู่แล้ว) ตามตัวอย่าง

หา Controller โดยใช้ method _printHierarchy

rootViewController = root view controller ของ window

_printHierarchy = method ที่ใช้แสดง view controller hierarchy ของ view controller นั้น ๆ ออกมา

พิสูจน์สมมติฐาน

เมื่อหา view controller และ instance พบแล้ว ขั้นตอนต่อไปก็คือการทดสอบเข้าถึง instance variable NSString *_pw ว่าเป็น variable ที่เก็บ password ไว้จริงหรือไม่ โดยใช้คำสั่งดังรูป

เข้าถึง instance variable _pw

0x109f195e0 คือ address ของ instance ของ PersonalPhotoStorageVC

password ของ exercise นี่คือ “opensesame” นั้นเอง

ทดสอบเรียกใช้งาน instance method — (id)thePw ก็พบว่าเป็นไปตามที่ได้ตั้งสมมติฐานไว้ คือ method ดังกล่าว return password กลับมา

เรียกใช้งาน method thePw

ขั้นตอนที่ 4 ทดสอบแก้ไข component

จากที่สามารถหา password ที่ถูกต้องได้แล้ว ขั้นตอนต่อไปคือการหา instance ของ text field เพื่อทำการแก้ไข text ให้แสดงเป็น password ที่เราพบ (instance variable _pw)

จาก Part 2 จะพบว่า class PersonalPhotoStorageVC มี property theTextField อยู่ ซึ่งเป็น text field เป้าหมายที่เรากำลังหาอยู่แน่นอน ว่าแล้วก็มาทดลองแก้ไขกันเลยครับ

ทดสอบแก้ไข property text ของ text field

บรรทัดที่ 1 คือ การเรียกใช้ method setText และส่ง argument เป็นค่าของ _pw เข้าไป

บรรทัดที่ 2 คือ การเรียก object ออกมาเพื่อดูว่า property text ได้เปลี่ยนไปเป็น password แล้วหรือไม่

Code ด้านล่างจะเป็นการเปลี่ยนสีตัวอักษรใน text field ให้เป็นสีแดง

cy# #0x109f195e0.theTextField.textColor = [UIColor redColor]

ผลลัพธ์สุดท้ายจะเห็นว่าเป็นไปตามเป้าหมายที่เราตั้งไว้

ผลลัพธ์สุดท้ายจากการทดสอบแก้ไข component

แต่ภารกิจยังไม่จบ.. จะเห็นว่าขั้นตอนข้างต้นเราเป็นคนหา instance และแก้ไขเองทั้งหมด แต่หากต้องการให้ เมื่อมีการ load หน้า exercise Personal Photo Storage ขึ้นมา ใน text field จะต้องแสดง password ที่ถูกต้องและเป็นตัวอักษรสีแดง โดยอัตโนมัติ จะทำอย่างไร?

คำตอบก็คือ จะต้องเอา code ที่เราทดสอบแก้ไขข้างต้น ไปเพิ่มใน method viewDidLoad ของ class PersonalPhotoStorageVC

โดย method viewDidLoad จะเป็น method ที่ถูกเรียกใช้อัตโนมัติเมื่อ view hierarchy ทั้งหมดของ view controller นั้นถูก load ไปบน memory เรียบร้อยแล้ว ซึ่งเป็นจุดที่เหมาะสมที่สุดที่จะใช้แก้ไข text field

คำถามต่อมาคือ แล้วจะเพิ่มอย่างไร? ก็เพิ่มโดยการใช้เทคนิคที่เรียกว่า method swizzling ที่เคยพูดถึงไปใน Part 1 นั้นเอง

Method swizzling

สำหรับการทำ method swizzling บน Cycript โดยเพิ่ม code ของเราต่อท้ายไปกับการทำงานเดิมของ method สามารถทำได้โดยการเรียกใช้งานฟังก์ชั่น MS.hookMessage (ซึ่งฟังก์ชั่นนี้ จะไปเรียกใช้ MSHookMessageEX ของ Cydia Substrate อีกที) โดยก่อนที่จะสามารถเรียกใช้งานฟังก์ชั่นนี้ได้ จะต้องทำการ import Cycript script ตาม code ต่อไปนี้ก่อน

@import com.saurik.substrate.MS

หาก script ที่ import มามีอาการแปลก ๆ หรือหาไฟล์ script ไม่เจอ สามารถไปดาวน์โหลดไฟล์ MS.cy ใหม่ได้ที่นี่ และนำไปไว้ที่ path /usr/lib/cycript0.9/com/saurik/substrate/ เท่านี้ MS.cy ก็จะสามารถใช้งานได้ปกติ

จากนั้นจึงเรียกใช้งานฟังก์ชั่น MS.hookMessage ซึ่งมี syntax ดังต่อไปนี้

MS.hookMessage(ClassName, @selector(MethodName), function(){ doSomething;}, old_method_pointer)

โดยส่วนของ function(){doSomething;} คือส่วน implementation ใหม่ที่จะเอาไปแทนที่ implementation เดิมของ method viewDidLoad

เมื่อเอาผลการวิเคราะห์และผลการทดสอบทั้งหมดมาประกอบกัน เราจะได้ Cycript script version สุดท้ายตามด้านล่าง

บรรทัดที่ 4 คือการเรียกใช้งาน implementation เดิมของ method viewDidLoad

บรรทัดที่ 5 คือการเปลี่ยน text ของ text field ให้แสดง password

บรรทัดที่ 6 คือการเปลี่ยนสีตัวอักษรใน text field ให้เป็นสีแดง

จากตัวอย่าง script ข้างบน เราสามารถที่จะพิมพ์ทีละบรรทัดลงไปใน Cycript console หรือเก็บเป็นไฟล์ และใช้ Cycript เรียกใช้งานในภายหลังก็ได้เช่นกัน โดยใช้คำสั่งด้านล่าง

cycript -p iGoat script.cy

ผลสุดท้ายที่ได้คือเมื่อเข้าไปยัง exercise PersonalPhotoStorageVC ในช่อง text field ก็จะปรากฎ password สีแดงที่ถูกต้องเสมอ

ผลลัพธ์สุดท้ายจากการใช้ Cycript script

Note: หากไม่ต้องการเรียกใช้งาน implementation เก่าของ method เราสามารถทำ method swizzling ได้ง่าย ๆ โดยใช้ syntax ClassName.prototype[“MethodName”] = function(){dosomething;}

ทิ้งท้าย…

จากขั้นตอนการวิเคราะห์ทั้งหมดจะเห็นได้ว่ามีขั้นตอนที่ค่อนข้างง่าย เพราะชื่อ class, method, variable และ property สื่อความได้ตรงไปตรงมา แต่หากชื่อของ class, method, variable และ property ไม่ได้บอกอะไรเราเลยหล่ะว่ามันมีไว้ใช้ทำอะไร? หรือเก็บค่าอะไร? เราก็จะต้องใช้เครื่องมืออื่นในการทำ static analysis (เช่น Hopper, IDA, Ghidra) และ dynamic analysis (เช่น LLDB, GDB) เพิ่มเติม เพื่อให้เข้าใจมากยิ่งขึ้นว่าใน class หรือ method นั้น ๆ มีการทำงานจริง ๆ เป็นอย่างไร โดยจะมีกล่าวถึงในบทความถัด ๆ ไป (ซึ่งไม่ใช่ในบทความ basic นี้) ครับ

เมื่อผ่านขั้นตอนที่ 4 แล้วเราก็จะได้ prototype Tweak (ที่เป็น Cycript script) ที่เราต้องการ และในขั้นตอนสุดท้าย คือ ขั้นตอนที่ 5 เราก็จะนำ prototype ที่ได้ ไปทำการพัฒนาโดยใช้ Theos ให้เป็น Tweak ที่สมบูรณ์ต่อไปครับ

Note: สำหรับ Trick ในการใช้งาน Cycript เพิ่มเติม สามารถดูได้ที่นี่

https://iphonedevwiki.net/index.php/Cycript

https://iphonedevwiki.net/index.php/Cycript_Tricks

http://iosre.com/t/powerful-private-methods-for-debugging-in-cycript-lldb/3414

--

--

Witsarut L.
INCOGNITO LAB

Penetration tester, security researcher at Incognito Lab.