├── My_Reverse_Book ├── .gitbook │ └── assets │ │ ├── image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (1) (1) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (1) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (1) (1) (1) (1) (1) (1).png │ │ ├── image (1) (1) (1) (1) (1).png │ │ ├── image (1) (1) (1) (1).png │ │ ├── image (1) (1) (1).png │ │ ├── image (1) (1).png │ │ ├── image (1).png │ │ ├── image (10) (1).png │ │ ├── image (10).png │ │ ├── image (11) (1).png │ │ ├── image (11).png │ │ ├── image (12) (1).png │ │ ├── image (12).png │ │ ├── image (13).png │ │ ├── image (14).png │ │ ├── image (15).png │ │ ├── image (16).png │ │ ├── image (17).png │ │ ├── image (18).png │ │ ├── image (19).png │ │ ├── image (2) (1) (1) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (2) (1) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (2) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (2) (1) (1) (1) (1) (1) (1).png │ │ ├── image (2) (1) (1) (1) (1) (1).png │ │ ├── image (2) (1) (1) (1) (1).png │ │ ├── image (2) (1) (1) (1).png │ │ ├── image (2) (1) (1).png │ │ ├── image (2) (1).png │ │ ├── image (2).png │ │ ├── image (20).png │ │ ├── image (21).png │ │ ├── image (22).png │ │ ├── image (23).png │ │ ├── image (24).png │ │ ├── image (25).png │ │ ├── image (26).png │ │ ├── image (27).png │ │ ├── image (28).png │ │ ├── image (29).png │ │ ├── image (3) (1) (1) (1) (1) (1) (1) (1).png │ │ ├── image (3) (1) (1) (1) (1) (1) (1).png │ │ ├── image (3) (1) (1) (1) (1) (1).png │ │ ├── image (3) (1) (1) (1) (1).png │ │ ├── image (3) (1) (1) (1).png │ │ ├── image (3) (1) (1).png │ │ ├── image (3) (1).png │ │ ├── image (3).png │ │ ├── image (30).png │ │ ├── image (31).png │ │ ├── image (32).png │ │ ├── image (33).png │ │ ├── image (34).png │ │ ├── image (35).png │ │ ├── image (36).png │ │ ├── image (37).png │ │ ├── image (38).png │ │ ├── image (39).png │ │ ├── image (4) (1) (1) (1) (1).png │ │ ├── image (4) (1) (1) (1).png │ │ ├── image (4) (1) (1).png │ │ ├── image (4) (1).png │ │ ├── image (4).png │ │ ├── image (40).png │ │ ├── image (41).png │ │ ├── image (42).png │ │ ├── image (43).png │ │ ├── image (44).png │ │ ├── image (45).png │ │ ├── image (5) (1) (1).png │ │ ├── image (5) (1).png │ │ ├── image (5).png │ │ ├── image (6) (1) (1).png │ │ ├── image (6) (1).png │ │ ├── image (6).png │ │ ├── image (7) (1) (1).png │ │ ├── image (7) (1).png │ │ ├── image (7).png │ │ ├── image (8) (1) (1).png │ │ ├── image (8) (1).png │ │ ├── image (8).png │ │ ├── image (9) (1).png │ │ ├── image (9).png │ │ └── image.png ├── README.md ├── SUMMARY.md └── my_reverse_notes │ ├── 0.-gan-xie-xian-hang-zhe.md │ ├── 0.-nei-rong-yue-ding.md │ ├── 1.-kai-pian-wei-shi-mo-ren-lei-kan-bu-dong-fan-hui-bian.md │ ├── 10.-an-li-fen-xi-ghidra0.md │ ├── 10.-an-li-fen-xi-jaxd1.md │ ├── 10.-an-li-fen-xi-shuo-ming.md │ ├── 2.-fan-bian-yi-tong-yong-jia-gou-yu-shi-li.md │ ├── 3.-di-gui-xia-jiang-fan-hui-bian-dai-ma-yu-shu-ju-hun-he-chang-jing-de-chu-li.md │ ├── 3.-hui-zong-fan-bian-yi-qi-zhong-de-fan-hui-bian.md │ ├── 3.-jian-jie-tiao-zhuan-yu-jian-jie-tiao-yong-de-chu-li.md │ ├── 3.-zhan-pian-yi-ping-heng-yu-ji-suan-shi-yong-ida-ju-li.md │ ├── 4.-biao-da-shi-chuan-bo-yu-gong-gong-zi-biao-da-shi-xiao-chu-cse.md │ ├── 4.-fan-bian-yi-de-zhong-jian-yu-yan-she-ji.md │ ├── 4.-kui-kong-you-hua-yi-chu-fa-huan-yuan-shi-li.md │ ├── 4.-si-dai-ma-xiao-chu-fu-zhi-chuan-bo-chang-liang-chuan-bo.md │ ├── 4.-tiao-yong-yue-ding-shi-bie-can-shu-yu-fan-hui-zhi.md │ ├── 5.-lei-xing-tui-dao.md │ ├── 6.-kong-zhi-liu-huan-yuan-1.md │ ├── 7.-bian-liang-huan-yuan-1-bian-liang-de-ji-qi-ji-biao-shi.md │ ├── 7.-bian-liang-huan-yuan-2-ji-cun-qi-fen-pei-yu-huan-yuan.md │ ├── bu-chong-zhang-jie-lun-wen-yue-du-usingsailronangrdecompiler.md │ ├── fan-hun-xiao-hua-zhi-ling-jian-jie-tiao-zhuan.md │ ├── fan-hun-xiao-ollvm-jing-tai-huan-yuan-0.md │ ├── fan-hun-xiao-ollvm-jing-tai-huan-yuan-1.md │ ├── fan-hun-xiao-zi-fu-chuan-jia-mi.md │ ├── qian-shui-qu-wan-shua-dong-jing-tai-jie-he-yu-qing-du-hun-xiao-huan-yuan.md │ ├── shen-shui-qu-re-shen-gao-du-hun-xiao-de-huan-yuan.md │ ├── vmp0-ru-he-dui-kang-shang-ye-ji-de-vm-bao-hu.md │ └── vmp1-chu-shi-gong-zuo-+-xun-zhao-vmp-de-ruo-dian.md └── README.md /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (10) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (10) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (10).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (10).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (11) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (11) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (11).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (11).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (12) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (12) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (12).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (12).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (13).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (13).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (14).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (14).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (15).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (15).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (16).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (16).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (17).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (17).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (18).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (18).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (19).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (19).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (2).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (2).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (20).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (20).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (21).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (21).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (22).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (22).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (23).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (23).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (24).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (24).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (25).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (25).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (26).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (26).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (27).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (27).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (28).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (28).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (29).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (29).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (3).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (3).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (30).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (30).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (31).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (31).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (32).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (32).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (33).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (33).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (34).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (34).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (35).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (35).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (36).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (36).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (37).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (37).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (38).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (38).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (39).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (39).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (4) (1) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (4) (1) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (4) (1) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (4) (1) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (4) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (4) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (4) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (4) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (4).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (4).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (40).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (40).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (41).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (41).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (42).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (42).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (43).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (43).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (44).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (44).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (45).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (45).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (5) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (5) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (5) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (5) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (5).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (5).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (6) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (6) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (6) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (6) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (6).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (6).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (7) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (7) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (7) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (7) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (7).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (7).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (8) (1) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (8) (1) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (8) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (8) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (8).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (8).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (9) (1).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (9) (1).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image (9).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image (9).png -------------------------------------------------------------------------------- /My_Reverse_Book/.gitbook/assets/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wINfOG/My_Reverse_Book/f346146cc1c9b59bc5cfc415e6e5dfbfe3e9ccde/My_Reverse_Book/.gitbook/assets/image.png -------------------------------------------------------------------------------- /My_Reverse_Book/README.md: -------------------------------------------------------------------------------- 1 | # 0. 这个笔记写了什么 2 | 3 | ## 开篇故事 4 | 5 | 开篇,我要先讲一个记忆深处的故事: 6 | 7 | 好多好多年前,在一个风和日丽的晚上;我们一群人走在黑漆漆的校园里,这时候丘比龙学长说: "你们知道吗,至今为止,我只见过三个人肉逆向机,一个是我的学长 monster,一个是清华的xxx, 一个是...." 8 | 9 | 那时懵懵懂懂的我啥也不懂,不理解"人肉逆向机"的含义。 10 | 11 | "那怎么才能成为人肉逆向机呢?" 12 | 13 | "我也不知道,但是我知道,无一例外,这几个人在十几岁开始就研究逆向了" 14 | 15 | “那时候我还在玩泥巴咧!” 16 | 17 | 这个故事到现在已经10多年了,至今我也不知道怎么成为"人肉逆向机"。 18 | 19 | 也许是天赋的问题?就好像“我看懂了,因此我知道我学不会”,这种类型兵种的根本没有重复批量生产的方法论?每个人都独一无二。 20 | 21 | 这个系列的文章是我对逆向工作的一些简单的总结,以及“我理想中的逆向世界”, 22 | 23 | **祝各位大佬人人都能成为究极人肉逆向机,看到汇编后源代码就从脑子往外冒。** 24 | 25 | ## 与同类作品有啥区别? 26 | 27 | **“我觉得,琢磨源码与二进制的对应关系是有乐趣的”** 28 | 29 | 国内的几个解放军工程学院的老师也有出版反编译器设计的old-school书籍,我看了,真是熟悉的感觉,回到了学习什么《信号与系统》、《通信原理》的时候。 30 | 31 | 这些教材“与其说是一本教学用的书籍,更像是博士毕业论文” 32 | 33 | 大量的程序分析算法,配合上学术论文的引用,很难看得下去。对于我这种不算学术界的人,学起来体验十分困难。 34 | 35 | 我觉得,对于我们平时的工作生活,很难用到这些定理与算法,明白反编译器为什么这么做,明确好输入与输出,确认使用的场景,更加重要。 36 | 37 | 因此,进阶的反编译教学或许应该长这样: 38 | 39 | * 最重要的,一定要有IDA的真实案例 40 | * 用提出问题 + 解决问题的方法叙述 41 | * 不要复杂的算法,不要学术语言,要有乐趣 42 | * 小白也能看懂+学到知识(嗯~会尽最大的能力做到的) 43 | * 学到的知识在实战中有意义! 44 | 45 | ## 为啥要学习这些知识? 46 | 47 | 当我还是一个小白的时候,只会用IDA的F5进行逆向;一旦F5趴窝了那我也歇菜了。 48 | 49 | 什么Angr,中间语言,OLLVM ,VMP等等,看的我是一脑袋浆糊。这还不是最麻烦的,最麻烦的是不知道自己哪里不懂。也不知道怎么接下去学习。 50 | 51 | 我希望通过这本书提供一个过渡的桥梁,通过理解IDA的F5或者其它反编译器类似的技术的核心,以此来逐渐脱离对F5的依赖,掌握静态逆向的核心技术—> 反“编译器”。 52 | 53 | 通过对反编译理论的学习,理解程序为什么编译成这个样子,怎么还原回去; 54 | 55 | 熟悉反编译器的架构,最终当F5出问题了,能够分析情况并解决问题,甚至写出自己的F5来。 56 | 57 | 同时,实践的章节还将一步一步的教会基于ida、ghidra、angr等工具自己开发自己的逆向能力+逆向插件。 58 | 59 | ## 其它的? 60 | 61 | 除了关于反编译技术的理论补充,也会加入一些深度混淆对抗的讨论章节。 62 | 63 | 随着反编译混淆技术的发展,原始的OLLVM,VMP等技术的进一步发展,以后的逆向对抗的难度一定是越来越深的。 64 | 65 | 从最基本的混淆,到复杂的OLLVM/VMP等;尝试设计一些针对不同的变种强混淆的深度定制还原技术,寻找更好的解决方案。 66 | -------------------------------------------------------------------------------- /My_Reverse_Book/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | ## My\_Reverse\_Notes 4 | 5 | * [0. 这个笔记写了什么](README.md) 6 | * [0. 感谢先行者](my_reverse_notes/0.-gan-xie-xian-hang-zhe.md) 7 | * [0. 内容约定](my_reverse_notes/0.-nei-rong-yue-ding.md) 8 | * [1. 开篇:为什么人类看不懂反汇编?](my_reverse_notes/1.-kai-pian-wei-shi-mo-ren-lei-kan-bu-dong-fan-hui-bian.md) 9 | * [2. 反编译通用架构与示例](my_reverse_notes/2.-fan-bian-yi-tong-yong-jia-gou-yu-shi-li.md) 10 | * [3. 递归下降反汇编-代码与数据混合场景的处理](my_reverse_notes/3.-di-gui-xia-jiang-fan-hui-bian-dai-ma-yu-shu-ju-hun-he-chang-jing-de-chu-li.md) 11 | * [3.间接跳转与间接调用的处理](my_reverse_notes/3.-jian-jie-tiao-zhuan-yu-jian-jie-tiao-yong-de-chu-li.md) 12 | * [3.栈偏移平衡与计算(使用IDA举例)](my_reverse_notes/3.-zhan-pian-yi-ping-heng-yu-ji-suan-shi-yong-ida-ju-li.md) 13 | * [3.汇总-反编译器中的反汇编](my_reverse_notes/3.-hui-zong-fan-bian-yi-qi-zhong-de-fan-hui-bian.md) 14 | * [4.反编译的中间语言设计](my_reverse_notes/4.-fan-bian-yi-de-zhong-jian-yu-yan-she-ji.md) 15 | * [4.调用约定识别-参数与返回值](my_reverse_notes/4.-tiao-yong-yue-ding-shi-bie-can-shu-yu-fan-hui-zhi.md) 16 | * [4.表达式传播与公共子表达式消除(CSE)](my_reverse_notes/4.-biao-da-shi-chuan-bo-yu-gong-gong-zi-biao-da-shi-xiao-chu-cse.md) 17 | * [4.死代码消除、复制传播、常量传播](my_reverse_notes/4.-si-dai-ma-xiao-chu-fu-zhi-chuan-bo-chang-liang-chuan-bo.md) 18 | * [4.窥孔优化(以除法还原示例)](my_reverse_notes/4.-kui-kong-you-hua-yi-chu-fa-huan-yuan-shi-li.md) 19 | * [5.类型推导](my_reverse_notes/5.-lei-xing-tui-dao.md) 20 | * [6.控制流还原(1)](my_reverse_notes/6.-kong-zhi-liu-huan-yuan-1.md) 21 | * [7.变量还原(1)变量的机器级表示](my_reverse_notes/7.-bian-liang-huan-yuan-1-bian-liang-de-ji-qi-ji-biao-shi.md) 22 | * [7.变量还原(2)寄存器分配与还原](my_reverse_notes/7.-bian-liang-huan-yuan-2-ji-cun-qi-fen-pei-yu-huan-yuan.md) 23 | * [10. 案例分析-说明](my_reverse_notes/10.-an-li-fen-xi-shuo-ming.md) 24 | * [10. 案例分析-JAXD-1](my_reverse_notes/10.-an-li-fen-xi-jaxd1.md) 25 | * [10. 案例分析-Ghidra-0](my_reverse_notes/10.-an-li-fen-xi-ghidra0.md) 26 | * [浅水区玩耍:动静态结合与轻度混淆还原](my_reverse_notes/qian-shui-qu-wan-shua-dong-jing-tai-jie-he-yu-qing-du-hun-xiao-huan-yuan.md) 27 | * [反混淆:花指令/间接跳转](my_reverse_notes/fan-hun-xiao-hua-zhi-ling-jian-jie-tiao-zhuan.md) 28 | * [反混淆:字符串加密](my_reverse_notes/fan-hun-xiao-zi-fu-chuan-jia-mi.md) 29 | * [深水区热身:高度混淆的还原](my_reverse_notes/shen-shui-qu-re-shen-gao-du-hun-xiao-de-huan-yuan.md) 30 | * [反混淆: OLLVM静态还原-0](my_reverse_notes/fan-hun-xiao-ollvm-jing-tai-huan-yuan-0.md) 31 | * [反混淆: OLLVM静态还原-1](my_reverse_notes/fan-hun-xiao-ollvm-jing-tai-huan-yuan-1.md) 32 | * [VMP-0:如何对抗商业级的VM保护?](my_reverse_notes/vmp0-ru-he-dui-kang-shang-ye-ji-de-vm-bao-hu.md) 33 | * [VMP-1:初始工作+寻找VMP的弱点](my_reverse_notes/vmp1-chu-shi-gong-zuo-+-xun-zhao-vmp-de-ruo-dian.md) 34 | * [补充章节-论文阅读:using-sailr-on-angr-decompiler](my_reverse_notes/bu-chong-zhang-jie-lun-wen-yue-du-usingsailronangrdecompiler.md) 35 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/0.-gan-xie-xian-hang-zhe.md: -------------------------------------------------------------------------------- 1 | # 0. 感谢先行者 2 | 3 | ### 本章**可跳过!!!** 4 | 5 | 致敬先行者们,是你们的努力探索让我们现在有这么多的好东西用。尤其是IDA和Ghidra这两个工具,让我们有可用的好工具。 6 | 7 | 感谢那些无私开源奉献的人们。 8 | 9 | ## 感谢奠基者 10 | 11 | Ilfak Guilfanov和他的IDA工具,199X年至今,历经30年余的不懈努力,逆向工程界的软件标杆 12 | 13 | [https://www.freebuf.com/sectool/275446.html](https://www.freebuf.com/sectool/275446.html) 14 | 15 | 可惜他输出的文章少了一点,最近的一次在2018年的black-hat描述了中间语言的结构。 16 | 17 | 18 | 19 | Cifuentes, C 博士论文以及对应的DCC反编译工具,奠定了反编译工具的整体流程• [Reverse Compilation Techniques](http://www.phatcode.net/res/228/files/decompilation_thesis.pdf) 20 | 21 | Mike Van Emmerik的博士论文《Static Single Assignment for Decompilation》奠定了 SSA 在反编译领域的基石,尤其在类型恢复中,他参与的Boomerang也很不错(虽然现在已经不再维护了) 22 | 23 | University of Queensland Binary Translator (UQBT) 作者”Cifuentes, C 和 Mike Van Emmerik” 24 | 25 | 虽然这个工具的设计不是用于反编译。它也是Ghidra工具后端Sleigh的前身,也是后续反编译理论的技术底座 26 | 27 | [https://github.com/osfree-project/uqbt](https://github.com/osfree-project/uqbt) 28 | 29 | 30 | 31 | 感谢Ghidra和sleigh的开源,从199X年到现在,不可思议,它出现时就走在正确的道路上,最开始使用SSA形态的中间语言,配合架构描述性语言(SLED)来适配多指令集。感谢它的开源,让我不必再混沌中摸索,如同启明星,有时候不知道怎么做那就看一下Ghidra的源码吧(Ghidra能不能把你那破UI修一修啊) 32 | 33 | 34 | 35 | 《No More Gotos》 **Khaled Yakdan** 博士论文 《A Human-Centric Approach For Binary Code Decompilatio》构建了Dream++反编译器从理论上证明不依赖于规则基于stucter恢复后可以将控制流还原成无Goto的形态。 36 | 37 | 38 | 39 | 《TIE》Principled Reverse Engineering of Types in Binary Programs. 整理并归纳了基于格理论+上下限约束推导的逆向类型推断系统。 40 | 41 | 42 | 43 | ## 感谢国内老师的课程和教材 44 | 45 | 非常感谢南京大学的谭添、李樾两位老师的《软件分析》课程: [https://github.com/pascal-lab/Tai-e-assignments](https://github.com/pascal-lab/Tai-e-assignments) 46 | 47 | 庞建民老师:《编译与反编译技术》 48 | 49 | 张平老师:《反编译技术》 50 | 51 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/0.-nei-rong-yue-ding.md: -------------------------------------------------------------------------------- 1 | # 0. 内容约定 2 | 3 | 太长不看:真传一句话,假传万卷书。所以这个系列的废话特别多! 4 | 5 | ## 目标读者 6 | 7 | 这一系列的文章目标读者是谁: 8 | 9 | * 已经有简单基础的逆向爱好者,不满足与拿到二进制就F5,想大概知道逆向工具内部原理,了解如何将汇编/字节码转换为伪代码,提升一些常识性知识,方便用于平时的工作生活。 10 | * 有编写逆向工具、逆向插件、混淆对抗;或者优化现有的反编译器,比如,让现有逆向工具支持新的指令集架构、新的可执行文件格式、新的语言等等需求。 11 | * 需要长时间对抗多种高强度混淆:嗯。。。什么工作需要长时间对抗多种高难度混淆的呢。。。这可不好说啊。 12 | * 大晚上睡不着,没事干,就要找点事情干干的(比如我!) 13 | 14 | 15 | 16 | ## 理论章节的文章约定 17 | 18 | 如果可以,这一系类的文章尽量使用并分析业界当前的最佳商用实践与开源工具 19 | 20 | 由于反编译技术由多个模块组成,因为编译后信息的损失,其中的很多难题是没有最优解的,因此后续的理论的章节将使用以下的结构 21 | 22 | 1、简要描述这一章节要说啥组件 23 | 24 | 2、说明这部分属于整个反编译流程中的哪个部分 25 | 26 | 3、说明这一章节所解决的难题,提供正向与反向的示例 27 | 28 | 4、如果有必要,说明并提供示例,解释编译器将C代码编译成二进制的过程中做了什么优化;为反编译器需要做对应的”反优化”提供可读性。 29 | 30 | 5、详细描述当前这一技术在IDA或者Ghidra等工具上的具体实现,提供示例 31 | 32 | 6、当前已有的实现存在什么缺陷?无法解决什么场景?使用源码举例为什么会出现这个场景。有没有更好的技术解决。逆向的工作中遇到了应该怎么办。 33 | 34 | 7、拓展话题,包括相关的静态分析对抗技术,我觉得有趣的东西等等。 35 | 36 | 8、学术相关,讨论学术界再这方便的研究历史与最新进展,但是学术界的东西通常晦涩难懂,上来就给你什么定理什么公式,我看着就头疼;因此这部分的部分内容可能要后续再补充了。 37 | 38 | 39 | 40 | 41 | 42 | 接着为了避免歧义,提前明确一些可能存在歧义的部分。 43 | 44 | 0、理论章节中所讨论的反汇编技术,默认目标都是正常编译的代码;加固、混淆、壳、等等不正常的东西,这部分的讨论都会在后面独立的部分。 45 | 46 | 1、编译器参数默认只启用-O3 47 | 48 | 2、文章里反编译器用的是IDA7.5+Ghidra10.3及以上版本的工具进行反编译 49 | 50 | 3、如果没有明确说明。文章中出现的 “常用的反编译工具”、“现在的反编译器” 这类语句中所说的反编译器,就是指IDA和Ghidra这两个最常用的软件 51 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/1.-kai-pian-wei-shi-mo-ren-lei-kan-bu-dong-fan-hui-bian.md: -------------------------------------------------------------------------------- 1 | # 1. 开篇:为什么人类看不懂反汇编? 2 | 3 | **也许不是所有的人不适合看反汇编总有人天赋异禀?就像传说中的“人肉逆向机”一样。** 4 | 5 | **总有人天赋异禀?** 6 | 7 | **反正我是做不到的啦~~** 8 | 9 | **我只是个普通的平凡人类~~** 10 | 11 | **平凡的人类有自己的笨办法~~** 12 | 13 | ## 身为人类的局限 14 | 15 | 人类短期记忆的容量大概在7个数字左右(左右范围是5到9个)这是著名的心理学家。。。。(嗯。。。他的名字超过了7个字母,所以我没记住)的研究结果。 16 | 17 | ref:1956年George Miller论文 [http://www.psychspace.com/psych/viewnews-12308](http://www.psychspace.com/psych/viewnews-12308) 18 | 19 | 有兴趣的可以去:[https://humanbenchmark.com/tests/number-memory](https://humanbenchmark.com/tests/number-memory) 自己测试一下“你”是不是有独特的记忆天赋,反正我这个人经常忘掉东西。 20 | 21 | 而常用的指令集比如x86\_64,哪怕把RIP/RBP/RSP/段寄存器/标志位全不算,剩下的寄存器还有10多个呢。要知道,记住一个寄存器的状态可比记一个数字难多了;更别说ARM指令这些了。 22 | 23 | ## 看不懂的汇编 24 | 25 | 汇编语言的设计就不是给人类看的。我举个最典型的例子,条件分支。 26 | 27 | 比如这样的代码 28 | 29 | ```jsx 30 | if (a == b && a == c) { 31 | //do something 32 | } else { 33 | //do something 34 | } 35 | ``` 36 | 37 | 编译成汇编后因为标志位的比较是独立的,汇编代码可能是这样 38 | 39 | ```jsx 40 | cmp a,b 41 | jz LABEL_1 42 | cmp a,c 43 | jz_LABEL_1 44 | ``` 45 | 46 | 如果有更加复杂的条件,循环,那就更是一坨了 47 | 48 | ## 来捣乱的编译器与伟大的F5 49 | 50 | 看不懂汇编和寄存器数量,他们还都不是主要的麻烦,可以通过反复训练+足够多的经验弥补。 51 | 52 | 最麻烦的还是编译器的优化,最典型的就是 指令选择+指令调度+寄存器分配+窥孔优化 (这几个话题每个都值得后面单独开一张来讲) 53 | 54 | 最终变成了一团奇奇怪怪的东西(尤其是大型软件的反编译) 55 | 56 | 幸好,我们还有反编译器来缓解这种问题。感谢伟大的F5。 57 | 58 | ## 为什么要学习这部分知识? 59 | 60 | 人类的局限告诉我们,绝大部分人是很难直接看懂反汇编的。 61 | 62 | 而逆向工程,尤其是对复杂软件的逆向工程,逆向目的往往不是看懂代码的每一处,而是在理解一段段业务在做什么,确认输入输出。而伟大的F5按钮能够极大的加速这个过程。 63 | 64 | **“但是,如果F5坏了或者不好用了怎么办!!!??”** 65 | 66 | 这在逆向工作中太常见了,我举几个大家一定遇到过的例子: 67 | 68 | * 花指令 69 | * switch跳转识别不出来 70 | * IDA说什么什么sp-error栈平衡错误是啥意思,咋解决 71 | * F5代码里充满了goto,根本看不懂 72 | * F5代码全是结构体偏移的指针计算,根本不想看 73 | * 他这个二进制是c++、go、rust写的,简直一坨 74 | * <省略> 75 | 76 | 那么为什么会有这些问题,能不能解决,怎么解决?应该在哪一步编写插件?应该如何编写插件完成优化? 77 | 78 | 如果理解了反编译器的内部构造,就能大概回答这些问题。 79 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/10.-an-li-fen-xi-ghidra0.md: -------------------------------------------------------------------------------- 1 | # 10. 案例分析-Ghidra-0 2 | 3 | ## 主体架构 4 | 5 | Ghidra反编译器分为两大块,第一块为上层的Java编写的二进制格式分析(ELF、PE等等)与加载,函数识别,二进制分析模组(Analysis) 6 | 7 | 第二块为底层由C++编写的函数级反编译器。这个功能可以独立编译、运行、调试,支持命令行与字符流的交互方法。 8 | 9 | 其简单架构如下图,上层通过字符流与下层进行通信。 10 | 11 | 搞逆向时候的通用的步骤:加载二进制,处理各种,通过特征找到关键逻辑,F5,混淆,手动恢复结构体等等、修复F5。 12 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/10.-an-li-fen-xi-jaxd1.md: -------------------------------------------------------------------------------- 1 | # 10. 案例分析-JAXD-1 2 | 3 | 示例Java代码 4 | 5 | ```java 6 | import java.util.Scanner; 7 | 8 | public class test { 9 | public static void main(String[] args) { 10 | Scanner myInput = new Scanner( System.in ); 11 | int a = myInput.nextInt();; 12 | int y = 1; 13 | while (y > 0) { 14 | while (a > 50) { 15 | a = a + 1; 16 | y = y + 1; 17 | if (a > 10) { 18 | if (a > 100) { 19 | a = a * 5; 20 | break; 21 | } else { 22 | y = y / a; 23 | } 24 | } 25 | } 26 | 27 | } 28 | System.out.println(y); 29 | } 30 | } 31 | ``` 32 | 33 | 编译成Smali后 34 | 35 | ```smali 36 | import java.util.Scanner; 37 | 38 | .method public static main([Ljava/lang/String;)V 39 | .registers 3 40 | .line 5 41 | p0 = new-instance java/util/Scanner; 42 | sget-object v0, Ljava/lang/System;->in:Ljava/io/InputStream; 43 | invoke-direct {p0, v0}, Ljava/util/Scanner;->(Ljava/io/InputStream;)V 44 | .line 6 45 | invoke-virtual {p0}, Ljava/util/Scanner;->nextInt()I 46 | move-result p0 47 | .line 7 48 | const/4 v0, 0x1 49 | .line 8 50 | :cond_c 51 | :goto_c 52 | if-lez v0, :cond_23 53 | .line 9 54 | :cond_e 55 | :goto_e 56 | const/16 v1, 0x32 57 | if-le p0, v1, :cond_c 58 | .line 10 59 | add-int/lit8 p0, p0, 0x1 60 | .line 11 61 | add-int/lit8 v0, v0, 0x1 62 | .line 12 63 | const/16 v1, 0xa 64 | if-le p0, v1, :cond_e 65 | .line 13 66 | const/16 v1, 0x64 67 | if-le p0, v1, :cond_21 68 | .line 14 69 | mul-int/lit8 p0, p0, 0x5 70 | .line 15 71 | goto :goto_c 72 | .line 17 73 | :cond_21 74 | div-int/2addr v0, p0 75 | goto :goto_e 76 | .line 23 77 | :cond_23 78 | sget-object p0, Ljava/lang/System;->out:Ljava/io/PrintStream; 79 | invoke-virtual {p0, v0}, Ljava/io/PrintStream;->println(I)V 80 | .line 24 81 | return-void 82 | .end method 83 | ``` 84 | 85 | 虽然相比于汇编,字节码还是很人性化的,为了观感,将这个代码进行优化 86 | 87 | 1、暂时不考虑try-exception-catch的情况 88 | 89 | 2、优化跳转指令展示,if + goto-label形式 90 | 91 | 3、将函数调用转换为便于阅读的形式,优化寄存器类型信息 92 | 93 | 4、删除冗余代码,将字节码指令优化,改为适合人类阅读的形态 94 | 95 | 现在,这个代码变成了这个样子 96 | 97 | ```java 98 | public static void main(java.lang.String[] r2) { 99 | java.util.Scanner r2 = new java.util.Scanner 100 | java.io.InputStream r0 = java.lang.System.in 101 | r2.(r0) 102 | int r2 = r2.nextInt() 103 | r0 = 1 104 | Lc: 105 | if (r0 <= 0) goto L23 106 | Le: 107 | r1 = 50 108 | if (r2 <= r1) goto Lc 109 | int r2 = r2 + 1 110 | int r0 = r0 + 1 111 | r1 = 10 112 | if (r2 <= r1) goto Le 113 | r1 = 100 114 | if (r2 <= r1) goto L21 115 | int r2 = r2 * 5 116 | goto Lc 117 | L21: 118 | int r0 = r0 / r2 119 | goto Le 120 | L23: 121 | java.io.PrintStream r2 = java.lang.System.out 122 | r2.println(r0) 123 | return 124 | } 125 | ``` 126 | 127 | 为了更好的理解,我简单的画了一下对应,当然稍微偷懒了省略了一写 128 | 129 | * 跳过了类型的判断 130 | * 跳过了代码的生成 131 | 132 |
133 | 134 |
135 | 136 | 137 | 138 | 好了已经很有伪代码的样子了,是啊,字节码就是这么轻松,不用解决各种麻烦的汇编转换的麻烦,再画一下CFG图 139 | 140 | 下一步,通过SSA+PHI还原变量,没有定义一个IR而是直接操作了原有的smali代码 141 | 142 | 1、生成CFG图 143 | 144 | 2、构建支配树、支配边界、:计算支配边界用于构造SSA, 循环不变量提取 145 | 146 | 算法来源:` 147 | 148 | 3.1、循环识别 149 | 150 | 3.2、构造SSA 151 | 152 | 在basic-block上构建寄存器的def-use结构,这里我给出上面代码的def-use结构 153 | 154 |
155 | 156 | 157 | 158 | | | DEF | USE | 159 | | -- | ------- | --------- | 160 | | R0 | 1,4,7 | 1,2,4,7,8 | 161 | | R1 | 3,4,5 | 3,4,5 | 162 | | R2 | 1,4,6,8 | 1,4,5,6,8 | 163 | 164 | register define-use-pair 165 | 166 | R0:(1,1) (1,2) (1,4) (1,8) (4,4) (4,2) (4,7) (4,8) (7,2) (7,4) (7,8) 167 | 168 | R1: (3,3) (4,4) (5,5) 169 | 170 | R2: (1,1) (1,4) (4,4) (4,5) (4,6) (6,4) (8,8) 171 | 172 | 基于def-use-pair,进行活跃变量分析(Live Variable Analysis)删除冗余的寄存器分配 173 | 174 | 也即是说存在两种情况,第一种,变量被定义了却没有被使用过(这里不存在),第二种,变量的定义与使用都在同一个基本块内,很明显,R1寄存器就是这种情况,这种寄存器将被活跃变量分析处理后inline掉 175 | 176 | 177 | 178 | 活跃变量分析后的register define-use-pair 179 | 180 | R0: (1,2) (1,4) (1,8) (4,2) (4,7) (4,8) (7,2) (7,4) (7,8) 181 | 182 | R2: (1,4) (4,5) (4,6) (6,4) 183 | 184 | 185 | 186 | 基于支配边界分配寄存器的PHI,即在变量定义的基本块的支配边界添加PHI,直到不动点(TODO,这个描述过于抽象,是否有办法更加简略一点),这里只要处理R1和R2: 187 | 188 | 关于支配边界+phi的构造关系,北大的熊英飞老师的课程有提供,虽然讲的不算详细,但是够用了 189 | 190 | 191 | 192 | | | 定义BasicBlock | 支配边界 | 193 | | -- | ------------ | ---- | 194 | | R0 | 1,4,7 | 2,3 | 195 | | R2 | 1,4,6 | 2,3 | 196 | | | | | 197 | 198 | R0支配边界计算过程: 199 | 200 | DF{1,4,7} ⇒ {3} : DF{1} = {}; DF{4} = {3}; DF{7} = {3}; 201 | 202 | DF1{1,3,4,7} ⇒ {2,3} : DF{3} = {2} 203 | 204 | DF2{1,2,3,4,7} ⇒ {2,3} : DF{2} = {} 205 | 206 | R2计算过程: 207 | 208 | DF{1,4,6} ⇒ {2,3} : DF{1} = {}; DF{4} = {3}; DF{6} = {2}; 209 | 210 | DF1{1,2,3,4,6} ⇒ {2,3}: DF{3} = {2}; DF{2} = {} 211 | 212 | 按照支配边界转换顺序,构建SSA并在BasicBlock 2与3中插入phi节点,插入并清理R1的残留后,带有SSA的结构如下 213 | 214 | 215 | 216 | 生成代码 217 | 218 | ``` 219 | public static void main(String[] strArr) { 220 | int nextInt = new Scanner(System.in).nextInt(); 221 | int i = 1; 222 | L2: 223 | if (i <= 0) goto L20; 224 | L4: 225 | if (nextInt <= 50) goto L2; 226 | nextInt = nextInt + 1; 227 | i = i + 1; 228 | if (nextInt <= 10) goto L4; 229 | if (nextInt > 100) goto L14; 230 | i = i / nextInt; 231 | goto L4 232 | L14: 233 | nextInt = nextInt * 5; 234 | goto L2 235 | L20: 236 | System.out.println(i); 237 | } 238 | ``` 239 | 240 | 最终翻译为伪代码 241 | 242 | ``` 243 | public static void main(String[] strArr) { 244 | int nextInt = new Scanner(System.in).nextInt(); 245 | int i = 1; 246 | while (i > 0) { 247 | while (true) { 248 | if (nextInt > 50) { 249 | nextInt++; 250 | i++; 251 | if (nextInt > 10) { 252 | if (nextInt > 100) { 253 | nextInt *= 5; 254 | break; 255 | } 256 | i /= nextInt; 257 | } 258 | } 259 | } 260 | } 261 | System.out.println(i); 262 | } 263 | ``` 264 | 265 | 266 | 267 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/10.-an-li-fen-xi-shuo-ming.md: -------------------------------------------------------------------------------- 1 | # 10. 案例分析-说明 2 | 3 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/2.-fan-bian-yi-tong-yong-jia-gou-yu-shi-li.md: -------------------------------------------------------------------------------- 1 | # 2. 反编译通用架构与示例 2 | 3 | 4 | 5 | ## 啥是反编译器,啥是逆向软件 6 | 7 |
8 | 9 | 如上图所示,是典型的学术形的反编译”算法”流程。流程描述了有哪些步骤,每一步在做什么。 10 | 11 | 而我更想讨论的逆向软件,相比于反编译器,逆向软件是一套完整的系统。 12 | 13 | 一套完整的系统还必须说明 14 | 15 | * 系统的架构,在哪一部分做哪些事。通用流程告诉你“今天晚上吃麦当劳”,但是我告诉你“麦当劳的甜筒领券今天免费”。 16 | * 关键组件在 不同逆向软件实现的异同点。为什么他们“这样实现”或者“不这样实现” 17 | * 对于实际的算法实现中,准确率与效率的取舍,毕竟世界上很难有两全其美的东西。 18 | * 关于二进制中各种特化的实现;学术界往往提出各种通用的解决方案,但是特化的方案,反而是正真反编译器最常用的那个。 19 | * 反编译器中的插件扩展能力。 20 | 21 | ## 反编译通用流程 22 | 23 | 以下为来自某个论文的通用反编译器图 24 | 25 |
26 | 27 | 注:这个系列不会说明将二进制文件解析的实现细节,有需要的可以看Angr-CLE等工具的相关文档。主要的内容是对中后端技术的讨论。 28 | 29 | ### **架构概述:** 30 | 31 | 其实反编译和编译器很像,当前主流的反编译器也使用了很多的编译器的技术与算法。 32 | 33 | 在整体结构上也区分为:前端、中端、后端; 34 | 35 | 但是相比与编译器他的结构是反过来的,即,反编译器的前端处理的是二进制与机器码,后端目标是生成人类可读甚至直接可编译的源代码。 36 | 37 | 这个系列的文章,主要内容集中在中端,中端优化的目标是提取汇编与程序结构中隐含的信息还原成人类熟悉的高层次结构。 38 | 39 | ### **前端:** 40 | 41 | **1、解析二进制文件结构** 42 | 43 | 二进制的格式有很多种,各种各样的格式也很复杂,各种压缩,各种偏移计算,就像上面说的那样这篇文章不会详细讨论这部分繁杂的内容 44 | 45 | **2、解析机器码,生成IR或者汇编(反汇编)** 46 | 47 | 反汇编,即解析机器码的的主要几个问题: 48 | 49 | * 怎么解决数据与代码逻辑混合的场景? 50 | * 怎么处理间接跳转与间接调用,有哪些特殊情况。 51 | * 从哪里开始反编译,如何从数据中找到所有函数的入口点? 52 | * 为什么要使用中间语言,主流的中间语言方案 53 | 54 | 关于IR(中间语言)的生成,大部分的反编译是直接将机器码转为IR的;(并不是先转换为汇编,再把汇编转换为IR) 55 | 56 | 还有关于IR的设计话题,这个话题太大了,有大佬说过”中间语言的设计相比于科学更像是艺术”; 57 | 58 | 截至目前(2023年)世界上的IR也太多太多了,我已经做不到将他们的各个设计总结出来,因此这部分的内容只能移到最后,去讨论现在的反编译工具比如IDA/GHIDRA/angr 所使用的IR。 59 | 60 | ### **中端:** 61 | 62 | **1、数据流分析(Data-Flow-Analysis)+规则分析** 63 | 64 | 这部分是反编译器中最复杂,内容最多的部分,涉及了各种丰富多彩的话题。比如: 65 | 66 | * 识别全局指针 67 | * CFG与SSA(low-level variables)的构建 68 | * 插入phi并处理关联 69 | * 死代码消除 70 | * 常量传播 71 | * 拷贝传播 72 | * 规则匹配类型优化 73 | * 函数调用的返回值与参数与归一化 74 | * 栈平衡与栈变量识别 75 | * 还原高层次变量 76 | 77 | 数据流分析的主要目的,一个是将相对低级的中间语言中的高级语意提取出来,一个是将高级语义进行各种特化规则的转化。 78 | 79 | **2、类型还原(Type-infer)** 80 | 81 | 现在常用的反编译器的核心设计时没有跨函数全局程序分析的场景,20年前也没有go或者rust这种复杂的东西,因此,类型系统的还原还处于一个比较初阶的阶段。类型还原的主要话题: 82 | 83 | * 类型还原系统的设计理念 84 | * 基于格理论的类型还原技术 85 | * 中间语言与类型约束 86 | * 结构体的识别 87 | * 数组的识别 88 | * 类型传播 89 | * 跨函数的全局变量类型恢复 90 | 91 | **3、控制流还原(也被称为结构分析struct)** 92 | 93 | 将CFG还原成由 if-else while for 组成的结构(尽量不包含goto)。 94 | 95 | 并将各种条件的变量进行合并,规约成适合人类阅读的模式。这里主要的困难在于如何解决控制流图中“不可规约结构”或者叫做”非标准化结构”的问题 96 | 97 | * 基于区间分析/控制分析的控制流还原技术 98 | * 真实逆向软件中的控制流恢复方案 99 | * 如何解决“图不可规约”问题 100 | * 条件语句归一化与条件合并 101 | * 约束与条件简化 102 | 103 | ### **后端:** 104 | 105 | **代码生成:** 106 | 107 | 将上述的各种还原后的东西最终组合,生成可读性高的代码 108 | 109 | 注意,代码并不是越简略可读性就越高,合理的加入中间变量与中间表达式更加合适。 110 | 111 | 后面在优化技术中会再次讨论这部分内容。 112 | 113 | ## 如何评判一个逆向软件的好坏? 114 | 115 | 1、支持的架构、ISA、指令集:虽然历史上的很多指令集现在不太能见得到(比如PPC之类的),但是一个成熟的工具至少要支持常见的ARM、x86\_64、MIPS架构 116 | 117 | 2、反编译效率:至少10M的文件要在半个小时能跑完吧,我还记得以前把带符号的kernel放到Ghidra里(100M+),第二天来了它还在跑呢(叹气)。 118 | 119 | 3、生成代码的准确度:简单的来说就是正确性和可读性,包括控制流结构、类型信息、变量信息还原等等,当然,前提是语义绝不能出错。 120 | 121 | 4、代码可读性:当前反编译生成的代码可读性还存在很多问题了;并且当前主流的反编译工具的设计往往在20年前,希望在下个十年能出现支持高级语言以及更高抽象语义的反编译器。 122 | 123 | ## 示例:一个简单程序的反编译流程示例 124 | 125 | 注意,这个流程中我简化或者抽象了大量的内容,其实可以不看,每个点后面会慢慢讲。 126 | 127 | 示例源代码: 128 | 129 | ```c 130 | #include 131 | 132 | int main() { 133 | int x, y; 134 | scanf("%d,%d", &x, &y); 135 | if (x > 0) { 136 | y = y / 3; 137 | } 138 | printf("y = %d", y); 139 | return 0; 140 | } 141 | ``` 142 | 143 | 现在使用clang -O3将它编译,然后进行反编译。(使用clang,是因为clang生成的代码废话少一点) 144 | 145 | 我跳过了二进制格式识别与汇编转换的流程,毕竟前者不是我们的重点,后者在这里讨论没有啥意义 146 | 147 | 直接看代码段的汇编: 148 | 149 | ```jsx 150 | ..text:001150 ; int main() 151 | .text:001150 var_10 = dword ptr -10h 152 | .text:001150 var_C = dword ptr -0Ch 153 | .text:001150 push rbx 154 | .text:001151 sub rsp, 10h 155 | .text:001155 lea rdi, 0x2004 ; "%d,%d" 156 | .text:00115C xor ebx, ebx 157 | .text:00115E lea rsi, [rsp+18h+var_10] 158 | .text:001163 lea rdx, [rsp+18h+var_C] 159 | .text:001168 xor eax, eax 160 | .text:00116A call ___isoc99_scanf 161 | .text:00116F cmp [rsp+18h+var_10], 0 162 | .text:001174 jz short loc_118B 163 | .text:001176 mov eax, [rsp+18h+var_C] 164 | .text:00117A mov ebx, 0AAAAAAABh 165 | .text:00117F imul rbx, rax 166 | .text:001183 shr rbx, 21h 167 | .text:001187 mov [rsp+18h+var_10], ebx 168 | .text:00118B loc_118B: ; CODE XREF: main+24 169 | .text:00118B lea rdi, 0x200A ; "y = %d" 170 | .text:001192 mov esi, ebx 171 | .text:001194 xor eax, eax 172 | .text:001196 call _printf 173 | .text:00119B xor eax, eax 174 | .text:00119D add rsp, 10h 175 | .text:0011A1 pop rbx 176 | .text:0011A2 retn 177 | ``` 178 | 179 | * **第一步**,将二进制转化为中间语言,但是现有的中间语言设计出来就没有适合人类阅读的,我只能再这里现场造一个“伪语言”临时用一下。当然,现场造的好处是我默认进行了“死代码消除”、“栈变量识别”、“条件跳转规约”这些步骤 180 | 181 | ```jsx 182 | ..text:001150 ; int main() 183 | .text:001150 var_10 = dword ptr -10h 184 | .text:001150 var_C = dword ptr -0Ch 185 | .text:001150 push rbx 186 | [rsp] = rbx 187 | rsp = rsp - 0x08 188 | 189 | .text:001151 sub rsp, 10h 190 | rsp = rsp - 0x10 191 | 192 | .text:001155 lea rdi, 0x2004 ; "%d,%d" 193 | rdi = 0x2004 194 | 195 | .text:00115C xor ebx, ebx 196 | ebx = 0 197 | 198 | .text:00115E lea rsi, [rsp+18h+var_10] 199 | rsi = addr stack_var_0x10 200 | 201 | .text:001163 lea rdx, [rsp+18h+var_C] 202 | rdx = addr stack_var_0x0C 203 | 204 | .text:001168 xor eax, eax 205 | eax = 0 206 | 207 | .text:00116A call ___isoc99_scanf 208 | scanf() 209 | 210 | .text:00116F cmp [rsp+18h+var_10], 0 211 | .text:001174 jz short loc_118B 212 | if stack_var_0x10 < 0; 213 | goto loc_118B 214 | 215 | .text:001176 mov eax, [rsp+18h+var_C] 216 | eax = stack_var_0x0C 217 | 218 | .text:00117A mov ebx, 0AAAAAAABh 219 | ebx = 0xAAAAAAAB 220 | 221 | .text:00117F imul rbx, rax 222 | rbx = rbx * rax 223 | 224 | .text:001183 shr rbx, 21h 225 | rbx = rbx >> 21 226 | 227 | .text:001187 mov [rsp+18h+var_10], ebx 228 | stack_var_0x10 = ebx 229 | 230 | .text:00118B loc_118B: ; CODE XREF: main+24 231 | .text:00118B lea rdi, 0x200A ; "y = %d" 232 | rdi = 0x200A 233 | 234 | .text:001192 mov esi, ebx 235 | esi = ebx 236 | 237 | .text:001194 xor eax, eax 238 | eax = 0 239 | 240 | .text:001196 call _printf 241 | printf() 242 | 243 | .text:00119B xor eax, eax 244 | eax = 0 245 | 246 | .text:00119D add rsp, 10h 247 | rsp = rsp + 0x10 248 | 249 | .text:0011A1 pop rbx 250 | rbx = [rsp] 251 | .text:0011A2 retn 252 | end-function 253 | ``` 254 | 255 | 我们翻译好的东西放到一起 256 | 257 | ```jsx 258 | int main() { 259 | [rsp] = rbx 260 | rsp = rsp - 0x08 261 | rsp = rsp - 0x10 262 | rdi = 0x2004 263 | ebx = 0 264 | rsi = addr stack_var_0x10 265 | rdx = addr stack_var_0x0C 266 | eax_1 = 0 267 | scanf() 268 | if stack_var_0x10 <= 0; 269 | goto loc_118B 270 | { 271 | eax = stack_var_0x0C 272 | ebx = 0xAAAAAAAB 273 | rbx = rbx * rax 274 | rbx = rbx >> 0x21 275 | stack_var_0x10 = ebx 276 | } 277 | loc_118B: 278 | rdi = 0x200A 279 | esi = ebx 280 | eax = 0 281 | printf() 282 | eax = 0 283 | rsp = rsp + 0x10 284 | rbx = [rsp] 285 | return 286 | } 287 | ``` 288 | 289 | 第二步 将上面的中间结果,转换为SSA模式,插入phi函数 290 | 291 | ```jsx 292 | int main() { 293 | [rsp0] = rbx_0 294 | rsp_1 = rsp_0 - 0x08 295 | rsp_2 = rsp_1 - 0x10 296 | rdi_1 = 0x2004 297 | ebx_1 = 0 298 | rsi_1 = rsp_2+0xC 299 | rdx_1 = rsp_2+0x10 300 | eax_1 = 0 301 | scanf() 302 | if stack_var_0x10 <= 0; 303 | goto loc_118B 304 | { 305 | eax_2 = rsp_2+0xC 306 | ebx_2 = 0xAAAAAAAB 307 | rbx_1 = zext(ebx_2) 308 | rbx_2 = rbx_1 * rax 309 | rbx_3 = rbx_2 >> 0x21 310 | [rsp_2+0x10] = ebx_2 311 | } 312 | rbx_4 = phi(rbx_3, rbx_1) 313 | eax_3 = phi(eax_1, eax_2) 314 | loc_118B: 315 | rdi_2 = 0x200A 316 | ebx_3 = zext(rbx_4) 317 | esi_1 = ebx_3 318 | eax_4 = 0 319 | printf() 320 | eax_5 = 0 321 | rsp_3 = rsp_2 + 0x10 322 | rbx_6 = [rsp_3] 323 | return 324 | } 325 | ``` 326 | 327 | 第三步 中间优化:消除死代码,识别函数调用参数与返回值、传播常量、平衡出入栈、规则优化。 328 | 329 | * 消除没有用的变量与指令 330 | * 通过调用约定来识别子函数调用的参数以及返回值 331 | * 传播常量变量,识别全局变量引用 332 | * 平衡这个函数入口与出口的栈操作,计算栈操作时的偏移信息,将栈偏移同步到栈变量中 333 | * 使用规则进行优化,这里能匹配到“除法优化”规则:(2\*\*0x21) // (0xAAAAAAAB-1) ==3,即可以知道 (X\*0xAAAAAAAB)>>0x21 算法本质上是源码中除以 X/3 被优化后的结果 334 | 335 | ```jsx 336 | int main() { 337 | scanf("%d,%d", (rsp_2+0x10), (rsp_2+0xC)) 338 | 339 | if [rsp_2+0x10] <= 0; 340 | goto loc_118B 341 | { 342 | eax_2 = rsp_2+0xC 343 | rbx_1 = zext(ebx_2) 344 | rbx_3 = rbx_1 /3 345 | [rsp_2+0x10] = ebx_2 346 | } 347 | rbx_4 = phi(rbx_3, rbx_1) 348 | eax_3 = phi(eax_1, eax_2) 349 | 350 | loc_118B: 351 | ebx_3 = zext(rbx_4) 352 | printf("y = %d", ebx_3) 353 | return 0 354 | } 355 | ``` 356 | 357 | 第四步,变量还原 358 | 359 | 基于phi函数与copy语句,以及栈变量的标记,将SSA形式的中间语言转换为实际的“高阶变量” 360 | 361 | ```jsx 362 | int main() { 363 | var stack_0x10 364 | var stack_0x0C 365 | var tmp_RBX 366 | scanf("%d,%d", &stack_0x10, &stack_0x0C) 367 | 368 | if stack_0x10 <= 0; 369 | goto loc_118B 370 | { 371 | tmp_RBX = stack_0x10 372 | tmp_RBX= tmp_RBX/3 373 | stack_0x10 = tmp_RBX 374 | } 375 | 376 | loc_118B: 377 | printf("y = %d", tmp_RBX) 378 | return 0 379 | } 380 | ``` 381 | 382 | 第五步,类型信息 383 | 384 | * 对于大量的计算的中间语言,它本身带有类型信息,中间语言生成时就已经完成这一步了 385 | * 将中间语言的类型信息同步到变量中去,基于约束求解与格理论,推测类型信息 386 | 387 | ```jsx 388 | int main() { 389 | var stack_0x10 -> int 390 | var stack_0x0C -> int 391 | var tmp_RBX 392 | scanf("%d,%d", &stack_0x10, &stack_0x0C) : int, int 393 | 394 | if stack_0x10 <= 0; 395 | goto loc_118B 396 | { 397 | tmp_RBX = stack_0x10 : 398 | tmp_RBX= tmp_RBX/3 : int 399 | stack_0x10 = tmp_RBX 400 | } 401 | 402 | loc_118B: 403 | printf("y = %d", tmp_RBX) : int 404 | return 0 405 | } 406 | ``` 407 | 408 | 第六步,还原控制流,归一化控制流格式 409 | 410 | ```jsx 411 | int main() { 412 | var stack_0x10 -> int 413 | var stack_0x0C -> int 414 | var tmp_RBX 415 | scanf("%d,%d", &stack_0x10, &stack_0x0C) : int, int 416 | 417 | if stack_0x10 > 0; 418 | { 419 | tmp_RBX = stack_0x10 : 420 | tmp_RBX= tmp_RBX/3 : int 421 | stack_0x10 = tmp_RBX 422 | } 423 | 424 | printf("y = %d", tmp_RBX) : int 425 | return 0 426 | } 427 | ``` 428 | 429 | 第七步,生成最终的代码(使用IDA作为参考) 430 | 431 | ```jsx 432 | int __cdecl main(int argc, const char **argv, const char **envp) 433 | { 434 | unsigned int v3; // ebx 435 | unsigned int v5; // [rsp+8h] [rbp-10h] BYREF 436 | unsigned int v6; // [rsp+Ch] [rbp-Ch] BYREF 437 | 438 | v3 = 0; 439 | __isoc99_scanf("%d,%d", &v5, &v6); 440 | if ( v5 ) 441 | { 442 | v3 = v6 / 3; 443 | } 444 | printf("y = %d", v3); 445 | return 0; 446 | } 447 | ``` 448 | 449 | 以上,就是这一章所有的内容了。 450 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/3.-di-gui-xia-jiang-fan-hui-bian-dai-ma-yu-shu-ju-hun-he-chang-jing-de-chu-li.md: -------------------------------------------------------------------------------- 1 | # 3. 递归下降反汇编-代码与数据混合场景的处理 2 | 3 | 这一章将介绍如何将机器码进行”反汇编“,虽然常用语句上的反汇编指的是将机器码转义为汇编;不过在反编译的语境中,“反汇编”往往是指将机器码转换为中间语言。 4 | 5 | 由于冯·诺依曼体系下的计算机结构在主存中是不区分指令和数据的(对应,哈弗结构将指令和数据存储分离)。反汇编的第一个难题就是从存储中区分数据与指令,精确的反汇编不能无条件的相信.text段里都全是指令。尤其实在CSIC复杂指令集的场景中,不定长的指令使用“线性扫描”功能很容易出现问题。 6 | 7 | ## 介绍:控制流依赖的中间语言翻译(反汇编)技术 8 | 9 | 控制流依赖的中间语言翻译,或者控制流依赖的反汇编技术目的是为了处理可能存在的数据与代码的混合情况。 10 | 11 | ### **问题举例** 12 | 13 | 为了避免将数据翻译成代码,现在的反编译工具使用基于控制流的递归下降算法进行反编译,来看下面这个示例。 14 | 15 | 在较为复杂的switch代码中,往往使用跳转表甚至多级跳转表进行判断,而有的编译器会将跳转表记录在函数体内部。例如glibc的pathconf函数,它本体是个巨大的switch语句 16 | 17 | [https://codebrowser.dev/glibc/glibc/sysdeps/posix/pathconf.c.html](https://codebrowser.dev/glibc/glibc/sysdeps/posix/pathconf.c.html) 18 | 19 | 在IDA中反编译这个函数,可以看到0x00B9D11这部分是数据,但是被保存在text段中。 20 | 21 |
22 | 23 | 跳转表在.text段并且IDA正确的识别出来。 24 | 25 | 但是如果我们使用objdump这一类的软件,它就啥都不管了”把text全反编译了“,就会变成这样,错误的将数据识别成代码。 26 | 27 |
28 | 29 | ### 实现简述: 30 | 31 | 当前主流的反汇编算法分为两大类:线性扫描和递归下降; 32 | 33 | 何为递归下降反汇编,简单说就是程序运行他得有个入口点是吧,经过这个入口点往下一直去找他调用的函数地址,把那个地址当作函数反汇编,层层递归去反汇编。 34 | 35 | 而线性扫描就是反汇编完这个函数,然后就把后面的地址直接当函数或者汇编直接给反汇编了,这样线性的去反汇编。 36 | 37 | ref: [https://bbs.kanxue.com/thread-269617.htm](https://bbs.kanxue.com/thread-269617.htm) 38 | 39 | 递归下降的实现基于控制流的反汇编能力。(注,现在的反编译器往往直接将机器码翻译成中间语言,汇编本身和中间语言在一个层次上) 40 | 41 | 并且在翻译中间语言过程中会注意到跳转与函数调用语义,根据内部的规则进一步判断如何更具控制流进行翻译。换句话说,在反汇编的时候,翻译器知道程序会如何运行。如下图 42 | 43 | | 普通指令 | 不改变流程 | 44 | | ----- | ------------ | 45 | | 条件跳转 | 将两个分支都加入处理队列 | 46 | | 无条件跳转 | 改变流程,进行跳转 | 47 | | 函数调用 | 目标加入子函数流程 | 48 | | 返回 | 结束流程 | 49 | 50 | 但是还有两个问题不好解决: 51 | 52 | * 我该从哪里开始翻译机器码,换句话说,需要找到所有函数的入口 53 | * 间接跳转,间接调用,比如 call eax、 jmp eax; 以及no-return-function 和 尾递归、尾调用应该怎么处理,反汇编的时候我怎么知道它会跳转到哪里去?函数会不会一去就不回来了。 54 | 55 | ## 难题:从哪里开始反汇编 56 | 57 | 那么剩下的第一个问题,找到函数的入口,确认反汇编起始点。 58 | 59 | 当前逆向工具的识别技术基本上有这么几类: 60 | 61 | * 通过符号识别函数起始位置,比如main等等 62 | * 基于规则匹配函数起始位置 63 | * 基于Xref寻找函数起始位置 64 | 65 | 对于main函数识别通用方法: 66 | 67 | * 基于\_libc\_start\_main 符号寻找 68 | * 去\_start/\_scrt\_common\_main\_seh里找 69 | 70 | 对于常用函数识别的通用方法: 71 | 72 | * 去二进制符号里找 73 | * 在异常处理结构里找 74 | * 识别并标记直接的函数调用,并尝试处理间接跳转 75 | * 基于特殊的决策树/规则直接匹配二进制寻找(较为少见) 76 | * 基于通用的二进制入口结构,比如\_init\_array和\_fini\_array 77 | 78 | ## 人工指定反编译起始位置 79 | 80 | 当然,如果上面的方法,都用不了,还可以在我们逆向的时候对函数人工进行标记。 81 | 82 | 对于控制流无法正常获取,或者奇奇怪怪的回调函数经常能用到这种方法。 83 | 84 | 在IDA中就能使用create-function功能强制标记,甚至可以指定方法的起始与结束位置 85 | 86 |
87 | 88 | ref:[https://hex-rays.com/blog/igors-tip-of-the-week-87-function-chunks-and-the-decompiler/](https://hex-rays.com/blog/igors-tip-of-the-week-87-function-chunks-and-the-decompiler/) 89 | 90 | ## 衍生:花指令对抗技术 91 | 92 | 看看下面这个代码示例: 93 | 94 | ```jsx 95 | // 正常的函数代码 96 | int add(int a, int b){ 97 | int c = 0; 98 | c = a + b; 99 | return c; 100 | } 101 | // 添加花指令的函数代码 102 | int add_with_junk(int a, int b){ 103 | int c = 0; 104 | __asm{ 105 | jz label; 106 | jnz label; 107 | _emit 0xe8; //0xe8是call 指令,后面加4bytes的地址偏移,因此导致反汇编器错误识别 108 | label: 109 | } 110 | c = a + b; 111 | return c; 112 | } 113 | ``` 114 | 115 | 十分有趣的花指令小玩意,其本身的防护强度不高。处理办法有很多,现场写pattern+patch都可以。大家在一些简单的ctf题目中一定经常遇到 116 | 117 | \--—————————————————————- 118 | 119 | **为什么花指令能够对抗基于控制流的反编译?** 120 | 121 | 因为,若不添加额外的转换规则,反编译器并不知道 jz+jnz ==> jmp;本质上就是一个跳转。 122 | 123 | 基于控制流的反编译处理就会把跳转的目标加入处理队列,使用默认方法继续顺着控制流往下翻译(将0xe8当成call进行翻译),最后产生反编译的冲突。 124 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/3.-hui-zong-fan-bian-yi-qi-zhong-de-fan-hui-bian.md: -------------------------------------------------------------------------------- 1 | # 3.汇总-反编译器中的反汇编 2 | 3 | 4 | 5 | 这是第三章的最后一个小节;这一节将会把之前的内容进行整理,并分析这些之前的组件在IDA和Ghidra中的具体实现方法。 6 | 7 | 并且,这一章节与下一章节是从反编译器前端到中端的过渡章节,会涉及到一部分中端优化的情况。 8 | 9 | 当然,先假定一个场景:**“有一套新的指令集或者字节码,需要编写反编译器插件,让反编译器支持新指令集架构的反汇编功能”。** 10 | 11 | 那么如何在在IDA和Ghidra中实现这个场景,就是这一章节的主要内容。 12 | 13 | ## IDA与反汇编 14 | 15 | ida的中间语言使用的是micorocode,这个中间语言在30年前就已经设计出来了,IDA从7.0版本开始提供中间语言的相关API。具体的中间语言的相关内容知识,将在下一章细说。 16 | 17 | IDA的指令集支持插件,称为“IDA processor module”,这个在栈平衡中章节已经介绍过了。如果需要支持一套新的指令集,需要在这个模块中完成以下的组件。 18 | 19 | * 指令的二进制数据与中间语言的转换(体力活,要定义所有的指令、寄存器等等) 20 | * 注册所有的跳转指令,声明跳转指令的类型,是绝对跳转、条件跳转、间接跳转、函数调用、函数返回等等 21 | * 处理所有可能的数据段引用场景 22 | * 处理所有可能的指令段引用场景 23 | * (可选)定义栈指针,并定义所有的对栈的操作,IDA将基于这个定义计算栈平衡 24 | * (可选)处理并分析间接跳转,将switch所产生的间接跳转还原 25 | * (可选)分析并还原栈帧。 26 | 27 | 这么一看,会发现,如果要完整的支持一套新的指令集,那要做的事情也太多了吧!而且好难。 28 | 29 | 毕竟很少有人从事专业的反编译开发工作;尤其是还原switch语句以及还原栈帧等复杂场景,从无到有要做很多功课也不一定写的好。 30 | 31 | ### 函数识别 32 | 33 | IDA的函数识别使用了通用的方法,即通过call指令以及符号和文件入口识别函数。 34 | 35 | 这也是为什么我们经常能在IDA中看到红色的代码的原因,因为它虽然能够正常的进行反汇编,但是却无法 36 | 37 | ### 栈指针与栈平衡 38 | 39 | 参考上一章的内容 40 | 41 | ## Ghidra与反汇编 42 | 43 | ghidra的中间语言来自于QUBT,称为pcode,可以说是学术界教科书式的中间语言设计,这一部分将在下一章进一步说明。 44 | 45 | Ghidra工程本身的设计存在一个严重的缺陷,它的函数级的反编译器和上层的是割裂的,中间通过符号流进行交互。可以说是解耦结果头了,反编译器的调试的确方便了,很多功能却不好实现了。 46 | 47 | 主要问题在于,底层的反编译是不支持文件结构的解析与函数识别的功能;需要在上层java中实现,而反编译器本体只支持单个函数的分析,跨函数的全局程序分析的反编译变得更为复杂。 48 | 49 | ### 流敏感的反编译 50 | 51 | Ghidra反编译器内部实现了流敏感的反编译能力;因此只需要翻译好指令到中间语言的部分,其它的事情内部引擎会处理。 52 | 53 | 注意,由于流敏感的识别的所有工作,都是在Ghidra内部引擎完成的,因此从机器码到中间语言的翻译必须足够准确。尤其是函数的返回本身依赖于中间语言的翻译。 54 | 55 | ### switch间接跳转 56 | 57 | Ghidra内置了后向切片与部分控制流分析的能力,以此来实现将间接跳转中还原出jump-table的技术。 58 | 59 | 也就是说Ghidra内置了switch语句的还原能力,这个工作反编译器帮你完成了,你看这不比IDA舒服的多。 60 | 61 | Ghidra将switch间接跳转特征分为三个部分:Guard, jump-table, jup-register 62 | 63 | 并常识通过后向切片与copy-propagation (拷贝传播),在ghidra中也被称为还原高层变量varnode。依次识别这三个元素 64 | 65 | 参考论文:[http://www.sciencedirect.com/science/article/pii/S0167642301000144/pdf?md5=ed116622d77cdb914fb644cad3604bb3\&pid=1-s2.0-S0167642301000144-main.pdf](http://www.sciencedirect.com/science/article/pii/S0167642301000144/pdf?md5=ed116622d77cdb914fb644cad3604bb3\&pid=1-s2.0-S0167642301000144-main.pdf) 66 | 67 | 快速解读原理: 68 | 69 | 这种恢复技术的前提是,switch的实现符合论文中的三类模型。 70 | 71 |
72 | 73 | 这三类模型实际上都有这么些特征,伪代码举例 74 | 75 | ```jsx 76 | int a = input() 77 | switch(a) { 78 | case 1: 79 | // something 80 | case 2: 81 | // something 82 | ...... 83 | default: 84 | // something 85 | } 86 | ``` 87 | 88 | * 第一步,最开始,一定有个判断jcond,判断switch的对象是否太大,而走到default或者一个都匹配不上 89 | * 第二步,通过三类之一的跳转表,计算目标地址 90 | * 第三步,完成跳转 91 | 92 | 跳转表的识别方法,在跳转点生成后向切片,获得程序的状态。 93 | 94 | 通过上述的切片,使用程序分析技术(主要是copy传播),找到第一步的判断逻辑(称为guard) 95 | 96 | 最后,使用论文中的模型,找到switch中跳转的计算方法,还原跳转表的形态以及大小。 97 | 98 | ### 函数识别 99 | 100 | 使用了两套函数识别机制,都在上层中定义 101 | 102 | Ghidra支持配置指令特征进行函数起始位置识别,例如在x86\_64指令集中,几种常见的函数起始汇编格式为: 103 | 104 | ```c 105 | push rbp 106 | mov rbp, rsp 107 | ========================== 108 | push r15 109 | push r14 110 | push r13 111 | push r12 112 | ======================== 113 | push rbp 114 | push rbx 115 | mov rxx, rxx 116 | ``` 117 | 118 | Ghidra有自定义的一套二进制匹配的函数头识别功能,例如x86\_64体系下是的就在 119 | 120 | \Ghidra\Processors\x86\data\patterns\x86-64gcc\_patterns.xml 121 | 122 | 文件中 123 | 124 |
125 | 126 | 较为常见指令集的都已经支持了这种匹配方法。具体可以去每个指令集下的patterns目录里找。 127 | 128 | 剩下的函数入口识别组件就是通用的做法了,可以在主页的analysis功能中查看,也可以自己勾选上跑一下试试。相关信息在“递归下降的反汇编”章节已经有说明。 129 | 130 |
131 | 132 | ### 栈指针与栈偏移计算 133 | 134 | 与IDA不同,Ghidra的栈局部变量展示是基于栈顶(rsp)的。 135 | 136 | 这种做法的好处是反编译器全程只需要跟踪栈顶以及栈空间的大小,这只需要一个寄存器,毕竟在绝大多数场景,栈帧大小在函数头开辟后就固定了。 137 | 138 | 缺点是,有时候,有的人看栈上的局部变量偏移会觉得有些困惑。可以参考ref2中的配置进行修改。 139 | 140 | ref1:[https://github.com/NationalSecurityAgency/ghidra/issues/223](https://github.com/NationalSecurityAgency/ghidra/issues/223) 141 | 142 | ref2:[https://liwugang.github.io/2020/08/01/ghidra.html](https://liwugang.github.io/2020/08/01/ghidra.html) 143 | 144 | 并且,Ghidra的内部反编译器在反编译过程中不动态计算栈平衡,且不处理动态开辟栈指针的情况。在某些alloc的情况下会导致生成奇怪的反编译代码。它默认认为,栈顶除了函数调用情况,在一个函数内部作不会再有变化。 145 | 146 | ## 不转换中间语言的反编译器 147 | 148 | 在通用反编译器中,中间语言的主要功能之一,是为了兼容多种指令集架构; 149 | 150 | 与编译器类似,反编译器前端将各种指令集架构翻译为中间语言,后续的中端在中间语言上直接执行各种优化。那么支持一个新指令集的工作就会简单很多,因为很多的事情框架帮你实现了。 151 | 152 | 但是,如果反编译器的目标明确且固定,那么它也可以不需要执行“中间语言转换”。 153 | 154 | 最典型的就是java的JVM字节码、android的Dex字节码等,这一类完全特化的反编译器,它的目标十分明确就是输出尽可能精确的源码。 155 | 156 | 注意,**不转换中间语言,不意味着不使用中间语言;** 157 | 158 | 或者说,学术界认为**jvm的字节码本身就是一种中间语言**,因此这一类的反编译器“直接拿来用就行”,不再需要额外的转换,因此后续的中端优化还是通用的。 159 | 160 | 不做额外的转换,也是这一类完全特化的反编译器能输出和源码十分相近的主要因素。例如jadx工作已经支持直接把apk导出成gradle工程了。 161 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/3.-jian-jie-tiao-zhuan-yu-jian-jie-tiao-yong-de-chu-li.md: -------------------------------------------------------------------------------- 1 | # 3.间接跳转与间接调用的处理 2 | 3 | 上一章“递归下降的反汇编”还留了另一个问题,即“间接跳转与间接调用”应该如何跟踪控制流。这一章节将针对这个话题进行扩展,尽可能的将反汇编过程中跳转与调用的特殊情况都覆盖了。 4 | 5 | ## 问题定义 6 | 7 | 如下图,如果IDA无法正常的处理间接跳转,它可能会直接内联一个jmp \ 进来。 8 | 9 |
10 | 11 | 典型场景举例1-间接调用: 12 | 13 | ```jsx 14 | //省略之前的反汇编结果 15 | call eax // <-- 出现了一个间接调用,他会跳转去哪里呢? 16 | ;; < -- 后面的“机器码”应该继续翻译下去吗? 17 | ;; < -- 会不会call了之后不再回来了? 18 | ;; < -- 会不会,后面的内容根本不是合法的指令? 19 | ``` 20 | 21 | 典型场景举例2-间接跳转 22 | 23 | ```c 24 | //省略之前的反汇编结果 25 | jmp eax // <-- 出现了一个间接跳转,他会跳转去哪里呢? 26 | ;; < -- 这种情况,后面的“机器码”应该继续翻译下去吗? 27 | ``` 28 | 29 | 那么什么情况下,编译器会生成间接跳转或者间接调用? 30 | 31 | 函数调用一定使用Call指令吗,还可能存在什么特殊的情况? 32 | 33 | 反编译器又是如何判断这些情况的呢? 34 | 35 | 在什么情况下,编译器会生成这几类特殊的代码? 36 | 37 | 那么反编译器又怎么处理与识别? 38 | 39 | ## 核心问题:反汇编是如何处理间接跳转? 40 | 41 | 间接跳转的处理主要包含在二进制翻译以及数据流分析的步骤中,可以说是一个混合功能。当然,前面的章节先不讨论数据流分析的复杂场景;这里我尽量列举所有的可能场景,并尝试说明解决方案。 42 | 43 | 下图为学术界中对于相对跳转的一种处理方法,我称为”不完全流跟踪技术“,Ghidra就使用了这种技术。将在后面的章节单独描述 44 | 45 |
46 | 47 | #### 0、默认场景 48 | 49 | 如果反编译器无法推断出间接跳转、间接调用的类型或者目标。一般情况下,反编译器使用默认的规则,即: 50 | 51 | * 对于条件跳转,直接顺着控制流依次翻译两个分支。 52 | * 对于间接跳转 jmp eax 这种,如果不知道目标位置,停止翻译,进行标记。 53 | * 对于间接调用 call eax 这种,如果不知道目标位置,则翻译成函数指针,并继续顺着流程翻译。 54 | 55 | #### 1、tail-call场景 56 | 57 | 示例。考虑下面这个代码 58 | 59 | ```c 60 | #include 61 | void test(int a, int b, int c) 62 | { 63 | int ADD = a + b + c + 100; 64 | printf("%d", ADD); 65 | int SUB = a - b - c -100; 66 | printf("%d", SUB); 67 | printf("%d", ADD * SUB * 100); //<-- 这里最后一句是printf 68 | } 69 | ``` 70 | 71 | 如果使用GCC-X86\_64-O3编译,会将最后一个printf优化为jmp,像这样 72 | 73 | ```c 74 | ...省略前面的汇编 75 | lea rsi, aDDD+8 ; "%d" 76 | pop r12 77 | xor eax, eax 78 | pop r13 79 | pop r14 80 | jmp ___printf_chk ; 直接变成一个jmp-tail-call. 函数结束了 81 | ``` 82 | 83 | 这个场景,我们想要讨论的是**控制流依赖场景中**如何处理jmp类型的语句。这种情况被称为“tail-call” 84 | 85 | 根据经验,tail-call可能会有一下的特征,反编译器也是基于这些特征识别的 86 | 87 | * 跳转的位置是一个已经被识别到的”函数起始地址” 88 | * 跳转的距离很长,而且绝不会跳转到正在翻译的函数体内。 89 | * 跳转的距离很长,越过了好几个已经被识别到的函数,即跨函数跳转 90 | * tail-call的跳转不会是条件跳转 91 | * tail-call之前的汇编,为了平衡栈,往往会执行出栈操作 92 | * tail-call跳转的目标(target),不会是其它条件跳转的目标 93 | 94 | 反编译器会智能的识别tail-call场景,并停止进行翻译。 95 | 96 | #### 1-1、更为极端的tail-call场景 97 | 98 | 还存在一种更为极端的tail-call场景,即函数指针参数与tail-call混合的情况,看下面这个代码示例 99 | 100 | ```c 101 | int som_func( int(*myFuncVar)(int a, int b) ) 102 | { 103 | return myFuncVar(1, 2); 104 | } 105 | ``` 106 | 107 | 这种情况下,生成的函数将会出现一个相对跳转的情况(O3优化) 108 | 109 | ```c 110 | myMianOp(int (*)(int, int)): 111 | mov rax, rdi 112 | mov esi, 2 113 | mov edi, 1 114 | jmp rax 115 | ``` 116 | 117 | 虽然真实世界中存在的可能性不多,但是这个例子很好的展示出了“编译器多种优化混合”导致的复杂场景,也是反编译器的主要麻烦来源。 118 | 119 | #### 2、switch语句中的间接跳转 120 | 121 | switch语句主要会被翻译成两种形态的汇编 122 | 123 | ref: [https://github.com/snowcra5h/branch-tables-and-jump-tables](https://github.com/snowcra5h/branch-tables-and-jump-tables) 124 | 125 | * 最直接就是将switch翻译成多个同语义的if-else-if语句。 126 | * **这里讨论的典型场景,基于跳转表的实现场景。** 127 | 128 | 注意,在通用的逆向软件中,switch跳转的实现与恢复都是依赖于目标编译器的模型的。 129 | 130 | 虽然学术界后续也提出了一些不需要依赖编译器模型的算法,但是这不是这里的重点(少一点算法,多一点示例)。 131 | 132 | 代码示例: 133 | 134 | ```jsx 135 | int test_switch(){ 136 | int i ; 137 | int a = std::rand(); 138 | switch(a){ 139 | case 0: i = 0;break; 140 | case 1: i = 1;break; 141 | case 2: i = 2;break; 142 | case 3: i = 3;break; 143 | case 4: i = 4;break; 144 | case 5: i = 5;break; 145 | case 6: i = 6;break; 146 | case 7: i = 7;break; 147 | case 8: i = 8;break; 148 | case 9: i = 9;break; 149 | default: i = 10;break; 150 | } 151 | return i; 152 | } 153 | ``` 154 | 155 | 这种每个case的条件值很近的情况,有可能生成跳转表形式的汇编指令。 156 | 157 | 例如上面这个例子,i的取值是1\~10,可以用10个元素的静态数组存储switch的跳转地址,这样跳转的目标地址可以直接通过i的值索引到。 158 | 159 | 最终生成类似与下图的使用间接跳转实现的跳转表 160 | 161 |
162 | 163 |
164 | 165 | 反编译器将在中间语言翻译时,还原上面这种情况的switch语句。 166 | 167 | 更为准确的说法应该叫做 ”jump-table还原“,因为在这里反编译器的目标是找到并格式化跳转表,并将跳转表关联到中断的控制流上,而不是还原switch-case-break-default这一套语句。 168 | 169 | 基于跳转表生成的switch语句,一般来说有下面几种形态(限定gcc与clang编译器) 170 | 171 | 其中jump address是最终的跳转地址,table\_X为跳转表,expression是计算跳转的表达式,padding为用于增加的偏移常量,%PC为当前的指令地址。 172 | 173 | * jump address = table\_1\[expression \* 4];最经典的场景,直接使用一个静态数组存储跳转表。 174 | * jump address = table\_2\[table\_1\[expression \* 4]];不太常见的场景,使用了两级跳转表一般出现在跳转情况较为复杂,case中的代码特别长的场景 175 | * jump address = table\_1\[%PC + expression \* 4] + %PC;跳转表存储在代码段的情况,表中存储的将会是一个对于当前地址的相对偏移 176 | * jump address = table\_1\[%PC + expression \* 4 - padding] + %PC - padding; 更为复杂的情况,跳转不仅查表,还需要进行进一步的计算,甚至使用减法替代加法。 177 | * 其它场景更为罕见的情况,这取决于编译器的优化,例如跳转表中不存储地址而是存指令等等。 178 | 179 | 还原switch的间接跳转的算法有很多,主要的手段都是通过控制流跟踪+规则匹配的方法的方法还原跳转表。具体方法将在后续章节中针对不同的反编译器专门介绍。 180 | 181 | #### 2-1、手动定位switch跳转表(以IDA作为示例) 182 | 183 | 有时候IDA不太聪明,无法正确的还原switch的跳转表,就需要我们手动 帮帮忙 184 | 185 | 指针选中应该是switch的jump \ 语句上,点击edit->other->specify switch idiom 186 | 187 |
188 | 189 |
190 | 191 | 这个表很复杂,但是利用上面的知识,我们可以知道它的意思 192 | 193 | **跳转表信息** 194 | 195 | 1、Address of jump table:跳转表的地址。 196 | 197 | 1、Address of jump table:跳转表的地址。 198 | 199 | 2、Number of elements:跳转表中元素的个数。 200 | 201 | 3、Size of table element:跳转表中每个元素的字节数(1/2/4/8)。 202 | 203 | 4、Element shift amount:**一般情况下保持默认的 0** 即可。除非跳转表中存储的元素并不是跳转的目标地址,而是需要通过 target = base +/- (table\_element << shift) 这个公式计算得出,这种情况需要作为 shift 的值提供。 204 | 205 | 5、Element base value:参考计算方法中的说明,如果是绝对地址填0,如果是相对偏移填写相对偏移的基址(一般来说是和Address of jump table 相同)。 206 | 207 | **跳转信息** 208 | 209 | 6、Start of the switch idiom:switch 语句的首个指令的地址,在打开“Specify switch idiom”窗口时,光标处的地址会被自动填写到这里,就是jump \的指令地址 210 | 211 | 7、Input register of switch:存储 switch 语句输入的寄存器,即存储 switch(input) {...} 中input变量的寄存器,也是jump \中的寄存器。 212 | 213 | 8、First(lowest) input value:最小的 case 值,如果有default情况,default会占用0。 214 | 215 | 9、Default jump address:default case 的跳转目标地址,可以不指定,不指定时对于 default case 以 case 0 的形式显示。 216 | 217 | **特殊信息** 218 | 219 | 10、Separate value table is present:当switch是以二级跳转表形态表示时,启用这个选项;二级跳转对应这上述的jump address = table\_2\[table\_1\[expression \* 4]] 这种形态。 11、Signed jump table elements:跳转表中的元素是以有符号值时需要勾选。 12、Subtract table elements:计算跳转表元素时用减法而不是用加法,参考计算方法中的说明。 13、Table element is insn:跳转表中存储的不是目标地址而直接是指令时需要勾选。这种情况比较罕见,在ref中提供了ARM的直接使用跳转表存储jump指令的示例情况。 220 | 221 | ref:[https://hex-rays.com/blog/igors-tip-of-the-week-53-manual-switch-idioms/](https://hex-rays.com/blog/igors-tip-of-the-week-53-manual-switch-idioms/) 222 | 223 | #### 3、no-return 场景 224 | 225 | 本来这个情况不应该放在这里的,但是别的地方更加不合适,所以放在这里了。 226 | 227 | 看下面这个代码示例 228 | 229 | ```jsx 230 | #include 231 | #include 232 | 233 | void some_func() { 234 | //跳过一大堆代码 235 | printf("exit"); 236 | exit(-2); // 这里直接退出了, 函数永远不会返回 237 | } 238 | int main() { 239 | some_func(); 240 | printf("never here"); 241 | return 0; 242 | } 243 | ``` 244 | 245 | 在优化后的代码中,main函数中的some\_func后的内容printf("never here");不会被生成,即被优化掉。因为main函数不再返回,后面的代码也就是死代码了。 246 | 247 | 这种情况在真实代码中很常见,比如各种assert 或者 error 或者 exception函数的使用。 248 | 249 | 但是反编译器未必知道some\_func函数不会再返回,可能还会继续翻译main函数下去,获得错误的内容。 250 | 251 | #### 3-1、IDA中的no\_return函数判断规则 252 | 253 | 对于no-return场景当前主要的判断还是基于规则。可以参考IDA-TIPS中对于no\_return\_function 特征识别的描述:”IDA使用函数名来识别non-returning 函数“规则 254 | 255 | 大家也可以看一下自己的IDA中“IDA Pro\cfg\noret.cfg”文件的配置,一般来说配置了100多个函数名。 256 | 257 |
258 | 259 | ref:[https://hex-rays.com/blog/igors-tip-of-the-week-126-non-returning-functions/](https://hex-rays.com/blog/igors-tip-of-the-week-126-non-returning-functions/) 260 | 261 | #### 3-2、Ghidra中的no\_return函数判断规则 262 | 263 | 同理,在Ghidra中也有类似的配置文件,不过Ghidra的配置更为细致;对于不同的二进制格式(ELF、PE、Mach-O等)都有独立的判断规则。 264 | 265 | 规则文件在 “Ghidra\Features\Base\data\noReturnFunctionConstraints.xml” 266 | 267 | ```c 268 | 269 | 270 | 271 | GolangFunctionsThatDoNotReturn 272 | 273 | ElfFunctionsThatDoNotReturn 274 | 275 | 276 | MachOFunctionsThatDoNotReturn 277 | 278 | 279 | MachOFunctionsThatDoNotReturn 280 | 281 | 282 | 283 | GolangFunctionsThatDoNotReturn 284 | 285 | PEFunctionsThatDoNotReturn 286 | 287 | 288 | ``` 289 | 290 | #### 4、函数指针 291 | 292 | 最后一种场景,也是比较常见的场景,即函数指针。 293 | 294 | 函数指针的使用在多态中十分常见,比如C++的虚表指针,或者C语言中各种回调等等。 295 | 296 | 反编译使用预置的调用约定,很容易识别函数指针。但是在逆向工作中真正的困难是没有高层次的类型结构。 297 | 298 | 然而很不幸,当前常用的反编译器的反编译维度都只有函数级(我不确定binary-ninja对于C++语言的类型结构恢复能力,我看它的产品宣传有这部分的能力)。即,不支持对于类层次结构的恢复。关于高级语义恢复的话题,只能留到后面讨论了。 299 | 300 | ### 3、其它情况:隐式控制流 301 | 302 | 这一章节虽然讨论了一些主流的场景,但是这些场景的前提都是“显式控制流”,即,有专门的跳转指令(虽然反编译器未必可以正确的推导出来对应的地址) 303 | 304 | 还有一大部分没有考虑到的隐式控制流场景,他们往往和语言特性以及操作系统特性有关。例如:系统中断、try-catch异常、系统回调的注册、信号处理函数。 305 | 306 | 当前的常用反编译器往往也不能正确的处理这些场景,我目前没有对这部分有较为深入的研究。如果有时间,以后再把这部分补上。 307 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/3.-zhan-pian-yi-ping-heng-yu-ji-suan-shi-yong-ida-ju-li.md: -------------------------------------------------------------------------------- 1 | # 3.栈偏移平衡与计算(使用IDA举例) 2 | 3 | ## 典型问题示例 4 | 5 | 相信大家在按F5时,一定遇到过类似的情况 6 | 7 |
8 | 9 |
10 | 11 | * **“sp-analysis failed”** 12 | * **“positive sp value has been detected”** 13 | 14 | [https://hex-rays.com/blog/igors-tip-of-the-week-27-fixing-the-stack-pointer/](https://hex-rays.com/blog/igors-tip-of-the-week-27-fixing-the-stack-pointer/) 15 | 16 | 如果没有遇到过,那这里我造一个类似的情况(注意编译时添加-masm=intel参数): 17 | 18 | ```c 19 | //https://blog.ret2.io/2017/11/16/dangers-of-the-decompiler/ 20 | // gcc ./test.c -masm=intel 21 | #include 22 | 23 | #define positive_sp_predicate \\ 24 | __asm__ (" push rax \\n"\\ 25 | " xor eax, eax \\n"\\ 26 | " jz opaque \\n"\\ 27 | " add rsp, 4 \\n"\\ 28 | " nop \\n"\\ 29 | "opaque: \\n"\\ 30 | " pop rax \\n"); 31 | void protected() 32 | { 33 | positive_sp_predicate; 34 | puts("Can't decompile this function"); 35 | } 36 | 37 | void main() 38 | { 39 | protected(); 40 | } 41 | ``` 42 | 43 | 这一章的主题,说明“为什么反编译器要处理栈平衡”,”反编译器如何处理栈平衡“、“为什么IDA会出现上面这些问题”、“遇到上面的问题应该怎么办” 44 | 45 | ## 反编译器与栈偏移 46 | 47 | 不同的反编译器对于栈平衡与栈上局部变量的处理方法不同,这里我以最常用的IDA作为举例。 48 | 49 | ### 什么是栈平衡(栈帧)? 50 | 51 | 这个东西应该属于基础知识,这里不做介绍。 52 | 53 | 有需要可以看下面的材料,以及自己搜索一下: 54 | 55 | [https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/stack-intro/](https://ctf-wiki.org/pwn/linux/user-mode/stackoverflow/x86/stack-intro/) 56 | 57 | ### 为什么反编译器需要推导栈偏移 58 | 59 | 最重要的两个目标,一个是识别栈上的局部变量,一个是用于判断函数调用约定。 60 | 61 | 在一般情况下,栈的开辟任务在函数入口完成,并在函数退出时进行回收(依照具体的调用约定),在大部分场景下,栈空间(栈顶)的大小不会变化,调用约定往往也会处理好栈的平衡,保证出入一致; 62 | 63 | 但是总有些特殊场景: 64 | 65 | * 在C语言可以使用alloc动态分配栈长度,函数中的栈顶(sp)可能是会动态变化的。 66 | * C语言编译器支持可变的局部数组,可能会动态分配栈空间,例如下面这个代码 67 | 68 | ```c 69 | void function_test(int n) { 70 | int array[n] = {0}; 71 | // 省略 72 | } 73 | ``` 74 | 75 | 并且,无论是使用栈底(bp)还是使用栈顶(sp)来引用栈上的局部变量,反编译器都需要归一化的处理方法,归一化的方案就是栈平衡的计算。 76 | 77 | 在IDA中栈平衡就是计算SP的偏移,局部变量的识别也是基于栈底计算的,后面作为主要的举例。 78 | 79 | 如果栈平衡推导出现错误,那么对于栈上局部变量的还原就可能出错,因为对应到了错误的偏移。 80 | 81 | ### 通用栈平衡推导过程 82 | 83 | 栈偏移的处理存在与反编译器的多个阶段,包括 84 | 85 | * 反编译器前端转换过程中,中间语言会处理记录所有对于栈指针的操作。 86 | * 数据流分析,栈操作是识别函数调用参数的重要信息。 87 | * 数据流分析,通过栈指针的操作识别局部变量。 88 | * 数据流分析,将对于栈指针+偏移的”读取”与”写入“内存操作转换为对函数局部变量的修改。 89 | * 代码生成,会标记局部变量在栈上的偏移。 90 | 91 |
92 | 93 | 当然,在这一章节,我们只讨论最上面的\*\*“反编译器前端转换过程中,中间语言会处理记录所有对于栈指针的操作”\*\*这一步骤 94 | 95 | 一旦栈偏移的推导完成,对于所有局部变量的引用,反编译器就会归一化,在IDA中就是变为局部变量偏移的引用。 96 | 97 | 如下图,原先的汇编是\[rsp+0x30]和\[rsp+0x08] 98 | 99 |
100 | 101 | IDA判断出当前栈偏移是108,就会将\[rsp+0x30] 和 \[rsp+0x08]转换为相对于栈底位置的局部变量的操作,当然最后算出来的实际值是一样的。 102 | 103 | 栈局部变量var\_D8 = qword ptr -0D8h 104 | 105 | 栈局部变量var\_100 = qword ptr -100h 106 | 107 |
108 | 109 | ### IDA中控制流的栈偏移计算方法 110 | 111 | 在IDA中,栈偏移的计算是在反汇编步骤就已经完成了。栈偏移作为基础信息参与后面的工作。 112 | 113 | IDA中的Options → General 中可以开启栈指针的显示 114 | 115 |
116 | 117 | 开启后在汇编代码的这部分,就会显示栈的偏移了,简单的说前面的数字记录了**这行汇编执行前**,栈顶相对于栈底的偏移。 118 | 119 |
120 | 121 | 这个数字的计算方法也很简单,在中间语言生成时,计算所有栈操作的指令对栈顶的影响(比如`push eax` 就+4, `push rbp` 就+8) 122 | 123 | 在遇到函数调用时,处理调用约定对于栈的影响。 124 | 125 | 函数开始时这个值为0,如果一切顺利,函数结束时这个值也为零(栈平衡了)。 126 | 127 | **当遇到通过栈指针+偏移对于栈的内存读取与写入操作,反编译器依赖于这个值,就能知道任意指令情况下的栈顶与栈底的偏移,很容易的就能转换为对实际栈变量的操作。进而识别局部变量在栈上的位置。** 128 | 129 | ### IDA控制流的栈偏移的不足 130 | 131 | 看到了上面的例子,聪明的你一定想到了,IDA这种方法虽然适用与大部分情况,但是存在一定的不足。这也是导致出现\*\*“sp-analysis failed”**和**“positive sp value has been detected”\*\*的主要原因。 132 | 133 | 1、无法处理的分支情况 134 | 135 | 再次看一下这个代码 136 | 137 | ```c 138 | //https://blog.ret2.io/2017/11/16/dangers-of-the-decompiler/ 139 | // gcc ./test.c -masm=intel 140 | #include 141 | 142 | #define positive_sp_predicate \\ 143 | __asm__ (" push rax \\n"\\ 144 | " xor eax, eax \\n"\\ 145 | " jz opaque \\n"\\ 146 | " add rsp, 4 \\n"\\ 147 | " nop \\n"\\ 148 | "opaque: \\n"\\ 149 | " pop rax \\n"); 150 | void protected() 151 | { 152 | positive_sp_predicate; 153 | puts("Can't decompile this function"); 154 | } 155 | 156 | void main() 157 | { 158 | protected(); 159 | } 160 | ``` 161 | 162 | 在IDA他会出现这样的问题,当两个控制流合并时,一边栈偏移是0x10,一边栈偏移是0x0C。IDA不知道应该使用哪个,就会出现栈偏移错误的问题。 163 | 164 | 遇到这种问题,IDA就直接“先到先得”哪个控制流先处理,就用哪个控制流的栈偏移。 165 | 166 |
167 | 168 | 2、依赖于调用约定的识别 169 | 170 | 一些函数调用约定会影响栈的平衡,如果函数调用约定以及调用参数没有正确的识别,就会导致栈平衡的问题。 171 | 172 | ### 栈平衡错误的常见场景与应对方法 173 | 174 | 在IDA中最常用的就是强行调整栈平衡以及配置调用约定. 175 | 176 | 把光标放到需要调整的汇编语句,使用快捷键alr+k,强制修改栈顶的相对偏移。注意,哪怕修改后IDA的\*\*“sp-analysis failed”\*\*等告警仍可能会存在,只要保证反编译F5能够正常使用就行了。 177 | 178 |
179 | 180 | 至于调用约定的识别错误导致的反编译问题,可以通过修改调用约定或者自定义调用约定实现 181 | 182 | ref:[https://hex-rays.com/blog/igors-tip-of-the-week-51-custom-calling-conventions/](https://hex-rays.com/blog/igors-tip-of-the-week-51-custom-calling-conventions/) 183 | 184 | ## IDA中栈计算平衡的内部实现 185 | 186 | 在IDA中的架构中,栈平衡的计算,在机器码转汇编语言时顺带完成了。 187 | 188 | 这一组件称为“IDA processor module”,它在ida-sdk的idasdkXX\idasdkXX\module文件夹内。 189 | 190 | 参考下图,指令集中会定义trace\_sp功能,专门对可能影响栈的指令进行处理(先判断操作的寄存器是不是栈寄存器,在判断操作数) 191 | 192 | 例如,下图中的add\_stkpnt,就是对栈操作的IDA-API。 193 | 194 |
195 | 196 | ref:[https://blog.quarkslab.com/ida-processor-module.html](https://blog.quarkslab.com/ida-processor-module.html) 197 | 198 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/4.-biao-da-shi-chuan-bo-yu-gong-gong-zi-biao-da-shi-xiao-chu-cse.md: -------------------------------------------------------------------------------- 1 | # 4.表达式传播与公共子表达式消除(CSE) 2 | 3 | 公共子表达式消除 **Common Sub-Expression optimization** 4 | 5 | 公共子表达式消除(CSE)是编译器中的一个优化技术,可以通过消除重复的计算来提高代码的性能。它的原理是在表达式中查找重复的计算,并将其替换为一个变量,以避免重复计算的开销。 6 | 7 | ## 编译器:公共子表达式消除(CSE) 8 | 9 | 考虑到下列代码: 10 | 11 | ```java 12 | a = b * c + g; 13 | d = b * c + e; 14 | ``` 15 | 16 | 可以观察到 `b * c` 是两项表达式中的公共子表达式。 17 | 18 | 如果计算这个子表达式并将其计算结果存储起来的开销,低于重复计算这个子表达式的开销,则能够将以上代码转换成以下代码: 19 | 20 | ``` 21 | temp = b * c; 22 | a = temp + g; 23 | d = temp + e; 24 | ``` 25 | 26 | 示例1: 27 | 28 | ```c 29 | #include 30 | int main(){ 31 | long x,y,z; 32 | scanf("%ld %ld", &x, &y); 33 | z = (x + y) * (x + y); 34 | printf("%ld", z); 35 | return 0; 36 | } 37 | ``` 38 | 39 | 编译 : clang -O3 ./test.c 40 | 41 | 对于编译器来说 42 | 43 | ```c 44 | z = (x + y) * (x + y); 45 | ``` 46 | 47 | 这句话经过CAS(公共子表达式消除)后会变成 48 | 49 | ```c 50 | tmp = x + y 51 | z = tmp * tmp 52 | ``` 53 | 54 | 可以通过查看LLVM翻译后的IR:clang -S -emit-llvm -O3 ./test.c 55 | 56 | 进行确认。这里我省略了很多生命周期以及函数调用的代码,并与源码的对应关系进行注释 57 | 58 | ```c 59 | ; Function Attrs: nounwind uwtable 60 | define dso_local i32 @main() local_unnamed_addr #0 { 61 | %1 = alloca i64, align 8 62 | %2 = alloca i64, align 8 63 | %3 = bitcast i64* %1 to i8* 64 | %4 = bitcast i64* %2 to i8* 65 | %5 = call i32 (i8*, ...) @__isoc99_scanf(...) //scanf("%ld %ld", &x, &y); 66 | %6 = load i64, i64* %1, align 8, !tbaa !2 67 | %7 = load i64, i64* %2, align 8, !tbaa !2 68 | **%8 = add nsw i64 %7, %6 //**tmp = x + y 69 | **%9 = mul nsw i64 %8, %8 //**z = tmp * tmp 70 | %10 = call i32 (i8*, ...) @printf(...) // printf("%ld", z); 71 | ret i32 0 //return 0; 72 | } 73 | ``` 74 | 75 | ## 反编译器:**表达式传播** 76 | 77 | 了解了编译器的这一技术,反编译器只要逆着来就行了,将临时变量的表达式给填充回去 78 | 79 | 这一技术称为:**Expression Propagation (表达式传播)** 80 | 81 | 也就是将 82 | 83 | ```c 84 | tmp = x + y 85 | z = tmp * tmp 86 | ``` 87 | 88 | 反向转换为 89 | 90 | ```c 91 | z = (x + y) * (x + y); 92 | ``` 93 | 94 | 这里需要补充一点点的理论知识 95 | 96 | **对于 x := expression 语句到y点,满足以下两个条件即可执行expression 化简** 97 | 98 | **1、在到达y点时,x的定义有且只有expression一个(类似到达定值分析)** 99 | 100 | **2、在到达y点时,x的expression 不会在任何路径中被重定义(类似变量存活分析,x不能被kill掉)** 101 | 102 | **3、在到达y点后,x不再其它地方使用(x被kill掉了)** 103 | 104 | 那么化简一定是好的吗?不一定,化简后的语句可读性反而会降低,尤其是在大型代码中。 105 | 106 | 因为**Expression Propagation**这一技术不仅会对公共子表达式消除(CSE)技术起效,可能也会影响其它的部分。 107 | 108 | 例如以下的伪代码 109 | 110 | ```c 111 | int a = test_1() 112 | int b = test_2() 113 | int c = <很长的一个表达式> 114 | if(a && b && c) { 115 | //some function 116 | } 117 | ``` 118 | 119 | 反编译器可能会将所有的代码全部优化到if语句中去,将所有的中间变量全部消除掉,十分的难懂 120 | 121 | ```c 122 | if(test_1() && test_2() && <很长的一个表达式>) 123 | ``` 124 | 125 | 在大型软件中这种情况很常见,尤其是对于结构体的操作,例如下面这段反编译后的IDA代码: 126 | 127 |
128 | 129 | 它本身的含义大概长下面这个样子,但是IDA会使用**表达式传播**技术,将下面这一大串的表达式压成一行: 130 | 131 | ```c 132 | tmp_edx = v4[84] 133 | tmp_edx = tmp_edx & 0xFE 134 | 135 | tmp_ecx = *(v3+36) 136 | tmp_ecx = tmp_ecx & 0xF 137 | tmp_eax = 19283 >> (~ tmp_ecx) 138 | 139 | tmp_edx = tmp_edx | tmp_eax 140 | tmp_edx = tmp_edx + tmp_edx 141 | ``` 142 | 143 | 这也导致了IDA-debugger调试的时候,如果断点在F5代码里,会出现问题,例如我在IDA中的110行下断点。 144 | 145 | 但是,其实如果调试时,需要的是\*\*v4\[84]\*\*这个数据值;就出问题了,因为,这一行代码是好多个汇编语言压成的; 146 | 147 | 无法确定这个断点会在哪个汇编执行后;可能**v4\[84]**这个数据已经被覆盖了。 148 | 149 |
150 | 151 | 这种情况,建议把断点下在汇编上。这么看,**表达式传播**这种技术也有很多值得优化的部分。 152 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/4.-fan-bian-yi-de-zhong-jian-yu-yan-she-ji.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 过渡章节 3 | --- 4 | 5 | # 4.反编译的中间语言设计 6 | 7 | **注意:** 8 | 9 | **这一章有太多的我的臆想以及不知道对不对的理论内容(毕竟我不是中间表示设计的专家),可以跳过不看。** 10 | 11 | 这一章作为第三节与第四节的过渡章节,涉及到很多也许没什么用的理论知识:反编译器中的中间语言设计的理论知识。 12 | 13 | 但是,样本量太少了,没法判断哪个是最佳实践,而且我不是编译器或者中间语言的专家,说的可能不对,因此看着也就图一乐。 14 | 15 | 16 | 17 | ## 中间语言与反编译器 18 | 19 | 目前,通用反编译器全部使用的是龙书系的三地址码形态的中间语言 dest = op(src1, src2)。 20 | 21 | 因此本文不对中间语言的表示方式进行讨论(不讨论波兰式、树状等其它情况),一律使用三地址码或者类似的中间语言。 22 | 23 | ### 底层 24 | 25 | 底层的中间语言的目标是适配多种指令集与架构,并识别出具体的函数开头与结尾,分离指令和数据,处理间接跳转,将各种语言、架构归一化到一个指令集中; 26 | 27 | 使用中间语言,链接了指令集与反编译优化的实现。在后续若要支持新的指令集,只需要完成从指令到中间语言的翻译步骤,大大省略了其它工作。 28 | 29 | Ghidra使用了来自于SLED、SSL的描述类语言,IDA则需要编写代码。 30 | 31 | SLED-RED:[https://dl.acm.org/doi/abs/10.1145/256167.256225](https://dl.acm.org/doi/abs/10.1145/256167.256225) 32 | 33 | ### 中层 34 | 35 | 中层优化,是中间语言最为复杂,工作内容最为多的步骤;中层主要目标是从汇编级别的中间语言,向上还原(lift)立体的高级语言抽象。比如控制流、变量、类型系统、语义等等。 36 | 37 | IDA中间语言使用了8层转换完成这一步,每向上一层中间语言就能缩减一部分,从下到上依次为: 38 | 39 | * MMAT\_GENERATED:等价于汇编 40 | * MMAT\_PREOPTIMIZED:在basic-block上构造def+use+kill关系 41 | * MMAT\_LOCOPT:在basic-block完成局部优化,构造CFG,处理基于规则的窥孔优化 42 | * MMAT\_CALLS:基于调用约定识别函数调用、参数、返回值 43 | * MMAT\_GLBOPT1:将call的各种block进行合并,全局优化,全局死代码消除 44 | * MMAT\_GLBOPT2:全局优化,全局常量传播与条件优化 45 | * MMAT\_GLBOPT3:全局窥孔优化 46 | * MMAT\_LVARS:局部变量还原 47 | 48 | 因为IDA给源码给的很少,很难细化分析每个阶段的内部细节 49 | 50 | ida-micorocode-ref: [https://gist.github.com/icecr4ck/6c744d489efbb07a32bb22e8a3c748e3](https://gist.github.com/icecr4ck/6c744d489efbb07a32bb22e8a3c748e3) 51 | 52 | Ghidra的反编译器在编写时,没有相对明确的层次; 53 | 54 | 我按照个人的经验,将Ghidra的反编译器中端从里到外、从前到后整理,依次为: 55 | 56 | * action-base:通过流敏感技术,将机器码翻译成中间语言,识别函数,处理间接跳转等等。 57 | * action-stackstall:抽象操作栈信息,恢复栈变量 58 | * Varnode:还原高层变量信息 59 | * action-rule:窥孔优化,通过预置的规则对语义进行等价还原 60 | * typerecovery:还原类型信息 61 | * struct: 还原控制流信息 62 | 63 | ghidra-decompiler-ref: [https://www.lockshaw.io/static/ghidra/decompiler/doc/index.html](https://www.lockshaw.io/static/ghidra/decompiler/doc/index.html) 64 | 65 | ### 高层 66 | 67 | 高层次的中间语言,其实已经和中间语言没有太多的关系了,更像是抽象语法树(AST)的形态。 68 | 69 | 此时,高层次的抽象还原都已经完成;最终输出为人类可读的语言。 70 | 71 | 典型的任务就是生成变量名、函数名:对于反编译器来说局部变量没有名字,有很多种方法进行取名。 72 | 73 | 如果粗暴一点,那就分为:全局变量、函数参数、栈上局部变量、寄存器局部变量、指针、数组。通过变量的特征进行分类,加个前后缀就完成了。 74 | 75 | 对于字节码等有更多信息的反编译器结果,就有更好的选择方案,可以识别语义命名变量与函数。 76 | 77 | ## 反编译器的中间语言设计 78 | 79 | 这是一个很有意思的话题,如何设计通用反编译器的中间语言。但我也不是这方面的专家,说的不一定对,因此如果觉得有问题,请和我进行交流。 80 | 81 | 我列出了反编译器中间语言设计的关键决策的点;抛砖引玉,希望能有大佬帮忙看看我说的对不对。 82 | 83 | ref:[https://jonpalmisc.com/assets/slides/0x41con-2023-GetToKnowYourDecompiler.PUBLIC.pdf](https://jonpalmisc.com/assets/slides/0x41con-2023-GetToKnowYourDecompiler.PUBLIC.pdf) 84 | 85 | ### 是否使用一套IR贯穿整个反编译器设计? 86 | 87 | 这个问题很难回答,因为现有的反编译器中IDA和Ghidra使用了一整个IR贯穿式的流程,即所有的优化都在这个IR上; 88 | 89 | 而就我所知,只有binary-ninja使用了上中下的完整的三层IR(这是不是又用力过猛了?); 90 | 91 | 还有jadx这种为了最大程度的保留语义,几乎不转换IR,直接在原有的字节码上开整的。 92 | 93 | 我观察了binary-ninja的实现,并不认为完整的三层IR对于反编译器有特别大的好处,反而是一些同样的工作需要在各层上重复实现。 94 | 95 | 较为合理的做法,应该是学习Ghidra,在底层使用SSL描述形语言实现多指令集架构的支持,并使用一套IR贯穿整个中层优化,最后在上层特化的编写高级语言的输出。 96 | 97 | ### 多层显示中间表示?单层显示中间表示? 98 | 99 | 这也是一个很重要的话题,多层还是单层的选择,直接决定了后续反编译器优化功能的设计。尤其是如果选择了多层的显示中间表示,优化步骤在哪一层执行十分重要。 100 | 101 | 在编译优化中,LLVM就使用了单层显示中间表示;方舟编译器Maple使用多层显示中间表示(待确认)。 102 | 103 | 在反编译器中Ghidra使用了单层显示中间表示,而IDA选择多层显示中间表示。 104 | 105 | 106 | 107 | **1、单层中间表示** 108 | 109 | 单层显示中间表示:意味着所有的优化工作都在一个层上完成,整个中间层是一个大状态,每个反编译组件维护自己的小状态。 110 | 111 | * 没有明确的层次区分,优化之间可以嵌套,整个中层优化是一个整体,无法单独展示单个优化。 112 | * 各个优化循环的执行,直到状态不再改变,意味着无法继续优化,即反编译结束。 113 | 114 | 典型的实现就是Ghidra,可以参考后续章节中对Ghidra架构的说明(在写了,在写了) 115 | 116 | 117 | 118 | **2、多层中间表示** 119 | 120 | 多层中间表示,意味者将优化显示的分为几个层次,每层只干自己的事情。 121 | 122 | * 越在底层,越扁平,信息越少,越接近机器语言(汇编)。 123 | * 越到高层,中间表示的抽象程度越高,越立体,越接近高级语言; 124 | * 只能从底层到高层单向执行,每一层有独立的状态,每一次都能单独输出并展示。 125 | * 每一个优化功能,有明确的层级,只能在对应的层级执行,使用对应层级的资源。 126 | 127 | 典型实现就是IDA,看下面的截图,很好的描述了IDA反编译内部的分层。后续将在IDA中间语言优化插件编写章节更为详细的讨论这一部分。 128 | 129 | ida-ref: [https://github.com/gaasedelen/lucid](https://github.com/gaasedelen/lucid) 130 | 131 |
132 | 133 | ### SSA(**静态单赋值形式**) 134 | 135 | 这个属于编译原理基础,如果有需要可以看下面这个链接: 136 | 137 | {% embed url="https://zh.wikipedia.org/zh-cn/%E9%9D%99%E6%80%81%E5%8D%95%E8%B5%8B%E5%80%BC%E5%BD%A2%E5%BC%8F" %} 138 | 139 | ### 中间语言与SSA 140 | 141 | 反编译器的中间语言优化要不要使用SSA? 142 | 143 | 目前来看,IDA的中间优化没有使用SSA技术(IDA只在最后一步MMAT\_LVARS变量还原中使用了类似于SSA构造的技术),为什么呢? 144 | 145 | **我个人猜测**:第一个原因,因为IDA中间语言设计的时间大概在1993年左右,那时候和现在不一样,并不流行以SSA作为中间语言的表达形式。IDA的设计受到了Cristina Cifuentes的博士论文《Reverse Compilation Techniques》和这一论文提出的DCC反编译器原型的影响。 146 | 147 | 第二个原因,Mike Van Emmerik的博士论文《Static Single Assignment for Decompilation》 很好的说清楚了在反编译器上使用SSA形态的好处。这篇论文也深刻的影响了后续反编译工具,例如Ghidra的中间优化的设计。但是这篇论文发表举距离IDA的出现晚了10多年。 148 | 149 | **我用自己的话来描述一下,《Static Single Assignment for Decompilation》这个论文的关键思想:** 150 | 151 | ```c 152 | 你写个反编译器,最常用的优化就是各种dead-code-elimnation 以及 各种propagation,包括你的变量还原。这么多的功能,都要在中间语言上去算define与use关系。 153 | 154 | 如果不用SSA,就要去在basic-block上算下面这个数据流方程: 155 | Data-flow equations 156 | in[n] = use[n] ∪ (out[n] – def[n]) 157 | out[n] = ∪ in[s] 158 | 159 | 这个方程可不好算,代码不好写,看起来也不直接(是的IDA就是用这个方程做的) 160 | 既然这样,不如用我论文提出来的方法,计算use-define-chain同时,把寄存器和内存的使用转换为SSA形式。这样,后面的各种优化工作写起来就方便多了。输出的东西也直观。 161 | ``` 162 | 163 | 因此,后续反编译器的设计,将大多数优化步骤都在SSA中间语言完成 164 | 165 | ### 什么时候进入SSA的形式?什么时候退出SSA形式? 166 | 167 | IDA只有再最后进行变量识别与整合的时候,会使用类似与SSA形式转换的算法。这是因为的寄存器分配算法和反编译器的变量还原算法,有太多的相似的地方。 168 | 169 | Ghidra则是一旦进入数据流分析,在完成基于调用约定的函数原型绑定后,立即进入SSA形式,直到变量还原工作完成,在控制流还原前才不再对SSA进行操作。 170 | 171 | Binary-ninja更为特别,它的前端、中端、后端都有独立的中间语言,三层的中间语言又有各自的SSA形态。也就是说,它会反复横跳+多次转换。 172 | 173 | ### SSA要和寄存器和局部变量同步处理吗? 174 | 175 | * IDA的做法:完全不使用SSA,寄存器和栈做为变量,再生成一些临时变量。这种做法的好处是,反编译可以优化到十分的准确,坏处是每一种指令集架构都要重头开始写转换代码,十分的痛苦。 176 | * Ghidra的做法:寄存器和SSA并列同步处理,好处很明显,将对SSA的处理同步到CPU架构上,可以脱离各个指令集,只编写一套处理机制的代码;并且一定程度上复用代码。缺点是实现算法特别难写,反编译效果往往也没想像中好。 177 | * RetDec的做法(待确认):抛弃寄存器完全使用SSA,通过一系列的规则,将所有的内容转化为一种SSA的中间语言(比如LLVM)后,和以前的二进制进行切割完全没有关系了。我觉得这种做法的唯一好处是,工作量比较小,因为LLVM这种中间语言已经有很多人写了各种优化算法了,直接拿来用就行。 178 | 179 | ## IDA的中间语言-micorocode 180 | 181 | 不使用SSA形式,而是自己内部在每个basic-block上实现了use-define-kill的功能,和SSA形态的能力类似;但是在30年后今天,这种做法已不这么流行。有点像**Testarossa**的设计 182 | 183 | ref:[https://zhuanlan.zhihu.com/p/22508731](https://zhuanlan.zhihu.com/p/22508731) 184 | 185 | 很不幸IDA的中间语言的资料十分少,唯一能看的是IDA创始人的演讲 186 | 187 | Ilfak's presentation at Recon 2018:[https://recon.cx/2018/brussels/resources/slides/RECON-BRX-2018-Decompiler-internals-microcode.pdf](https://recon.cx/2018/brussels/resources/slides/RECON-BRX-2018-Decompiler-internals-microcode.pdf) 188 | 189 | ## Ghidra的中间语言-pcode 190 | 191 | Ghidra的中间语言相比之下,资源就比较多了,毕竟是开源的。 192 | 193 | ref: 194 | 195 | [https://www.riverloopsecurity.com/blog/2019/05/pcode/](https://www.riverloopsecurity.com/blog/2019/05/pcode/) 196 | 197 | [https://www.anquanke.com/post/id/196851](https://www.anquanke.com/post/id/196851) 198 | 199 | [https://spinsel.dev/assets/2020-06-17-ghidra-brainfuck-processor-1/ghidra\_docs/language\_spec/html/pcoderef.html](https://spinsel.dev/assets/2020-06-17-ghidra-brainfuck-processor-1/ghidra_docs/language_spec/html/pcoderef.html) 200 | 201 | ## 其它的中间语言 202 | 203 | 截至目前,世界上的中间语言太多太多了,看看下面这个表: 204 | 205 |
206 | 207 | 经过几十年的发展,似乎每一种优化工具、编译器。都自己做一套中间语言,这已经是一种流行的趋势了。 208 | 209 | 当然,看看世界上的上千种编程语言,中间语言的数量可能还算少? 210 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/4.-kui-kong-you-hua-yi-chu-fa-huan-yuan-shi-li.md: -------------------------------------------------------------------------------- 1 | # 4.窥孔优化(以除法还原示例) 2 | 3 | 窥孔优化是反编译器一个很重要的还原技术。而且这个技术没有捷径可言,大部分情况,都需要特定规则的集合。 4 | 5 | 这一章将以最经典的”整数除法“为例,展示编译器和反编译器的对于整数除法的优化与还原技术。 6 | 7 | ## 编译器对乘法与除法优化: 8 | 9 | 对于很多算数表达式,为了提升效率编译器会进行等价的替换。 10 | 11 | 典型案例之一就是使用左移替代2的倍数的乘法: 12 | 13 | ```c 14 | //源码示例,这里a为无符号整数 15 | a = a * 4 16 | 17 | //编译器优化后 18 | a = a << 2 19 | ``` 20 | 21 | 这一优化就是”窥孔优化“的一种,因为在现代CPU中除法和乘法运算是十分昂贵的,尤其是除法(据说以前除法比加法慢50倍以上,比乘法慢10倍以上,直到Intel的Cannon Lake架构出现,Cannon Lake将64位整数除法的最大延时从96个周期降为了18个周期。这样除法就只比加法慢20倍,比乘法慢5倍) 22 | 23 | 实际的情况比想象中更加的复杂,单说加减乘除就有各种优化方法,下面是几个常见的典型除法类的优化示例: 24 | 25 | 1. 无符号除法优化 [https://godbolt.org/z/acm19\_div3](https://godbolt.org/z/acm19\_div3) 26 | 27 | ```c 28 | //除法优化示例,这里edi为无符号整数 29 | edi = edi / 3 30 | 31 | //优化后编译成汇编 即 edi = (edi * 0xaaaaaaab) >> 33 32 | mov eax, edi ; eax = edi 33 | mov edi, 2863311531 ; edi = 0xaaaaaaab 34 | imul rax, rdi ; rax = rax * 0xaaaaaaab 35 | shr rax, 33 ; rax >>= 33 36 | ``` 37 | 38 | 2.有符号除法优化 39 | 40 | ```c 41 | //除法优化示例,这里edi为有符号整数 42 | edi = edi / 2; 43 | 44 | //优化后编译成汇编 即 edi = (edi + (edi >> 33)) >> 1 45 | //其中所有的右移都为有符号右移 46 | mov eax, edi ; eax = edi 47 | shr eax, 31 ; eax = eax >> 31 48 | add eax, edi ; eax = eax + edi 49 | sar eax ; eax = eax >> 1 50 | ``` 51 | 52 | 3.无符号取余优化 53 | 54 | ```c 55 | //取余优化示例,这里edi为无符号整数 56 | edi % 3 == 0 57 | 58 | //优化后编译成汇编 59 | imul edi, edi, -1431655765 ; edi = edi * 0xaaaaaaab 60 | cmp edi, 1431655765 ; compare with 0x55555555 61 | setbe al ; return 1 if edi <= 0x55555555 62 | 63 | ``` 64 | 65 | 在GCC中,有专门的规则实现这一类优化,叫做**match.pd**的文件专门描述这一类优化。他是一种类LISP规则语言,反正我是看不懂,可以看上面的注释,看看具体实现了哪种优化。 66 | 67 | {% embed url="https://github.com/gcc-mirror/gcc/blob/master/gcc/match.pd" %} 68 | 69 |
70 | 71 | ## 反编译器的反窥孔优化: 72 | 73 | 为了将优化后的算式“反向优化”为人类可理解的算法,反编译器和编译器一样,都有这一类的匹配规则,称之为“反窥孔优化”。但是,编译器的窥孔优化不一定是绝对能还原的,所以 74 | 75 | 我这里以Ghidra为例(IDA不开源,有空再研究这玩意咋实现),Ghidra提供了一些列的规则处理这种特殊情况 76 | 77 | [https://www.lockshaw.io/static/ghidra/decompiler/doc/classRule.html#details](https://www.lockshaw.io/static/ghidra/decompiler/doc/classRule.html#details) 78 | 79 | 其中的除法优化示例:\*\*RuleDivOpt 类,\*\*他会将优化后乘法+右移的功能反编译成除法 80 | 81 | 公式为: 82 | 83 | * `sub( (zext(V)*c)>>n, 0) => V / (2^n/(c-1))` 84 | * `sub( (sext(V)*c)s>>n, 0) => V s/ (2^n/(c-1))` 85 | 86 | 看上面这个公式不太直观。还是举个例子后模拟ghidra的运算方法 87 | 88 | ```c 89 | # 源码 90 | unsigned divideByFive(unsigned x) 91 | { 92 | return x / 5; 93 | } 94 | # 编译器优化后的汇编实现 95 | mov eax, edi 96 | mov edi, 3435973837 97 | imul rax, rdi 98 | shr rax, 34 99 | 100 | ``` 101 | 102 | 参考汇编,代入公式 ( 2^n / (c-1) ) 其中 n=34, c=3435973837 103 | 104 | ```jsx 105 | Python 3.10.6 106 | >>> n=34 107 | >>> c=3435973837 108 | >>> ( 2**n // (c-1) ) 109 | 5 110 | ``` 111 | 112 | 换成整数,约为5,因此可以反编译优化为 x / 5 113 | 114 | ## 反窥孔优化简单汇总 115 | 116 | 下面简单汇总了一些反窥孔优化的项目,都是从ghidra抄的 117 | 118 | 其中 V、W、X代表变量 119 | 120 | c、d代表常量 121 | 122 | ```jsx 123 | 124 | -V => V * -1 125 | V + -W ==> V - W 126 | ((V + c) + d) => V + (c+d) 127 | ((V * c) * d) => V * (c*d) 128 | ((V + (W + c)) + d) => (W + (c+d)) + V 129 | V + 0xff... => V - 0x00... 130 | (V << W) & d => (V & (W >> c)) << c 131 | (V & c) & d => V & (c & d) 132 | (V >> X) | (W >> X) => (V | W) >> X 133 | !!V => V 134 | !(V == W) => V != W 135 | !(V < W) => W <= V 136 | !(V <= W) => W < V 137 | !(V != W) => V == W 138 | V ^^ W => V != W 139 | V * c + V * d => V * (c + d) 140 | sub( (zext(V)*c)>>n, 0) => V / (2^n/(c-1)) 141 | sub( (sext(V)*c)s>>n, 0) => V s/ (2^n/(c-1)) 142 | sub(ext(V)*c,b)>>d + V -> sub( (ext(V)*(c+2^n))>>n,0) 143 | W+((V-W)>>1) =>sub( (zext(V)*(c+2^n))>>(n+1), 0) 144 | (V << c) << d => V << (c+d) 145 | (V << c) >> c => V & 0xff 146 | sub( concat(V,W), 0) => W 147 | sub( concat(V,W), c) => V 148 | sub( concat(V,W), c) => sub(V,c) 149 | V * -1 == c => V == -c 150 | V + c == d => V == (d-c) 151 | ~V == c => V == ~c 152 | ((V + c) + W) & 0xfff0 => (V + (c & 0xfff0)) + W 153 | ``` 154 | 155 | ## 对抗:不透明谓词混淆 156 | 157 | ref:[https://www.zhihu.com/question/46259412](https://www.zhihu.com/question/46259412) 158 | 159 | ref:[https://bbs.kanxue.com/thread-270019.htm](https://bbs.kanxue.com/thread-270019.htm) 160 | 161 | 不透明谓词是一套很有趣且实用的静态反编译对抗技术。 162 | 163 | 它和窥孔优化很像,只不过是反向。窥孔优化从复杂到简单,它从简单到复杂。 164 | 165 | 通过添加反编译器无法计算的条件进行混淆。 166 | 167 | todo:介绍对抗不透明谓词的反编译技术 168 | 169 | ### reference 170 | 171 | [https://medium.com/@prathamesh1615/adding-peephole-optimization-to-gcc-89c329dd27b3](https://medium.com/@prathamesh1615/adding-peephole-optimization-to-gcc-89c329dd27b3) 172 | 173 | [https://fuzhe1989.github.io/2020/01/22/optimizations-in-cpp-compilers/](https://fuzhe1989.github.io/2020/01/22/optimizations-in-cpp-compilers/) 174 | 175 | [https://www.computer.org/csdl/proceedings-article/arith/2005/23660131/12OmNyQYt7U](https://www.computer.org/csdl/proceedings-article/arith/2005/23660131/12OmNyQYt7U) 176 | 177 | [https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/](https://lemire.me/blog/2019/02/08/faster-remainders-when-the-divisor-is-a-constant-beating-compilers-and-libdivide/) 178 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/4.-si-dai-ma-xiao-chu-fu-zhi-chuan-bo-chang-liang-chuan-bo.md: -------------------------------------------------------------------------------- 1 | # 4.死代码消除、复制传播、常量传播 2 | 3 | 把他们放在一起,是因为这两个技术的特点很像:它们在编译器与反编译器中实现的方法一样。 4 | 5 | 6 | 7 | ## 编译器中的死代码消除 8 | 9 | 死代码是指程序中不会被执行到的代码,但仍然存在于程序中,浪费了空间和资源。为了提高程序的性能和可维护性,需要进行死代码消除。 10 | 11 | 在编译原理中,死代码消除是后端优化的一种方法,即在代码生成阶段对程序进行优化。通过分析程序的控制流图和数据流图,可以确定哪些代码是死代码,并将其从生成的代码中删除。 12 | 13 | 死代码消除通常是与其他优化技术结合使用的,例如常量传播、复写传播和循环优化等。这些技术可以进一步减少生成的代码中的死代码数量,提高程序的性能和效率。 14 | 15 | 对于反编译器,死代码消除的作用有限;想想也是,编译器为了节省资源,在高优化的级别下,很难说有多少“死代码”留给反编译器。并且“递归下降”这种反汇编的模式,不会对无法到达的块执行反编译。 16 | 17 | 我列举了一些在真实反编译器中能遇到的典型场景。 18 | 19 | ## 死代码消除-消除flag的副作用 20 | 21 | 最经典的用法就是消除指令对于flags指令”副作用”的削减。 22 | 23 | 例如,xor指令经常被用于寄存器清零;像eax=0这个代码往往在编译后就变为XOR eax,eax 24 | 25 | 这是在汇编汇中十分常见的一种做法(相比于 mov eax,0 xor eax,eax 指令更加的短) 26 | 27 | 但是,参考指令集:[https://www.felixcloutier.com/x86/xor](https://www.felixcloutier.com/x86/xor) 中对亦或指令的定义: 28 | 29 | **“The OF and CF flags are cleared; the SF, ZF, and PF flags are set according to the result. The state of the AF flag is undefined.”** 30 | 31 | XOR指令还会影响CF、SF、ZF等标志位,因此在中间语言翻译的时候,需要把这部分内容也一起翻译了,因为那时候不知道这些标志位会不会被后面用到,我们拿IDA-micorocode作为例子 32 | 33 | ```c 34 | //汇编 35 | xor ebx, ebx 36 | 37 | //IDA翻译后的中间语言-5条micorocode 38 | 0 mov #0.4, ebx.4 39 | 1 mov #0.1, cf.1 40 | 2 mov #0.1, of.1 41 | 3 setz ebx.4, #0.4, zf.1 42 | 4 setp ebx.4, #0.4, pf.1 43 | 5 sets ebx.4, sf.1 44 | ``` 45 | 46 | 但是,太多的时候,我们只是为了把清零ebx,比如在for(int i = 0; i <10; i++) 这样的代码中。 47 | 48 | 对于flags设置的中间语言语句都是无用的,反编译器提供专门的”死代码消除”功能处理这一类问题。 49 | 50 | 死变量消除的方法太多了,下面这几个方案都能用: 51 | 52 | * 通过数据流分析构建各个对象的define-use关系,就可以消除死变量了。 53 | * 如果是SSA那就更好,只要把use=0的项直接去了就好 54 | * 构建变量的数据依赖图DDG(Data Dependence Graph),将图中无法连接上的节点消除 55 | 56 | ## 复制传播 57 | 58 | 一句话概括这个的优化技术: 59 | 60 | ```jsx 61 | 因为: 62 | ebx = eax 63 | ecx = ebx 64 | 所以可以优化为: 65 | ecx = eax 66 | ``` 67 | 68 | 下图就很好的展示了这种技术,可以直接把t3的值跳过x的拷贝 69 | 70 |
71 | 72 | 它和死代码传播很像,考虑到编译器的优化;在高级别的编译器优化选项开启时,高效的寄存器着色技术很难给反编译器留有什么优化的空间。 73 | 74 | 其中比较典型的场景:调用约定的寄存器使用,因为调用约定的寄存器使用顺序是“固定”的,因此代码生成时,会出现寄存器使用可以通过拷贝传播优化的场景。 75 | 76 | 看下面这个图,mov ebx, edi; 以及 mov \[rax+4], ebx 这两条指令,通过复制传播,可以推断出函数的入参RDI放入了\[rax+4]中。 77 | 78 |
79 | 80 | ## 常量传播 81 | 82 | 类似,还是一句话概括这个的优化技术: 83 | 84 | ```jsx 85 | 因为: 86 | eax = 32 87 | ecx = eax 88 | 所以可以优化为: 89 | ecx = 32 90 | ``` 91 | 92 | 与复制传播类似,典型的场景就是函数调用 93 | 94 | 同理,还是同样的图,在malloc调用的时候,由于调用约定的要求,第一个参数通过RDI寄存器传入。通过常量传播技术,可以知道EDI=0x20,因此在最终的F5代码生成时,就能清楚的知道malloc的参数为常量0x20。 95 | 96 |
97 | 98 | 可能读到这里,有的朋友会感觉有点奇怪,因为,这里有个十分重要问题没有讨论:**“对于反编译而言,什么是变量?”**这部分内容将在后面的变量恢复章节进一步讨论。 99 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/4.-tiao-yong-yue-ding-shi-bie-can-shu-yu-fan-hui-zhi.md: -------------------------------------------------------------------------------- 1 | # 4.调用约定识别-参数与返回值 2 | 3 | 下面这个图十分重要,是我们会在反编译过程中遇到的最常见的各种调用约定。这整一张的内容就从这个图开始吧。 4 | 5 | ref: [https://www.agner.org/optimize/calling\_conventions.pdf](https://www.agner.org/optimize/calling\_conventions.pdf) 6 | 7 |
8 | 9 | ## 什么是调用约定 10 | 11 | 调用约定,或者说是ABI,实际上是编译器+指令集+平台的一类特定规范;使用这种规范,调用约定确保了子函数调用的局部性。 12 | 13 | ref:[https://en.wikipedia.org/wiki/Application\_binary\_interface](https://en.wikipedia.org/wiki/Application\_binary\_interface) 14 | 15 | ref:[https://www.agner.org/optimize/calling\_conventions.pdf](https://www.agner.org/optimize/calling\_conventions.pdf) 16 | 17 | ## 为什么调用约定的识别那么重要? 18 | 19 | 当然是因为调用约定包含了大量的编译器高层抽象信息啊!! 20 | 21 | 要知道,反编译反编译,反的是什么,当然是“编译器”啊。哪个功能包含的编译器信息多,哪个功能就重要。所以调用约定放到最前面来。 22 | 23 | 对于反编译器,识别出编译器的调用约定不仅对于还原函数参数原型,对于其它分析也有很重要的意义,下面是一个简单的例子。 24 | 25 | ```jsx 26 | EAX = 1 27 | EBX = 2 28 | ECX = 3 29 | //进行了一次函数的调用 30 | call func_test 31 | //函数调用返回后,寄存器的值会变吗?(寄存器会被kill掉吗 32 | EAX = ? // EAX==1 ?? 33 | EBX = ? // EBX==2 ?? 34 | ECX = ? // ECX==3 ?? 35 | ``` 36 | 37 | 调用约定的其它功能就是处理函数调用前后的各种事情,比如: 38 | 39 | * 什么寄存器会被kill掉(在子函数中会被覆盖) 40 | * 什么寄存器会被保留(在子函数不被覆盖) 41 | * 哪些寄存器由调用者维护 42 | * 哪些寄存器由被调用者维护(维护,一般就是指push入栈,出来后pop回去) 43 | * 函数参数如何进行传递 44 | * 函数返回值如何传递 45 | * 函数栈平衡在哪里维护,调用者和被调用者怎么管理栈 46 | 47 | ## 调用约定—反编译的困难 48 | 49 | 主要的困难在于:调用约定有很多种,不同的语言,不同的编译器、不同的平台上都用的不一样。 50 | 51 | windows和linux有各自的ABI;还有编译器的问题,对这很麻烦,比如: 52 | 53 | * gcc编译器 54 | * LLVM编译器 55 | * Go语言 56 | * Rust语言 57 | * C++语言的this指针 58 | * objective-C的msgSend 59 | 60 | 等等,无论是编译器/编程语言/执行平台,它们的调用约定都是可能不同的。 61 | 62 | 对于调用约定的识别,本身是对二进制编写的语言平台架构、编写语言、程序上下文、栈平衡、跨函数的程序分析一些列的混合工作。 63 | 64 | 判断以下的情况: 65 | 66 | * 反编译如何自动判断二进制是用什么编译器编出来的? 67 | * 如何自动判断二进制是哪种语言写的? 68 | * 正确的处理的处理子函数调用约定 69 | * 混合更多识别方法的调用约定识别场景 70 | * 对于无法正确识别的调用约定,怎么处理? 71 | * 用户如何自定义调用约定 72 | 73 | ## 反编译器调用约定处理的通用方案 74 | 75 | 调用约定的推导,从文件载入的时候就开始了,通过文件格式,能够知道文件运行的平台是linux、windows、mac等等。 76 | 77 | 绝大多数可执行文件格式,会声明执行文件的指令集abi等等,例如elf文件就可以使用readelf -h 指令查看: 78 | 79 | ```jsx 80 | ╰─$ readelf -h a.out 81 | ELF Header: 82 | Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 83 | Class: ELF64 84 | Data: 2's complement, little endian 85 | Version: 1 (current) 86 | OS/ABI: UNIX - System V 87 | ABI Version: 0 88 | Type: DYN (Position-Independent Executable file) 89 | Machine: Advanced Micro Devices X86-64 90 | 91 | ``` 92 | 93 | 完成对于指令集和平台的判断,对于C、C++这一类低级语言,我们已经可以进行调用约定推导了。 94 | 95 | 但是对于Go、rust等高级编译型语言的支持。业界对于这部分的研究是不足的,通用的反编译器支持方法不同。 96 | 97 | ## Ghidra中的调用约定推导 98 | 99 | Ghidra中的调用约定来自于用户的配置,用户需要在二进制导入时选择使用哪个调用约定描述文件。 100 | 101 | ### 调用约定描述文件CSPES 102 | 103 | Ghidra的配置项中提供了**xxxx.cspec**文件,专门用于描述调用约定。 104 | 105 | ref:[https://spinsel.dev/assets/2020-06-17-ghidra-brainfuck-processor-1/ghidra\_docs/compiler\_spec/index.htm](https://spinsel.dev/assets/2020-06-17-ghidra-brainfuck-processor-1/ghidra\_docs/compiler\_spec/index.htm) 106 | 107 | 这个文件详细的描述了:平台+指令集+编译器,对不同的架构都有对应的配置文件 108 | 109 | 例如x86指令集的gcc编译器在Ghidra的Processors\x86\data\languages\x86gcc.cspec中。 110 | 111 | 下面这段代码是EBPF的调用约定描述(不愧是开源,Ghidra居然支持这玩意?)大家可以参考并体验一下。 112 | 113 | ```jsx 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | ``` 178 | 179 | 但是Ghidra并不支持对语言的识别,而是将这个工作交给了用户,当导入二进制时需要手动选择语言; 180 | 181 | 如下图,我导入了一个32位的程序,Ghidra支持go、gcc、VS、Delphi等编译器。用户选择了哪个编译器,就会使用哪个.cspec文件中的调用约定配置。 182 | 183 | 不对!这不是甩锅给用户吗?Ghidra你是不是不太偷懒了啊??这个功能都不愿意做吗?? 184 | 185 |
186 | 187 | ## IDA中的调用约定推导 188 | 189 | 你这IDA都已经8.3版本了,对自定义的调用约定的支持可以说是几乎没有。 190 | 191 | 虽然IDA支持通过usercall等关键词来指定自己的调用约定,可是用过的都知道这玩意一点也不好用,临时应急都不好使。 192 | 193 | ref:[https://hex-rays.com/blog/igors-tip-of-the-week-51-custom-calling-conventions/](https://hex-rays.com/blog/igors-tip-of-the-week-51-custom-calling-conventions/) 194 | 195 | 翻看IDA-sdk-processor-module ,会发现IDA的宏写死了对调用约定(在IDA中称为CC)的类型。其中最新的一个是对Go语言的支持。 196 | 197 | 至于IDA的调用约定推导机制更是不公开的,在ida/ida64.dll中,因此没办法通过正常方法知道它是怎么实现的。 198 | 199 | ## 函数的参数识别 200 | 201 | 存在两种典型场景,第一种,函数识别自己本体的参数,第二种,识别调用的函数的参数。 202 | 203 | 现在的反编译器,识别自己本身的函数参数准确度已经比较高了。但是由于缺少跨函数的分析,哪怕是IDA在对调用函数的参数判断上,也经常会出问题。 204 | 205 | 一个通用的本函数参数的识别算法描述(来自与angr的实现): 206 | 207 | 1、构造函数本体的define-use关系 208 | 209 | 2、从函数入口开始,列举所有有ues却没有define的“寄存器” 210 | 211 | 3、将这些寄存器依次放入二进制架构与编译器中可能调用约定的进行匹配,选出最适合的。 212 | 213 | 如下图就能确定使用的是fastcall的rdi、rsi、rdx三个参数 214 | 215 |
216 | 217 | ## 返回值识别 218 | 219 | 与参数相反,准确的识别函数自己本身的返回值,比较困难。因为传入的函数参数参数会被直接使用; 220 | 221 | 而对于返回值,比如x86\_64指令集最常使用的rax作为返回值, 222 | 223 | 反编译器往往选择比较保守的做法,只要返回值的寄存器在函数退出时被定义过,那就反编译器就认为反编译的函数有返回值。 224 | 225 | 一个通用的调用子函数的返回值算法描述, 226 | 227 | 1、构建define-use关系 228 | 229 | 2、优先通过传入参数,判断可能的调用约定(如果函数未进行分析) 230 | 231 | 3、基于调用约定与define-use关系,增加对返回值define的信息。 232 | 233 | 234 | 235 | ## 栈平衡 236 | 237 | 在X86\_64的平台,最典型的的做法,在函数开头将寄存器push到栈中,在函数结束的时候再pop出来。反编译器也会依照调用约定中的“不受影响寄存器”,尝试处理栈平衡。 238 | 239 |
240 | 241 | 例如上图使用了IDA作为示例,IDA的寄存器栈平衡处理在转换中间语言前就完成了,IDA在转换micorocode前会将能够相互对应的入栈与出栈的语句全部删去,这样后续的优化就不用烦恼这些问题了。 242 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/5.-lei-xing-tui-dao.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: 通用方法的类型还原 3 | --- 4 | 5 | # 5.类型推导 6 | 7 | 所谓类型推导,就是从低级语言中还原出高级语言的类型信息; 8 | 9 | 但是,现有的传统技术并不足以完美的进行还原。 10 | 11 | 本章将分析目前通用的反编译器的类型还原技术,总得来说分为两步:类型推导和类型传播。 12 | 13 | 14 | 15 | ## 类型定义 16 | 17 | 现有的通用反编译器,无论是IDA、Ghidra、JEB还是其它的,目标都是还原出C-like的高层伪代码,因此他们的类型系统设计时也是C-like形态。 18 | 19 | ref: [https://www.geeksforgeeks.org/data-types-in-c/](https://www.geeksforgeeks.org/data-types-in-c/) 20 | 21 | 同时,考虑到不同架构上的int整形长度可能有差别,为了避免歧义,反编译器定义的类型同时往往会带上具体长度。 22 | 23 | 24 | 25 | 在Ghidra推导支持的类型信息在Ghidra\Features\Decompiler\src\decompile\cpp\\[typeop.cc](http://typeop.cc/)中进行了定义,具体如下,注意Ghidra的类型具体长度是与程序的架构相关的: 26 | 27 | ``` 28 | /// The core meta-types supported by the decompiler. These are sizeless templates 29 | /// for the elements making up the type algebra. Index is important for Datatype::base2sub array. 30 | enum type_metatype { 31 | TYPE_VOID = 14, ///< Standard "void" type, absence of type 32 | TYPE_SPACEBASE = 13, ///< Placeholder for symbol/type look-up calculations 33 | TYPE_UNKNOWN = 12, ///< An unknown low-level type. Treated as an unsigned integer. 34 | TYPE_INT = 11, ///< Signed integer. Signed is considered less specific than unsigned in C 35 | TYPE_UINT = 10, ///< Unsigned integer 36 | TYPE_BOOL = 9, ///< Boolean 37 | TYPE_CODE = 8, ///< Data is actual executable code 38 | TYPE_FLOAT = 7, ///< Floating-point 39 | 40 | TYPE_PTR = 6, ///< Pointer data-type 41 | TYPE_PTRREL = 5, ///< Pointer relative to another data-type (specialization of TYPE_PTR) 42 | TYPE_ARRAY = 4, ///< Array data-type, made up of a sequence of "element" datatype 43 | TYPE_STRUCT = 3, ///< Structure data-type, made up of component datatypes 44 | TYPE_UNION = 2, ///< An overlapping union of multiple datatypes 45 | TYPE_PARTIALSTRUCT = 1, ///< Part of a structure, stored separately from the whole 46 | TYPE_PARTIALUNION = 0 ///< Part of a union 47 | }; 48 | ``` 49 | 50 | 51 | 52 | IDA 也是类似,但更加直观,直接把类型限制死了,管你是哪个平台算出来都是这么几个 53 | 54 |

ref:https://hex-rays.com/blog/igors-tip-of-the-week-45-decompiler-types/

55 | 56 | ## 推导初始类型 57 | 58 | 在反编译中通过两个维度推导一个变量的初始类型:操作长度和操作类型 59 | 60 | 例如,mov rax, \[rbx] 这个指令会对rbx进行取址,那么此时rbx就是一个长度为64bit的指针类型(这好像是废话,64位程序的指针长度只能是64位),rax是一个64bit的未知类型。 61 | 62 | 首先,反编译会对中间语言的各个操作符预先定义好类型信息。 63 | 64 | 我们以Ghidra为例,在\Ghidra\Features\Decompiler\src\decompile\cpp\\[typeop.cc](http://typeop.cc/)一开头就有一段很长的代码,把所有涉及到的中间语言操作对应的预制类型信息初始化了。 65 | 66 |
67 | 68 | 初始化将类型分为有符号、算数、逻辑操作、浮点等几类(指针类型的操作判断是在类型传播过程中完成的) 69 | 70 | ``` 71 | enum { 72 | inherits_sign = 1, ///< Operator token inherits signedness from its inputs 73 | inherits_sign_zero = 2, ///< Only inherits sign from first operand, not the second 74 | shift_op = 4, ///< Shift operation 75 | arithmetic_op = 8, ///< Operation involving addition, multiplication, or division 76 | logical_op = 0x10, ///< Logical operation 77 | floatingpoint_op = 0x20 ///< Floating-point operation 78 | }; 79 | ``` 80 | 81 | ## 传播类型与类型约束 82 | 83 | 介绍基于”格”理论的类型传播与约束系统:参考下图,所有的变量的类型都是从上到下变换。其中,上界(T) == unknow-类型未推导,下界(⊥) == conflict-类型冲突 84 | 85 |
86 | 87 | 每当一个变量进行了某些操作,就会在这个图上走下来。在上图中,类型有且只能从上往下走,最终对一个变量的所有操作都执行过后,它最终停留的点就是推断的类型。 88 | 89 | 例如,读取内存和写入内存的中间语言很明显就意味着指针类型。这意味着某个变量很可能是指针 90 | 91 |

ref: https://www.ndss-symposium.org/wp-content/uploads/2017/09/lee2.pdf

92 | 93 | ## 展示类型和类型转换 94 | 95 | 所有的变量类型都已经完成推导,现在假定我们已经到了展示最终代码的截断,那还存在两个问题 96 | 97 | * 类型操作存在冲突怎么办 98 | * 已有的信息不足以推导出有用的类型,那如何展示 99 | 100 | 第一个问题,如果推断的类型存在冲突,通用的处理方法:插入类型转换的提示信息。 101 | 102 |
103 | 104 | 第二个问题就比较麻烦了,目前来说有这么两种方案。 105 | 106 | 第一种就直接啥都不管直接输出一个unknow、undefine或者推断的上界(也就是T); 107 | 108 | Ghidra就是这样做的,我觉得这种做法十分的糟糕,尤其是大型程序里看到这种东西,十分令人不舒服。 109 | 110 |
111 | 112 | 第二种,展示有效的信息,比如IDA就会展示变量的\_BYTE、\_WORD、\_DWORD等宽度信息: 113 | 114 |
115 | 116 | ### 反编译器预置的类型信息 117 | 118 | 为了缓解反编译器不支持结构体识别的缺陷,主流的做法是预置各种常用的结构体类型信息。 119 | 120 | 比如常用系统调用返回的结构体、JNI的各种结构体等等。 121 | 122 | 123 | 124 | ## 通用方案缺陷 125 | 126 | 通用方案有很多很多的缺陷,甚至可以说,反编译F5的结果看的很累,很大程度上也是因为类型系统还原技术的不足。 127 | 128 | 当然,也不能全怪反编译器,因为这个功能的确很难做,很多问题还是无解的。 129 | 130 | 因此,接下来的补充章节,将介绍这10多年来学术界对这方面的探索,希望有落地的那一天,解放逆向工作者。 131 | 132 | ### 缺陷1:无法全自动的识别结构体 133 | 134 | 是无法获得任何结构体的信息的,除非进行预置。 135 | 136 | ### 缺陷2:不支持面向对象语言 137 | 138 | 和结构体问题类似,当前通用反编译器的反编译层级是“函数”,这是典型的过程间抽象语言的概念。考虑到IDA与Ghidra等通用反编译器的设计是在30年前,那时候没有什么高层级抽象的语言。这也可以理解。 139 | 140 | 但是,现代的程序有很多使用Go、Rust、现代C++等“高级语言”进行编写。 141 | 142 | 这一类的语言的反编译器开发的工作量更为复杂,尤其是还原其中的各种class的类型等等工作;甚至说:“我们已经没有必要把所有程序都还原成C-like的形态” 143 | 144 | ### 缺陷3:无法获得全局以及指针传播的间接类型 145 | 146 | 考虑到对于大型二进制(超过100M)的分析效率是个难题,随着程序规模的增大,全局指针分析技术的性能消耗也会指数级增加。现在反编译器的做法是不处理全局对象;也不使用全局的程序分析技术。 147 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/6.-kong-zhi-liu-huan-yuan-1.md: -------------------------------------------------------------------------------- 1 | # 6.控制流还原(1) 2 | 3 | 控制流还原是反编译器中端优化流程的最后几步了,其目标是将原有的CFG图,还原成 while/if/switch 组成的高层次结构。 4 | 5 | 人类很难阅读并理解完全由jmp/goto跳转组合成的CFG结构图,因此还原出高层的控制流结构,能够极大的提升反编译结果的可读性。 6 | 7 | 以前的,还原后的结构标准:goto语句越少越好; 8 | 9 | 但是随着goto-less的算法研究(参考补充章节中的no-more-goto论文),以及对于编译器,尽可能的还原出接近源码的反编译结果,因为源码的可读性是最佳的。 10 | 11 | 可以移步到补充章节,在那里我讨论了和控制流还原相关的两篇很有意思的论文: 12 | 13 | * “Dream: no-more-goto”:这篇论文提出了一种no-more-goto算法,从理论上证明了任何的CFG都能还原出不带goto的高层次控制流结构。 14 | * “Sailr:no-need no-more-goto”:这个更有牛逼,驳斥了上面的no-more-goto算法,认为判断控制流还原算法应该和编译前的源码对比,并给出了更为先进的控制流还原理论。 15 | 16 | 好吧,先找我们从传统的控制流还原开始讲起吧。 17 | 18 | ## 介绍:传统控制流还原的实现 19 | 20 | IDA和Ghidra的控制流还原技术均使用了“区间分析/结构分析”的经典编译器技术。这一理论来源与编译原理中的控制流分析技术。需要先介绍一个CFG流图关键的特性:可规约性(reducible) 21 | 22 | 23 | 24 | ### 可规约性(reducible): 25 | 26 | 可规约性(reducible),这个词用来描述不够清楚,它的本质含义是“结构良好的流图”(well-structured),但是既然业界都这样说,那么后面还是用”可规约”这样来描述。 27 | 28 | 可归于的定义本身很复杂 29 | 30 | 一种定义方法是:如果图中的循环没有多个出口/多个入口,那么这个图一般而言就是可规约的。如果一个图不可规约,那么基于结构分析的技术,还原后一定会有goto语句(无法正确还原)。 31 | 32 | **上面这个定义太难懂了。** 33 | 34 | 所以,在本单元的反编译器理论中,使用另一种简略的定义;对一个流图进行预定义的各种变幻,如果流图塌缩成单个结点,则称这个流图是可归约的,否则就是不可规约的。 35 | 36 | 如果一个图可规约,那么图中的循环就能被正确的还原出控制流(控制流还原的主要复杂读就是在于循环) 37 | 38 | 至于“塌缩”的意思,往下看。 39 | 40 | ### 区间分析与结构分析在反编译器中的运用 41 | 42 | 其基本原理即是:通过某一些列的规则将原有的流图不断的“塌缩”,最后成为一个点。 43 | 44 | 45 | 46 | ### 塌缩规则 47 | 48 | * 基础规则 - 连续两个基础块可以直接压缩成一个 49 | 50 |
51 | 52 | * 循环规则-自循环 53 | 54 |
55 | 56 | * 循环规则-while循环 57 | 58 |
59 | 60 | * 循环规则-do+while循环 61 | 62 |
63 | 64 | * 条件分支- if+then 65 | 66 |
67 | 68 | * 条件分支- if+else+then 69 | 70 |
71 | 72 | * 条件分支-switch+case 73 | 74 |
75 | 76 | ### 示例: 77 | 78 | 参考:《高级编译器设计与实现》第七章 79 | 80 | todo 81 | 82 | 83 | 84 | ## 难题:对于不可规约图的处理 85 | 86 | 87 | 88 | 于源码来说,不可规约的情况很少很少; 89 | 90 | 例如,js这种不支持goto语句的语言,源码中基本上不会产生不可规约的情况。常见的流程控制指令 (例如IF、FOR、WHILE、BREAK、CONTINUE)都会产生可规约控制流图。 91 | 92 | 在支持goto语句,比如C语言中,源码中不可规约流图常常出现在goto跳出多重循环等场景。 93 | 94 | 但是事情总会出岔子,编译器的总能给你找事情做。 95 | 96 | 97 | 98 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/7.-bian-liang-huan-yuan-1-bian-liang-de-ji-qi-ji-biao-shi.md: -------------------------------------------------------------------------------- 1 | # 7.变量还原(1)变量的机器级表示 2 | 3 | ## 什么是变量? 4 | 5 | 看到这个有人会问,我这每天写代码,这变量是啥我还不知道吗? 6 | 7 | 那么对于计算机,或者对于汇编,对于机器指令,有没有变量这个概念? 8 | 9 | 甚至说,对于计算机来说,有没有"**编程语言"**这个概念? 10 | 11 | 有人说:计算机系统的本质就是一组离散状态空间及其变化规则,程序不过是符合规则的一组变化动作的组合。 12 | 13 | 有人说:电子计算机,是人利用电子电路和电子运动的物理规律,精心构建输入与输出效果,表达自己的设定。 14 | 15 | 有人说。。。。。。 16 | 17 | \------------------------------ 18 | 19 | 好吧,这里也不是讨论哲学问题的地方,让我们回到现实里来。 20 | 21 | **什么是变量?:对于计算机来说没有变量,只有状态间的变化。编程语言和变量都是给人类看的,它们是一种符合程序员思考并操作的状态抽象。** 22 | 23 | **什么是反编译的变量还原:通过研究编译器如何将上层抽象转换到计算机底层实现,依照一系列的约定与特征,从汇编中还原出上层抽象。** 24 | 25 | **因此反编译的前提是:明确编译器的行为。** 26 | 27 | \------------------------------ 28 | 29 | 这还有一个绕不开的问题:在反编译器的眼中,变量是什么? 30 | 31 | 对于现在流行的编译器而言,变量是什么?变量就是SSA。就是有且在一个地方定义,在其它地方使用的东西。 32 | 33 | 34 | 35 | ## 变量在汇编中的表示(x86\_64\_ELF示例) 36 | 37 | 38 | 39 | 拆分常见的各种变量,展示他们在二进制汇编中的样子。知道了编译器对变量的表示才知道如何进行还原。 40 | 41 | ### 全局变量 42 | 43 | 全局变量在汇编中是比较好识别的,通常是对BSS、data、rdata段上数据的引用; 44 | 45 | 他们的地址固定(静态分析场景,不考虑动态的地址随机化保护),在IDA中会以unk\_\
标签展示,本质上是第一个数据地址的立即数: 46 | 47 |
48 | 49 | 50 | 51 | ### 函数局部变量(函数参数) 52 | 53 | 函数参数的局部变量,取决于函数的调用约定。通常是在栈与寄存器上;具体的判断逻辑,可以回头看一下之前调用约定与函数推导的那一节。 54 | 55 | 在IDA中,将鼠标放到F5后的函数参数上,会提示这个参数是如何传入的。比如下面这个this指正就是rdi传入的。 56 | 57 |
58 | 59 | ### 函数局部变量(寄存器) 60 | 61 | 编译器的寄存器着色分配算法;会尽可能的将所有的变量分配到寄存器中。如果变量很多,还会尽可能的复用寄存器。 62 | 63 | 在IDA的F5代码中,会在局部变量后的注释显示使用的寄存器: 64 | 65 |
66 | 67 | 多个变量经常使用相同的寄存器,这很正常,因为寄存器复用的机制。 68 | 69 | 70 | 71 | ### 函数局部变量(栈) 72 | 73 | 局部变量分配到栈上,有这么几个场景: 74 | 75 | * 数组形态的局部变量,如果数组很大会在栈上开辟固定长度的空间 76 | * 无法分配到寄存器的局部变量:寄存器不够用了,或者强制要求在栈上的变量(比如canary) 77 | * 动态开辟的栈空间(这种情况比较罕见) 78 | 79 | 在IDA中栈上变量后的注释会以相对于栈顶和栈底的偏移,注意,这里面的rbp/rsp只是栈顶和栈底的抽象,并不一定是真正存在的。 80 | 81 |
82 | 83 | ### 堆变量 84 | 85 | 现在的反编译的维度通常是函数级的,没有对于动态分配的“堆抽象”因此,堆变量就会被翻译成指针操作。 86 | 87 | 88 | 89 | ### 临时变量 90 | 91 | 除此之外,还有一些临时变量,他们是数据操作过程的中间产物。比如,代码中有一个很复杂的计算语句: 92 | 93 | a = b + c + d + e ....... 94 | 95 | 在编译器转换为ssa的过程中,每个加法操作符,都会添加一个ssa的临时变量。转换到汇编中去,如果寄存器着色算法能够分配足够的寄存器存放临时变量,那么寄存器会被多次重用,否则就需要栈变量作为临时变量的存放地点了 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/7.-bian-liang-huan-yuan-2-ji-cun-qi-fen-pei-yu-huan-yuan.md: -------------------------------------------------------------------------------- 1 | # 7.变量还原(2)寄存器分配与还原 2 | 3 | 对于编译器来说,变量分配是用最少的寄存器覆盖最多的变量。 4 | 5 | 在反编译中,这又有所不同 6 | 7 | 8 | 9 | ## 变量分配技术与SSA 10 | 11 | 熟悉汇编语言的都知道,编译后的二进制的变量最终都体现在寄存器、栈、堆、data段、bss段等的操作上(X86\_64体系)。 12 | 13 | 当然,在这个系类文章中,反汇编器在还原的过程中是看不到寄存器,在这个阶段它的眼里都是SSA了。 14 | 15 | 那么如何将SSA中提取出实际的可读的变量,就是变量还原的算法的工作。 16 | 17 | 变量还原算法在不同的反编译器中实现方法不一样,总得来说有这么几种关联方式: 18 | 19 | 1. 在SSA翻译后插入phi语句的同时,将phi语句关联的变量进行关联。 20 | 2. 如果存在栈,那么可以直接分割栈结构标记并还原变量;即,函数栈可以看成一个巨大的结构体,对于栈变量的还原就是对结构体的偏移分割。 21 | 3. 在中间语句优化完成后,依照关联关系尝试进一步进行优化,整理出实际的变量。 22 | 23 | ## 编译器的寄存器分配 24 | 25 | 要知道反编译器的寄存器还原技术,需要先对编译器的寄存器分配算法有一定的了解。 26 | 27 | 寄存器分配算法的基本规则与目标: 28 | 29 | 1. 变量的数量可以是”无限“的,但CPU 寄存器的数量是有限 30 | 2. 尽量让变量的值或计算结果保留在寄存器中 31 | 32 | (寄存器分配算法本身是个NP困难问题) 33 | 34 | 让我用一个教科书上的例子 35 | 36 | > [https://pages.cs.wisc.edu/\~horwitz/CS701-NOTES/5.REGISTER-ALLOCATION.html](https://pages.cs.wisc.edu/~horwitz/CS701-NOTES/5.REGISTER-ALLOCATION.html) 37 | 38 | ``` 39 | int main() 40 | { 41 | int b; 42 | int a = getchar(); 43 | int x = getchar(); 44 | if (x > 0) { 45 | if (a > 0) { 46 | x = 10; 47 | } else { 48 | x = 20; 49 | } 50 | } else { 51 | a = 100; 52 | b = getchar(); 53 | x = a * b; 54 | while (x > a + b) { 55 | x = x / 2; 56 | } 57 | } 58 | printf("%d", x); 59 | 60 | return 0; 61 | } 62 | ``` 63 | 64 | 画出上面代码的CFG图 65 | 66 | 67 | 68 |
69 | 70 | 计算变量的存活范围 71 | 72 | ``` 73 | Def of a at node (1), {1, 2, 3, 4} 74 | Def of x at node (2), {2, 3} 75 | Def of x at node (5), {5, 7} 76 | Def of x at node (6), {6, 7} 77 | Def of a at node (8), {8, 9, 10, 11, 12} 78 | Def of b at node (9), {9, 10, 11, 12} 79 | Def of x at node (10), {7, 10, 11, 12} 80 | Def of x at node (12), {7, 11, 12} 81 | 82 | 最终结果为 83 | ( , {1, 2, 3, 4} ) 84 | ( , {2, 3} ) 85 | ( , {5, 6, 7, 10, 11, 12} ) 86 | ( , {8, 9, 10, 11, 12} ) 87 | ( , {9, 10, 11, 12} ) 88 | ``` 89 | 90 | 即如下图所示 91 | 92 |
93 | 94 | 使用图着色算法,进行着色后就变成了下面这个样子,更加直观了 95 | 96 |
97 | 98 | ``` 99 | Live Range Color (register) 100 | ======================== ================ 101 | x {5, 6, 7, 10, 11, 12} R1 102 | b {9, 10, 11, 12} R2 103 | a {8, 9, 10, 11, 12} -- no color -- 104 | x {2, 3} R1 105 | a {1, 2, 3, 4} R2 106 | ``` 107 | 108 | 最终分配好寄存器 109 | 110 | ``` 111 | read(R2); 112 | read(R1); 113 | if (R1 > 0) { 114 | if (R2 > 0) R1 = 10; 115 | else (R1 = 20); 116 | } 117 | else { 118 | a = 100; 119 | read(R2); 120 | R1 = a * R2; 121 | while (R1 > A + R2) { 122 | R1 = R1 / 2; 123 | } 124 | } 125 | print(R1 126 | ``` 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/bu-chong-zhang-jie-lun-wen-yue-du-usingsailronangrdecompiler.md: -------------------------------------------------------------------------------- 1 | --- 2 | description: >- 3 | 补充章节+论文阅读+结构化分析+Ahoy SAILR! There is No Need to DREAM of C: A Compiler-Aware 4 | Structuring Algorithm for Binary Decompilation 5 | --- 6 | 7 | # 补充章节-论文阅读:using-sailr-on-angr-decompiler 8 | 9 | ## 简介 10 | 11 | 论文标题: 12 | 13 | 《Ahoy SAILR! There is No Need to DREAM of C: A Compiler-Aware Structuring Algorithm for Binary Decompilation》 14 | 15 | 16 | 17 | 代码Github地址: 18 | 19 | {% embed url="https://github.com/mahaloz/sailr-eval#using-sailr-on-angr-decompiler" %} 20 | 21 | ## 论文作者 22 | 23 | **Zion Leonahenahe Basque(mahaloz)** 24 | 25 | 作者是shellphish成员,angr的开发者之一,这也可以解释为什么论文的代码实现使用了angr。 26 | 27 | 作者的个人博客:[https://mahaloz.re/about.html](https://mahaloz.re/about.html) 28 | 29 | 个人主页:[https://www.zionbasque.com/](https://www.zionbasque.com/) 30 | 31 | github:[https://github.com/mahaloz](https://github.com/mahaloz) 32 | 33 | ## "我"的评价 34 | 35 | 我认为,这篇论文的价值不在于它的优化算法,以及和DREAK论文的比较部分。 36 | 37 | 它的价值在于: 38 | 39 | 第一:作者从真正逆向工程的角度出发,提出了适用于判断反编译生成CFG结构优劣的标准+算法,即和源码直接比较。之前的算法都是从圈复杂度,goto数量等等源码的判断方法出发,而忽略了可读性这个最重要的指标。 40 | 41 | 第二:作者详细的分析了编译器的哪些编译优化选项会生成不可规约的CFG图,这是一个比较繁琐的工作。熟悉这些编译选项以及优化方法,对于逆向工程有很大的帮助。 42 | 43 | 44 | 45 | ## 反编译的控制流还原 46 | 47 | 在我[之前的文章](https://bbs.kanxue.com/thread-278789.htm),有一些简略的介绍。这里快速的总结一下。 48 | 49 | 50 | 51 | ### 什么是控制流还原? 52 | 53 | 在反编译器的执行过程中,控制流还原的目标是将CFG还原成由if、while、for等组成的高级抽象结构。如下图有个控制流图,他的所有边在汇编中只是个jmp,反编译器需要理解控制流的结构,把条件分支和循环识别出来。 54 | 55 | 最终,生成更高级的,更上层的控制流结构更适合人类阅读与理解,毕竟正常人应该很难理解全是goto的CFG结构,相比之下分支和循环就好理解多了。 56 | 57 |
58 | 59 | ### 主流反编译器的控制流还原算法如何实现? 60 | 61 | 主流的反编译,也就是IDA和Ghidra,所使用的控制流还原算法来自于编译器的【结构分析】技术。 62 | 63 | 可以参考鲸书:《高级编译器的设计与实现》第7章。其原理大致是预置好各种流图模式,然后在CFG中寻找能对应上的模式后,把流图按照规则进行"塌缩",直到塌缩成一个点就完成分析了。 64 | 65 | 参考下面这张图,塌缩的规则: 66 | 67 |
68 | 69 | 以及一个还原的案例: 70 | 71 |
72 | 73 | ### 什么是“可规约的图”? 74 | 75 | 在学术上结构化分析的前提条件是控制流图必须是可规约的。 76 | 77 | “可规约”可以简单的理解为,这个图能够按照一定的“塌缩规则”,最终压缩成一个点。否则这个CFG图无法正常的进行还原。 78 | 79 | ### 主流反编译器的控制流还原算法有什么缺陷? 80 | 81 | 如下图,节点2是个典型if结分支构,但是他的后继节点3却不被节点2支配; 82 | 83 | 所谓不被支配,意思是有一个【节点1 到节点3】 的路径绕不经过节点2,因此节点3与节点4不被节点2支配。 84 | 85 |
86 | 87 | 这违反了SESE(single entry + single exit)的well-define结构化分析规则。 88 | 89 | 所以,结构化分析还原的理论上来说,“钻石形”的cfg是不可规约的的,无法被正常还原。 90 | 91 | ### 为什么会出现这种缺陷? 92 | 93 | 正常写代码,只要不使用goto等非常规语法,写出来的代码控制流结构都是可以被还原的。 94 | 95 | 但是,编译器会对cfg图进行优化,主要目的是精简重复代码,减少条件判断语句增加效率等等。这一系列的优化可能会生成不可规约的控制流结构,造成【结构分析】技术无法正常还原,只能将边删除后插入goto来缓解。 96 | 97 | 虽然我[之前的文章](https://bbs.kanxue.com/thread-278789.htm)已经给了一个案例;不过《Ahoy SAILR! There is No Need to DREAM of C: A Compiler-Aware Structuring Algorithm for Binary Decompilation》这篇论文做得更加完美。 98 | 99 | 他将GCC编译器中能对控制流造成影响,会产生不可规约图结构的编译选项全部列了出来,还给出了影响程度的分析。 100 | 101 | ### 补充:为什么从理论上来说,“钻石形”的cfg无法被还原 102 | 103 |
104 | 105 | 如图,节点2明显是个if语句的入口,但是他的后继节点3却不被节点2支配;即,可以从节点1 ->节点3 这个路径绕过去; 106 | 107 | 因此节点3和节点4不被节点2支配,所以违反了SESE(single entry + single exit)的well-define结构化分析规则,所以“钻石形”的cfg无法被还原! 108 | 109 | 110 | 111 | ## 论文的主要目的 112 | 113 | 我们从论文的标题看起,这就很有趣。 114 | 115 | 《Ahoy SAILR! There is No Need to DREAM of C: A Compiler-Aware Structuring Algorithm for Binary Decompilation》 116 | 117 | 标题中的“DREAM”指的是NDSS 2015的论文 《No More Gotos: Decompilation Using Pattern-Independent Control-Flow Structuring and Semantics-Preserving Transformations》,后续用Dream来指代。 118 | 119 | 2015年的Dream论文提出了一种与图匹配完全不同的方式:排序路径和抵达路径的条件,然后顺序的组装出条件代码配合图优化;这种方法从理论上证明了任何一个cfg图都能还原成不包含goto语句的控制流结构。 120 | 121 | 那么,SAILR这篇论文看标题就知道是反驳DREAM的方法,论文的主要观点是,没有goto或者说goto-less的结构并不意味着生成源码可读性高,还是应该在原有的结构化算法回归并改进。 122 | 123 | 124 | 125 | ## 补充:Dream论文的goto-less方案 126 | 127 | Dream论文-ndss2015:[https://net.cs.uni-bonn.de/fileadmin/ag/martini/Staff/yakdan/dream\_ndss2015.pdf](https://net.cs.uni-bonn.de/fileadmin/ag/martini/Staff/yakdan/dream_ndss2015.pdf) 128 | 129 | 这篇论文的贡献是,相比于传统的“结构化分析”技术,论文提出了一种完全不同的方法。 130 | 131 | 这种方法从理论上证明,不用考虑图规约问题,任意的cfg图都能还原成不包含goto语句的高级控制流结构。 132 | 133 | 具体还原方法请参考目录里关于dream/no-more-goto的补充章节(可能没写完) 134 | 135 | 136 | 137 | ## 论文主体 138 | 139 | 首先,论文提出了no-more-goto这篇论文中将所有的goto全部消除这种做法不可取,提出了以下的观点: 140 | 141 | * 虽然goto-less算法具有先进性,但是会大规模的修改cfg,导致降低可读性。 142 | * 在现实中,有的代码里就是有goto的(把这部分的goto优化反而导致可读性降低),需要有区分的判断。 143 | * 反编译结果的可读性不应当以生成的goto多少进行判断,应当与编译前的源码结构进行比较 144 | * 编译优化是产生的不可规约控制流的主要原因。 145 | * 当前反编译的结构化的技术要不是没有开源,要不是太久没有更新了,需要针对于特定编译器的更先进的反编译技术。 146 | 147 | 148 | 149 | ## 编译器优化对于控制流的影响 150 | 151 | 既然导致不可规约控制流产生的主要原因是编译选项,反编译器设计的时候就需要明确,那些编译选项会有这些影响? 152 | 153 | 论文的测试方法:使用GCC编译器开启O2优化后,分别关掉各个优化选项;使用IDA反编译整个二进制,统计生成goto语句的数量,论文结果如下。 154 | 155 |
156 | 157 | 可以看出,生成goto语句影响较大的编译选项有以下两个: 158 | 159 | * A:Jump Threading 160 | * D:Cross Jumping 161 | 162 | 163 | 164 | 而且,哪怕使用O0进行编译,还是会有goto语句生成。 165 | 166 | 下面稍微解释一下这些编译选项对于控制流的影响。 167 | 168 | ### A:Jump Threading 169 | 170 | 参考wiki:[https://en.wikipedia.org/wiki/Jump\_threading](https://en.wikipedia.org/wiki/Jump_threading) 中的例子,Jump Threading编译优化方案用于简化条件逻辑,可以有效的减少分支判断次数。由于现代CPU流水线和分支预测的特性,减少分支判断可以显著提升性能。 171 | 172 | 插入的基本块会影响控制流结构 173 | 174 |
175 | 176 | ### B:Common Subexpression Elimination(CSE) 177 | 178 | {% embed url="https://en.wikipedia.org/wiki/Common_subexpression_elimination" %} 179 | 180 | 参考本书主要章节对于CSE优化的说明,编译器可能会将多条CSE语句压缩到一个块里,然后进行跳转。 181 | 182 | ### C:Switch Conversion 183 | 184 | 和上面的CSE类似,只不过条件更为苛刻,对于swich中的赋值语句,编译器可能会通过goto来压缩语句,优化前的示例代码: 185 | 186 | ``` 187 | switch(a) { 188 | case 1: 189 | //do-something 190 | ret = 100; 191 | error = "error"; 192 | break; 193 | case 2: 194 | //do-something 195 | ret = 100; 196 | error = "error"; 197 | break; 198 | ..... 199 | } 200 | ``` 201 | 202 | 优化后,将两个case尾部相同的代码使用goto连接,压缩代码大小。 203 | 204 | ``` 205 | switch(a) { 206 | case 1: 207 | //do-something 208 | ret = 100; 209 | error = "error"; 210 | goto label; 211 | case 2: 212 | //do-something 213 | label: 214 | ret = 100; 215 | error = "error"; 216 | break; 217 | ..... 218 | } 219 | ``` 220 | 221 | 222 | 223 | ### D:Cross Jumping 224 | 225 | 参考我[之前的文章](https://bbs.kanxue.com/thread-278789.htm) “为什么IDA F5后的代码会有GOTO语句?” 中的案例 226 | 227 | 优化前的代码: 228 | 229 | ``` 230 | int fun(int a, int b) 231 | { 232 | int ret = 0; 233 | if (getchar() > 0x10) { 234 | ret = a+b; 235 | } else { 236 | ret = a-b; 237 | if (getchar()>0x20) { 238 | printf("hello"); 239 | printf("ret=xx"); 240 | return ret; 241 | } 242 | } 243 | printf("hi"); 244 | printf("ret=xx"); 245 | return ret; 246 | } 247 | ``` 248 | 249 | 编译器优化中发现, printf("ret=xx"); return ret; 这两句代码完全一致,为了减少代码量就插入一个goto语句将这两个尾部进行融合。优化后插入了goto。 250 | 251 | ``` 252 | int fun(int a, int b) 253 | { 254 | int ret = 0; 255 | if (getchar() > 0x10) { 256 | ret = a+b; 257 | } else { 258 | ret = a-b; 259 | if (getchar()>0x20) { 260 | printf("hello"); 261 | goto LABEL 262 | } 263 | } 264 | printf("hi"); 265 | LABEL: 266 | printf("ret=xx"); 267 | return ret; 268 | } 269 | 270 | ``` 271 | 272 | 如下图 273 | 274 |
275 | 276 | ### E:Software Thread Cache Reordering 277 | 278 | 没太读明白,看着意思是在多线程情况下,对于热点代码,通过代码复制增加效率 279 | 280 | 281 | 282 | ### F:Loop Header Optimizations 283 | 284 | 循环头部提取优化(循环不变优化) 285 | 286 | 对于循环体内开头不变且不受循环影响的部分,提取到循环体外避免每次都执行,但是如果这部分代码中有跳转,就会导致出现goto语句。 287 | 288 | 简单代码案例: 289 | 290 | ``` 291 | for (int i = 0; i < 0x100; i++) { 292 | if (g_datas > 0) { 293 | break; 294 | } 295 | ..... // do-something 296 | } 297 | ``` 298 | 299 | 编译器优化后会将g\_datas 的判断移出来 300 | 301 | ``` 302 | if (g_datas > 0) { 303 | goto label; 304 | } 305 | for (int i = 0; i < 0x100; i++) { 306 | ..... // do-something 307 | } 308 | label: 309 | ``` 310 | 311 | ### G:Builtin Inlining 312 | 313 | 为了执行效率,编译器会把strcmp memset这种函数用内置的汇编inline掉。因而这部分代码中的判断条件会产生goto语句 314 | 315 | ### H:Switch Lowering 316 | 317 | 对于大型switch语句的二分与hash化查找优化,可能会产生goto语句 318 | 319 | ### I:no-return function 320 | 321 | 参考主体章节中“间接跳转与间接调用的处理”中关于no-return的内容 322 | 323 | exit等这一类函数执行后永远不会返回,编译器也就不再生成后续的代码。 324 | 325 | 如果反编译器没有正确的识别并传播这类信息,就会导致多加一个边,进而产生反编译问题。 326 | 327 | 328 | 329 | ## 如何判断反编译的结构化分析效果 330 | 331 | 之前的反编译论文中,往往以goto 数量+圈复杂度+代码行数对反结构化分析效果在进行评测。 332 | 333 | 但是,论文中指出,无论是goto 数量/圈复杂度/代码行,这都是对源码的分析方法,对于二进制的反编译结果并不是那么可靠。 334 | 335 | 最可靠的方法应当是:默认源代码的可读性是最优的,直接拿编译前的源代码和反编译F5后的结果进行对比。 336 | 337 | 论文提出了使用GED(Control-Flow Graph Edit Distance)图距离算法(这个指标以前用于二进制的图相似度匹配)但是GED是一个NP-hard问题且开销很高。因此做了一个简化版本的GED算法,我不是算法专家,不做展开。 338 | 339 | 340 | 341 | ## 对于不可规约控制流的还原的研究 342 | 343 | 既然上面的章节提出并整理了编译选项对于控制流的影响,作者提出了三种修改的控制流的场景。分别为 344 | 345 | * ISD:Irreducible Statement Duplication(A/E/F)编译器将一个语句扩展成多个语句的场景。 346 | * ISC:Irreducible Statement Condensing (B/C/D)编译器将多个语句压缩成单个语句的场景。 347 | * MISC:不属于上面两类的都算在这里。 348 | 349 | 这些优化所有的目的都是为了生成更适合人类阅读的控制流图。 350 | 351 | ### 对抗ISD优化 352 | 353 | ISD:Irreducible Statement Duplication,指的是编译器将一个语句扩展成多个语句的优化场景。 354 | 355 | 对于反编译器,需要把重复的语句合并掉,来生成更好的控制流图。 356 | 357 | 反编译器优化案例如下: 358 | 359 |
360 | 361 | 算法步骤概要 362 | 363 | * KMP算法,寻找一个CFG中完全重复的两段代码对。 364 | * 寻找满足要求的代码对,1. 代码对后续存在一个goto语句导致控制流不完整; 2. 它们的后继节点是同一个(中间可以插入其它节点) 365 | * 将两个重复代码合并成一个节点,判断节点的进出条件,重新组装CFG 366 | 367 | 368 | 369 | ### 对抗ISC优化 370 | 371 | ISC:Irreducible Statement Condensing ,指的是编译器将多个语句压缩成单个语句的优化场景。 372 | 373 | 反编译器的优化核心是找到合适的需要重复的代码,通过复制CFG的节点来减少最后的不可规约场景。如果一个goto语句的后继节点通过复制可以减少goto 的数量,那就执行优化。 374 | 375 | 当然,这个优化算法有着明显的性能问题,为了知道拿个边是goto语句,需要先完成至少一轮的结构分析。 376 | 377 |
378 | 379 | ### 其它(MISC)类型优化 380 | 381 | 不是主要内容,跳过。 382 | 383 | ## 效果展示 384 | 385 | 可以看到,使用论文中的GED图距离算法对比源码和反编译后的CFG图,SAILR有着明显的优势。 386 | 387 | 而DREAM的goto-less算法虽然goto的数量没有,但是GED却很高。 388 | 389 |
390 | 391 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/fan-hun-xiao-hua-zhi-ling-jian-jie-tiao-zhuan.md: -------------------------------------------------------------------------------- 1 | # 反混淆:花指令/间接跳转 2 | 3 | 4 | 5 | 花指令或者中间跳转是常见的基础混淆。虽然强度不高,但是其实这是一个很不错的混淆技术:没什么性能损耗,能直接干扰F5反编译(外行一看就感觉有效果),甚至可以在源码层实现不用动中间语言。 6 | 7 | 常见的混淆场景有以下三种: 8 | 9 | 1. 最基础也是最常见的,函数头部的花指令或者间接跳转,一般来说他们成片的出现。 10 | 2. 不太常见,间接跳转在函数体中间,是上面的加强版,通常和上面一起出现 11 | 3. 最少见的,混淆将会把函数中几乎所有的直接跳转语句变成间接跳转,这种情况已经接近深度混淆了,但是由于“跳转”的局限性,只要能够找到方法处理控制流,就能解决。 12 | 13 | 14 | 15 | ## 场景1. 函数头部的花指令 16 | 17 | 最常见的,就是在IDA中按F5后,变成了下面这样 18 | 19 |
20 | 21 |
22 | 23 | 他的代码大概会是下面这样个逻辑,有的时候IDA的常量传播优化能够直接计算出目标地址。 24 | 25 | ``` 26 | func sub_sample() { 27 | //一大堆对reg_xx的计算。。。 28 | br reg_xx 29 | //可能会出现的干扰垃圾指令 30 | //正在的函数起始部分 31 | } 32 | ``` 33 | 34 | 对于间接跳转,最有效的两个通用对抗方案是: 35 | 36 | 1. 通过观察规律找到BR指令前的汇编计算模式,在脚本中计算真实跳转后替换指令。 37 | 2. 混合模拟执行器,直接模拟执行出目标地址。 38 | 39 | 我不太推荐第一种观察规律的方案,对于静态还原观察规律的脚本很难再次复用,而且现在稍微复杂一些的混淆模式不太能观察出有效的规律。 40 | 41 | 第二种,以看我的项目中的IDA反混淆sample,依赖flare\_emu(本质上也是Unicorn) 42 | 43 | > [https://github.com/wINfOG/IDA\_Easy\_Life/blob/main/emu\_fast\_br.py](https://github.com/wINfOG/IDA_Easy_Life/blob/main/emu_fast_br.py) 44 | 45 | 46 | 47 | 48 | 49 | ## 场景2. 函数中间的中间跳转 50 | 51 |
52 | 53 | 54 | 55 | 可以看出IDA能够分析出跳转的目标。 56 | 57 | 最主要的问题是不是很好模拟的开头,如果以basic-block作为开头,业务逻辑代码混合在中间跳转的逻辑中;缺少了上下文构造的环境,比较容易出现内存异常,通用性难做的好。 58 | 59 | 60 | 61 | ## 场景3. 完全的中间跳转函数 62 | 63 | 64 | 65 | 将所有的分支跳转都替换为间接跳转语句,分支语句。 66 | 67 | 68 | 69 | ## 其它场景 70 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/fan-hun-xiao-ollvm-jing-tai-huan-yuan-0.md: -------------------------------------------------------------------------------- 1 | # 反混淆: OLLVM静态还原-0 2 | 3 | ## 反混淆的主流程 4 | 5 | 在接下来的好几个章节中,我们将讨论如何静态对抗Flat控制流混淆技术,包括从较为初级的裸状态变量的Flat混淆到一些对于状态变量保护后的flat混淆。 6 | 7 | 首先,对于Flat混淆技术的对抗核心的问题是攻击状态变量来还原控制流,会包含以下这些流程: 8 | 9 | 1. 定位状态变量 10 | 2. 定位分发块/真实块等 11 | 3. 还原状态变量在真实块的值,确认真实的控制流 12 | 4. 使用上面的信息,最终还原控制流结构 13 | 14 | 15 | 16 | 但是,在当前的对抗环境下,每一步有很多的困难,尤其是各种增强或者变种后的Flat技术,这些技术会想办法来保护混淆状态变量,比如: 17 | 18 | 1. 状态变量特殊化,不再是一个局部变量,甚至是多重状态变量 19 | 2. 控制流插入状态变量的计算后比较逻辑,不直接比较大数 20 | 3. 虚假控制流干扰对于分支逻辑的判断 21 | 4. 状态变量混合执行流程 22 | 5. 混合其它的混淆方案 23 | 24 | 25 | 26 | 使用IDA Pro和他的中间语言micorocode作为的还原的工具,选择IDA的原因是这个工作很方便,大家都会用; 27 | 28 | 虽然他的micorocode相关的资料比较匮乏,但是现有的开源资料已经足够完成后面的工作了。 29 | 30 | 31 | 32 | 样例二进制(借用一下OBPO): 33 | 34 | {% embed url="https://github.com/obpo-project/samples/blob/main/arm64/libnative-lib.so" %} 35 | 36 | ## 熟悉IDA-micorocode 37 | 38 | {% embed url="http://www.hex-rays.com/products/ida/support/ppt/recon2018.ppt" %} 39 | 40 | 安装lucid插件: 41 | 42 | 43 | 44 | 首先需要清楚IDA实现F5中的几个优化过程: 45 | 46 |
MMAT_ZERO microcode does not exist
MMAT_GENERATED generated microcode
MMAT_PREOPTIMIZED preoptimized pass is complete
MMAT_LOCOPT

local optimization of each basic block is complete.

control flow graph is ready too.

MMAT_CALLS detected call arguments. see also hxe_calls_done
MMAT_GLBOPT1 performed the first pass of global optimization
MMAT_GLBOPT2 most global optimization passes are done
MMAT_GLBOPT3 completed all global optimization. microcode is fixed now.
MMAT_LVARS allocated local variables
47 | 48 | 在这个系列中,我们尽可能的在更高层的中间语言执行优化,也就是说尽可能在IDA的其它优化之后执行优化。这么做的原因是,借用IDA的常量传播等优化功能,可以省去很多复杂的跨basic-block的分析工作。 49 | 50 | 51 | 52 | ### 插件优化层次的选择 53 | 54 | 越是往后,IDA本身的优化越多。 55 | 56 | 插件的优化所在的层次和插件本身的功能和需求有关。例如,这一次我实现的是一个以攻击状态变量为主的OLLVM还原插件,那么可以列出以下这些要求: 57 | 58 | * 需要有清晰的状态变量+用常量给状态变量赋值+状态变量比较常量 59 | * 状态变量的个数,尽可能收敛 60 | 61 | 通过对中间语言的查看,上面的需求至少是要在MMAT\_CALLS及以上实现。 62 | 63 | 说明:比如汇编里面有这样的代码 64 | 65 | ``` 66 | .text:000000000001744C MOVK W11, #0xC0B6,LSL#16 67 | .text:0000000000017450 MOVK W12, #0xC265,LSL#16 68 | //... 中间隔了很多的其它代码 69 | .text:0000000000017568 CMP W8, W11 70 | .text:000000000001756C B.EQ loc_17758 71 | .text:0000000000017570 CMP W8, W12 72 | .text:0000000000017574 B.EQ loc_17594 73 | ``` 74 | 75 | 在MMAT\_LOCOPT优化层,中间语言长这样,和汇编的意思差不多: 76 | 77 |
78 | 79 | 但是MMAT\_CALLS 优化层,由于执行了常量传播+替换,w11和w12直接被替换成了常量,因此就可以直接看出来是状态变量的比较操作了。 80 | 81 |
82 | 83 | 所以这个反混淆优化的层次,最少要在MMAT\_CALLS之上实现, 84 | 85 | 86 | 87 | ## 基础样例的反混淆流程 88 | 89 | * 识别状态变量: 90 | * 状态变量区分基本块: 91 | * 识别分发块 -> 真是块 状态变量的入度: 92 | * 找到真实块中状态变量的赋值点: 93 | * 在中间语言还原控制流: 94 | 95 | 96 | 97 | 98 | 99 | ## 识别状态变量 100 | 101 | 使用一种较为朴素的方案: 102 | 103 | 遍历所有的中间语言,查找最多的用于比较固定值后跳转的变量,代码如下: 104 | 105 | ``` 106 | import ida_hexrays 107 | import ida_kernwin 108 | import idaapi 109 | from ida_hexrays import * 110 | 111 | 112 | def is_jmp_condition(opcode: ida_hexrays.mop_t) -> bool: 113 | if opcode in [m_jcnd, m_jnz, m_jz, m_jae, m_jb, m_ja, m_jbe, m_jg, m_jge, m_jl, m_jle, m_jtbl]: 114 | return True 115 | return False 116 | 117 | class jz_info_t: 118 | def __init__(self, op=None, nseen=0): 119 | self.op = op 120 | self.nseen = nseen 121 | self.nums = [] 122 | 123 | class compare_collector(minsn_visitor_t): 124 | """ 125 | Looks for jz, jg comparisons against constant values. Utilised jz_info_t class. 126 | """ 127 | def __init__(self): 128 | minsn_visitor_t.__init__(self) 129 | self.seen_comparisons = [] 130 | 131 | def visit_minsn(self): 132 | ins: minsn_t = self.curins 133 | 134 | if is_jmp_condition(ins.opcode): 135 | print(f"jz_collector_x: {ins._print()}") 136 | else: 137 | return 0 138 | if ins.r.t != mop_n: # 这里假定了都是在比较常量,在初始的flat的确是这样,但是变种情况有很多 139 | return 0 140 | 141 | found = 0 142 | this_mop = ins.l 143 | idx_found = 0 144 | # 这里直接拿了D810的统计方法 145 | for sc in self.seen_comparisons: 146 | if sc.op.equal_mops(this_mop, EQ_IGNSIZE): # 如果左侧变量相同,进行合并 147 | sc.nseen += 1 148 | sc.nums.append(ins.r) 149 | found = sc.nseen 150 | break 151 | idx_found += 1 152 | 153 | # 如果在已有的内容不存在,加入一个 154 | if not found: 155 | jz = jz_info_t() 156 | jz.op = this_mop 157 | jz.nseen = 1 158 | jz.nums.append(ins.r) 159 | self.seen_comparisons.append(jz) 160 | 161 | return 0 162 | 163 | 164 | g_Last = ida_hexrays.MMAT_ZERO 165 | class BlockOptimizerManager(optblock_t): 166 | def __init__(self, *args): 167 | super().__init__(*args) 168 | 169 | def func(self, blk: mblock_t): 170 | global g_Last 171 | 172 | mba: mbl_array_t = blk.mba 173 | mba_maturity = mba.maturity 174 | # 这个优化方式每个层级只执行一次 175 | if g_Last == mba_maturity: 176 | return 0 177 | g_Last = mba_maturity 178 | # 这个优化只在MMAT_GLBOPT1层级执行 179 | if mba_maturity != ida_hexrays.MMAT_GLBOPT1: 180 | return 0 181 | self.get_compare_vars(mba) 182 | return 0 183 | 184 | def get_compare_vars(self, mba: mbl_array_t): 185 | """ 186 | 处理状态变量,通过比较的次数进行判断 187 | """ 188 | print(f"Run the compare_collector") 189 | jzc = compare_collector() 190 | mba.for_all_topinsns(jzc) 191 | 192 | jzc.seen_comparisons.sort(key=lambda x: x.nseen) 193 | 194 | for one in jzc.seen_comparisons: 195 | op = one.op 196 | times = one.nseen 197 | print(f"Compare variable={op.dstr()} times={times}") 198 | 199 | if __name__ == '__main__': 200 | form = ida_kernwin.find_widget("Output window") 201 | ida_kernwin.activate_widget(form, True) 202 | idaapi.process_ui_action("msglist:Clear") 203 | 204 | testPlugin = BlockOptimizerManager() 205 | is_remove = testPlugin.remove() 206 | 207 | if not is_remove: 208 | print(f"testPlugin.remove {is_remove}") 209 | testPlugin.install() 210 | 211 | ``` 212 | 213 | 可以直接在IDA的File -> Script Command 复制黏贴并执行这部分代码,然后在sub\_172BC函数按下F5就能在console中看到输出结果了。 214 | 215 | ``` 216 | Compare variable=%var_91.1 times=1 217 | Compare variable=%var_58.1 times=1 218 | Compare variable=w11.4 times=2 219 | Compare variable=w10.4 times=6 220 | Compare variable=w9.4 times=18 221 | Compare variable=w8.4 times=43 222 | ``` 223 | 224 | 因此,w8.4 w9.1 w10.4 这三个变量的使用此时明显超过了正常的情况,很有可能是状态变量。 225 | 226 | 可以在lucid的输出结果中与汇编进行对比。 227 | 228 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/fan-hun-xiao-ollvm-jing-tai-huan-yuan-1.md: -------------------------------------------------------------------------------- 1 | # 反混淆: OLLVM静态还原-1 2 | 3 | 样例二进制(借用一下OBPO): 4 | 5 | {% embed url="https://github.com/obpo-project/samples/blob/main/arm64/libnative-lib.so" %} 6 | 7 | ## 分发块 8 | 9 | 分发块的特点: 10 | 11 | * 只对状态变量进行比较,不会进行赋值。 12 | * 只进行比较与分发,没有执行真实的逻辑。 13 | 14 | 15 | 16 | 头部分发块的特点: 17 | 18 | * 头部分发块一定有很多的入度,这些入度来自于真实块 19 | * 头部分发块支配之后所有的块 20 | 21 | ## 真实块 22 | 23 | 真实块的特点: 24 | 25 | * 可能含有真实的执行逻辑 26 | * 执行后会对状态变量赋新值(如果不是末尾的返回块)。 27 | 28 | 29 | 30 | ## 块分类的功能实现 31 | 32 | 之前的代码已经能简单的实现状态变量的识别,这一次也一样,我们遍历所有的块+块中的代码,搜索指定的特征进行分类: 33 | 34 | * 默认一个块不存在多种角色的可能性 35 | * 如果有状态变量的比较+跳转,那就是分发块 36 | * 如果有状态变量的赋值,那就是真实块 37 | * 如果都没有,那就认为是无关的块,后面再处理 38 | 39 | 当然,还有另一种识别方法,在MMAT\_PREOPTIMIZED 级别优化后,IDA再每个块提供了的def-undef-use关系,可以直接判断块中的这个关系来进行分类。 40 | 41 | 关键代码有下面两部分,第一部分是通过任意一个mba可以遍历所有的basic-block 42 | 43 | ``` 44 | current_block = mba.get_mblock(0) 45 | i = 0 46 | 47 | # 既然获得了状态变量,通过对于状态变量的操作给块作记号(因为没有精确到block的instruction能力) 48 | all_block_status = {} 49 | 50 | while current_block.nextb != None: 51 | current_block: mblock_t = mba.get_mblock(i) 52 | # print(f"block-visitor: {hex(current_block.start)} - {hex(current_block.end)}") 53 | block_kind = check_block(current_block) 54 | # 通过block的define-use判断block的形态 55 | all_block_status[current_block] = block_kind 56 | i += 1 57 | ``` 58 | 59 | check\_block 函数的通过对于状态变量的def-undef-use关系,对block进行分类 60 | 61 | 注意,为了方便判断,这里写死了在bit-map中作为状态变量伪寄存器的bit号=72,完整的代码参考(todo需要完整的参考代码) 62 | 63 | ``` 64 | def check_block(one_block, reg_bit_value = 72) -> int: 65 | must_use = one_block.mustbuse 66 | may_use = one_block.maybuse 67 | must_def = one_block.mustbdef 68 | may_def = one_block.maybdef 69 | 70 | use_reg = False 71 | define_reg = False 72 | if must_use.reg.has(reg_bit_value) or may_use.reg.has(reg_bit_value): 73 | use_reg = True 74 | 75 | if must_def.reg.has(reg_bit_value) or may_def.reg.has(reg_bit_value): 76 | define_reg = True 77 | 78 | if use_reg and (not define_reg): 79 | # 使用但是不定义,这是分发块 80 | print(f"dispatch-block: {hex(one_block.start)} - {hex(one_block.end)}") 81 | return 2 82 | elif define_reg: 83 | # 定义了,要不是真实块,要不是头部块 84 | print(f"real-block: {hex(one_block.start)} - {hex(one_block.end)}") 85 | return 1 86 | else: 87 | print(f"unknown-block: {hex(one_block.start)} - {hex(one_block.end)}") 88 | return 0 89 | ``` 90 | 91 | 当然,有的block既没有使用(must-use/may-use),也没有定义(define/redefine)状态变量;我们需要仔细的判断OLLVM混淆的具体场景;在这个混淆的技术中,只要区分好分发块和真实块的边界就没问题了。 92 | 93 | ## 真实块中状态变量的值 - 分发块与真实块的分割点 94 | 95 | 96 | 97 | 98 | 99 | ## 真实块中状态变量的值 - 真实块中状态变量的变化 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/fan-hun-xiao-zi-fu-chuan-jia-mi.md: -------------------------------------------------------------------------------- 1 | # 反混淆:字符串加密 2 | 3 | 字符串与常量加密也是常规的混淆方案,很常见。 4 | 5 | 这一章会介绍常见的字符串加密场景,以及静态对抗字符串加密混淆的常用方案**。** 6 | 7 | ## 通用的字符串加密对抗 8 | 9 | 字符串加密有很多种小情况:解密的方式,什么时候解密,解密数据放到哪里,等等 10 | 11 | 总的来说,有这么几种通用的方法: 12 | 13 | * Dump内存:如果解密逻辑都放在初始化中(比如JNI\_Onload 或者 init\_func),程序执行后Dump内存是一种很棒的方法。但是,现在复杂的混淆都会边执行函数边解密,这就使得dump内存的方法不那么好用了;还存在一些其它的情况,比如解密之后把数据加密回去。 14 | * 模拟执行:模拟执行也是很通用的方法,通常用于对抗将字符串解密放到函数开头的场景。 15 | 16 | 17 | 18 | 还有一些小技巧,比如在IDA中,我们可以将\_\_DATA段设置为只读的,这样IDA通过常量计算的能力,在F5中能看到部分的数据。 19 | 20 | ## 独立的字符串解密函数 21 | 22 | 比较早期的字符串加密方案,将字符串全部加密存储后,运行时调用解密函数。 23 | 24 | 分析解密函数的逻辑后,通过XREF找到所有的调用点,分析传入的参数就能很容易的写出解密逻辑。 25 | 26 | 样例: 27 | 28 |
29 | 30 | 如上图,decrypt\_str\_buffer\_1CE30函数将会传入四个参数,进行解密;我们可以简单的编写脚本,进行解密,解密的结果如下: 31 | 32 |
33 | 34 | 下面是我写的一个简单的脚本 35 | 36 | ``` 37 | import flare_emu 38 | import idc 39 | import idaapi 40 | import ida_bytes 41 | import idautils 42 | import keypatch 43 | 44 | 45 | func_addr = 0 46 | wait_string_addr = [] 47 | if func_addr == 0: 48 | func_addr = idaapi.get_screen_ea() 49 | do_function = idaapi.get_func(func_addr) 50 | flowchart = idaapi.FlowChart(do_function) 51 | # 遍历流图中的所有function-call 52 | for block in flowchart: 53 | # 打印当前basic-block的起始与结束地址 54 | # print("Basic-block: 0x%x - 0x%x" % (block.start_ea, block.end_ea)) 55 | for head in idautils.Heads(block.start_ea, block.end_ea): 56 | if not idaapi.is_call_insn(head): 57 | continue 58 | call_name = idc.GetDisasm(head) 59 | if "decrypt_str_buffer_1CE30" not in call_name: 60 | continue 61 | 62 | # 直接模拟获得返回值 63 | eh = flare_emu.EmuHelper() 64 | # 传入待选参数 endAddr,和第一个参数共同标识一段地址区间 65 | eh.emulateRange(block.start_ea, endAddr=head, skipCalls=True) 66 | p0 = eh.getRegVal("X0") 67 | p1 = eh.getRegVal("X1") 68 | p2 = eh.getRegVal("W2") 69 | p3 = eh.getRegVal("W3") 70 | if p0 == 0 or p1 == 0 or p2 ==0 or p3 == 0: 71 | continue 72 | print(f"call at {hex(head)}, x0={hex(p0)}, x1={hex(p1)}, x2={hex(p2)}, x3={hex(p3)}") 73 | #wait_string_addr.append(ret) 74 | before_bytes = idaapi.get_bytes(p1, p3) 75 | result = "" 76 | xor_data = p2 77 | for each in before_bytes: 78 | result = result + chr(xor_data ^ each) 79 | xor_data = xor_data+3 80 | 81 | ida_bytes.patch_bytes(p0 ,result.encode()+ b"\x00") 82 | idc.create_strlit(p0, p0+p3) 83 | 84 | set_type = idc.parse_decl(f"const char[{p3}];",idc.PT_SILENT) 85 | idc.apply_type(p0, set_type) 86 | ``` 87 | 88 | 配合着脚本,解释一些对抗中可能会遇到一些小问题: 89 | 90 | * 如何获得函数传入的参数:静态还原工具不太好直接提取出,为了通用性上面的脚本使用了模拟执行,还有一种做法是向上解析汇编。 91 | * 解密的结果在动态分配的内存:有的时候,解密的结果会放到malloc的内存中,通过一个全局或者局部的指针使用,这种情况解密的结果需要可能要处理内存对象,不太好处理,比较偷懒的办法就是加注释。 92 | 93 | ## 解密逻辑在每个函数头部 94 | 95 | 也是很常见的字符串加密,通常来说会有个原子atomic全局变量包裹,整个逻辑inline在每个函数的头部, 96 | 97 | 案例: 98 | 99 |
100 | 101 | 我通常使用模拟执行对抗。模拟执行头部,监控所有的\_\_data段写入; 102 | 103 | 参考下面我这个通用的代码: 104 | 105 | > [https://github.com/wINfOG/IDA\_Easy\_Life/blob/main/emu\_fast\_string.py](https://github.com/wINfOG/IDA_Easy_Life/blob/main/emu_fast_string.py) 106 | > 107 | > [https://github.com/wINfOG/IDA\_Easy\_Life/blob/main/unicorn\_fast\_string.py](https://github.com/wINfOG/IDA_Easy_Life/blob/main/unicorn_fast_string.py) 108 | 109 | 110 | 111 | ## 解密逻辑在支配树父节点 112 | 113 | 可以说是上面的升级版本,这个方案会使得字符串加密逻辑更加的复杂,更加的细碎。 114 | 115 | 原理是:支配树中的父节点是当前basic-block的必经节点,因此只要将当前使用的字符串解密逻辑放到父节点里面去,就确保解密逻辑一定执行。 116 | 117 | 我没有找到比较合适的案例,因此这部分不再展开了,依照类似的思路应该是能够完成一个较为通用的方案:找到所有的basic-block块 ;判断里面的字符串解密部分;模拟执行或者模式匹配每个basic-block中的字符串解密;把数据填回去。 118 | 119 | 120 | 121 | ## 混合其他混淆的字符串加密 122 | 123 | 还有一个点不得不说,字符串加密通常不单独出现,他只是作为混淆的基坐。 124 | 125 | 最典型的场景是和OLLVM一起出现。这导致逻辑更加的难看懂。 126 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/qian-shui-qu-wan-shua-dong-jing-tai-jie-he-yu-qing-du-hun-xiao-huan-yuan.md: -------------------------------------------------------------------------------- 1 | # 浅水区玩耍:动静态结合与轻度混淆还原 2 | 3 | 在进入令人头疼的深度混淆对抗之前,先找几个软柿子捏一下吧。 4 | 5 | 将先介绍动静态结合的混淆对抗思路; 6 | 7 | 当然,整个书的主要内容集中在静态分析层面的对抗,所以不会太多的介绍各种动态执行/模拟执行的框架,而是着重介绍如何在静态分析工作中比较容易的集成这些工具,毕竟每次重新开个新的工程挺麻烦的。 8 | 9 | 我们将对以下两个简单的场景作为案例。 10 | 11 | * 花指令 12 | * 字符串加密 13 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/shen-shui-qu-re-shen-gao-du-hun-xiao-de-huan-yuan.md: -------------------------------------------------------------------------------- 1 | # 深水区热身:高度混淆的还原 2 | 3 | 如无必要,不要尝试正面还原高度混淆的程序! 4 | 5 | 6 | 7 | 对于变种Ollvm、VMP 等高强度的混淆,通常没有完美的还原方法; 8 | 9 | 首先,我认为有以下几点必须清楚 10 | 11 | * 对高强度混淆的还原,是一系列系统的工作,不会有通用的方案。 12 | * 不同的混淆方案所需要的还原方案都不同,寻找薄弱点来对抗。 13 | * 本质上,这是在和在保护开发者进行对抗,是两方精力和工时的互换。 14 | * 纯静态的还原复杂度高,复用性差;动静态结合才能节约时间。 15 | 16 | 17 | 18 | 19 | 20 | 攻击状态变量并还原真实块的控制流结构 21 | 22 | * 基础Flat 23 | * \+虚假控制流 24 | * \+不透明谓词 25 | * \+状态变量改造 26 | * \+多重状态变量 27 | * \+状态变量混合程序主逻辑 28 | 29 | 30 | 31 | 将VMP中的虚拟指令进行还原 32 | 33 | * 堆栈形态 34 | * 寄存器 35 | * SSA形态 36 | 37 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/vmp0-ru-he-dui-kang-shang-ye-ji-de-vm-bao-hu.md: -------------------------------------------------------------------------------- 1 | # VMP-0:如何对抗商业级的VM保护? 2 | 3 | **提前说清楚:非必要,不建议正面静态还原VMP,耗时耗力。可以通过动态的Trace或者其它的结合方案**。 4 | 5 | VMP指的是将代码在一种“虚拟机”上运行。 6 | 7 | 这是现今反混淆对抗中最难+工作量最为庞大的部分;这部分我也不知道说的对不对;让我来尝试一下进行理论化和流程化吧。 8 | 9 | 10 | 11 | ## 悖论 12 | 13 | * 练习VMP分析,需要VMP分析的经验 --> 没有VMP分析的经验,练习VMP会很难 14 | * 现在保护都是超高强度的商业化VMP,或者强度一般的比赛题目。新开始分析,有一个巨大的台阶要上。 15 | * 编写VMP分析的代码,需要分析VMP的经验,否则写出来的代码必然有缺陷 --> 分析VM需要分析VM的经验 16 | 17 | 那可咋办啊? \ 18 | 那就让我来打破这个循环! 19 | 20 | 21 | 22 | ## 了解正向体系 23 | 24 | 理解 VMP 混淆的对抗策略,需要先探究其基于指令转换的实现原理。 25 | 26 | 从当前典型的开发团队分工和产品形态来看:设备指纹、风控算法、请求签名等功能通常由不同团队协作开发。负责 VMP 安全保护的团队会对外提供编译器插件或应用加固工具。这类工具能在编译阶段,直接对请求签名等核心算法进行混淆加固,并将加固产物无缝集成到最终应用中。 27 | 28 | 市场上也存在直接对二进制进行加固的 VMP 解决方案。然而,若需同时覆盖 Android、iOS、Windows 等多平台,此类二进制加固方案的投入成本过高。相比之下,在源码级别通过编译器插件实施保护是更具可行性的方案。 29 | 30 | 无论采用何种形式,一个集成到编译流程中的 VMP 插件(例如基于 LLVM IR 的方案),必然需要介入编译器的指令选择(Instruction Selection)阶段。这意味着其虚拟机架构必须构建在目标编译器中间表示(如 LLVM IR)的基础之上。因此,其生成的保护代码不可避免地会包含 mov、add 等与常规指令集高度相似的底层操作。 31 | 32 | ## 寻找VM的弱点 33 | 34 | 类似地,逆向分析一个复杂的VM系统时,必须认识到:**重新构建一套完整的、独立的 `编译 -> 链接 -> 装载 -> 执行` 工具链是极其不现实的**。这源于其**庞大的工程规模**和**难以承受的工作量**。 35 | 36 | 因此,VM的开发者必然会做出妥协,省略或简化其中的部分环节。这些省略点,恰恰是逆向工程师需要重点关注的“薄弱环节” 37 | 38 | 这些妥协的薄弱点保留了编译前源码的高层信息,能大大加速我们的分析工作: 39 | 40 | \- 41 | 42 | 43 | 44 | ## 动态trace+调试VM 45 | 46 | 在识别出VMP的潜在弱点之后,对抗的基础工作是**构建动态分析能力**。 47 | 48 | 1. **函数调用逻辑:**划定VMP的保护边界,明确核心函数、关键算法何时以及如何被调用,尤其是识别它们**何时进入VMP环境**执行,何时在原生代码中运行。 49 | 2. **精细化模块级追踪:** 构建监控机制,精确判定程序中的哪些代码模块(函数、代码块)被VMP引擎接管执行,绘制出VMP实际保护的“领地”图谱。 50 | 51 | 52 | 53 | ## 静态+动静态结合 54 | 55 | 纯静态分析 VMP极具挑战性。 VMP 的核心保护机制在于将代码与数据高度混淆。这使得常用的动态调试技术通常只能停留在指令执行层面。而要进行有效的静态分析,则需先理解其自定义的指令集架构、执行上下文等关键信息。因此,实现纯静态分析组件往往是在其他工作都完成了后,才采取的最终步骤。 56 | 57 | ## 完美分析 58 | -------------------------------------------------------------------------------- /My_Reverse_Book/my_reverse_notes/vmp1-chu-shi-gong-zuo-+-xun-zhao-vmp-de-ruo-dian.md: -------------------------------------------------------------------------------- 1 | # VMP-1:初始工作+寻找VMP的弱点 2 | 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 我理想的反编译书籍 2 | 3 | 因为是理想,所以永远不可能达成。 4 | 5 | 既然没有其它人写,那我来写写看。 6 | 7 | todo:施工中,看来还要施工好久;承诺群友争取2025年写完 8 | 9 | # 在线阅读 10 | 11 | https://tommy-3.gitbook.io/my_reverse_book/ 12 | 13 | # 项目目录(画饼) 14 | 15 | ## 第一章 16 | 17 | 人类有什么缺陷? 18 | 19 | 为什么要学习反编译的知识? 20 | 21 | 快速过一遍逆向工程中的反编译技术。 22 | 23 | ## 第二章 24 | 25 | 反编译技术发展历史、通用反编译器架构; 26 | 27 | 关键技术概览和反编译整体流程简述。 28 | 29 | ## 第三章 30 | 31 | 介绍SSA与反编译器,详细说明反编译整体流程,为什么SSA这么重要。 32 | 33 | 从二进制(机器码)→ IR → BasicBlock→ CFG的主要流程与关键技术和难题。 34 | 35 | ## 第四章 36 | 37 | 内容最多的一章,数据流分析与编译中端优化技术合集。 38 | 39 | ## 第五章 40 | 41 | 反编译器的控制流还原技术(struct) 42 | 43 | ## 第六章 44 | 45 | 反编译器的类型系统还原技术(typing) 46 | 47 | ## 第七章 48 | 49 | 现代反编译技术+查漏补缺 50 | 51 | ## 第八章 52 | 53 | 第八章为案例分析章节,案例分析章节将分析真实世界中的反编译器是如何实现的,他们的架构啥样,有什么缺陷。 54 | 55 | ## 第九章 56 | 57 | 案例分析一:jadx反编译器架构、关键技术、案例讲解 58 | 59 | ## 第十章 60 | 61 | 案例分析二:IDA反编译器架构、关键技术、案例讲解 62 | 63 | 以及 64 | 65 | 案例分析三:Ghidra反编译器架构、关键技术、案例讲解 66 | 67 | ## 理论补充章节A 68 | 69 | 从199?年到20??年间,反编译技术发展中,学术界关键论文的阅读笔记。 70 | 71 | # 实践章节 72 | 73 | 实践章节的目标是使用已有的成熟反编译码框架,对抗越来越艰难的外部环境。 74 | 75 | ## 动静态结合 76 | 77 | 介绍动静态结合的常用方法,能大大加速逆向效率 78 | 79 | ## 对抗轻度混淆 80 | 81 | 介绍对抗间接跳转,花指令,字符串加密等,比较简单混淆的快速批量还原方法。 82 | 83 | ## IDA+mircocode 84 | 85 | 在IDA中编写自己的mircocode中间语言优化插件;学习编写去除Ollvm-flat的Demo。 86 | 87 | ## Ghira+Sleigh 88 | 89 | 使用Ghidra和它的sleigh框架支持任意指令集的反编译+F5能力,可以拿一些vmp来练手。 90 | 91 | ## Binary-Ninja 92 | 93 | //todo 94 | --------------------------------------------------------------------------------