Hacking Yoosee camera | VN-Zoom | Cộng đồng Chia Sẻ Kiến Thức Công Nghệ và Phần Mềm Máy Tính

Adblocker detected! Please consider reading this notice.

We've detected that you are using AdBlock Plus or some other adblocking software which is preventing the page from fully loading.

We need money to operate the site, and almost all of it comes from our online advertising.

If possible, please support us by clicking on the advertisements.

Please add vn-z.vn to your ad blocking whitelist or disable your adblocking software.

×

Hacking Yoosee camera

minhchau2vitcon

Búa Gỗ

Hacking Yoosee camera Nguồn từ blog thao.pw . Khá là hay , copy về cho anh em vn-zoom tham khảo​

Mở đầu​

Chuyện là gần đây mình có thuê một căn chung cư mini. Trong khoảng thời gian ở đó, đôi khi rảnh rỗi mình có nghía qua hệ thống các thiết bị mạng và nhận thấy một vài target thú vị, ví dụ như khoá vân tay, wifi router và camera. Trong đó mình đặc biệt chú ý tới camera vì có vẻ như chỉ có một mẫu camera được dùng cho cả toà nhà này, và tên của nó là Yoosee. Vậy khả năng cao nếu như mình tìm được exploit của mẫu camera này thì sẽ có thể cầm nó đi hack tất cả cam trong toà nhà. Mình quyết định chọn con camera này làm đối tượng nghiên cứu cho con pet project đầu tiên liên quan tới hardware. Một lý do khác khiến mình chọn mục tiêu này là vì nhìn lướt qua thiết bị mình cảm thấy mức độ hoàn thiện của nó không được tốt, có lẽ là một mẫu camera rẻ tiền, điều này khiến mình linh cảm rằng khả năng nó có lỗ hổng cao hơn so với những sản phẩm hoàn thiện tốt và đến từ những hãng có tiếng tăm.

Không như nhiều thiết bị khác với dòng tên rất bé hoặc thậm chí không ghi tên hãng, con camera này có một cái tên được ghi to chình ình ở phía trước của thiết bị, chỉ cần nghía qua là có thể thấy ngay. Điều này giúp ích rất nhiều trong việc xác định mục tiêu mà mình cần tấn công là gì.

image.png
Ảnh mạng, chỉ mang tính chất minh hoạ

Tóm tắt (TL;DR)​

Bắt đầu từ con số 0, mình và Nguyễn Quốc Trung (@xikhud) đã tìm hiểu và hack thiết bị camera của hãng Yoosee. Khởi đầu từ việc nghiên cứu phần cứng, giao thức debug cho tới dump và nghiên cứu trên firmware. Bọn mình đã tìm thành công 2 lỗ hổng là heap overflow và RTSP unauthenticated stream access & control, các lỗ hổng này tạo ra các kịch bản tấn công cho phép xem video và điều khiển thiết bị không cần đăng nhập cũng như khiến cho thiết bị không thể hoạt động được.

Bài viết trình bày đầy đủ tất cả quá trình và các nhận xét, lựa chọn hướng đi của tác giả, với mục tiêu cung cấp cho người đọc cái nhìn toàn diện nhất về quá trình nghiên cứu cũng như hỗ trợ cho những ai bắt đầu nghiên cứu sản phẩm này trong tương lai có thể sử dụng nghiên cứu này như một cơ sở để khởi đầu.

Tìm hiểu thiết bị, lựa chọn hướng đi​

Sau khi có cái tên làm từ khoá, mình lên mạng tìm mua con cam về tháo ra thì thấy nó có 2 board mạch trong đó có board chính chứa SoC (màu xanh) và chip nhớ flash (màu đỏ), ngoài ra còn có một số chân cắm trông rất giống UART debug interface (màu vàng) như sau:

board-1.jpg

Sau khi đọc một số bài viết về việc nghiên cứu phần cứng thì mình biết rằng hướng mình có thể chọn để nghiên cứu như sau:

  • Có shell, backdoor được thiết bị
  • Tìm firmware của thiết bị, có thể download trên internet hoặc dump trực tiếp từ thiết bị ra (qua shell hoặc dump thẳng từ chip nhớ flash)
  • Dịch ngược firmware này tìm lỗ hổng

Trau dồi kiến thức​

Với một người có kinh nghiệm thì có lẽ UART protocol - Universal asynchronous receiver transmitter protocol - Dịch nôm na là giao thức truyền nhận bất đồng bộ - là cách đơn giản nhất để có thể truy cập vào shell của thiết bị. Ở thời điểm ấy mình cũng có nghĩ đến phương án này tuy nhiên với khả năng của một người mới bắt đầu thì mình không biết làm sao để có thể xác định được các chân TX, RX của UART interface. Mình bắt đầu học bằng cách xem video sau:


Có 2 điều cơ bản và quan trọng nhất mình học được từ video này:

1. UART interface có 4 chân cơ bản, đó là RX, TX, VCC và GND, trong đó có 3 chân thường thấy là RX, TX và GND có công dụng như sau:

  • TX: Transmitter - Chân truyền dữ liệu
  • RX: Receiver - Chân nhận dữ liệu
  • GND: Ground reference - Tham chiếu "đất", dùng làm điểm để tham chiếu cho giá trị 0V và khử các tín hiệu nhiễu
image-1.png

2. Có thể phân biệt các chân UART bằng đồng hồ vạn năng

image-2.png

Tiếp cận 1: Giao thức UART​

Sau khi biết được rằng có thể đo các chân UART bằng đồng hồ đo điện, mình có tiến hành đo thử, tuy nhiên kết quả khiến mình vô cùng bối rối vì thấy có 1 chân 0V và 2 chân 3.3V. Chân 0V thì có thể đoán được là ground reference tuy nhiên 2 nhân 3.3V kia thì mình không phân biệt được cái nào là TX và cái nào là RX.

Không tìm được thì đành đoán mò, mà không những phải đoán mò xem chân nào làm nhiệm vụ gì mà lại còn phải chọn đại 1 cái baud rate - hay nói nôm na là tần số gửi tín hiệu - ở đây mình chọn bừa một con số phổ biến là 115200. Sau một hồi cắm thử thì tình cờ mình nhận được ít logs của chiếc cam này. Khoảnh khắc ấy với mình thì phải nói là mừng như bắt được vàng.

image-3.png

Tuy nhiên việc đoán mò cũng có cái giá của nó, mặc dù đã có thể xem logs nhưng có vẻ rằng mình hoàn toàn không thể gửi một chút dữ liệu hay câu lệnh nào cho cái cam nay. Lúc này mình cũng hoàn toàn không biết liệu nó có shell không hay là read-only nữa.

Tiếp cận 2: Firmware​

Bế tắc trong hướng đi đầu tiên, mình đành chuyển qua giải pháp thứ 2 đó là tìm firmware của thiết bị này để nghiên cứu.

Tìm kiếm firmware trên internet​

Mình đã thử tìm kiếm trên Google cũng như forum của nsx tuy nhiên mình nhận thấy rằng họ chỉ chia sẻ firmware đã bị outdate nên không thể dùng để cập nhật cho thiết bị được.

Chưa kể rằng trong quá trình này mình phát hiện ra các bản firmware mới đã được mã hoá và chúng chứa một private RSA key để giải mã bản firmware tiếp theo, điều này khẳng định rằng gần như ta sẽ không thể downgrade một phiên bản cũ đè lên phiên bản hiện hành của thiết bị do key RSA không khớp.

Thử dump firmware bằng giao thức SPI​

Do vậy mình quyết định dump firmware trực tiếp từ chip nhớ flash để sử dụng. Tuy nhiên để dump được từ chip flash thì mình cần biết cách phân biệt chip và giao thức mà nó sử dụng. Trước đó khi đọc cuốn sách "The Hardware Hacking Handbook", mình biết qua rằng chip flash thường sử dụng giao thức SPI. Vì đem dòng chữ trên chip search mãi không ra thông tin gì nên mình quyết định nối "bừa" theo cái sơ đồ chân sau:

image-19.png

