[{"categories":null,"content":"HITCON_2023 Tải file tại đây. LessEQualmore Bài này dựa trên bài tham khảo của brosu (người đã hoàn thành trong giải)\nTrước khi tìm hiểu cách giải bài này ta sẽ cùng tham khảo qua bài viết tại đây. Bài này đầu tiên khi debug thật sự cũng chả thấy gì, ngoài thấy chỉ load nội dung ở file chal.txt vào (nhưng có lẽ sẽ rất khó thấy quy luật bởi có lúc thì nó sẽ nhảy tới 3 có lúc thì nhảy tới 6) và sau đó nhập vào để check flag. Sau khi một hồi ngồi decompile lại toàn bộ nội dung của chương trình, thì sẽ thấy được đây chính là một bài vm dùng để check flag.\nKhông hiểu sao lần này IDA trả về mã giả khá ngu. Đây chính là nội dung mà IDA trả về:\nNếu để ý kỹ hơn thì ta sẽ so sánh với ASM như sau:\nTa thấy được ở ASM chỉ với hai biến được sử dụng đó là v1 và re nhưng ở mã giả trả về lại tới 3. ¯_(ツ)_/¯\nVì đoạn mã ASM này cũng khá ngắn nên mình đã quyết định viết lại nó và đây là toàn bộ đoạn mã của chương trình:\nmem : list[int] = [] def is_negative(a: int) -\u0026gt; bool: return a \u0026lt; 0 def getchar() -\u0026gt; str: ip = input(\u0026#34;Enter char: \u0026#34;) if len(ip) == 0: return \u0026#39;\\n\u0026#39; else: return ip[0] def get_bignum() -\u0026gt; int: v1 : str ret : int v1 = getchar() if v1 == \u0026#39;%\u0026#39;: v1 = getchar() if v1 == \u0026#39;x\u0026#39;: ret = 0 v1 = getchar() while v1 != \u0026#39;\\n\u0026#39;: ret *= 16 if ord(v1) \u0026lt;= 64 or ord(v1) \u0026gt; 70: if ord(v1) \u0026lt;= 96 or ord(v1) \u0026gt; 102: ret += ord(v1) - 48 else: ret += ord(v1) - 87 else: ret += ord(v1) - 55 v1 = getchar() else: if ord(v1) \u0026lt;= 0x78: if v1 == \u0026#39;%\u0026#39;: ret = ord(v1) return -ret if ord(v1) \u0026gt;= 0x25 and ord(v1) - 48 \u0026lt;= 9: ret = ord(v1) - 48 v1 = getchar() while v1 != \u0026#39;\\n\u0026#39;: ret = 10 * ret + ord(v1) - 48 v1 = getchar() return -ret else: return - ord(v1) else: return -ord(v1) else: return -ord(v1) def bignum_sub(a1: int, a2: int) -\u0026gt; int: return a2 - a1 def op1(a1: int, a2: int): bignum : int tmp1 = is_negative(a1) tmp2 = is_negative(a2) if tmp1: bignum = get_bignum() else: bignum = mem[a1] if tmp2: print(chr(bignum), end=\u0026#39;\u0026#39;) else: mem[a2] = bignum_sub(bignum, mem[a2]) def runProg(): ins = 0 while ins \u0026gt;= 0: op1(mem[ins], mem[ins +1]) if mem[mem[ins +1]] \u0026lt;= 0: ins = mem[ins + 2] else: ins += 3 def readProg(filename : str): with open(filename, \u0026#34;r\u0026#34;) as file: for line in file.readlines(): mem.extend([int(a) for a in line.strip(\u0026#34;\\n \u0026#34;).split(\u0026#34; \u0026#34;)]) def main(): readProg(\u0026#34;chal.txt\u0026#34;) runProg() if __name__ == \u0026#34;__main__\u0026#34;: main() Toàn bộ hàm trên mình đã decompile bằng IDA chỉ có readProg là tham khảo thêm của brosu (vì dài quá nên mình lười :)) )\nCó thể kiểm tra double check với debug. Qua đó ta sẽ thấy rõ hơn đây chính là vm và nó sẽ check từng chữ. Nhưng check bằng cách nào ??? Ta sẽ tiến hành đào sâu vào nó.\nĐầu tiên với getchar ta sẽ thử đổi lại lưu toàn bộ input vào thay vì chỉ lưu từng ký tự:\nstrBuf : str def getchar() -\u0026gt; str: global strBuf if len(strBuf) == 0: ip = \u0026#39;\\n\u0026#39; else: ip = strBuf[0] strBuf = strBuf[1:] return ip VM có đặc tính đó chính là chạy những ins để setup và sau đó sẽ thao tác với đầu vào và sau cùng là so sánh trên những ins đó, đặc biệt mỗi lần chạy và setup như thế thì nó chỉ thao tác với đúng một ký tự trong flag, và nếu chỉ 1 chữ sai thì sẽ trả về toàn bộ sai, đôi khi cũng có những chương trình không cần detect 1 chữ sai thì sẽ trả về sai mà nó vẫn sẽ lưu lại đó và thao tác tiếp những ký tự tiếp theo nhưng khi thao tác ký tự mới thì nó sẽ load lại những ins.\nBây giờ, ta sẽ kiểm tra xem nó đang thực sự làm gì với đầu vào, ta sẽ đặt cnt tại hàm runProg() để đếm xem số lần chạy những ins cho những lần ký tự được nhập vào. Có vẻ như chương trình không phải là loại nếu gặp 1 ký tự sai thì thoát ngay, nên mình sẽ tận dụng điều này để nhập mỗi lần một tăng dần cho chương trình. Nó cũng giống như cách mình brute, ví dụ nó sẽ dựa vào những chữ đúng phía trước rồi sẽ brute ký tự tiếp theo, còn đối với bài này thì tương tự như thế nhưng bây giờ sẽ ví dụ như mình vẫn chưa biết được các ký tự đầu tiên là gì nên mình sẽ nhập lần lượt a, aa, aaa,... :\ninList : list[int] = [] def plotgrap(inList: list[int]): plt.scatter([i for i in range(len(inList))], inList) plt.show() def main(): global strBuf global inList for i in range(0,100): strBuf = \u0026#34;a\u0026#34;*i readProg(\u0026#34;chal.txt\u0026#34;) inList.append(runProg()) print(strBuf) plotgrap(inList) Ta sẽ nhận được một đồ thị như hình trên, với trục hoành là số ký tự nhập vào, còn trục tung là số ins sau tất cả lần chạy với những ký tự nhập vào.\nThấy được rằng 7 chunk đầu tiên mỗi chunk gồm 8 ký tự được phân chia khá rõ ràng.\nVì đã biết được đầu flag có dạng hitcon{ nhưng dạng này chỉ có 7 ký tự, nhưng trên hình mình đã đoán nó phải 8 ký tự nên sẽ brute ký tự còn lại. Cách brute được thực hiện như sau:\ninList : list[int] = [] charList = [] def plotgrap(charList, inList: list[int]): plt.scatter(charList, inList) plt.show() def main(): global strBuf global inList global charList for ch in printable: charList.append(ch) strBuf = \u0026#34;hitcon{\u0026#34; + ch readProg(\u0026#34;chal.txt\u0026#34;) inList.append(runProg()) print(strBuf) plotgrap(charList, inList) Để ý thấy được số lượng ins tăng đột biến ở r nên khả năng cao 8 bytes đầu của flag là hitcon{r\nTa sẽ thử lại một lần nữa và đây chính là kết quả:\nThấy được lúc đầu 7 chunk, lúc sau chỉ còn 6 chunk vậy giả thiết ban đầu đã đúng. Chương trình được chia làm 8 chunk mỗi chunk có 8 bytes để so sánh. (vì rất có thể chunk cuối cùng không có sự phân chia vì nó đã sai hoàn toàn nên nó sẽ dính liền với những ký tự rác phía sau)\nVậy câu hỏi bây giờ là có brute được không ?? Có thể là không bởi brute một lần 8 byte mà tới 256 ký tự, =)))\nVì đã viết ra được chương trình, nên ta sẽ xem trong số các ins load vào để thực hiện vm đó thì đâu là nơi chúng lưu những byte đầu tiên của input. Vì như đã nói ban đầu ins không chỉ có thao tác và kiểm tra mà nó còn thêm cả setup nên không đoán được chúng sẽ lưu ở đâu.\nVì mem chỉ thao tác ở hàm runProg() nên ta sẽ đào sâu việc tìm mem ở đây:\ndef runProg(): cnt = 0 ins = 0 while ins \u0026gt;= 0: cnt += 1 op1(mem[ins], mem[ins +1]) if mem[mem[ins +1]] \u0026lt;= 0: ins = mem[ins + 2] else: ins += 3 if cnt % 1500 == 0: for i in range(len(mem)): if mem[i:i+8] == [104, 105, 116, 99, 111, 110, 123, 114]: print(f\u0026#34;idx: {i}\u0026#34;) return cnt # output: 16 Như vậy đã xác định được vị trí đầu tiên lưu đầu vào nhập vào chính là mem[16]\nCó được vị trí đầu vào tiếp đến, ta sẽ xem xem sau khi chạy xong vm thì đầu ra của ta sẽ nhận được những gì. Vì như đã nói ban đầu bài này phải nhập hết rồi nó sẽ kiểm tra từng từ nên mình sẽ giả dụ như đã nhập đủ flag và xem kết quả được in ra:\ndef main(): global strBuf global inList global charList strBuf = \u0026#39;hitcon{r\u0026#39; + \u0026#39;aaaaaaaa\u0026#39; + \u0026#39;aaaaaaaa\u0026#39; + \u0026#39;aaaaaaaa\u0026#39; + \u0026#39;aaaaaaaa\u0026#39; + \u0026#39;aaaaaaaa\u0026#39; + \u0026#39;aaaaaaaa\u0026#39; + \u0026#39;aaaaaaaa\u0026#39; tmp = strBuf readProg(\u0026#34;chal.txt\u0026#34;) runProg() for i in range(16, len(tmp),8): print(mem[i:i+8], end=\u0026#39; \u0026#39;) # output: [16774200, 1411, 16776275, 3646, 1532, 6451, 2510, 16777141] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] Tương tự như lúc debug, ta sẽ thử thay đổi 1 ký tự trong chuỗi aaaaaaaa thành aaaaaaab để xem nó thành gì. Và đây là kết quả:\n[16774200, 1411, 16776275, 3646, 1532, 6451, 2510, 16777141] [16774493, 1161, 16776349, 3305, 1265, 5634, 2341, 15] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] [16774500, 1164, 16776343, 3298, 1261, 5626, 2328, 0] Thấy rằng từ hàng thứ 2 và những hàng sau có sự thay đổi khi thay giá trị. Cụ thể nó đã thay đổi một lượng -7\t-3\t6\t7\t4\t8\t13\t15, những dãy dưới tương tự vì cùng aaaaaaaa không thay đổi. Làm tương tự cho các index còn lại của aaaaaaaa ví dụ tiếp đến thành aaaaaaba ,\u0026hellip; ta sẽ thu được bảng như sau:\n-7\t-3\t6\t7\t4\t8\t13\t15 -2\t11\t-2\t2\t9\t20\t-6\t-11 -13\t-10\t-5\t17\t-8\t1\t22\t15 4 6 4 -6 6 5 -8 -5 -4 5 -3 5 3 13 1 -6 3 2 -3 -3 -1 -2 -6 -8 -2 3 -4 3 2 7 -1 -5 -7 -2 -2 9 -2 6 9 5 Vậy tổng quan của chương trình đó là input -\u0026gt; 8 chunk -\u0026gt; biến đổi qua những hằng số nhất định -\u0026gt; so sánh với enc\nNhư bài viết đã tham khảo ban đầu 8 chunk chúng biến đổi qua những hằng số và có kết quả mới, thì cũng tương tự như việc nhân ma trận AX với A là ma trận chứa -7,-3,\u0026hellip; còn X là ma trận chứa 8 chunk. Việc cuối cùng ta chỉ cần giải phương trình AX=B là hoàn tất\nĐây là script giải của bài này:\nfrom sage.all import* A = [[-7, -2, -2, 9, -2, 6, 9, 5], [-2, 3, -4, 3, 2, 7, -1, -5], [3, 2, -3, -3, -1, -2, -6, -8], [-4, 5, -3, 5, 3, 13, 1, -6], [4, 6, 4, -6, 6, 5, -8, -5], [-13, -10, -5, 17, -8, 1, 22, 15], [-2, 11, -2, 2, 9, 20, -6, -11], [-7, -3, 6, 7, 4, 8, 13, 15]] x = Matrix(Zmod(2**24),A) y = x.inverse() # print(y) chal = [[16774200, 1411, 16776275, 3646, 1532, 6451, 2510, 16777141], [16775256, 2061, 16776706, 2260, 2107, 6124, 878, 16776140], [16775299, 1374, 16776956, 2212, 1577, 4993, 1351, 16777040], [16774665, 1498, 16776379, 3062, 1593, 5966, 1924, 16776815], [16774318, 851, 16775763, 3663, 711, 5193, 2591, 16777069], [16774005, 1189, 16776283, 3892, 1372, 6362, 2910, 307], [16775169, 1031, 16776798, 2426, 1171, 4570, 1728, 33], [16775201, 819, 16776898, 2370, 1132, 4255, 1900, 347]] for tar in chal: target = vector(tar) decr = vector(target*y) print(decr) CrazyArcade Đây là một bài chạy với file .exe. Ta sẽ mở phân tích nó bằng IDA\nĐầu tiên với hàm main, hầu như đã có tất cả symbol nên việc nhìn thấy công dụng của chúng khá dễ dàng, chỉ phát hiện duy nhất một hàm này:\nTiến hành đào sâu vào bên trong hàm này:\nCó thể thấy được sơ lược qua, đầu tiên nó sẽ cop một file có nội dung là CrazyArcade.sys tiếp đến nó load số lượng ký tự vào buffer rồi tiếp đến nó đang bảo phải chạy dưới quyền admin. Tiếp đến một hDevice được tạo, ta đã biết được thường các hDevice tạo ra nhằm mục đích để chuyển hướng chương trình đang chạy đến một chương trình khác.\nTiếp đến, chương trình load một lượng dữ liệu vào buffer, ta có thể nhận biết được bằng symbol DeviceIoControl , sau cùng thì chương trình load những hiệu ứng của trò chơi vào và chạy chương trình.\nDebug chương trình này, bắt gặp được đoạn code để chạy con game nằm ở đây:\nBên trong hàm này, nếu debug hoàn toàn thì chỉ có thể thấy được dường như nó chỉ chạy chương trình, và chả có gì đặc biệt ở đây.\nNhưng ta sẽ thử tham khảo thử DeviceIoControl nó được lấy từ đâu, bởi vì nếu chơi chiến thắng game có flag thì có lẽ weight đã không tới 100 =))) và hơn thế nữa tại sao lại chỉ trò chơi, nhưng điều khiển chương trình thực thi một chương trình khác làm gì ??\nĐây là kết quả nhận lại được:\nCó thể thấy chỉ có 2 hàm duy nhất của chương trình chứa DeviceIoControl đó là hàm đang phân tích và một hàm nữa có tên sub_7FF6930B30A0 (vì chương trình đã debug nên nó đã cộng thêm imagebase nhưng có thể check bằng 4 số cuối)\nKhi vào hàm này và đây là kết quả:\nỞ đây, hình như đang thực hiện xor một thứ gì đó, static đối với những người lâu năm thì có lẽ khá dễ dàng nhận ra, nhưng với mình mình sẽ debug đoạn này để xem nó đang xor cái gì ở đây.\nĐầu tiên nó đang load một dữ liệu từ đâu đó\nTiếp đến nó load dữ liệu được lấy từ đoạn phân tích với DeviceIoControl ở trên, sau cùng nó tiến hành 2 lần xor. Một lần xor với r8d và một lần xor với ecx.\nỞ đây mình đã cố tình patch lại để cho có thể nhảy trực tiếp vào đoạn xor này, có thể nói là không theo dự định của tác giả, vì sau nhiều lần debug kể cả có kill được những con bot hay thua hay di chuyển trong game thì mình vẫn ko thể debug theo ý muốn được, từng bước di chuyển hay khi giết hoặc chết mặc dù đang debug nhưng IDA vẫn không detect được. Ý định ban đầu muốn thử xem các sự kiện trong game sau mỗi lần thao tác có dừng lại để chạy asm được hay không ?? Nhưng không thể.\nVậy công việc cuối cùng bây giờ ta phải xác định được dữ liệu khi load vào ecx được lấy từ đâu ? Nếu nhìn kĩ lại chương trình này nó còn gọi một chương trình khác có cùng tên nhưng file là .sys. Ta sẽ mở file này lên xem có gì ở đây.\nFile này có rất nhiều hàm nhưng đa số được sử dụng hàm sub_11450. Nếu đã từng làm ở giải SEKAI, thì ở giải đó chính là phân tích thẳng kernel, đây cũng tương tự, nó đang tạo ra một drive để thực hiện một số điều gì đó, ta kiểm tra hàm được sử dụng nhiều nhất và bump:\nThật là quen :))) vậy tất cả đã rõ, bây giờ chỉ còn việc viết script để hoàn tất:\nwith open(\u0026#39;CrazyArcade.sys\u0026#39;, \u0026#39;rb\u0026#39;) as file: bytes = file.read() index = bytes.find(b\u0026#39;\\x48\\x89\\x54\\x24\\x10\u0026#39;) print(hex(index)) flag = bytearray(b\u0026#39;\\xb7\\x8a\\x19\\x7fT-\\x81\\xf0\\xb8\\xdd\\xca\\xc9\\xd3\\xc3#2\\xbaA\\x81\\xab\\x02S\\xc9.\\xd6~ \\xad\\xab\\xed\\x95\\xd2\\xb6\\xe7*\\x92\u0026gt;\u0026#39;) sth = open(\u0026#34;./CrazyArcade.sys\u0026#34;, \u0026#34;rb\u0026#34;).read() sth = sth[0x850:] r8d = 0 for _ in range(len(sth)): for i in range(37): flag[i] ^= (r8d ^ sth[r8d % 0x584])\u0026amp;0xff r8d += 1 if b\u0026#39;hitcon{\u0026#39; in flag: print(flag) exit(0) The-blade (Rust) Đây là một chương trình được viết bằng rust - một ngôn ngữ khá trầm cảm khi rev. Nếu cho mình xếp độ khó thì mình chọn bài này khó nhất và hay nhất, bởi mình đã mất rất nhiều ngày để hoàn thành được nó, mặc dù sau giải :(((\nKhông như những bài khác, đối với bài này, mình không làm tà đạo bằng cách mở thẳng IDA và chạy ngay, thay vào đó sẽ mở xem thử chương trình chạy thế nào. Bởi có mở IDA liền thì cũng chả xác định được gì ngoài một núi hàm :))\nĐây là những gì ta nhận được khi bắt đầu chạy chương trình:\nTiếp đến, ta sẽ thử hết những lệnh ở đây, thấy được chỉ có mỗi server là thứ có vẻ ta nên tập trung vào.\nKhi chạy server ta sẽ nhận được kết nối đến client(ở localhost).\nỞ đây, thấy được rằng có một đoạn shellcode được hiện ra ở server, ngoài ra khi có được shell thì ta chẳng thấy được điều gì khác nữa. Vậy câu hỏi đặt ra đó là flag ở đâu ?? làm gì để check flag khi có shell ??\nThử tất cả các cách từ debug đến dò từng hàm, vẫn không thấy bất cứ điều gì, sau cùng mình chỉ còn cách nghĩ đến strings nguyên file này thử xem có flag hay không. Điều này có vẻ khá ngớ ngẫn đối với các giải lớn nhưng vẫn cứ thử thôi. Và đây là thứ mình đã có được:\nMở IDA kiểm tra xem có gì ở đó không. Cứ theo hàm main ta sẽ thấy được một hàm như sau:\nTại sao ở đây có rất nhiều hàm nhưng mình chỉ focus vào cái này? Đó là bởi nếu đọc kỹ comment ta sẽ phần nào đoán được đoạn code này đang promt gì đó với shell, còn các hàm kia chỉ promt, print, display, color, \u0026hellip; trên server nên Không có gì đáng để quan tâm các hàm này.\nTiếp tục, cứ xem thử từng hàm thì ta sẽ thấy được một hàm rất khả nghi có tên verify. Nó khả nghi là bởi tất cả các hàm khác chỉ convert, up file, netcat,\u0026hellip;(có thể để ý thêm tất cả các này đều hiển thị khi netcat thành công). Và hàm này có thể đoán được nó đang check flag, bởi một phần vì tên của nó, một phần vì nó không liên quan gì tới những thứ hiển thị khi ta có được shell.\nĐây chính là hàm checkflag:\nKết hợp với dò từng hàm cũng như nhận thấy hàm checkflag, ta có thể rút ra được vài kết quả như sau: Chương trình chạy server bằng server -\u0026gt; run -\u0026gt; nc, sau đó sẽ check flag bằng lệnh flag hitcon{...}(với chiều dài flag là 64).\nSau khi xác định được tất cả những thứ đó, bây giờ ta sẽ tập trung vào hàm check này.\nNhìn vào đoạn code, có rất nhiều do while ở đây, theo mình đã tiếp xúc, đối với những đoạn như này nếu thuật toán ngắn (đoạn code ngắn) như bài ở DucCTF(transform) thì mình có thể dịch lại và code lại để có thể dễ debug. Còn nếu đoạn code dài như ở bài này thì ta có thể đoán như bài ở giải SEKAI(Sahuang flag checker), đó là mỗi do while nó chia flag ra thành từng phần để tính toán và thao tác.\nNhư đã nói ở trên, ta sẽ đi từng phần. Đầu tiên, thấy được rất nhiều đoạn code khá giống nhau. Chúng thực hiện memcpy và sau đó swap một thứ gì đó. Để xem hoàn toàn nó thực sự đang làm gì ở đây mình sẽ tiến hành debug, với 2 breakpoint được đặt tại đầu và cuối cùng của đoạn code giống nhau này.\nĐầu tiên ta thấy được đoạn code sau:\nỞ đây đoạn ASM này đang tiến hành load input vào và sau đó tiến hành thao tác trên input.\nSau khi chạy xong đoạn memcpy và đến do while tiếp theo thì đây là kết quả đã thu được:\nTa cùng nhau nhìn lại một xíu về đoạn này. Với đầu vào được đưa vào đó là flag abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/ và đầu ra Rp5v+AZmM8XWy1sgNhTB/oCzYVdPrGn6KD3Q9lke4qtFxHb0uUOcS2jIEJfL7aiw. Đầu vào và đầu ra đều có chiều dài như nhau, ta lại thử tiếp một lần với input toàn là a và đây là kết quả:\nTiếp tục với abcd, ta cũng có được output như sau:\nSở dĩ có thêm dấu +/ là bởi có một hàm memcpy(dest, \u0026quot;/\u0026quot;, 0x200uLL); mình cũng không thực sự nó có tác dụng gì nên mình thêm vào luôn input.\nVậy từ 3 kết quả trên ta thấy được input[i] đầu vào sẽ được map tương ứng với input[j] . Nên bây giờ ta sẽ lấy kết quả đầu tiên vì kết quả đầu tiên bao gồm a...zA...Z0...9+\\ hầu như đã có gần hết các ký tự có thể in được. Ta sẽ làm lại đoạn code map do while như sau:\nip = \u0026#34;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/\u0026#34; resmap = \u0026#34;Rp5v+AZmM8XWy1sgNhTB/oCzYVdPrGn6KD3Q9lke4qtFxHb0uUOcS2jIEJfL7aiw\u0026#34; mp = [0]*64 for idx, ch in enumerate(ip): mp[idx] = resmap.index(ch) Đoạn code trên đã làm lại đoạn map trong chương trình, có vẻ như nó giống với hoán vị hơn bởi mỗi vị trí ouput tương ứng chính là vị trí của đầu vào đã có.\nĐể rev lại đoạn code này khá dễ chỉ cần làm ngược lại là xong.\nTiếp đến đoạn tiếp theo:\nĐoạn code này, trong quá trình debug có thể thấy được từ kết quả hoán vị của phần trên thì với mỗi ký tự sau khi hoán vị nó được map thành một output nhất định tương ứng. Lúc đầu nhìn thấy chỉ xor và cộng trừ nên mình đã thử viết lại nhưng đoạn do ở trên mình chưa nhìn được nó là gì nên mình chả viết lại để rev được.\nKhi không hiểu rõ thuật toán chương trình mà cũng không thể code lại được thuật toán đó thì mình chỉ còn cách chạy thẳng chương trình lưu kết quả trước và sau khi thực hiện.\nĐối với lần đầu tiên thì chương trình lấy kết quả đã hoán vị của input để map:\nLần tiếp theo chạy nó lại tiếp tục load những bytes đã set sẵn để map:\nSau nhiều lần chạy với những input khác nhau, ta để ý mỗi ký tự sẽ được map với 1 số nhất định dù cho input có khác nhau đi nữa. Vậy bây giờ công việc của mình phải xem hết 256 bytes thì với mỗi bytes thì thuật toán map tương ứng với giá trị nào. Và đây là kết quả:\nvalmap = [0x52, 0x70, 0x35, 0x76, 0x2B, 0x41, 0x5A, 0x6D, 0x4D, 0x38, 0x58, 0x57, 0x79, 0x31, 0x73, 0x67, 0x4E, 0x68, 0x54, 0x42, 0x2F, 0x6F, 0x43, 0x7A, 0x59, 0x56, 0x64, 0x50, 0x72, 0x47, 0x6E, 0x36, 0x4B, 0x44, 0x33, 0x51, 0x39, 0x6C, 0x6B, 0x65, 0x34, 0x71, 0x74, 0x46, 0x78, 0x48, 0x62, 0x30, 0x75, 0x55, 0x4F, 0x63, 0x53, 0x32, 0x6A, 0x49, 0x45, 0x4A, 0x66, 0x4C, 0x37, 0x61, 0x69, 0x77, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x1C, 0x1D, 0x1E, 0x1F, 0x18, 0x19, 0x1A, 0x1B, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x2D, 0x2E, 0x3A, 0x3B, 0x28, 0x29, 0x2A, 0x2C, 0x40, 0x5B, 0x5C, 0x5D, 0x3C, 0x3D, 0x3E, 0x3F, 0x7C, 0x7D, 0x7E, 0x7F, 0x5E, 0x5F, 0x60, 0x7B, 0x84, 0x85, 0x86, 0x87, 0x80, 0x81, 0x82, 0x83, 0x8C, 0x8D, 0x8E, 0x8F, 0x88, 0x89, 0x8A, 0x8B, 0x94, 0x95, 0x96,0x97, 0x90, 0x91, 0x92, 0x93, 0x9C, 0x9D, 0x9E, 0x9F, 0x98, 0x99, 0x9A, 0x9B, 0xA4, 0xA5, 0xA6, 0xA7, 0xA0, 0xA1, 0xA2, 0xA3, 0xAC, 0xAD, 0xAE, 0xAF, 0xA8, 0xA9, 0xAA, 0xAB, 0xB4, 0xB5, 0xB6, 0xB7, 0xB0, 0xB1, 0xB2, 0xB3, 0xBC, 0xBD, 0xBE, 0xBF, 0xB8, 0xB9, 0xBA, 0xBB, 0xC4, 0xC5, 0xC6, 0xC7, 0xC0, 0xC1, 0xC2, 0xC3, 0xCC, 0xCD, 0xCE, 0xCF, 0xC8, 0xC9, 0xCA, 0xCB, 0xD4, 0xD5, 0xD6, 0xD7, 0xD0, 0xD1, 0xD2, 0xD3, 0xDC, 0xDD, 0xDE, 0xDF, 0xD8, 0xD9, 0xDA, 0xDB, 0xE4, 0xE5, 0xE6, 0xE7, 0xE0, 0xE1, 0xE2, 0xE3, 0xEC, 0xED, 0xEE, 0xEF, 0xE8, 0xE9, 0xEA, 0xEB, 0xF4, 0xF5, 0xF6, 0xF7, 0xF0, 0xF1, 0xF2, 0xF3, 0xFC, 0xFD, 0xFE, 0xFF, 0xF8, 0xF9, 0xFA, 0xFB] resmap = [0x58, 0x6C, 0x61, 0x2E, 0x69, 0x32, 0xCB, 0xE2, 0xB3, 0xE0, 0x02, 0xA0, 0x86, 0x1C, 0x6B, 0xC1, 0xEC, 0x9C, 0x79, 0xD2, 0x9E, 0xC2, 0xD9, 0x74, 0x0C, 0x3B, 0x04, 0x9F, 0x1E, 0x03, 0x14, 0xED, 0xA2, 0x8F, 0x97, 0xCA, 0xDA, 0xD8, 0xA4, 0x39, 0x5B, 0x64, 0x7E, 0xAF, 0x0B, 0x93, 0x71, 0x0F, 0x99, 0xFD, 0x81, 0x0A, 0xB7, 0x66, 0xEF, 0x3A, 0xEE, 0x00, 0xFF, 0xE1, 0xAD, 0x75, 0xAB, 0x09, 0x51, 0x15, 0x8D, 0xDB, 0xFB, 0x7B, 0x4E, 0xBB, 0xAA, 0xB2, 0x60, 0xEB, 0xB0, 0xAC, 0xA5, 0x8E, 0x2B, 0xC6, 0xA6, 0x35, 0x63, 0x5C, 0xDE, 0x42, 0xBD, 0x24, 0xB1, 0xE3, 0x30, 0x43, 0xD6, 0x5F, 0x7C, 0x6D, 0x8B, 0x17, 0x8C, 0xA7, 0xD5, 0x2A, 0x59, 0xA9, 0x27, 0x06, 0x9D, 0x83, 0xFE, 0x10, 0x41, 0xA8, 0x80, 0xC0, 0x25, 0xDC, 0x5E, 0xE7, 0xC4, 0x2D, 0x4F, 0xF9, 0x16, 0x4D, 0x2F, 0x6A, 0x89, 0x6F, 0x5D, 0xE8, 0xFA, 0x94, 0xB6, 0x1F, 0x88, 0xC5, 0x7F, 0x77, 0xEA, 0xB5, 0x5A, 0x65, 0x3F, 0xF4, 0x48,0x47, 0x11, 0xCF, 0xF1, 0x1B, 0xE9, 0x62, 0x6E, 0xB4, 0x12, 0xE4, 0xBA, 0xDF, 0x4B, 0x28, 0xD7, 0xD1, 0x96, 0xCD, 0x13, 0x53, 0x2C, 0x9B, 0x29, 0x44, 0x33, 0xB8, 0xE6, 0x7A, 0x31, 0xD3, 0xB9, 0x40, 0x52, 0xF7, 0x20, 0xF2, 0x1A, 0x01, 0xA1, 0x92, 0xD0, 0x34, 0xF5, 0x54, 0xDD, 0xBC, 0x19, 0xF3, 0xFC, 0x85, 0x07, 0xBE, 0x4C, 0x7D, 0xC7, 0xD4, 0x36, 0xF6, 0x72, 0x98, 0x8A, 0xE5, 0x50, 0x46, 0x45, 0x4A, 0x9A, 0xC3, 0xC9, 0x0E, 0x3C, 0x57, 0xCC, 0x68, 0x76, 0x67, 0x84, 0x0D, 0x90, 0xA3, 0xF0, 0x22, 0xBF, 0x26, 0x91, 0x05, 0x87, 0x70, 0xAE, 0x3D, 0x1D, 0xC8, 0x55, 0x3E, 0x37, 0x23, 0x08, 0x73, 0x21, 0x49, 0x38, 0x95, 0x78, 0xF8, 0x18, 0x56, 0xCE, 0x82] mp = [0]*256 rmp = [0]*256 for a,b in zip(valmap, resmap): mp[a] = b Ở đây, ta sẽ lợi dụng IDA để làm việc này, ta thấy được nội dung được lưu ở heap nên ta sẽ vào thẳng heap để chỉnh lại từng bytes sau đó lưu lại và chạy tiếp chương trình thì sẽ thu được kết quả tương ứng\nSau khi xong được 2 phần trên, sau cùng là đoạn mình cho là hay nhất trong bài.\nCuối cùng là đoạn shellcode:\nSở dĩ tại sao nhận ra được đây là đoạn shellcode là bởi debug thì ở client nhận được một đoạn bytes, ta chỉ việc lấy những bytes đó đưa về shellcode. Và đây là đoạn code thực hiện điều này:\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; unsigned char shellcode[] = {0x54, 0x5d, 0x31, 0xf6, 0x48, 0xb9, 0xa1, 0x57, 0x06, 0xb8, 0x62, 0x3a, 0x9f, 0x37, 0x48, 0xba, 0x8e, 0x35, 0x6f, 0xd6, 0x4d, 0x49, 0xf7, 0x37, 0x48, 0x31, 0xd1, 0x51, 0x54, 0x5f, 0x6a, 0x02, 0x58, 0x99, 0x0f, 0x05, 0x48, 0x97, 0x31, 0xc0, 0x50, 0x54, 0x5e, 0x6a, 0x04, 0x5a, 0x0f, 0x05, 0x41, 0x5c, 0x6a, 0x03, 0x58, 0x0f, 0x05, 0x31, 0xf6, 0x48, 0xb9, 0x3b, 0x3b, 0x6f, 0xc3, 0x63, 0x64, 0xc0, 0xaa, 0x48, 0xba, 0x48, 0x4c, 0x0b, 0xc3, 0x63, 0x64, 0xc0, 0xaa, 0x48, 0x31, 0xd1, 0x51, 0x48, 0xb9, 0x8c, 0x57, 0x82, 0x75, 0xd6, 0xf8, 0xa9, 0x7d, 0x48, 0xba, 0xa3, 0x32, 0xf6, 0x16, 0xf9, 0x88, 0xc8, 0x0e, 0x48, 0x31, 0xd1, 0x51, 0x54, 0x5f, 0x6a, 0x02, 0x58, 0x99, 0x0f, 0x05, 0x48, 0x97, 0x31, 0xc0, 0x50, 0x54, 0x5e, 0x6a, 0x04, 0x5a, 0x0f, 0x05, 0x41, 0x5d, 0x6a, 0x03, 0x58, 0x0f, 0x05, 0x31, 0xf6, 0x6a, 0x6f, 0x48, 0xb9, 0x59, 0xe5, 0x06, 0x0c, 0x2d, 0xf6, 0xd9, 0x77, 0x48, 0xba, 0x76, 0x81, 0x63, 0x7a, 0x02, 0x8c, 0xbc, 0x05, 0x48, 0x31, 0xd1, 0x51, 0x54, 0x5f, 0x6a, 0x02, 0x58, 0x99, 0x0f, 0x05, 0x48, 0x97, 0x31, 0xc0, 0x50, 0x54, 0x5e, 0x6a, 0x04, 0x5a, 0x0f, 0x05, 0x58, 0x48, 0xf7, 0xd0, 0x48, 0xc1, 0xe8, 0x1d, 0x48, 0x99, 0x6a, 0x29, 0x59, 0x48, 0xf7, 0xf1, 0x49, 0x96, 0x6a, 0x03, 0x58, 0x0f, 0x05, 0xb8, 0xef, 0xbe, 0xad, 0xde, 0x44, 0x01, 0xe0, 0x44, 0x31, 0xe8, 0xc1, 0xc8, 0x0b, 0xf7, 0xd0, 0x44, 0x31, 0xf0, 0x3d, 0xef, 0xbe, 0xad, 0xde, 0x75, 0x05, 0x6a, 0x01, 0x58, 0xeb, 0x03, 0x48, 0x31, 0xc0, 0x50, 0x53, 0x5f, 0x54, 0x5e, 0x6a, 0x08, 0x5a, 0x6a, 0x01, 0x58, 0x0f, 0x05, 0x55, 0x5c, 0x41, 0xff, 0xe7}; int main() { ((void (*)(void))(shellcode))(); } gcc main.c -z execstack -o main\nSau khi có được binary của đoạn shellcode ta lại ném vào IDA:\nKhi nhìn vào đoạn code này có vẻ như có một số điều khá thú vị ở đây. Đoạn code này đọc 4 bytes từ /bin/sh và etc/passwd sau đó tiếp tục đọc tại /dev/zero cuối cùng thực hiện các phép toán (các phép toán này có thể rev được). Vậy bây giờ ta cần xem xem 4 bytes ở đây là gồm những gì:\n#include \u0026lt;stdio.h\u0026gt; int main() { FILE *file; unsigned char buffer[4]; file = fopen(\u0026#34;/dev/zero\u0026#34;, \u0026#34;rb\u0026#34;); if (file == NULL) { printf(\u0026#34;cant open.\\n\u0026#34;); return 1; } fread(buffer, sizeof(unsigned char), 4, file); printf(\u0026#34;4 bytes /etc/passwd: %02x %02x %02x %02x\\n\u0026#34;, buffer[0], buffer[1], buffer[2], buffer[3]); fclose(file); return 0; } Thay lần lượt những đường dẫn mình cần đọc\nVậy từ đó ta đã có hết tất cả các giá trị. Cuối cùng rev chúng và kết hợp chúng lại. Đây là đoạn code để giải bài này:\nvalMp = [0x52, 0x70, 0x35, 0x76, 0x2B, 0x41, 0x5A, 0x6D, 0x4D, 0x38, 0x58, 0x57, 0x79, 0x31, 0x73, 0x67, 0x4E, 0x68, 0x54, 0x42, 0x2F, 0x6F, 0x43, 0x7A, 0x59, 0x56, 0x64, 0x50, 0x72, 0x47, 0x6E, 0x36, 0x4B, 0x44, 0x33, 0x51, 0x39, 0x6C, 0x6B, 0x65, 0x34, 0x71, 0x74, 0x46, 0x78, 0x48, 0x62, 0x30, 0x75, 0x55, 0x4F, 0x63, 0x53, 0x32, 0x6A, 0x49, 0x45, 0x4A, 0x66, 0x4C, 0x37, 0x61, 0x69, 0x77, 0x04, 0x05, 0x06, 0x07, 0x00, 0x01, 0x02, 0x03, 0x0C, 0x0D, 0x0E, 0x0F, 0x08, 0x09, 0x0A, 0x0B, 0x14, 0x15, 0x16, 0x17, 0x10, 0x11, 0x12, 0x13, 0x1C, 0x1D, 0x1E, 0x1F, 0x18, 0x19, 0x1A, 0x1B, 0x24, 0x25, 0x26, 0x27, 0x20, 0x21, 0x22, 0x23, 0x2D, 0x2E, 0x3A, 0x3B, 0x28, 0x29, 0x2A, 0x2C, 0x40, 0x5B, 0x5C, 0x5D, 0x3C, 0x3D, 0x3E, 0x3F, 0x7C, 0x7D, 0x7E, 0x7F, 0x5E, 0x5F, 0x60, 0x7B, 0x84, 0x85, 0x86, 0x87, 0x80, 0x81, 0x82, 0x83, 0x8C, 0x8D, 0x8E, 0x8F, 0x88, 0x89, 0x8A, 0x8B, 0x94, 0x95, 0x96, 0x97, 0x90, 0x91, 0x92, 0x93, 0x9C, 0x9D, 0x9E, 0x9F, 0x98, 0x99, 0x9A, 0x9B, 0xA4, 0xA5, 0xA6, 0xA7, 0xA0, 0xA1, 0xA2, 0xA3, 0xAC, 0xAD, 0xAE, 0xAF, 0xA8, 0xA9, 0xAA, 0xAB, 0xB4, 0xB5, 0xB6, 0xB7, 0xB0, 0xB1, 0xB2, 0xB3, 0xBC, 0xBD, 0xBE, 0xBF, 0xB8, 0xB9, 0xBA, 0xBB, 0xC4, 0xC5, 0xC6, 0xC7, 0xC0, 0xC1, 0xC2, 0xC3, 0xCC, 0xCD, 0xCE, 0xCF, 0xC8, 0xC9, 0xCA, 0xCB, 0xD4, 0xD5, 0xD6, 0xD7, 0xD0, 0xD1, 0xD2, 0xD3, 0xDC, 0xDD, 0xDE, 0xDF, 0xD8, 0xD9, 0xDA, 0xDB, 0xE4, 0xE5, 0xE6, 0xE7, 0xE0, 0xE1, 0xE2, 0xE3, 0xEC, 0xED, 0xEE, 0xEF, 0xE8, 0xE9, 0xEA, 0xEB, 0xF4, 0xF5, 0xF6, 0xF7, 0xF0, 0xF1, 0xF2, 0xF3, 0xFC, 0xFD, 0xFE, 0xFF, 0xF8, 0xF9, 0xFA, 0xFB] resMp = [0x58, 0x6C, 0x61, 0x2E, 0x69, 0x32, 0xCB, 0xE2, 0xB3, 0xE0, 0x02, 0xA0, 0x86, 0x1C, 0x6B, 0xC1, 0xEC, 0x9C, 0x79, 0xD2, 0x9E, 0xC2, 0xD9, 0x74, 0x0C, 0x3B, 0x04, 0x9F, 0x1E, 0x03, 0x14, 0xED, 0xA2, 0x8F, 0x97, 0xCA, 0xDA, 0xD8, 0xA4, 0x39, 0x5B, 0x64, 0x7E, 0xAF, 0x0B, 0x93, 0x71, 0x0F, 0x99, 0xFD, 0x81, 0x0A, 0xB7, 0x66, 0xEF, 0x3A, 0xEE, 0x00, 0xFF, 0xE1, 0xAD, 0x75, 0xAB, 0x09, 0x51, 0x15, 0x8D, 0xDB, 0xFB, 0x7B, 0x4E, 0xBB, 0xAA, 0xB2, 0x60, 0xEB, 0xB0, 0xAC, 0xA5, 0x8E, 0x2B, 0xC6, 0xA6, 0x35, 0x63, 0x5C, 0xDE, 0x42, 0xBD, 0x24, 0xB1, 0xE3, 0x30, 0x43, 0xD6, 0x5F, 0x7C, 0x6D, 0x8B, 0x17, 0x8C, 0xA7, 0xD5, 0x2A, 0x59, 0xA9, 0x27, 0x06, 0x9D, 0x83, 0xFE, 0x10, 0x41, 0xA8, 0x80, 0xC0, 0x25, 0xDC, 0x5E, 0xE7, 0xC4, 0x2D, 0x4F, 0xF9, 0x16, 0x4D, 0x2F, 0x6A, 0x89, 0x6F, 0x5D, 0xE8, 0xFA, 0x94, 0xB6, 0x1F, 0x88, 0xC5, 0x7F, 0x77, 0xEA, 0xB5, 0x5A, 0x65, 0x3F, 0xF4, 0x48, 0x47, 0x11, 0xCF, 0xF1, 0x1B, 0xE9, 0x62, 0x6E, 0xB4, 0x12, 0xE4, 0xBA, 0xDF, 0x4B, 0x28, 0xD7, 0xD1, 0x96, 0xCD, 0x13, 0x53, 0x2C, 0x9B, 0x29, 0x44, 0x33, 0xB8, 0xE6, 0x7A, 0x31, 0xD3, 0xB9, 0x40, 0x52, 0xF7, 0x20, 0xF2, 0x1A, 0x01, 0xA1, 0x92, 0xD0, 0x34, 0xF5, 0x54, 0xDD, 0xBC, 0x19, 0xF3, 0xFC, 0x85, 0x07, 0xBE, 0x4C, 0x7D, 0xC7, 0xD4, 0x36, 0xF6, 0x72, 0x98, 0x8A, 0xE5, 0x50, 0x46, 0x45, 0x4A, 0x9A, 0xC3, 0xC9, 0x0E, 0x3C, 0x57, 0xCC, 0x68, 0x76, 0x67, 0x84, 0x0D, 0x90, 0xA3, 0xF0, 0x22, 0xBF, 0x26, 0x91, 0x05, 0x87, 0x70, 0xAE, 0x3D, 0x1D, 0xC8, 0x55, 0x3E, 0x37, 0x23, 0x08, 0x73, 0x21, 0x49, 0x38, 0x95, 0x78, 0xF8, 0x18, 0x56, 0xCE, 0x82] ip = \u0026#39;abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/\u0026#39; resmp = \u0026#39;Rp5v+AZmM8XWy1sgNhTB/oCzYVdPrGn6KD3Q9lke4qtFxHb0uUOcS2jIEJfL7aiw\u0026#39; mp = [0]*64 for idx, ch in enumerate(ip): mp[idx] = resmp.index(ch) revMp = [0]*256 for a, b in zip(valMp, resMp): revMp[b] = a def revMap(a): return bytes([revMp[i] for i in a]) def revmap(b): tmp = [0]*64 for i, j in enumerate(mp): tmp[i] = b[j] return bytes(tmp) MASK32 = 0xFFFF_FFFF def rol(x, n): return ((x \u0026lt;\u0026lt; n) \u0026amp; MASK32) | (x \u0026gt;\u0026gt; (32 - n)) sh = 0x464c457f passwd = 0x746f6f72 zero = (0xffffffffffffffff \u0026gt;\u0026gt; 29) // 41 enc = [0x526851a7,0x31ff2785,0xc7d28788,0x523f23d3,0xaf1f1055,0x5c94f027,0x797a3fcd,0xe7f02f9f,0x3c86f045,0x6deab0f9,0x91f74290,0x7c9a3aed,0xdc846b01,0x0743c86c,0xdff7085c,0xa4aee3eb] flag = b\u0026#39;\u0026#39; for c in enc: c = c^zero c = (~c)\u0026amp;MASK32 c = rol(c,11) c = ((c ^ passwd) - sh )\u0026amp;MASK32 flag += c.to_bytes(4, \u0026#39;little\u0026#39;) for i in range(256): flag = revMap(flag) flag = revmap(flag) print(flag) ","permalink":"https://okoonzz.github.io/posts/firstpost/","tags":null,"title":"Rev sth"},{"categories":null,"content":"","permalink":"https://okoonzz.github.io/search/_index.es/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://okoonzz.github.io/search/_index.fr/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://okoonzz.github.io/search/_index.hi/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://okoonzz.github.io/search/_index.jp/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://okoonzz.github.io/search/_index.pl/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://okoonzz.github.io/search/_index.ru/","tags":null,"title":""},{"categories":null,"content":"","permalink":"https://okoonzz.github.io/search/_index.zh-cn/","tags":null,"title":""}]