การวิเคราะห์โปรแกรมประสงค์ร้ายบนระบบแอนดรอยด์ (Analysis on com.gzrtnq.Bumble-Part 1)

ปัจจุบันโปรแกรมประสงค์ร้าย (Malware) ที่แฝงมากับภัยเงียบในระบบแอนดรอยด์ (Android) มีแนวโน้มการโจมตีบ่อยขึ้นในช่วง 2-3 ปี ให้หลัง ทาง McAidenจึงได้ทำการวิเคราะห์โปรแกรมดังกล่าว (Application) ที่ถูกเผยแพร่สื่อต่าง ๆ ซึ่งสามารถดาวโหลด เพื่อนำไปศึกษา ได้ตามลิงค์นี้ https://cc2.lol

เขียน Juttikhun Jirathanan
แปล/เรียบเรียง Pongsathon Sirithanyakul และ Warunyou Sunpachit

ตัวอย่าง ข้อความสนทนาของเหยื่อ อ้างอิงจากบทความ Medium – Narunc

เมื่อเข้าไปหน้าเว็บไซต์ดังกล่าว (สำหรับเครื่องติดตั้ง Antivirus อาจจะมีการแจ้งเตือหรือป้องกันถึงภัยคุกคาม) จะมีหน้าสำหรับดาวน์โหลดโปรแกรมใน 2 ระบบปฏิบัติการ ทั้ง iOS และ Android แต่เราสามารถดาวน์โหลดได้เฉพาะ Android เท่านั้น

ตัวอย่างหน้าจอของการดาวน์โหลดของโปรแกรมประสงค์ร้าย

Application Structure

ทางทีมได้ทำการตรวจสอบโครงสร้างของโปรแกรมดังกล่าว โดยใช้วิธีการวิเคราะห์แบบ Static มีลักษณะดังต่อไปนี้

โครงสร้างของโปรแกรมประสงค์ร้าย