Hình ảnh thực tế, mình dùng kit PCBite để nối chân và ESP32 để đọc dữ liệu, vì kit PCBite của mình chỉ có 4 chân nên hình như mình đã nối theo như tổ tiên mách bảo, hình như 4 chân mình chọn lúc ấy là: Clock (CLK), Chip select (CS), Master in slave out (MISO), Master out slave in (MOSI). Và tất nhiên đã không biết lại còn nối bừa thì kết quả của mình lúc này thu được nó là con số 0 :)).

photo_2022-05-31_17-16-59.jpg

Tìm hiểu, dump firmware bằng mạch có sẵn​

Vì việc làm mò này không có tác dụng nên cuối cùng mình quyết định lên đặt câu hỏi trên diễn đàn Arduino Việt Nam - Một diễn đàn lớn về điện tử và lập trình nhúng trong đó cụ thể là Arduino.

Screenshot_200.png

Rất may mắn mình nhận được một số câu trả lời vô cùng chi tiết và hữu ích:

image-20.png
image-21.png

Vì vậy sau đó mình đã quyết định mua set mạch CH341A với giá 100k trên Shopee về để thử nghiệm :)).

image-22.png

Hình ảnh thực tế:

image-23.png

Correct/extract firmware​

Vì flash chip nằm trên mạch và dữ liệu được đọc dưới dạng tín hiệu điện tử nên sẽ có nhiễu khiến cho một số byte dữ liệu bị sai khác dẫn tới extract firmware ra bị lỗi, vì vậy mình viết một đoạn code nho nhỏ để sửa lỗi như sau:

raycast-untitled--2-.png
Ý tưởng của đoạn code khá đơn giản đó là lấy dữ liệu từ nhiều file dump sau đó tại mỗi vị trí (mỗi byte) sẽ chọn ra giá trị giống nhau giữa nhiều file nhất để làm kết quả.
Kết quả: Mình đã sử dụng thành công binwalk để nhận diện và extract file firmware:

image-24.png

Bước ngoặt: Đồng đội mới​

Thật tình cờ vào thời điểm đó mình lại nhận được tin nhắn ngỏ ý muốn hợp tác của Trung (@xikhud). Mình xem profile thì biết rằng Trung mới hoàn thành xong Flareon - Một cuộc thi về dịch ngược hàng năm do FireEye, một công ty an ninh mạng lớn, tổ chức.

Screenshot_201.png

Ở thời điểm mà mình bắt đầu có firmware để nghiên cứu mà lại có 1 reverser join vào thì là một cơ hội tốt :3 chưa kể mình cũng còn bị thiếu kinh nghiệm dịch ngược nữa nên việc có được Trung trong team là một điều tuyệt vời.

Xác định lại mục tiêu​

Như đã nói ở trên thì mục tiêu của mình lúc này đó là tìm cách để có được shell trên thiết bị, vì vậy giờ khi có 2 người thì mục tiêu chính của bọn mình cũng là:

  • Trung dịch ngược firmware và tìm ra cách vào shell từ một interface nào đó trên thiết bị -> Thành công theo hướng này cũng gần như đồng nghĩa với việc bọn mình có thể hack được vào thiết bị từ bên ngoài.
  • Mình tìm được cách vào shell bằng cách backdoor thiết bị từ cơ chế update -> Hướng đi này sẽ dùng làm bàn đạp để có persistent access và debug firmware ngay trên thiết bị. Tuy nhiên hướng đi này cần có tác động vật lý tới thiết bị vì vậy bọn mình vẫn sẽ cần Trung nghiên cứu thêm bug để có thể hack một cách thuyết phục hơn.
Ở đây mình sẽ trình bày hướng đi của mình.

Tìm hiểu cấu trúc firmware​

Trong quá trình đọc logs từ UART mình thấy thiết bị có định kì gửi HTTP request để check for update và lấy được luôn URL để tải file update này.

image.png
URL update có dạng như trên hình
Tuy nhiên như mình nói ở trên đó là file update này được mã hoá và cần có key RSA tương ứng ở trong thiết bị để giải mã. Vì bọn mình đã extract được firmware nên có thể tìm thấy ngay file private key này trên thiết bị với cái tên 1912ak1.5.00.prv.

image-1.png

À, đọc đến đây có thể bạn sẽ thắc mắc rằng tại sao bọn mình không patch file firmware đã dump ra rồi flash ngược lại vào trong thiết bị? Tại vì như mình nói trước đó do flash chip vẫn đang nằm trên mạch nên nó bị nhiễu bởi các thành phần điện tử khác, khiến cho việc flash lại firmware bị sai sót và "tạch" luôn thiết bị. Mặt khác firmware mình dump ra dù đã được correct nhưng ít nhiều nó vẫn có thể có những byte bị sai dẫn đến hậu quả tương tự.

Vì lý do như vậy nên mình chọn hướng đi đó là giải mã file update -> đặt backdoor -> encrypt lại -> update thiết bị bằng file mới -> shell.

Ok, tiếp tục câu chuyện đó là giờ có file update đã mã hoá + private key -> Đã có thể giải mã file update rồi, tuy nhiên để giải mã được thì mình còn cần biết nó được mã hoá bằng cách nào nữa (tuy gọi là RSA nhưng vẫn có nhiều cách cài đặt mã hoá khác nhau).

Trong quá trình tìm kiếm, mình tìm thấy một file chịu trách nhiệm cho quá trình update này với cái tên là gwellupdater.sh.

Đoạn code đáng chú ý đầu tiên đó là đoạn giải mã file update bằng lệnh rsa_dec. Tên của file đã được mã hoá luôn là upg.bin.enc (do tên file này là đã được cố định ở biến $FILENAME). Tên file sau khi giải mã là upg.bin

raycast-untitled--5-.png

Tiếp theo là cắt file firmware ra thành 3 phần bằng lệnh dd:

  • bootupdater.sh
  • payload.md5
  • payload.bin
raycast-untitled--6-.png
image-6.png
Trung là người nhìn thấy chi tiết này trước mình
So sánh md5 hash của file payload.bin với nội dung hash trong file payload.md5. Mục đích của việc này là check sự toàn vẹn của file update.

raycast-untitled--7--2.png

Chạy file bootupdater.sh với payload.bin là tham số bằng lệnh sh. Xoá các file tạm được tạo ra trong quá trình update để giải phóng dung lượng. Kiểm tra và reboot nếu file /tmp/do_not_reboot_after_update không tồn tại.

raycast-untitled--8-.png

Tới đây mình rút ra được một số nhận xét như sau:

  • Nếu có thể mã hoá lại được file update upg.bin.enc sau khi chỉnh sửa thì mình có thể cập nhật lên thiết bị này một bản update bất kì.
  • Trong quá trình update thì thiết bị chạy file bootupdater.sh, mà file này lại được cắt ra từ chính file update đã được giải mã (upg.bin).
-> Nếu có thể mã hoá được file update thì mình có thể trực tiếp chèn 1 đoạn script backdoor thiết bị (không cần chờ quá trình update thành công).

Nghiên cứu thuật toán mã hoá & giải mã update file​

Việc đầu tiên mà mình làm đó cứ phải là giải mã file update đã. Mình lấy file rsa_dec từ firmware trước đó lấy được từ thiết bị, đồng thời cũng phát hiện ra file này sử dụng mã nguồn tại repo sau.

image-8.png
Mã nguồn mã hoá RSA mà mình tìm thấy
Sau đó mình đọc nội dung file bootupdater.sh.

Trong đó mình chú ý tới các đoạn code sau:

Sử dụng dd để cắt file firmware (upg.bin) thành 2 phần, app.binuImage:

raycast-untitled--10-.png

Dùng dd để write file app.bin vào /dev/mtdblock6:

raycast-untitled--11-.png

Update kernel, dùng dd để write file uImage vào /dev/mtdblock1

raycast-untitled--11--1.png

Như vậy có thể thấy bootupdater.sh có thể tương tác với filesystem, thậm chí có thể flash lại cả kernel và firmware.

Mình tiếp tục extract file app.bin để xem nội dung. Có vẻ như đây là phần core của firmware, chứa các file binary và scripts cần thiết để thực hiện mọi tác vụ.

image-7.png

Khai thác lỗ hổng trong cơ chế mã hoá để backdoor update file​

Phần mà mình quan tâm nhất lúc này đó là làm sao để pack lại file update, vì vậy nên mình để Trung tiếp tục nghiên cứu trên firmware, còn mình thì tập trung vào tìm hiểu cách hoạt động của mã nguồn RSA.

Compile và chạy thử file rsa_dec, thấy có các options sau:

image-9.png

So sánh với câu lệnh giải mã lúc nãy thấy trong bash script, ta thấy các options -o, -v và -f được sử dụng.

raycast-untitled--16-.png

Như vậy câu lệnh cuối cùng được thực thi đó là

rsa_dec -o -v -f upg.bin.enc
Ý nghĩa của câu lệnh này là giải mã và giữ file gốc (-o), in ra các thông tin trong quá trình giải mã (-v), với tên file đầu vào là upg.bin.enc (-f).

Vì đã biết tính năng nào sẽ được sử dụng nên mình đọc source code và thấy phần xử lý quan trọng nhất nằm trong hàm sau:

rsa_dec.c_rsa_decrypt.png

Từ đây mình tóm tắt lại được quy trình giải mã như sau:

rsa_decrypt:

  • rsa_decrypt_prolog:
    • Mở các file chuẩn bị cho quá trình đọc/ghi
    • Tìm các private key và thêm vào "key ring"
    • Kiểm tra xem private key nào khớp với ciphertext
  • rsa_decrypt_quick/rsa_decrypt_full:
    • Thực hiện giải mã
  • rsa_decrypt_epilog:
    • Đóng các file key, plaintext, ciphertext
    • Xoá file gốc nếu cần
Sau đó khi debug thử quá trình giải mã với file update ban đầu thì mình phát hiện ra nó sử dụng hàm rsa_decrypt_quick, và mình khá bất ngờ khi đọc source code của hàm này:

rsa_dec.c_rsa_decrypt--1-.png

Về cơ bản thì hàm này chỉ đem từng block 64 bit xor với 64 bit RSA_RANDOM(), ở đây chính là hàm random().

image-11.png

Đến đây thì mình đoán được rằng quy trình giải mã này sẽ tạo ra một dãy số random từ 1 seed nào đó nằm trong private key. Thật vậy, khi đọc lại hàm rsa_decrypt_prolog, mình thấy có 1 call chain như sau:

  • rsa_dec.c/number_random_seed:
    • rsa_dec.c/rsa_decrypte_header_common:
      • rsa.c/rsa_decode(&seed, &seed, &key->exp, &key->n);
      • rsa_num.c/number_seed_set_fixed(&seed);
        • rsa_num.c/number_seed_set
          • srandom(number_random_seed);
Điều này có nghĩa là trong quá trình chuẩn bị private key để decrypt thì đồng thời hàm prolog này cũng decrypt và set luôn random seed bằng srandom.

Và...ủa, nghĩa là để pack lại file update kia thì mình chỉ cần extract được seed và xor lại phần data mình sửa lại với dãy số random được tạo ra bởi cái seed kia thôi?

Khỏi phải nói, việc này QUÁ DỄ!

Sau khi hiểu ra vấn đề thì mình có build/code ra một số file sau:

  • rsa_dec (modified): Dùng để extract seed từ file update và private key.
  • randgen: Dùng để gen ra dãy số random từ seed.
  • fileparser.py: Gọi là fileparser vì tưởng ban đầu của mình là parse file update này ra để xử lý tuy nhiên mình đã code luôn nó để pack code backdoor vào file update có sẵn :)).
image-12.png

Đây là source code của file fileparser.py mà mình viết:

fileparser.py--1-.png

Pwned​

Việc còn lại của mình lúc này đó là ném file update mới này vào thẻ nhớ sau đó cắm vào thiết bị, bật lên đồng thời giữ nút reset trong 2-3s.

Kết quả:

image-13.png

Oops...​

Vài hôm sau khi backdoor được thiết bị thì mình có thử ngồi cắm lại chân UART thì phát hiện ra các chân này có đủ read/write, sau khi cắm xong thì mình có vào được shell với credentials là root/<no password> =)).

Một bài học cho việc test không cẩn thận phải trả giá bằng việc mất thời gian và đi con đường vòng. Tuy nhiên mình vẫn rất vui do trước đó không tìm được cách nào khác nên đã phải thử thách và học được nhiều điều khi chọn con đường vòng này.

Persistent access​

Sau khi pwn được thiết bị, mình bắt đầu patch lại file app.bin trước đó để đặt reverse shell vào /etc/init.d/rc.local, vì script này sẽ được chạy trong quá trình boot nên mỗi khi thiết bị khởi động là bọn mình đều có reverse shell.

Mình và Trung bắt đầu đẩy một số static binary lên thiết bị như gdbserver, busybox để có thể debug trực tiếp các binary có sẵn của thiết bị.

Có shell thì làm gì? (từ góc nhìn của Trung)​

Việc đầu tiên khi có shell mình làm là xác định các nơi mà con camera này nhận input từ người dùng. Để làm việc này, mình đã copy netstat từ ngoài vào để chạy và xem camera này đang listen trên những port nào.

nDC_AJiGBvDUG9p2KMrWx9MfmjFE7sKc4gvJTQ6akmeG8V2cJqGW6E3G_tpX1JpjE0Br98AOvFh6F4Gc5iK9YsqbMjyo-sdMrXzWrYGRJotd3ZttCwIcYnqS0Glw1EN1umPI0SSXjRzngh_ZIw

Có nhiều port đang được lắng nghe, trong đó port 5000 nằm ở đầu tiên, vì vậy mình quyết định đi tìm đoạn code thực hiện việc xử lý dữ liệu tới port 5000. netstat còn báo rằng program đang sử dụng port 5000 là “ipc”, nên mình chỉ việc tìm file này, decompile nó và bắt đầu tìm đoạn code đó.

Khi decompile file “ipc”, mình khá sốc vì hàm main của nó rất lớn, decompile mất rất nhiều thời gian. Mình có cảm giác như anh lập trình viên đã viết tất cả mọi thứ vào trong hàm main vậy. Với đoạn code lớn như vậy, việc tìm đoạn code xử lý port 5000 gần như là mò kim đáy bể. Vì mình đã từng lập trình socket bằng C, nên mình biết để listen trên port nào đó, thì trong C phải dùng hàm “bind”.

5rV8oeNTQJr4xqrkrAb9SkrO60LJpA0LXdygkpE961INcKYHor9g2O-tgWwoA_FdxuoOBAc0nfungGZNxcK0jkHRwmPVrjTalLWBxQqzmJDTKgOqiNq3jfsuwnEgpPxDEBIG7zr7Vw_bFRm8cA

Mình dùng chức năng cross-reference của IDA thì thấy chỉ có 28 lời gọi tới hàm bind, giờ mình chỉ việc check từng chỗ là được.

vJM60nzj3ESCPrYORUu8Zj-D6y4Mag3zaxtcp-ULefVPjRItbcvQsm_p_znx_YdqgZy70ybmIR5Wek58VD-umdXwBMUbIWl7qCi8PiZmSgu-7jPnCP1TwPCohY-oTDMdQAsIWyj8_7J9nTbcAA

Cuối cùng mình đã tìm được hàm ở 0x43D2C. Ở trên có đoạn “addr.sin_port = 0x8813”, 0x8813 chính là dạng big endian của số 0x1388, mà 0x1388 chính là 5000. Vậy đây chính xác là đoạn code lắng nghe trên port 5000. Sau đó mình chỉ việc follow hàm này để xem nó xử lý gì với dữ liệu nhận được trên port 5000.

Lỗ hổng đầu tiên: Heap overflow (từ góc nhìn của Trung)​

Không lâu sau khi bọn mình có thể bắt đầu debug trên thiết bị, Trung tìm ra lỗ hổng đầu tiên. Một lỗi heap overflow gây crash khiến cho thiết bị tự khởi động lại và không ghi lại được bất cứ hình ảnh nào trong khoảng thời gian này.

Dưới đây là phần trình bày lỗ hổng từ góc nhìn của Trung:

Lỗ hổng nằm trong hàm ở 0x4706C