หลังจากทำการ Decompileตัวโปรแกรมดังกล่าว โดยใช้ jadx (https://github.com/skylot/jadx) พบว่ามีบางไฟล์ที่ดูน่าสนใจ ดังนี้

  • “libjiagu.so”  ซึ่งใช้สำหรับป้องกันไฟล์ APKจากการทำ Reverse engineering ได้ง่าย ๆ โดยการเก็บขั้นตอนต่าง ๆ ที่โปรแกรมประสงค์ร้ายจะทำงาน (app logics) และเปิดเผยมันออกมาเฉพาะตอนใช้งานแล้วเท่านั้น (Runtime)
  • “easyagent” ซึ่งเป็นไฟล์ APK
  • “tg.iapk” ซึ่งเป็นไฟล์ ZIP และ password ที่ถูกป้องกัน และ APK file ที่ถูกเข้ารหัสไว้

AndroidManifest.xml

โดยทั่วไปโปรแกรมที่จะติดตั้งและใช้งานบน Android จำเป็นต้องประกาศ Permissionsและ Attributeในไฟล์ AndriodManifest.xml เพื่อให้ได้สิทธิต่าง ๆ ในการใช้งานบนระบบปฏิบัติการ Android

จากการวิเคราะห์โปรแกรมสามารถอธิบายสิทธิที่สำคัญ (Android Dangerous Permission) ที่ถูกร้องขอโดยโปรแกรมประสงคร้ายดังนี้

Permission RequestedDescription
android.permission.CALL_PHONEอนุญาตให้แอปพลิเคชันสามารถโทรออกโดยไม่ต้องมีการกระทำจากผู้ใช้งานบนโทรศัพท์ Android
android.permission.PROCESS_
OUTGOING_CALLS
อนุญาตให้แอปพลิเคชันแก้ไขการโทรออกเช่น เปลี่ยนหมายเลขโทรออก หรือแม้แต่วางหรือเปลี่ยนเส้นทางการโทร
android.permission.GET_TASKSอนุญาตให้แอปพลิเคชันสามารถดึงข้อมูลเกี่ยวกับงาน (tasks) ที่กำลังทำงานอยู่ในขณะนั้นบนอุปกรณ์ Android ได้
android.permission.MOUNT_UNMOUNT_
FILESYSTEMS
อนุญาตให้แอปพลิเคชัน Mount และ Unmount ระบบไฟล์
android.permission.READ_SMSอนุญาตให้แอปพลิเคชันอ่าน SMS เช่น OTP
android.permission.SEND_SMSอนุญาตให้แอปพลิเคชันส่งข้อความ SMS
android.permission.RECORD_AUDIOอนุญาตให้แอปพลิเคชันบันทึกเสียง
android.permission.REQUEST_
INSTALL_PACKAGES
อนุญาตให้แอปพลิเคชันขอให้ผู้ใช้ติดตั้งแพ็คเกจอื่น ๆ
android.permission.WRITE_APN_SETTINGSอนุญาตให้แอปพลิเคชันแก้ไข APN เซลลูลาร์
android.permission.WRITE_SETTINGSอนุญาตให้แอปพลิเคชันแก้ไขการตั้งค่าอุปกรณ์
android.permission.INJECT_EVENTSอนุญาตให้แอปพลิเคชันส่งเหตุการณ์อินพุต (การแตะหน้าจอ ฯลฯ) ไปยังแอปพลิเคชันอื่น
android.permission.SYSTEM_ALERT_WINDOWอนุญาตให้แอปพลิเคชันสร้างหน้าต่าง เหนือ Activity อื่นๆ (นำไปสู่การ overlay attack)
android.permission.BIND_
ACCESSIBILITY_
SERVICE
อนุญาตให้แอปพลิเคชันเข้าถึงเนื้อหาของแอปพลิเคชันที่กำลังทำงานอยู่ (รวมถึงแอปพลิเคชันอื่นๆ)

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

File: tg.iapk

ตารางต่อไปนี้อธิบายข้อมูลไฟล์:

File name tg.iapk
File signature 634e70a18127375f47c7b40564142ca5597640056a1745b701f7cec3c7701ff1
File Size 2 MB
File Type ZIP & Password Protected & Encrypted

Decrypting tg.iapk

หลังจากพยายามแตกไฟล์ tg.iapk พบว่าไฟล์นั้นมีการใส่รหัสผ่านป้องกันเอาไว้

ไม่สามารถแตกไฟล์ ZIP ได้

พบรหัสผ่านในส่วนของ Comment ของ ZIP ซึ่งเป็นข้อความต่อไปนี้:

Archive:  tg.iapk
1@386662363963333636636566653438373535363939303135383730373632356332316564636239302d356463312d343737322d623936352d396361653239613234363637
   creating: tg.iapk_unzip/com/plugin/
   creating: tg.iapk_unzip/com/plugin/tePlugin/
[tg.iapk] com/plugin/tePlugin/activityUtil.java password:

การแยกค่าฐานสิบหก (Hex) และถอดรหัส จะได้ข้อความต่อไปนี้ (Actual ZIP password):

$ python
>>> hexStr = "386662363963333636636566653438373535363939303135383730373632356332316564636239302d356463312d343737322d623936352d396361653239613234363637"
>>> bytearray.fromhex(hexStr).decode()
'8fb69c366cefe487556990158707625c21edcb90-5dc1-4772-b965-9cae29a24667'
>>>

หลังจากแตกไฟล์ tg.iapk แล้วพบว่าไฟล์ในนั้นถูกเข้ารหัสและไม่สามารถอ่านได้ง่าย

$ file tg.iapk_unzip/com/plugin/tePlugin/activityUtil.java 
tg.iapk_unzip/com/plugin/tePlugin/activityUtil.java: data
                                                                                                                                                                                                       
$ strings tg.iapk_unzip/com/plugin/tePlugin/activityUtil.java 
3864303638303136376538646535616239316235373665633534323030363465313637323739363638313330357c3132333435367c313637323739363638313330357c31353239
VTC^A^CNbC^[
]VAVGVT\VPR
G[BP^Y
CRg[BP^Y
:=^ZGXEC
[...]

$ hd -C tg.iapk_unzip/com/plugin/tePlugin/activityUtil.java
00000000  00 09 23 83 00 00 00 8e  33 38 36 34 33 30 33 36  |..#.....38643036|
00000000  00 09 23 83 00 00 00 8e  33 38 36 34 33 30 33 36  |..#.....38643036|
00000010  33 38 33 30 33 31 33 36  33 37 36 35 33 38 36 34  |3830313637653864|
00000010  33 38 33 30 33 31 33 36  33 37 36 35 33 38 36 34  |3830313637653864|
00000020  36 35 33 35 36 31 36 32  33 39 33 31 36 32 33 35  |6535616239316235|
00000020  36 35 33 35 36 31 36 32  33 39 33 31 36 32 33 35  |6535616239316235|
00000030  33 37 33 36 36 35 36 33  33 35 33 34 33 32 33 30  |3736656335343230|
00000030  33 37 33 36 36 35 36 33  33 35 33 34 33 32 33 30  |3736656335343230|
[...]

รูปต่อไปนี้แสดงตัวอย่างไฟล์ที่เข้ารหัสทั้งหมด (update.json):

ตัวอย่างของเนื้อหาไฟล์ที่ถูกเข้ารหัส

หลังจากทำการวิเคราะห์การเข้ารหัสในไฟล์ที่เข้ารหัสแล้ว เราก็หาวิธีค้นหาคีย์เข้ารหัสได้ (encryption key) ไฟล์ถูกเข้ารหัสโดยใช้อัลกอริทึม XOR ไฟล์ต่างๆ จะถูกเข้ารหัสด้วยคีย์ที่แตกต่างกัน หากต้องการค้นหาคีย์สำหรับแต่ละไฟล์ เราจะ XOR ส่วนชื่อไฟล์ที่เข้ารหัสด้วยชื่อไฟล์จริง จากนั้นจึงเปิดเผยคีย์ นี่แสดงโครงสร้างไฟล์:

4 bytes [file magic number]
4 bytes [file header length]
N bytes [file header]
4 bytes [file name length]
[don’t care]
N bytes [file name]
N bytes [file content]

ในการสาธิต เราใช้ไฟล์เข้ารหัส “update.json” ซึ่งมีเนื้อหาฐานสิบหก (Hex) ดังนี้:

File: update.json

00092383 0000008A 33303336 33323339 33303331 33363337 33313335 36323335 33323633 33303632 33313336 36333331 33333632 36323635 33353633 36363633 36363632 33313634 33313336 33373332 33373339 33363336 33383331 33363330 33323763 33313332 33333334 33353336 37633331 33363337 33323337 33393336 33363338 33313336 33303332 37633334 33340000 000B0000 002C181D 090C1908 43071E02 0316674D 4D4F181D 090C1908 32181F01 4F574D4F 4F41674D 4D4F1B08 1F1E0402 034F574D 4F5F435C 435F4F67 10

File magic number: 00092383

File header length: 0000008A (138 bytes)

File header content: 33303336 33323339 33303331 33363337 33313335 36323335 33323633 33303632 33313336 36333331 33333632 36323635 33353633 36363633 36363632 33313634 33313336 33373332 33373339 33363336 33383331 33363330 33323763 33313332 33333334 33353336 37633331 33363337 33323337 33393336 33363338 33313336 33303332 37633334 3334

File header (decode as hex):

3036323930313637313562353263306231366331336262653563666366623164313637323739363638313630327c3132333435367c313637323739363638313630327c3434

// decode as hex again
0629016715b52c0b16c13bbe5cfcfb1d1672796681602|123456|1672796681602|44

เลขฐานสิบสุดท้ายระบุความยาวของเนื้อหาไฟล์ ในกรณีนี้คือ 44 ไบต์

ความยาวชื่อไฟล์: 000B0000 (11 bytes)

ชื่อไฟล์: 181D090C 19084307 1E0203

เนื้อหาไฟล์: 16674D4D 4F181D09 0C190832 181F014F 574D4F4F 41674D4D 4F1B081F 1E040203 4F574D4F 5F435C43 5F4F6710

ชื่อไฟล์คือ “update.json” เราสามารถใช้กับ XOR ได้ด้วยชื่อไฟล์ที่เข้ารหัสดังนี้:

สามารถค้นพบกุญแจ (key) สำหรับการเข้ารหัสไฟล์

คีย์ที่ใช้เข้ารหัสไฟล์คือ “m” หรือ 0x6d เราสามารถใช้เพื่อถอดรหัสเนื้อหาไฟล์:

การถอดรหัสของเนื้อหาไฟล์

ไฟล์ python ถูกสร้างขึ้นเพื่ออำนวยความสะดวกในการถอดรหัสไฟล์ที่เข้ารหัส:

#!/usr/bin/env python


# Author: McAiden Consulting Co., Ltd. (2023.01.23)
# To PoC how to decrypt files in tg.iapk
import sys
import binascii

def xor(b1, b2): # use xor for bytes
	result = b""
	for b1, b2 in zip(b1, b2):
		result += bytes([b1 ^ b2])
	return result


with open(sys.argv[1], 'rb') as f:
	fileName = sys.argv[1]
	data = f.read()
	fileMagic = binascii.hexlify(data[0:4]).decode("ascii")
	fileHeaderLengthStr = binascii.hexlify(data[4:8]).decode("ascii")
	fileHeaderLength = int(fileHeaderLengthStr,16)
	fileHeader = binascii.hexlify(data[8:8+fileHeaderLength])
	decodedFileHeader = bytes.fromhex(bytes.fromhex(fileHeader.decode("ascii")).decode("ascii")).decode("ascii")

	fileContentLength = int(str(decodedFileHeader).rsplit('|', 1)[-1],0)
	fileNameLength = len(fileName)
	encryptedFileName = data[(-1)*(fileNameLength + fileContentLength):len(data) - fileContentLength]
	fileContent = data[(-1)*fileContentLength:]

	print("[!] fileMagic: " + str(fileMagic))
	print("[!] fileHeaderLength: " + str(fileHeaderLengthStr) + ", " + str(fileHeaderLength))
	print("[!] fileHeader: " + str(fileHeader))
	print("[!] decodedFileHeader: " + str(decodedFileHeader))
	print("[!] fileContentLength: " + str(fileContentLength))
	print("[!] fileNameLength: " + str(fileNameLength))
	print("[!] encryptedFileName: " + str(binascii.hexlify(encryptedFileName)))
	print("[!] fileContent: " + str(binascii.hexlify(fileContent)))

	a = fileName.encode('utf-8')[0:1]
	b = encryptedFileName[0:1]
	print("[!] a: " + str(a))
	print("[!] b: " + str(b))
	
	c = xor(a,b)
	key = c
	print("[!] key: " + str(key))

	decryptedContent = b""
	for i in range(0,len(fileContent)):
		decryptedContent += xor(fileContent[i:i+1], key)

	try:
		newFileName = "decrypted_"+fileName
		f = open(newFileName, "wb")
		f.write(decryptedContent)
		f.close()
		print("[!] saved to file: " + newFileName)
	except Exception as e:
		raise e

ถอดรหัสไฟล์ (decrypt)

การรันสคริปต์ภาษาไพธอนเพื่อถอดรหัสไฟล์ AES.java
ตัวอย่างการถอดรหัส AES.java

tg.iapk Structure

รูปต่อไปนี้แสดงโครงสร้างไฟล์ tg.iapk:

 โครงสร้างไฟล์ tg.iapk

มีหลายไฟล์ที่น่าสนใจ เราถอดรหัสไฟล์ทั้งหมดเริ่มต้นด้วย main.jar

main.jar ที่ถอดรหัสเป็นไฟล์คลาสJava โดยการถอดรหัสโดยใช้คำสั่ง jadx ต่อไปนี้

$ jadx --comments-level debug decrypted_main.jar -d decrypted_main_jadx

ในไฟล์นี้ เราพบคลาสที่น่าสนใจ ใน main.jar เราพบว่ามีการประกาศซึ่งประกอบด้วยชื่อแพ็กเกจ MOBILE BANKING (9 แอป) และ CRYPTO WALLET (6 แอป)

ตัวอย่างการถอดรหัส main.jar

มีการประกาศชื่อแพ็คเกจที่ระบุว่ากำหนดเป้าหมายแอพ crypto wallet, แอพธนาคารไทย (7 แอพ) และสิงคโปร์ (2 แอพ) นี่ไม่ได้หมายความว่าแอปในรายการมีจุดอ่อนหรือช่องโหว่เฉพาะเจาะจง แต่เป็นเพียงรายการที่แอปมุ่งเน้น

Overlay Attack

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

โค้ดที่ถอดรหัสมาเกี่ยวข้องกับการโจมตีแบบซ้อนทับ (overlay attack)

หน้าต่างที่แอปสร้างขึ้นจะส่งเหตุการณ์ไปยังกิจกรรมด้านล่างเช่น แตะโดยใช้  FLAG_NOT_TOUCHABLE ซึ่งหมายความว่าไม่สามารถสัมผัสหน้าต่างซ้อนทับได้โดยตรง (มองไม่เห็นและไม่สามารถสัมผัสได้)

Android Accessibility Service

Android’s Accessibility Services (AAS) ของ Android เป็นคุณลักษณะที่ออกแบบมาสำหรับผู้ใช้ที่มีความพิการบางอย่าง สามารถใช้งาน Android อย่างสะดวกขึ้น ผู้ใช้ต้องเปิดใช้งานในการตั้งค่าอุปกรณ์

หน้าต่างแสดงคำอธิบายที่เกี่ยวกับการอนุญาต Android’s Accessibility Services (AAS)

เมื่อเปิดใช้งาน AAS สำหรับแอปแล้ว แอปจะสามารถช่วยผู้พิการได้โดยทำสิ่งต่อไปนี้:

  • อ่านข้อความและพูด
  • เปลี่ยนการตั้งค่าการแสดงผลเช่น สี ขนาดตัวอักษร
  • ทำการจดจำเสียงหรือท่าทางตามความสามารถของอุปกรณ์

มัลแวร์นี้ยังขอให้ผู้ใช้เปิดใช้งานการเข้าถึงสำหรับแอป

ตัวอย่างขอแอปขอใช้งาน AAS

การประกาศการเข้าถึงใน AndroidManifest.xml:

 Accessibility service จะต้องถูกประกาศใน AndroidManifest.xml

นี่คือการกำหนด Accessibility Service  ใน a.xml:

 Accessibility service ถูกตั้งค่าอยู่ใน /res/xml/a.xml

ตรวจสอบการตั้งค่า AAS ใน a.xml แอตทริบิวต์ canRetrieveWindowContent ช่วยให้แอปดึงเนื้อหาจากหน้าต่างที่ใช้งานอยู่ซึ่งผู้ใช้เปิดอยู่ accessibilityEventTypes="typeAllMask" ซึ่งช่วยให้ AAS จัดการกิจกรรมการเข้าถึงทุกประเภท รวมถึง TYPE_WINDOW_CONTENT_CHANGED สิ่งนี้นำไปสู่ความเป็นไปได้ในการใช้งานอย่างน้อย 2 ประการ:

  • อ่านข้อความจากหน้าจอ เนื่องจากสามารถสังเกตการเปลี่ยนแปลงของเนื้อหาได้ ดังนั้น AAS จึงรู้เนื้อหาบนหน้าจอและบันทึกได้ง่าย
  • การบันทึกคีย์ หากผู้ใช้พิมพ์บนหน้าจอและเนื้อหาหน้าจอมีการเปลี่ยนแปลง เพื่อให้ AAS สามารถสังเกตได้

เฉพาะ AAS เท่านั้น หากเปิดใช้งานบนอุปกรณ์สำหรับมัลแวร์ ก็เพียงพอแล้วสำหรับผู้โจมตีที่จะดำเนินกิจกรรมที่อันตราย เช่น การขโมยข้อมูล ในส่วน AAS ไม่เพียงแต่สังเกตหน้าจอเพื่ออ่านเท่านั้น แต่สามารถดำเนินการแทนผู้ใช้ได้ รูปต่อไปนี้แสดงวิธีใช้ AAS เพื่อดำเนินการ ACTION_CLICK(16) โดยการเรียก AccessibilityNodeInfo.performAction(16) บนหน้าจอโดยที่ผู้ใช้ไม่ต้องสัมผัส:

ขณะนี้ AAS สามารถสังเกตเนื้อหาบนหน้าจอ จากนั้นดำเนินการคลิก สิ่งนี้นำไปสู่ความสามารถดังต่อไปนี้:

  • ขออนุญาตใหม่และอนุญาตโดยอัตโนมัติ AAS ช่วยให้แอปสามารถสังเกตเห็นได้หากมีการเปลี่ยนแปลงเนื้อหาของหน้าต่าง จากนั้นแอปจะสามารถดำเนินการคลิกที่ปุ่ม “อนุญาต” ของหน้าต่างโต้ตอบการขออนุญาต

สำหรับนักวิเคราะห์ที่ต้องการวิเคราะห์เพิ่มเติม ให้เริ่มจากคลาส com.gibb.WebService

Dynamic Analysis

Class loading

เนื่องจากแอปพลิเคชันถูกทำการแพ็ค (pack) ด้วยโปรแกรมช่วยแพ็ค (packer) ซึ่งหมายความว่าเป็นการยากที่จะวิเคราะห์แอปแบบ Static เนื่องจากคลาสส่วนใหญ่จะไม่โหลดหากแอปไม่ได้รัน เราจำเป็นต้องเรียกใช้แอพและตรวจสอบการโหลดคลาส สามารถใช้สคริปต์ Frida ต่อไปนี้เพื่อตรวจสอบคลาสภายนอกที่โหลดได้

Java.perform(function() {
    // awaitForClassLoaded("com.js.main", traceClass); // does not work

    // let dalvik = Java.use("dalvik.system.DexFile");
    // let dalvik2 = Java.use("dalvik.system.DexClassLoader");
    let dexclassLoader = Java.use("dalvik.system.DexClassLoader");
    dexclassLoader.$init.implementation = function(a, b, c, d) {
        console.log(colors.green + " [!] DexClassLoader loads class from: ", a, colors.default)
        this.$init(a, b, c, d)
        try {
            // traceClass(this)
            if (a.includes("main")) {
                // console.log("[!] main.dex is loaded");
                // traceClass(this, "com.js.main");
            }
        } catch (e) {
            console.log(colors.red + e, colors.default)
        }
    }
});

รูปต่อไปนี้แสดงคลาสการโหลด DexClassLoader จากไฟล์ภายนอกที่แอปทิ้งระหว่างรันไทม์:

การโหลดคลาส (class) จากไฟล์ภายนอก

เมื่อใช้การวิเคราะห์แบบ Dynamic เราสามารถข้ามขั้นตอนในทุก ๆ อย่าง (เช่น การแตกและถอดรหัสไฟล์) เพื่อทำการเรียกใช้งาน main.dex (maindex.dex) และ ui.dex ได้ เนื่องจากแอปจะประมวลผลขั้นตอนต่าง ๆ ในขณะใช้งานโปรแกรมและวางไฟล์ที่ถอดรหัสไว้ใน “/files” Directory ซึ่งหมายความว่าเราสามารถแยกไฟล์ที่ถอดรหัสออกจากโฟลเดอร์แอพใน “files” Directory

หลังจากวิเคราะห์แอปแล้ว จะเห็นได้ชัดว่า maindex.dex มี main logics ของแอป อาจใช้คอมโพเนนต์อื่นๆ เช่น easyagent, ui.dex และ defaultplugin.apk เพื่อวัตถุประสงค์เฉพาะ แต่หัวใจของแอปอยู่ที่ maindex.dex

Traffic Monitoring

แอปพลิเคชันสื่อสารกับเซิร์ฟเวอร์โดยใช้ HTTPS และ websocket เราจะดักจับการรับส่งข้อมูลโดยใช้โปรแกรม Burp suite กับเซิร์ฟเวอร์ VPN ด้วยการตั้งค่าเซิร์ฟเวอร์ VPN และให้มือถือเชื่อมต่อกับ VPN เราสามารถจัดการการรับส่งข้อมูลทั้งหมดที่ส่งจากมือถือไปยังทุกที่ที่เราต้องการ ในกรณีนี้ เราจะเปลี่ยนเส้นทางการรับส่งข้อมูลไปที่ Burp Suite อย่างไรก็ตามข้อมูลรับส่งผ่านระหว่างโปรแกรม และ Server จะถูกเข้ารหัสทั้งใน HTTPS และ websocket ดังตัวอย่างต่อไปนี้:

ตัวอย่างการดักจับข้อมูล

แน่นอนว่าทราฟฟิกถูกเข้ารหัส ในการถอดรหัส WS (Websocket) traffic จะต้องรู้จักอัลกอริทึมและคีย์ เราใช้สคริปต์ Frida ต่อไปนี้เพื่อแยกคีย์และอัลกอริทึม:

function get_IV() {
    //hooking IvParameterSpec's constructor to get the IV 
    var iv_parameter_spec = Java.use("javax.crypto.spec.IvParameterSpec");
    iv_parameter_spec.$init.overload("[B").implementation = function(x) {
        console.log(colors.yellow,'\t[!] IV found: ' + bytesToHex(new Uint8Array(x)),colors.default);
        return this.$init(x);
    }
}

function get_algorithm() {
    var Cipher = Java.use("javax.crypto.Cipher");
    var algo = null;
    Cipher.getInstance.overload("java.lang.String").implementation = function(x) {
        console.log("[!] Hooking javax.crypto.Cipher.getInstance");
        console.log("\t[!] Encryption algorithm: " + x);
        algo = x;
        return Cipher.getInstance.overload("java.lang.String").call(this, x);
    }
    return algo;
}

function get_crypto_info() {
    var secretKeySpec = Java.use('javax.crypto.spec.SecretKeySpec');
    secretKeySpec.$init.overload('[B', 'java.lang.String').implementation = function(a, b) {
        var result = this.$init(a, b);
        console.log("======================================");
        console.log("[!] algoritm:" + b + "| HexKey:" + util_bytesToHex(new Uint8Array(a)));
        return result;
    }

    var encKey = "N/A";
    var secret_key_spec = Java.use("javax.crypto.spec.SecretKeySpec");
    var iv_parameter_spec_gcm = Java.use("javax.crypto.spec.GCMParameterSpec");
    var iv_parameter_spec = Java.use('javax.crypto.spec.IvParameterSpec');
    var cipher = Java.use("javax.crypto.Cipher");
    cipher.init.overload("int", "java.security.Key", "java.security.spec.AlgorithmParameterSpec").implementation = function(x, y, z) {
        console.log("[!] Hooking AlgorithmParameterSpec");

        // 1 means Cipher.MODE_ENCRYPT 
        if (x == 1) { // 1 means Cipher.MODE_ENCRYPT 
            console.log("\t[!] Mode: MODE_ENCRYPT");
        } else {
            // In this android app it is either 1 (Cipher.MODE_ENCRYPT) or 2 (Cipher.MODE_DECRYPT)
            console.log("\t[!] Mode: MODE_DECRYPT");
        }
        encKey = util_bytesToHex(new Uint8Array(y.getEncoded()));
        console.log(colors.green,"\t[!] Key: " + encKey, colors.default);
        try {
            console.log(colors.yellow,"\t[!] IV: " + util_bytesToHex(new Uint8Array(Java.cast(z, iv_parameter_spec_gcm).getIV())),colors.default);
            console.log("\t[!] Tag length: " + Java.cast(z, iv_parameter_spec_gcm).getTLen());
        } catch (error) {

        }

        //init must be called this way to work properly
        return cipher.init.overload("int", "java.security.Key", "java.security.spec.AlgorithmParameterSpec").call(this, x, y, z);

    }


    cipher.doFinal.overload("[B").implementation = function(x) {
        console.log("\t[!] Calling doFinal(Byte) ");
        console.log("\t\t[!] Before doFinal: " + util_bytesToHex(new Uint8Array(x)));
        var ret = cipher.doFinal.overload("[B").call(this, x);
        console.log("\t\t[!] After doFinal: " + util_bytesToHex(new Uint8Array(ret)));
        return ret;
    }

    cipher.doFinal.overload("[B", "int", "int").implementation = function(x, y, z) {
        console.log("\t[!] Calling doFinal(Byte, int, int) ");
        console.log("\t[!] Before doFinal: " + util_bytesToHex(new Uint8Array(x)));
        var ret = cipher.doFinal.overload("[B", "int", "int").call(this, x, y, z);
        console.log("\t[!] After doFinal: " + util_bytesToHex(new Uint8Array(ret)));
        return ret;
    }

    cipher.update.overload("[B").implementation = function(x) {
        console.log("\t[!] Calling update(Byte) ");
        console.log("\t\t[!] Before update: " + util_bytesToHex(new Uint8Array(x)));
        var ret = cipher.update.overload("[B").call(this, x);
        console.log("\t\t[!] After update: " + util_bytesToHex(new Uint8Array(ret)));
        return ret;
    }

    cipher.update.overload("[B", "int", "int").implementation = function(x, y, z) {
        console.log("\t[!] Calling update(Byte, int, int) ");
        console.log("\t[!] Before update: " + util_bytesToHex(new Uint8Array(x)));
        var ret = cipher.update.overload("[B", "int", "int").call(this, x, y, z);
        console.log("\t[!] After update: " + util_bytesToHex(new Uint8Array(ret)));
        return ret;
    }

    cipher.updateAAD.overload("[B").implementation = function(x) {
        console.log("\t[!] Calling updateAAD(Byte) ");
        console.log("\t[!] AAD: " + util_bytesToHex(new Uint8Array(x)));
        var ret = cipher.updateAAD.overload("[B").call(this, x);
        return ret;
    }
}

เมื่อแอปเข้ารหัสข้อมูลก่อนที่จะส่งไปยังเซิร์ฟเวอร์ javax.crypto.Cipher.init() จะถูกเรียกด้วย java.securityKey เป็นหนึ่งใน Argument

กุญแจที่ใช้ในการเข้ารหัสข้อมูล

ตัวอย่างข้อความ WS request

ถอดรหัสข้อมูลใน Websocket โดยใช้กุญแจที่ค้นพบ

ในการถอดรหัสทราฟฟิกอื่น ๆ เราไม่ได้พิสูจน์ว่าแอปทำงานอย่างไร แต่เราสันนิษฐานว่าอาจเป็นไปได้ที่จะแยกคีย์โดยใช้ Frida และใช้เพื่อถอดรหัสทราฟฟิกอื่น ๆ เช่น คำขอ HTTP/ตอบกลับไปยัง/จาก *.xdrig.com ซึ่งใช้ RC4/ECB/NoPadding พร้อม hardcoded key เพื่อเข้ารหัสเนื้อหา

IoC

APK (MD5) 39425fd18017c751d546b3f839495e8a
maindex.dex (MD5) 00563ef29eb24da1e9b6c5717ca81da3
tg.iapk (MD5) f91773645d3fed91e99d7d19c2c01c70
ui.dex (MD5) 7fc744141c4bacdfc38529b203aee8d5
defaultplugin.apk (MD5) 51a211128d77f9d567b2900f2f7be63e
package installed com.gzrtnq.Bumble

สำหรับผู้ใช้ทั่วไปที่ต้องการทราบว่ามีการติดตั้งแอปบนโทรศัพท์ของคุณหรือไม่ ให้ไปที่  Settings > Apps > Manage apps แล้วค้นหา Bumble ตรวจสอบชื่อแพ็กเกจแอปและถอนการติดตั้งหากตรงกับข้อมูลด้านบน

หากผู้ใช้ไม่สามารถถอนการติดตั้งโดยใช้ UI ได้ อาจจำเป็นต้องใช้เครื่องมือ ADB และควรดำเนินการโดยผู้เชี่ยวชาญ หากต้องการหยุดแอปเราต้องการ ADB โดยเรียกใช้คำสั่งต่อไปนี้:

$ adb shell am force-stop com.gzrtnq.Bumble

เมื่อแอปหยุดลง ACC จะถูกเพิกถอนจากแอป แต่แนะนำให้ลบออก. หากต้องการถอนการติดตั้งแอป เราต้องใช้ ADB โดยเรียกใช้คำสั่งต่อไปนี้:

$ adb uninstall com.gzrtnq.Bumble

Summary

Android Accessibility Service ของ Android เป็นสิทธิ์ที่อันตรายที่สุดซึ่งจำเป็นต้องเปิดใช้งานโดยการนำทางผ่านหน้าจอการตั้งค่า Android สามารถใช้งานได้หลายวัตถุประสงค์ เป็นที่ทราบกันมานานหลายปีว่ามี Malware ที่ใช้ความสามารถนี้เพื่อโจมตีแอปทางการเงิน เรากำลังมองหาโซลูชันที่เหมาะสมสำหรับนักพัฒนาเพื่อช่วยบรรเทาการโจมตีจาก Overlay Attack และความสามารถในการเข้าถึง แอพ CoinBase ใช้เพื่อตรวจจับการเข้าถึงและแจ้งให้ผู้ใช้ทราบหาก AAS เปิดใช้งานและโต้ตอบกับแอพ CoinBase ผู้ใช้ต้องเขย่าโทรศัพท์เพื่อใช้งานแอปต่อเมื่อเปิดใช้งาน AAS

อย่างไรก็ตาม เราขอเตือนผู้ใช้ที่ใช้โทรศัพท์ Android อย่าติดตั้งแอปนอก Play Store ขณะนี้ Google กำลังพยายามปกป้องผู้ใช้ของตนโดยป้องกันไม่ให้แอปที่ใช้ AAS ในทางที่ผิดไม่ให้แสดงอยู่ใน Play Store ดังนั้นจึงไม่ความจำเป็นที่คุณต้องดาวน์โหลดแอปจากแหล่ง อื่น ๆ

อภิธานศัพท์

  • Malware ย่อมาจากคำว่า “Malicious software” หรือภาษาไทยเรียกว่า “ซอฟต์แวร์ประสงค์ร้าย”
  • Android เป็นระบบปฏิบัติการสำหรับอุปกรณ์เคลื่อนที่ (mobile operating system) ซึ่งถูกพัฒนาโดยบริษัท Google
  • Application หรือเรียกสั้น ๆ ว่า “App” ในที่นี้หมายถึง Mobile Application โปรแกรมคอมพิวเตอร์ที่ถูกออกแบบมาให้ใช้งานบนอุปกรณ์เคลื่อนที่ เช่น สมาร์ทโฟน แท็บเล็ต หรืออุปกรณ์เสริมต่าง ๆ
  • Static เป็นกระบวนการตรวจสอบโค้ดหรือโปรแกรมโดยไม่ต้องรันโปรแกรมนั้นๆ โดยวิเคราะห์โค้ดหรือไฟล์ของโปรแกรมเพื่อหาข้อผิดพลาด
  • Decompiling เป็นกระบวนการแปลงโค้ดที่ถูกคอมไพล์เป็นโค้ดที่เข้าใจง่ายขึ้น โดยปกติแล้วโค้ดที่ถูกคอมไพล์จะถูกแปลงเป็นภาษาเครื่อง (machine language) ซึ่งเป็นภาษาที่ยากต่อการอ่านและแก้ไข แต่ถ้าเราต้องการแก้ไขหรือปรับปรุงโปรแกรมที่ถูกคอมไพล์แล้ว เราจำเป็นต้องมีโค้ดต้นฉบับที่เข้าใจง่ายกว่าภาษาเครื่อง Decompiling สามารถทำได้โดยใช้เครื่องมือ (tool) ที่เรียกว่า decompiler ซึ่งจะแปลงโค้ดเครื่องกลับมาเป็นโค้ดต้นฉบับของภาษาต้นฉบับ
  • APK ย่อมาจาก Android Package Kit ซึ่งเป็นไฟล์ที่ใช้ในการติดตั้งแอปพลิเคชันบนระบบปฏิบัติการ Android โดยส่วนมากจะถูกนำมาจาก Google Play Store หรือเว็บไซต์อื่น ๆ ที่ให้บริการดาวน์โหลดแอปพลิเคชัน Android
  • Reverse Engineering หรือ วิศวกรรมย้อนกลับ คือ กระบวนการค้นหาโครงสร้าง ฟังก์ชันการทำงานของอุปกรณ์หรือระบบหนึ่ง ๆ มักเกี่ยวข้องกับการแยกชิ้นส่วนของอุปกรณ์ออกจากกัน (ได้แก่ เครื่องกลอุปกรณ์อิเล็กทรอนิกส์ซอฟต์แวร์) แล้ววิเคราะห์การทำงานในแต่ละส่วน จากนั้นจึงนำมาสร้างอุปกรณ์ใหม่หรือโปรแกรมใหม่ ที่ทำงานได้เหมือนเดิม โดยปราศจากการคัดลอกจากต้นแบบ
  • Runtime หมายถึงช่วงเวลาที่โปรแกรมหรือแอปพลิเคชันทำงานอยู่ในระบบหรือเครื่องคอมพิวเตอร์ โดยมักใช้ในบริบทของภาษาโปรแกรมมิ่ง ซึ่งระบุถึงระยะเวลาที่โปรแกรมทำงานจริงๆ ตั้งแต่ที่เปิดใช้งานไปจนถึงเมื่อปิดใช้งาน ในระหว่างเวลานี้ โปรแกรมจะทำงานและใช้ทรัพยากรของเครื่องคอมพิวเตอร์ เช่น หน่วยประมวลผล หน่วยความจำ ฯลฯ
  • Android Dangerous Permission หมายถึงสิทธิ์การเข้าถึงข้อมูลหรือทรัพยากรที่อาจเป็นอันตรายหรือเสี่ยงต่อความเป็นส่วนตัวของผู้ใช้งาน ซึ่งต้องได้รับการอนุญาตจากผู้ใช้งานก่อนที่แอปพลิเคชันจะสามารถเข้าถึงได้ โดยสิทธิ์เหล่านี้จะถูกกำหนดในไฟล์ AndroidManifest.xml ของแอปพลิเคชัน
  • Android’s Accessibility Services (AAS) เป็นเทคโนโลยีที่ออกแบบมาเพื่อช่วยให้ผู้ใช้งาน Android ที่มีความบกพร่องทางร่างกายหรือสมองสามารถใช้งานอุปกรณ์ได้ง่ายขึ้น โดย AAS จะช่วยให้ผู้ใช้งานสามารถเข้าถึงและใช้งานฟีเจอร์ต่างๆ บนอุปกรณ์ Android ได้สะดวกมากขึ้น โดยเฉพาะอย่างยิ่งในการทำงานที่ต้องใช้งานหน้าจอของอุปกรณ์ เช่น การเข้าถึงแอปพลิเคชัน การเปิดใช้งานฟีเจอร์ต่างๆ เป็นต้น นอกจากนี้ AAS ยังช่วยให้ผู้ใช้งาน Android ที่มีความบกพร่องทางการมองเห็นสามารถใช้งานอุปกรณ์ได้ง่ายขึ้นด้วยการแสดงผลในรูปแบบของเสียงและสัมผัส โดย AAS จะใช้ API และบริการต่างๆ ของระบบ Android เพื่อช่วยในการทำงานนี้.

อ้างอิง