3Qh3zgVs4xgf5TtuS8sR3m4TRb7gYtlJJeVhb2zADaUAkCOWu-q5WTS6UjzDGMaQ7IdZQewovsBY2tsJyrDoQ1n9MVi4sRmcB9syM8OcBA52NJNAGvbxK7AsPtRna6iVDeOmCl-py9vbwvn5ew

Ở hình trên, biến buf được cấp phát 0x2000 byte. Với lượng lớn dữ liệu như này, thông thường khi code socket C, người ta thường hay dùng một vòng lặp while (true), mỗi vòng lặp sẽ nhận một lượng nhỏ dữ liệu, tới khi nào đủ 0x2000 byte (hoặc khi không còn dữ liệu để nhận nữa) thì dừng. Tuy nhiên ở hình trên, thay vì kiểm tra “đủ 0x2000 byte thì dừng”, anh lập trình viên đã kiểm tra “khi nào không nhận được dữ liệu nữa thì dừng”. Từ đó dẫn đến việc user có thể gửi nhiều hơn 0x2000 byte, trong khi độ lớn của buffer chỉ là 0x2000 -> heap buffer overflow.
Code exploit:

raycast-untitled--22-.png

Đoạn code trên gửi 0x3000 byte tới port 5000 của camera. Nó sẽ ghi đè lên dữ liệu quan trọng trên heap của camera và khả năng cao sẽ làm camera bị crash.

Ngay lập tức bọn mình setup và làm một chiếc video demo lỗ hổng này:


Lỗ hổng thứ hai: Unauthenticated RTSP stream access & control​

Khi phân tích source code bằng IDA, mình thấy có hàm sub_ADB84 có một flow check loằng ngoằng và đồ sộ

image-17.png
Flow graph của sub_ADB84, có lẽ phải chụp thế này mới thấy được hết độ phức tạp của nó
Đọc qua các block trong hàm, mình thấy chúng chứa rất nhiều từ khoá liên quan tới giao thức RTSP

image-18.png
Các từ khoá RTSP, CSeq
image-19.png
image-20.png
OPTIONS, DESCRIBE, SETUP, TEARDOWN,...
Google search nhanh với từ khoá "RTSP methods", ta có thể thấy rõ đây là các command keywords rất đặc trưng của giao thức này:

image-21.png

Mình search cách xem stream RTSP camera Yoosee thì biết nó có dạng:

rtsp://admin:<password>@<ip>/onvif1
Có thể bỏ đường dẫn này vào VLC như sau:

image-22.png

Vì lúc này mình đã biết rằng dùng VLC có thể xem được video stream từ camera tuy nhiên lại chưa rõ về cấu trúc của 1 gói tin RTSP, nên mình viết một đoạn code để proxy giữa VLC và cam. Vì các gói tin RTSP ở dạng plaintext nên ở phía proxy mình có thể thoải mái log cũng như patch các gói tin qua lại giữa 2 bên.

Ý tưởng của mình là thay vì add đường dẫn RTSP như trên vào VLC thì mình sẽ sử dụng đường dẫn sau để gửi tới proxy:

rtsp://127.0.0.1:554?ip=<camera ip>
Sau đó ở phía proxy sẽ tạo 1 socket connection khác tới <camera ip> và chuyển tiếp packet body tới cam sau đó nhận response từ cam và phản hồi lại cho VLC. Phương pháp này khá giống với cách tấn công man in the middle, tuy nhiên ở đây phía proxy không intercept connection này mà nó được chọn để chuyển tiếp các gói tin luôn.

Đoạn code server của mình lúc này trông như sau (đã lược đi các hàm patch rườm rà):

raycast-untitled--17-.png

Vì lúc này đã có thể log và patch các request/response khá tiện rồi nên mình hướng tới tìm lỗi logic trong hàm sub_ADB84 ở trên. Mình đặt ra một số câu hỏi như sau:

  • Có những tác vụ cần auth (authentication/authorization), vậy chúng được coi là auth dựa trên những yếu tố gì?
  • Có những tác vụ không cần auth, vậy điều kiện nào để xác định một tác vụ như vậy?
  • Có cách nào "lừa" thiết bị rằng một tác vụ không cần auth trong khi nó có cần auth không?
image-23.png
Đây là flow check auth, nó sẽ trả về code 401 nếu như check fail
Đọc lại phần này trong code thì mình thấy khá liên quan. Trong khi đọc request log mình phát hiện ra rằng DESCRIBE là một tác vụ không cần auth và chắc hẳn đó cũng là một phần lý do nó được check riêng ở chỗ này:

image-24.png

Thật sự là viết đến đoạn này của chiếc blog thì mình đã ngồi nghĩ nát óc xem hồi đấy mình đã suy luận và khai thác lỗ hổng ở chỗ này ra sao. Mình chỉ nhớ rằng hồi đấy mình khai thác phần này cũng bằng tư duy logic từ các câu hỏi mình đặt ra ở trên, cộng với một số kinh nghiệm mình có được khi khai thác các lỗ hổng về authentication khi mình nghiên cứu web API.

Về cơ bản thì mình có một số nhận định như thế này:

  • Khi user đã authenticated thì server sẽ phải trả về một thông tin gì đó để lưu giữ phiên đăng nhập của user. Với web API thì đó thường là cookie, còn ở đây là header "Session".
  • Ngoài kiểm tra phiên đăng nhập ra thì server có thể check một số thông tin khác trong headers. Ví dụ như ở đoạn code trong ảnh trên có vẻ như thiết bị đang kiểm tra một số thông tin với các từ khoá "username", "realm", "nonce".
Khi kiểm thử các ứng dụng web mặc dù không có source code nhưng ta vẫn có thể biết được server check những header nào bằng cách thử loại bỏ từng header cho tới khi có lỗi xảy ra. Và đó cũng chính là cách mà mình đã thử.

Sau quá trình thử thêm, bớt headers, mình craft được đoạn code sau để patch request body và qua mặt được authentication flow trên thiết bị:

raycast-untitled--20--1.png

Ở đây mình đã check và thêm header "Accept""Authorization" vào tất cả các request, cũng như xoá đi header "Session".

Ngoài ra mình còn phát hiện thêm tác vụ "SET_PARAMETER" cho phép điều khiển hướng quay của thiết bị mà không cần chỉnh sửa bất cứ header nào.

raycast-untitled--21-.png
Một phần đoạn mã nguồn điều khiển hướng thiết bị
Và cũng như đối với lỗ hổng đầu tiên, mình đã làm ngay một chiếc demo cho nó:


PoC/Source code​

Mã nguồn của tất cả mọi nội dung mình trình bày ở trên nằm ở đây:

GitHub - t-rekttt/yoosee-exploit
Contribute to t-rekttt/yoosee-exploit development by creating an account on GitHub.
GitHubt-rekttt


Report timeline​

  • 10/3/2022: Gửi email thông báo cho vendor
  • 14/3/2022: Vendor yêu cầu thêm thông tin
  • 14/3/2022: Gửi demo video và timeline cho vendor
image-26.png

  • 16/3/2022: Feedback từ vendor nói rằng các tính năng này là bình thường
image-27.png

  • 16/3/2022: Giải thích thêm về lỗ hổng
image-29.png

  • 17/3/2022: Feedback từ vendor (vendor vẫn không hiểu cách tiếp cận của bọn mình)
image-28.png

  • 17/6/2022: Publish writeup

Credits & thanks to​

  • Nguyễn Quốc Trung (@xikhud): Teammate cùng đồng hành nghiên cứu. Đồng tác giả bài blog này.
  • Anh Trần Phạm Thành (@radcet): Hỗ trợ và tư vấn trong suốt quá trình nghiên cứu. Previewer.
  • Anh Nguyễn Hồng Phúc (@xnohat): Tư vấn, cung cấp một số công cụ dịch ngược/backdoor
  • Anh Vũ Huy Hưng (@James): Hỗ trợ sách & các khoá học về hardware hacking
  • Anh Mạnh Tuấn (@Juno Okyo): Previewer
Nguồn https://thao.pw/hacking-yoosee-camera/
 

tamthangia

Rìu Vàng Đôi
Thử thằng ezviz đi bạn , của thằng hkvision 😁 chứ yosee rẻ quá rồi
 

gabriel2512

Búa Gỗ Đôi

Hacking Yoosee camera Nguồn từ blog thao.pw . Khá là hay , copy về cho anh em vn-zoom tham khảo​

Mở đầu​

Chuyện là gần đây mình có thuê một căn chung cư mini. Trong khoảng thời gian ở đó, đôi khi rảnh rỗi mình có nghía qua hệ thống các thiết bị mạng và nhận thấy một vài target thú vị, ví dụ như khoá vân tay, wifi router và camera. Trong đó mình đặc biệt chú ý tới camera vì có vẻ như chỉ có một mẫu camera được dùng cho cả toà nhà này, và tên của nó là Yoosee. Vậy khả năng cao nếu như mình tìm được exploit của mẫu camera này thì sẽ có thể cầm nó đi hack tất cả cam trong toà nhà. Mình quyết định chọn con camera này làm đối tượng nghiên cứu cho con pet project đầu tiên liên quan tới hardware. Một lý do khác khiến mình chọn mục tiêu này là vì nhìn lướt qua thiết bị mình cảm thấy mức độ hoàn thiện của nó không được tốt, có lẽ là một mẫu camera rẻ tiền, điều này khiến mình linh cảm rằng khả năng nó có lỗ hổng cao hơn so với những sản phẩm hoàn thiện tốt và đến từ những hãng có tiếng tăm.

Không như nhiều thiết bị khác với dòng tên rất bé hoặc thậm chí không ghi tên hãng, con camera này có một cái tên được ghi to chình ình ở phía trước của thiết bị, chỉ cần nghía qua là có thể thấy ngay. Điều này giúp ích rất nhiều trong việc xác định mục tiêu mà mình cần tấn công là gì.

image.png
Ảnh mạng, chỉ mang tính chất minh hoạ

Tóm tắt (TL;DR)​

Bắt đầu từ con số 0, mình và Nguyễn Quốc Trung (@xikhud) đã tìm hiểu và hack thiết bị camera của hãng Yoosee. Khởi đầu từ việc nghiên cứu phần cứng, giao thức debug cho tới dump và nghiên cứu trên firmware. Bọn mình đã tìm thành công 2 lỗ hổng là heap overflow và RTSP unauthenticated stream access & control, các lỗ hổng này tạo ra các kịch bản tấn công cho phép xem video và điều khiển thiết bị không cần đăng nhập cũng như khiến cho thiết bị không thể hoạt động được.

Bài viết trình bày đầy đủ tất cả quá trình và các nhận xét, lựa chọn hướng đi của tác giả, với mục tiêu cung cấp cho người đọc cái nhìn toàn diện nhất về quá trình nghiên cứu cũng như hỗ trợ cho những ai bắt đầu nghiên cứu sản phẩm này trong tương lai có thể sử dụng nghiên cứu này như một cơ sở để khởi đầu.

Tìm hiểu thiết bị, lựa chọn hướng đi​

Sau khi có cái tên làm từ khoá, mình lên mạng tìm mua con cam về tháo ra thì thấy nó có 2 board mạch trong đó có board chính chứa SoC (màu xanh) và chip nhớ flash (màu đỏ), ngoài ra còn có một số chân cắm trông rất giống UART debug interface (màu vàng) như sau:

board-1.jpg

Sau khi đọc một số bài viết về việc nghiên cứu phần cứng thì mình biết rằng hướng mình có thể chọn để nghiên cứu như sau:

  • Có shell, backdoor được thiết bị
  • Tìm firmware của thiết bị, có thể download trên internet hoặc dump trực tiếp từ thiết bị ra (qua shell hoặc dump thẳng từ chip nhớ flash)
  • Dịch ngược firmware này tìm lỗ hổng

Trau dồi kiến thức​

Với một người có kinh nghiệm thì có lẽ UART protocol - Universal asynchronous receiver transmitter protocol - Dịch nôm na là giao thức truyền nhận bất đồng bộ - là cách đơn giản nhất để có thể truy cập vào shell của thiết bị. Ở thời điểm ấy mình cũng có nghĩ đến phương án này tuy nhiên với khả năng của một người mới bắt đầu thì mình không biết làm sao để có thể xác định được các chân TX, RX của UART interface. Mình bắt đầu học bằng cách xem video sau:


Có 2 điều cơ bản và quan trọng nhất mình học được từ video này:

1. UART interface có 4 chân cơ bản, đó là RX, TX, VCC và GND, trong đó có 3 chân thường thấy là RX, TX và GND có công dụng như sau:

  • TX: Transmitter - Chân truyền dữ liệu
  • RX: Receiver - Chân nhận dữ liệu
  • GND: Ground reference - Tham chiếu "đất", dùng làm điểm để tham chiếu cho giá trị 0V và khử các tín hiệu nhiễu
image-1.png

2. Có thể phân biệt các chân UART bằng đồng hồ vạn năng

image-2.png

Tiếp cận 1: Giao thức UART​

Sau khi biết được rằng có thể đo các chân UART bằng đồng hồ đo điện, mình có tiến hành đo thử, tuy nhiên kết quả khiến mình vô cùng bối rối vì thấy có 1 chân 0V và 2 chân 3.3V. Chân 0V thì có thể đoán được là ground reference tuy nhiên 2 nhân 3.3V kia thì mình không phân biệt được cái nào là TX và cái nào là RX.

Không tìm được thì đành đoán mò, mà không những phải đoán mò xem chân nào làm nhiệm vụ gì mà lại còn phải chọn đại 1 cái baud rate - hay nói nôm na là tần số gửi tín hiệu - ở đây mình chọn bừa một con số phổ biến là 115200. Sau một hồi cắm thử thì tình cờ mình nhận được ít logs của chiếc cam này. Khoảnh khắc ấy với mình thì phải nói là mừng như bắt được vàng.

image-3.png

Tuy nhiên việc đoán mò cũng có cái giá của nó, mặc dù đã có thể xem logs nhưng có vẻ rằng mình hoàn toàn không thể gửi một chút dữ liệu hay câu lệnh nào cho cái cam nay. Lúc này mình cũng hoàn toàn không biết liệu nó có shell không hay là read-only nữa.

Tiếp cận 2: Firmware​

Bế tắc trong hướng đi đầu tiên, mình đành chuyển qua giải pháp thứ 2 đó là tìm firmware của thiết bị này để nghiên cứu.

Tìm kiếm firmware trên internet​

Mình đã thử tìm kiếm trên Google cũng như forum của nsx tuy nhiên mình nhận thấy rằng họ chỉ chia sẻ firmware đã bị outdate nên không thể dùng để cập nhật cho thiết bị được.

Chưa kể rằng trong quá trình này mình phát hiện ra các bản firmware mới đã được mã hoá và chúng chứa một private RSA key để giải mã bản firmware tiếp theo, điều này khẳng định rằng gần như ta sẽ không thể downgrade một phiên bản cũ đè lên phiên bản hiện hành của thiết bị do key RSA không khớp.

Thử dump firmware bằng giao thức SPI​

Do vậy mình quyết định dump firmware trực tiếp từ chip nhớ flash để sử dụng. Tuy nhiên để dump được từ chip flash thì mình cần biết cách phân biệt chip và giao thức mà nó sử dụng. Trước đó khi đọc cuốn sách "The Hardware Hacking Handbook", mình biết qua rằng chip flash thường sử dụng giao thức SPI. Vì đem dòng chữ trên chip search mãi không ra thông tin gì nên mình quyết định nối "bừa" theo cái sơ đồ chân sau:

image-19.png

Hình ảnh thực tế, mình dùng kit PCBite để nối chân và ESP32 để đọc dữ liệu, vì kit PCBite của mình chỉ có 4 chân nên hình như mình đã nối theo như tổ tiên mách bảo, hình như 4 chân mình chọn lúc ấy là: Clock (CLK), Chip select (CS), Master in slave out (MISO), Master out slave in (MOSI). Và tất nhiên đã không biết lại còn nối bừa thì kết quả của mình lúc này thu được nó là con số 0 :)).

photo_2022-05-31_17-16-59.jpg

Tìm hiểu, dump firmware bằng mạch có sẵn​

Vì việc làm mò này không có tác dụng nên cuối cùng mình quyết định lên đặt câu hỏi trên diễn đàn Arduino Việt Nam - Một diễn đàn lớn về điện tử và lập trình nhúng trong đó cụ thể là Arduino.

Screenshot_200.png

Rất may mắn mình nhận được một số câu trả lời vô cùng chi tiết và hữu ích:

image-20.png
image-21.png

Vì vậy sau đó mình đã quyết định mua set mạch CH341A với giá 100k trên Shopee về để thử nghiệm :)).

image-22.png

Hình ảnh thực tế:

image-23.png

Correct/extract firmware​

Vì flash chip nằm trên mạch và dữ liệu được đọc dưới dạng tín hiệu điện tử nên sẽ có nhiễu khiến cho một số byte dữ liệu bị sai khác dẫn tới extract firmware ra bị lỗi, vì vậy mình viết một đoạn code nho nhỏ để sửa lỗi như sau:

raycast-untitled--2-.png
Ý tưởng của đoạn code khá đơn giản đó là lấy dữ liệu từ nhiều file dump sau đó tại mỗi vị trí (mỗi byte) sẽ chọn ra giá trị giống nhau giữa nhiều file nhất để làm kết quả.
Kết quả: Mình đã sử dụng thành công binwalk để nhận diện và extract file firmware:

image-24.png

Bước ngoặt: Đồng đội mới​

Thật tình cờ vào thời điểm đó mình lại nhận được tin nhắn ngỏ ý muốn hợp tác của Trung (@xikhud). Mình xem profile thì biết rằng Trung mới hoàn thành xong Flareon - Một cuộc thi về dịch ngược hàng năm do FireEye, một công ty an ninh mạng lớn, tổ chức.

Screenshot_201.png

Ở thời điểm mà mình bắt đầu có firmware để nghiên cứu mà lại có 1 reverser join vào thì là một cơ hội tốt :3 chưa kể mình cũng còn bị thiếu kinh nghiệm dịch ngược nữa nên việc có được Trung trong team là một điều tuyệt vời.

Xác định lại mục tiêu​

Như đã nói ở trên thì mục tiêu của mình lúc này đó là tìm cách để có được shell trên thiết bị, vì vậy giờ khi có 2 người thì mục tiêu chính của bọn mình cũng là:

  • Trung dịch ngược firmware và tìm ra cách vào shell từ một interface nào đó trên thiết bị -> Thành công theo hướng này cũng gần như đồng nghĩa với việc bọn mình có thể hack được vào thiết bị từ bên ngoài.
  • Mình tìm được cách vào shell bằng cách backdoor thiết bị từ cơ chế update -> Hướng đi này sẽ dùng làm bàn đạp để có persistent access và debug firmware ngay trên thiết bị. Tuy nhiên hướng đi này cần có tác động vật lý tới thiết bị vì vậy bọn mình vẫn sẽ cần Trung nghiên cứu thêm bug để có thể hack một cách thuyết phục hơn.
Ở đây mình sẽ trình bày hướng đi của mình.

Tìm hiểu cấu trúc firmware​

Trong quá trình đọc logs từ UART mình thấy thiết bị có định kì gửi HTTP request để check for update và lấy được luôn URL để tải file update này.

image.png
URL update có dạng như trên hình
Tuy nhiên như mình nói ở trên đó là file update này được mã hoá và cần có key RSA tương ứng ở trong thiết bị để giải mã. Vì bọn mình đã extract được firmware nên có thể tìm thấy ngay file private key này trên thiết bị với cái tên 1912ak1.5.00.prv.

image-1.png

À, đọc đến đây có thể bạn sẽ thắc mắc rằng tại sao bọn mình không patch file firmware đã dump ra rồi flash ngược lại vào trong thiết bị? Tại vì như mình nói trước đó do flash chip vẫn đang nằm trên mạch nên nó bị nhiễu bởi các thành phần điện tử khác, khiến cho việc flash lại firmware bị sai sót và "tạch" luôn thiết bị. Mặt khác firmware mình dump ra dù đã được correct nhưng ít nhiều nó vẫn có thể có những byte bị sai dẫn đến hậu quả tương tự.

Vì lý do như vậy nên mình chọn hướng đi đó là giải mã file update -> đặt backdoor -> encrypt lại -> update thiết bị bằng file mới -> shell.

Ok, tiếp tục câu chuyện đó là giờ có file update đã mã hoá + private key -> Đã có thể giải mã file update rồi, tuy nhiên để giải mã được thì mình còn cần biết nó được mã hoá bằng cách nào nữa (tuy gọi là RSA nhưng vẫn có nhiều cách cài đặt mã hoá khác nhau).

Trong quá trình tìm kiếm, mình tìm thấy một file chịu trách nhiệm cho quá trình update này với cái tên là gwellupdater.sh.

Đoạn code đáng chú ý đầu tiên đó là đoạn giải mã file update bằng lệnh rsa_dec. Tên của file đã được mã hoá luôn là upg.bin.enc (do tên file này là đã được cố định ở biến $FILENAME). Tên file sau khi giải mã là upg.bin

raycast-untitled--5-.png

Tiếp theo là cắt file firmware ra thành 3 phần bằng lệnh dd:

  • bootupdater.sh
  • payload.md5
  • payload.bin
raycast-untitled--6-.png
image-6.png
Trung là người nhìn thấy chi tiết này trước mình
So sánh md5 hash của file payload.bin với nội dung hash trong file payload.md5. Mục đích của việc này là check sự toàn vẹn của file update.

raycast-untitled--7--2.png

Chạy file bootupdater.sh với payload.bin là tham số bằng lệnh sh. Xoá các file tạm được tạo ra trong quá trình update để giải phóng dung lượng. Kiểm tra và reboot nếu file /tmp/do_not_reboot_after_update không tồn tại.

raycast-untitled--8-.png

Tới đây mình rút ra được một số nhận xét như sau:

  • Nếu có thể mã hoá lại được file update upg.bin.enc sau khi chỉnh sửa thì mình có thể cập nhật lên thiết bị này một bản update bất kì.
  • Trong quá trình update thì thiết bị chạy file bootupdater.sh, mà file này lại được cắt ra từ chính file update đã được giải mã (upg.bin).
-> Nếu có thể mã hoá được file update thì mình có thể trực tiếp chèn 1 đoạn script backdoor thiết bị (không cần chờ quá trình update thành công).

Nghiên cứu thuật toán mã hoá & giải mã update file​

Việc đầu tiên mà mình làm đó cứ phải là giải mã file update đã. Mình lấy file rsa_dec từ firmware trước đó lấy được từ thiết bị, đồng thời cũng phát hiện ra file này sử dụng mã nguồn tại repo sau.

image-8.png
Mã nguồn mã hoá RSA mà mình tìm thấy
Sau đó mình đọc nội dung file bootupdater.sh.

Trong đó mình chú ý tới các đoạn code sau:

Sử dụng dd để cắt file firmware (upg.bin) thành 2 phần, app.binuImage:

raycast-untitled--10-.png

Dùng dd để write file app.bin vào /dev/mtdblock6:

raycast-untitled--11-.png

Update kernel, dùng dd để write file uImage vào /dev/mtdblock1

raycast-untitled--11--1.png

Như vậy có thể thấy bootupdater.sh có thể tương tác với filesystem, thậm chí có thể flash lại cả kernel và firmware.

Mình tiếp tục extract file app.bin để xem nội dung. Có vẻ như đây là phần core của firmware, chứa các file binary và scripts cần thiết để thực hiện mọi tác vụ.

image-7.png

Khai thác lỗ hổng trong cơ chế mã hoá để backdoor update file​

Phần mà mình quan tâm nhất lúc này đó là làm sao để pack lại file update, vì vậy nên mình để Trung tiếp tục nghiên cứu trên firmware, còn mình thì tập trung vào tìm hiểu cách hoạt động của mã nguồn RSA.

Compile và chạy thử file rsa_dec, thấy có các options sau:

image-9.png

So sánh với câu lệnh giải mã lúc nãy thấy trong bash script, ta thấy các options -o, -v và -f được sử dụng.

raycast-untitled--16-.png

Như vậy câu lệnh cuối cùng được thực thi đó là

rsa_dec -o -v -f upg.bin.enc
Ý nghĩa của câu lệnh này là giải mã và giữ file gốc (-o), in ra các thông tin trong quá trình giải mã (-v), với tên file đầu vào là upg.bin.enc (-f).

Vì đã biết tính năng nào sẽ được sử dụng nên mình đọc source code và thấy phần xử lý quan trọng nhất nằm trong hàm sau:

rsa_dec.c_rsa_decrypt.png

Từ đây mình tóm tắt lại được quy trình giải mã như sau:

rsa_decrypt:

  • rsa_decrypt_prolog:
    • Mở các file chuẩn bị cho quá trình đọc/ghi
    • Tìm các private key và thêm vào "key ring"
    • Kiểm tra xem private key nào khớp với ciphertext
  • rsa_decrypt_quick/rsa_decrypt_full:
    • Thực hiện giải mã
  • rsa_decrypt_epilog:
    • Đóng các file key, plaintext, ciphertext
    • Xoá file gốc nếu cần
Sau đó khi debug thử quá trình giải mã với file update ban đầu thì mình phát hiện ra nó sử dụng hàm rsa_decrypt_quick, và mình khá bất ngờ khi đọc source code của hàm này:

rsa_dec.c_rsa_decrypt--1-.png

Về cơ bản thì hàm này chỉ đem từng block 64 bit xor với 64 bit RSA_RANDOM(), ở đây chính là hàm random().

image-11.png

Đến đây thì mình đoán được rằng quy trình giải mã này sẽ tạo ra một dãy số random từ 1 seed nào đó nằm trong private key. Thật vậy, khi đọc lại hàm rsa_decrypt_prolog, mình thấy có 1 call chain như sau:

  • rsa_dec.c/number_random_seed:
    • rsa_dec.c/rsa_decrypte_header_common:
      • rsa.c/rsa_decode(&seed, &seed, &key->exp, &key->n);
      • rsa_num.c/number_seed_set_fixed(&seed);
        • rsa_num.c/number_seed_set
          • srandom(number_random_seed);
Điều này có nghĩa là trong quá trình chuẩn bị private key để decrypt thì đồng thời hàm prolog này cũng decrypt và set luôn random seed bằng srandom.

Và...ủa, nghĩa là để pack lại file update kia thì mình chỉ cần extract được seed và xor lại phần data mình sửa lại với dãy số random được tạo ra bởi cái seed kia thôi?

Khỏi phải nói, việc này QUÁ DỄ!

Sau khi hiểu ra vấn đề thì mình có build/code ra một số file sau:

  • rsa_dec (modified): Dùng để extract seed từ file update và private key.
  • randgen: Dùng để gen ra dãy số random từ seed.
  • fileparser.py: Gọi là fileparser vì tưởng ban đầu của mình là parse file update này ra để xử lý tuy nhiên mình đã code luôn nó để pack code backdoor vào file update có sẵn :)).
image-12.png

Đây là source code của file fileparser.py mà mình viết:

fileparser.py--1-.png

Pwned​

Việc còn lại của mình lúc này đó là ném file update mới này vào thẻ nhớ sau đó cắm vào thiết bị, bật lên đồng thời giữ nút reset trong 2-3s.

Kết quả:

image-13.png

Oops...​

Vài hôm sau khi backdoor được thiết bị thì mình có thử ngồi cắm lại chân UART thì phát hiện ra các chân này có đủ read/write, sau khi cắm xong thì mình có vào được shell với credentials là root/<no password> =)).

Một bài học cho việc test không cẩn thận phải trả giá bằng việc mất thời gian và đi con đường vòng. Tuy nhiên mình vẫn rất vui do trước đó không tìm được cách nào khác nên đã phải thử thách và học được nhiều điều khi chọn con đường vòng này.

Persistent access​

Sau khi pwn được thiết bị, mình bắt đầu patch lại file app.bin trước đó để đặt reverse shell vào /etc/init.d/rc.local, vì script này sẽ được chạy trong quá trình boot nên mỗi khi thiết bị khởi động là bọn mình đều có reverse shell.

Mình và Trung bắt đầu đẩy một số static binary lên thiết bị như gdbserver, busybox để có thể debug trực tiếp các binary có sẵn của thiết bị.

Có shell thì làm gì? (từ góc nhìn của Trung)​

Việc đầu tiên khi có shell mình làm là xác định các nơi mà con camera này nhận input từ người dùng. Để làm việc này, mình đã copy netstat từ ngoài vào để chạy và xem camera này đang listen trên những port nào.

nDC_AJiGBvDUG9p2KMrWx9MfmjFE7sKc4gvJTQ6akmeG8V2cJqGW6E3G_tpX1JpjE0Br98AOvFh6F4Gc5iK9YsqbMjyo-sdMrXzWrYGRJotd3ZttCwIcYnqS0Glw1EN1umPI0SSXjRzngh_ZIw

Có nhiều port đang được lắng nghe, trong đó port 5000 nằm ở đầu tiên, vì vậy mình quyết định đi tìm đoạn code thực hiện việc xử lý dữ liệu tới port 5000. netstat còn báo rằng program đang sử dụng port 5000 là “ipc”, nên mình chỉ việc tìm file này, decompile nó và bắt đầu tìm đoạn code đó.

Khi decompile file “ipc”, mình khá sốc vì hàm main của nó rất lớn, decompile mất rất nhiều thời gian. Mình có cảm giác như anh lập trình viên đã viết tất cả mọi thứ vào trong hàm main vậy. Với đoạn code lớn như vậy, việc tìm đoạn code xử lý port 5000 gần như là mò kim đáy bể. Vì mình đã từng lập trình socket bằng C, nên mình biết để listen trên port nào đó, thì trong C phải dùng hàm “bind”.

5rV8oeNTQJr4xqrkrAb9SkrO60LJpA0LXdygkpE961INcKYHor9g2O-tgWwoA_FdxuoOBAc0nfungGZNxcK0jkHRwmPVrjTalLWBxQqzmJDTKgOqiNq3jfsuwnEgpPxDEBIG7zr7Vw_bFRm8cA

Mình dùng chức năng cross-reference của IDA thì thấy chỉ có 28 lời gọi tới hàm bind, giờ mình chỉ việc check từng chỗ là được.

vJM60nzj3ESCPrYORUu8Zj-D6y4Mag3zaxtcp-ULefVPjRItbcvQsm_p_znx_YdqgZy70ybmIR5Wek58VD-umdXwBMUbIWl7qCi8PiZmSgu-7jPnCP1TwPCohY-oTDMdQAsIWyj8_7J9nTbcAA

Cuối cùng mình đã tìm được hàm ở 0x43D2C. Ở trên có đoạn “addr.sin_port = 0x8813”, 0x8813 chính là dạng big endian của số 0x1388, mà 0x1388 chính là 5000. Vậy đây chính xác là đoạn code lắng nghe trên port 5000. Sau đó mình chỉ việc follow hàm này để xem nó xử lý gì với dữ liệu nhận được trên port 5000.

Lỗ hổng đầu tiên: Heap overflow (từ góc nhìn của Trung)​

Không lâu sau khi bọn mình có thể bắt đầu debug trên thiết bị, Trung tìm ra lỗ hổng đầu tiên. Một lỗi heap overflow gây crash khiến cho thiết bị tự khởi động lại và không ghi lại được bất cứ hình ảnh nào trong khoảng thời gian này.

Dưới đây là phần trình bày lỗ hổng từ góc nhìn của Trung:

Lỗ hổng nằm trong hàm ở 0x4706C

3Qh3zgVs4xgf5TtuS8sR3m4TRb7gYtlJJeVhb2zADaUAkCOWu-q5WTS6UjzDGMaQ7IdZQewovsBY2tsJyrDoQ1n9MVi4sRmcB9syM8OcBA52NJNAGvbxK7AsPtRna6iVDeOmCl-py9vbwvn5ew

Ở hình trên, biến buf được cấp phát 0x2000 byte. Với lượng lớn dữ liệu như này, thông thường khi code socket C, người ta thường hay dùng một vòng lặp while (true), mỗi vòng lặp sẽ nhận một lượng nhỏ dữ liệu, tới khi nào đủ 0x2000 byte (hoặc khi không còn dữ liệu để nhận nữa) thì dừng. Tuy nhiên ở hình trên, thay vì kiểm tra “đủ 0x2000 byte thì dừng”, anh lập trình viên đã kiểm tra “khi nào không nhận được dữ liệu nữa thì dừng”. Từ đó dẫn đến việc user có thể gửi nhiều hơn 0x2000 byte, trong khi độ lớn của buffer chỉ là 0x2000 -> heap buffer overflow.
Code exploit:

raycast-untitled--22-.png

Đoạn code trên gửi 0x3000 byte tới port 5000 của camera. Nó sẽ ghi đè lên dữ liệu quan trọng trên heap của camera và khả năng cao sẽ làm camera bị crash.

Ngay lập tức bọn mình setup và làm một chiếc video demo lỗ hổng này:


Lỗ hổng thứ hai: Unauthenticated RTSP stream access & control​

Khi phân tích source code bằng IDA, mình thấy có hàm sub_ADB84 có một flow check loằng ngoằng và đồ sộ

image-17.png
Flow graph của sub_ADB84, có lẽ phải chụp thế này mới thấy được hết độ phức tạp của nó
Đọc qua các block trong hàm, mình thấy chúng chứa rất nhiều từ khoá liên quan tới giao thức RTSP

image-18.png
Các từ khoá RTSP, CSeq
image-19.png
image-20.png
OPTIONS, DESCRIBE, SETUP, TEARDOWN,...
Google search nhanh với từ khoá "RTSP methods", ta có thể thấy rõ đây là các command keywords rất đặc trưng của giao thức này:

image-21.png

Mình search cách xem stream RTSP camera Yoosee thì biết nó có dạng:

rtsp://admin:<password>@<ip>/onvif1
Có thể bỏ đường dẫn này vào VLC như sau:

image-22.png

Vì lúc này mình đã biết rằng dùng VLC có thể xem được video stream từ camera tuy nhiên lại chưa rõ về cấu trúc của 1 gói tin RTSP, nên mình viết một đoạn code để proxy giữa VLC và cam. Vì các gói tin RTSP ở dạng plaintext nên ở phía proxy mình có thể thoải mái log cũng như patch các gói tin qua lại giữa 2 bên.

Ý tưởng của mình là thay vì add đường dẫn RTSP như trên vào VLC thì mình sẽ sử dụng đường dẫn sau để gửi tới proxy:

rtsp://127.0.0.1:554?ip=<camera ip>
Sau đó ở phía proxy sẽ tạo 1 socket connection khác tới <camera ip> và chuyển tiếp packet body tới cam sau đó nhận response từ cam và phản hồi lại cho VLC. Phương pháp này khá giống với cách tấn công man in the middle, tuy nhiên ở đây phía proxy không intercept connection này mà nó được chọn để chuyển tiếp các gói tin luôn.

Đoạn code server của mình lúc này trông như sau (đã lược đi các hàm patch rườm rà):

raycast-untitled--17-.png

Vì lúc này đã có thể log và patch các request/response khá tiện rồi nên mình hướng tới tìm lỗi logic trong hàm sub_ADB84 ở trên. Mình đặt ra một số câu hỏi như sau:

  • Có những tác vụ cần auth (authentication/authorization), vậy chúng được coi là auth dựa trên những yếu tố gì?
  • Có những tác vụ không cần auth, vậy điều kiện nào để xác định một tác vụ như vậy?
  • Có cách nào "lừa" thiết bị rằng một tác vụ không cần auth trong khi nó có cần auth không?
image-23.png
Đây là flow check auth, nó sẽ trả về code 401 nếu như check fail
Đọc lại phần này trong code thì mình thấy khá liên quan. Trong khi đọc request log mình phát hiện ra rằng DESCRIBE là một tác vụ không cần auth và chắc hẳn đó cũng là một phần lý do nó được check riêng ở chỗ này:

image-24.png

Thật sự là viết đến đoạn này của chiếc blog thì mình đã ngồi nghĩ nát óc xem hồi đấy mình đã suy luận và khai thác lỗ hổng ở chỗ này ra sao. Mình chỉ nhớ rằng hồi đấy mình khai thác phần này cũng bằng tư duy logic từ các câu hỏi mình đặt ra ở trên, cộng với một số kinh nghiệm mình có được khi khai thác các lỗ hổng về authentication khi mình nghiên cứu web API.

Về cơ bản thì mình có một số nhận định như thế này:

  • Khi user đã authenticated thì server sẽ phải trả về một thông tin gì đó để lưu giữ phiên đăng nhập của user. Với web API thì đó thường là cookie, còn ở đây là header "Session".
  • Ngoài kiểm tra phiên đăng nhập ra thì server có thể check một số thông tin khác trong headers. Ví dụ như ở đoạn code trong ảnh trên có vẻ như thiết bị đang kiểm tra một số thông tin với các từ khoá "username", "realm", "nonce".
Khi kiểm thử các ứng dụng web mặc dù không có source code nhưng ta vẫn có thể biết được server check những header nào bằng cách thử loại bỏ từng header cho tới khi có lỗi xảy ra. Và đó cũng chính là cách mà mình đã thử.

Sau quá trình thử thêm, bớt headers, mình craft được đoạn code sau để patch request body và qua mặt được authentication flow trên thiết bị:

raycast-untitled--20--1.png

Ở đây mình đã check và thêm header "Accept""Authorization" vào tất cả các request, cũng như xoá đi header "Session".

Ngoài ra mình còn phát hiện thêm tác vụ "SET_PARAMETER" cho phép điều khiển hướng quay của thiết bị mà không cần chỉnh sửa bất cứ header nào.

raycast-untitled--21-.png
Một phần đoạn mã nguồn điều khiển hướng thiết bị
Và cũng như đối với lỗ hổng đầu tiên, mình đã làm ngay một chiếc demo cho nó:


PoC/Source code​

Mã nguồn của tất cả mọi nội dung mình trình bày ở trên nằm ở đây:

GitHub - t-rekttt/yoosee-exploit
Contribute to t-rekttt/yoosee-exploit development by creating an account on GitHub.
GitHubt-rekttt


Report timeline​

  • 10/3/2022: Gửi email thông báo cho vendor
  • 14/3/2022: Vendor yêu cầu thêm thông tin
  • 14/3/2022: Gửi demo video và timeline cho vendor
image-26.png

  • 16/3/2022: Feedback từ vendor nói rằng các tính năng này là bình thường
image-27.png

  • 16/3/2022: Giải thích thêm về lỗ hổng
image-29.png

  • 17/3/2022: Feedback từ vendor (vendor vẫn không hiểu cách tiếp cận của bọn mình)
image-28.png

  • 17/6/2022: Publish writeup

Credits & thanks to​

  • Nguyễn Quốc Trung (@xikhud): Teammate cùng đồng hành nghiên cứu. Đồng tác giả bài blog này.
  • Anh Trần Phạm Thành (@radcet): Hỗ trợ và tư vấn trong suốt quá trình nghiên cứu. Previewer.
  • Anh Nguyễn Hồng Phúc (@xnohat): Tư vấn, cung cấp một số công cụ dịch ngược/backdoor
  • Anh Vũ Huy Hưng (@James): Hỗ trợ sách & các khoá học về hardware hacking
  • Anh Mạnh Tuấn (@Juno Okyo): Previewer
Nguồn https://thao.pw/hacking-yoosee-camera/
Dù không có đủ kiến thức để hiểu hết nội dung của bạn nhưng cảm thấy đam mê của bạn và team qua dự án này.
 

Camper

Gà con
Công phu quá, không phải ai cũng đủ kiến thức để hiểu hết bài này :(
 

thuuong1309

Rìu Sắt
Bài viết thật công phu, nhưng tôi chưa tới level này nên chỉ like thôi và đi ra {byebye}
 


Top