Announcement

Collapse
No announcement yet.

Writeups for CaptainHook Reverse Engineering Challenges

Collapse
X
 
  • Filter
  • Time
  • Show
Clear All
new posts

  • Writeups for CaptainHook Reverse Engineering Challenges

    CaptainHook.exe
    Last week, I solved one WarGame challenge called CaptainHook.exe.
    it was fun.
    One of my dreams is to live my whole life solving WarGame challenge without financial worries like when I was in college.
    Will I be able to achieve this goal someday?
    When I was a college student I didn't worry about money.
    When I was in college, I was really happy.
    But I have to go to work next week.


    On a site called DreamHack(https://dreamhack.io/), I solved a WarGame challenge called CaptainHook.exe.
    When you run this program, the following is displayed.


    Click image for larger version  Name:	CaptainHook-1.png Views:	0 Size:	48.1 KB ID:	631

    I was able to create an OCR template image like this and extract the binary using OpenCV's matchTemplate function.

    Click image for larger version  Name:	image_204.png Views:	3 Size:	6.3 KB ID:	632
    Code:
    import frida, sys
    import cv2
    from PIL import ImageGrab
    from PIL import ImageFilter
    import PIL.Image
    import PIL.Image as Image
    from tkinter import *
    import time
    from time import sleep
    import win32gui
    import pytesseract
    import numpy as np
    
    letter = b'4'
    extract_data = b'4'
    
    global_time = time
    
    def drawContour(m,s,c,RGB):
        """Draw edges of contour 'c' from segmented image 's' onto 'm' in colour 'RGB'"""
        # Fill contour "c" with white, make all else black
        thisContour = s.point(lambda p:p==c and 255)
        # DEBUG: thisContour.save(f"interim{c}.png")
    
        # Find edges of this contour and make into Numpy array
        thisEdges   = thisContour.filter(ImageFilter.FIND_EDGES)
        thisEdgesN  = np.array(thisEdges)
    
        # Paint locations of found edges in color "RGB" onto "main"
        m[np.nonzero(thisEdgesN)] = RGB
        return m
    
    def on_message(message, data):
        global letter
        global extract_data
        if message['type'] == 'send' and message['payload'] =='MessageBoxW':
            # if len(message['payload']) > 0:
            #     print("[*] {0}".format(message['payload']))
            print('[+] data length: ', len(extract_data))
            print(extract_data)
            file = open('ocr.bin','wb+')
            file.write(extract_data)
            file.close()
            exit()
        elif message['type'] == 'send' and message['payload'] == 'EndPaint':
            # if len(message['payload']) > 0:
            #     print("[*] {0}".format(message['payload']))
    
            hwnd = win32gui.FindWindow(None, r'CaptainHook')
            dimensions = win32gui.GetWindowRect(hwnd)
    
            CaptainHook_x = dimensions[0]
            CaptainHook_y = dimensions[1]
            CaptainHook_w = dimensions[2] - CaptainHook_x
            CaptainHook_h = dimensions[3] - CaptainHook_y
    
            # print("Window %s:" % win32gui.GetWindowText(hwnd))
            # print("Location: (%d, %d)" % (CaptainHook_x, CaptainHook_y))
            # print("Size: (%d, %d)" % (CaptainHook_w, CaptainHook_h))
    
            desktop = win32gui.GetDesktopWindow()
            screen_x, screen_y, screen_w, screen_h = win32gui.GetWindowRect(desktop)
            centre_x, centre_y = win32gui.ClientToScreen(desktop, ((screen_w - screen_x) // 2, (screen_h - screen_y) // 2))
            x = centre_x - (CaptainHook_w // 2)
            y = centre_y - (CaptainHook_h // 2)
            win32gui.MoveWindow(hwnd, x, y, CaptainHook_w, CaptainHook_h, 0)
            w = x - 25 + CaptainHook_w
            h = y - 25 + CaptainHook_h
            box = (x + 25, y + 25, w, h)
            img = ImageGrab.grab(box)
    
            # global global_time
            # now = global_time.localtime()
            # filename = "%04d-%02d-%02d-%02dh-%02dm-%02ds" % (now.tm_year, now.tm_mon, now.tm_mday, now.tm_hour, now.tm_min, now.tm_sec)
            # saveas = "{}{}".format(filename,'.png')
            # img.save(saveas)
            # img_color = cv2.imread(saveas)
            # img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
            img_color = np.array(img)
            img_gray = cv2.cvtColor(img_color, cv2.COLOR_BGR2GRAY)
            ret, img_binary = cv2.threshold(img_gray, 127, 255, 0)
            contours, hierarchy = cv2.findContours(img_binary, cv2.RETR_LIST, cv2.CHAIN_APPROX_SIMPLE)
            for cnt in contours:
                cv2.drawContours(img_color, [cnt], 0, (0, 0, 0,), 1)
            # cv2.imwrite(saveas, img_color)
            dim = (CaptainHook_w, CaptainHook_h)
            cropped = cv2.resize(img_color, dim, interpolation = cv2.INTER_AREA)
            cropped = cv2.cvtColor(cropped, cv2.IMREAD_GRAYSCALE)
            # target = pytesseract.image_to_string(img, lang='eng', config=r'--tessdata-dir "C:\letsgodigital" -c tessedit_char_whitelist=0123456789ABCDEF')
            # target = pytesseract.image_to_string(img_color, lang='eng', config=r'--oem 2 --psm 10 -c tessedit_char_whitelist=0123456789ABCDEF')
            methods = ['cv2.TM_CCOEFF', 'cv2.TM_CCOEFF_NORMED', 'cv2.TM_CCORR', 'cv2.TM_CCORR_NORMED', 'cv2.TM_SQDIFF', 'cv2.TM_SQDIFF_NORMED']
            s = ['0.png', '1.png', '2.png', '3.png', '4.png', '5.png', '6.png', '7.png', '8.png', '9.png', 'a.png', 'b.png', 'c.png', 'd.png', 'e.png', 'f.png', 'x.png']
            minValue = 1.0
            #for method in methods:
            for i in s:
                template = cv2.imread(i,0)
                template = cv2.resize(template, dim, interpolation = cv2.INTER_AREA)
                template = cv2.cvtColor(template, cv2.IMREAD_GRAYSCALE)
                res = cv2.matchTemplate(template, cropped, eval(methods[5]))
                min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res)
                if min_val < minValue:
                    minValue = min_val
                    if i[0] == 'x':
                        letter = b'0'
                    else:
                        letter = i[0].encode('ASCII')
    
            extract_data += letter
    
            if (len(extract_data) % 100) == 0:
                print(extract_data)
    
            '''
            #cv2.imshow("show", img_color)
            #cv2.moveWindow('image', x, y)
            #cv2.waitKey(0)
            '''
        else:
            print(message)
    
    jscode = """
        function memdump(info, addr, size) {
            if (addr.isNull())
                return;
            console.log('Data dump ' + info + ' :');
            var buf = Memory.readByteArray(addr, size);
            console.log(hexdump(buf, { offset: 0, length: size, header: true, ansi: false }));
        }
    
        var GdiPlus_module_base;
        var GdiPlus_module_size;
    
        var CaptainHook_module_base;
        var CaptainHook_module_size;
    
        Process.enumerateModules({
            onMatch: function(module) {
                if (module.name == "gdiplus.dll")
                {
                    GdiPlus_module_base = module.base;
                    GdiPlus_module_size = module.size;
                    console.log("- Module.name : " + module.name);
                    console.log("- Module.base : " + module.base);
                    console.log("- Module.size : " + module.size);
                    console.log('---------------------------------------------------------');
                }
    
                if (module.name == "CaptainHook.exe")
                {
                    CaptainHook_module_base = module.base;
                    CaptainHook_module_size = module.size;
                    console.log("- Module.name : " + module.name);
                    console.log("- Module.base : " + module.base);
                    console.log("- Module.size : " + module.size);
                    console.log('---------------------------------------------------------');
                }
            },
            onComplete: function() {
                console.log("[+] Process.enumerateModules Done.");
            }
        });
    
        Memory.protect(CaptainHook_module_base, CaptainHook_module_size, 'rwx');
    
        const Gdip_BaseAddress = Module.findBaseAddress('gdiplus.dll');
        const GdipDrawLineI = Module.findExportByName('gdiplus.dll', 'GdipDrawLineI');
    
        /*
        var _match = Memory.scanSync(CaptainHook_module_base, CaptainHook_module_size, 'E8 CA 1B 01 00');
        if (_match.length != 0) {
            console.log('[+] CaptainHook Address Found = ' + _match[0].address.toString(16));
            memdump('DUMP', _match[0].address, 0x80);
        }
        */
    
        console.log('=========================================================');
        var dump_address = CaptainHook_module_base.add(0x14001F3F0 - 0x140001000);
        console.log('[+] CaptainHook memdump = 0x' + dump_address.toString(16));
        memdump('CaptainHook', dump_address, 0x200);
    
        console.log('[+] gdiplus.dll base address: 0x' + Gdip_BaseAddress.toString(16));
    
        var GdipDrawLineI = new NativeFunction(Module.findExportByName('gdiplus.dll', 'GdipDrawLineI'), 'pointer', ['pointer', 'pointer', 'int', 'int', 'int', 'int']);
        Interceptor.replace(GdipDrawLineI, new NativeCallback(function(graphics, pen, x1, y1, x2, y2) {
            // console.log("GdipDrawLineI(graphics=\\"" + graphics.toString(16) + "\\"" + ", pen=\\"" + pen.toString(16) + "\\"" + ", x1=" + x1.toString() + ", y1=" + y1.toString() + ", x2=" + x2.toString() + ", y2=" + y2.toString());
            if ((x1 >= 74 && x1 <= 106) && (x2 >= 74 && x2 <= 106) &&
                (y1 >= 74 && y1 <= 136) && (y2 >= 74 && y2 <= 136))
            {
                return GdipDrawLineI(graphics, pen, x1, y1, x2, y2);
            }
            else {
                return 0;
            }
        }, 'pointer', ['pointer', 'pointer', 'int', 'int', 'int', 'int']));
    
        var EndPaint = new NativeFunction(Module.findExportByName('user32.dll', 'EndPaint'), 'bool', ['pointer', 'pointer']);
        Interceptor.replace(EndPaint, new NativeCallback(function(hWnd, lpPaint) {
            // console.log("EndPaint(hWnd=\\"" + hWnd.toString(16) + "\\"" + ", lpPaint=\\"" + lpPaint.toString(16) + "\\")");
            var result = EndPaint(hWnd, lpPaint);
            send('EndPaint');
            return result;
        }, 'bool', ['pointer', 'pointer']));
    
        var MessageBoxW = new NativeFunction(Module.findExportByName('user32.dll', 'MessageBoxW'), 'int', ['pointer', 'pointer', 'pointer', 'int']);
        Interceptor.replace(MessageBoxW, new NativeCallback(function(hWnd, lpText, lpCaption, uType) {
            send('MessageBoxW');
            return MessageBoxW(hWnd, lpText, lpCaption, uType);
        }, 'int', ['pointer', 'pointer', 'pointer', 'int']));
    
        /*
        Interceptor.attach(GdipDrawLineI, {
            onEnter: function (args) {
                console.log('=========================================================');
                console.log('[+] Called GdipDrawLineI [' + GdipDrawLineI + ']');
                console.log('=========================================================');
                console.log('[+] GpGraphics *graphics = [' + args[0].toString(16) + ']');
                memdump('args[0]', args[0], 0x16);
                console.log('---------------------------------------------------------');
                console.log('[+] GpPen *pen = [' + args[1].toString(16) + ']');
                memdump('args[1]', args[1], 0x16);
                console.log('---------------------------------------------------------');
                console.log('[+] x1 = ' + args[2].toInt32().toString());
                console.log('---------------------------------------------------------');
                console.log('[+] y1 = ' + args[3].toInt32().toString());
                console.log('---------------------------------------------------------');
                console.log('[+] x2 = ' + args[4].toInt32().toString());
                console.log('---------------------------------------------------------');
                console.log('[+] y2 = ' + args[5].toInt32().toString());
                console.log('---------------------------------------------------------');
                // console.log('Context: ' + JSON.stringify(this.context, null, 4));
            },
            onLeave: function (retval) {
                console.log('=========================================================');
                // console.log('Context: ' + JSON.stringify(this.context, null, 4));
                console.log('=========================================================');
                var dump_address = CaptainHook_module_base.add(0x14001F3F0 - 0x140001000);
                console.log('[+] CaptainHook memdump = 0x' + dump_address.toString(16));
                memdump('CaptainHook', dump_address, 0x20);
            }
        });
        */
    """
    
    if __name__ == "__main__":
        session = frida.attach("CaptainHook.exe")
        script = session.create_script(jscode)
        script.on('message', on_message)
        script.load()
        sys.stdin.read()
    Using the frida (DBI tool) code I programmed as shown in the following picture, you can hook the GdipDrawLineI function to output a clean image.

    Click image for larger version  Name:	CaptainHook-Cleaning.png Views:	0 Size:	319.9 KB ID:	637

    Click image for larger version  Name:	CaptainHook_1.png Views:	0 Size:	775.7 KB ID:	634

    After extracting all the binary data, you can run the flag output program as follows.

    Click image for larger version  Name:	CaptainHook_2.png Views:	0 Size:	2.30 MB ID:	635

    Hooking the GdipDrawLineI function in the extracted program will output a clean image of the hidden part.

    Code:
    import frida, sys
    import cv2
    from PIL import ImageGrab
    from PIL import ImageFilter
    import PIL.Image
    import PIL.Image as Image
    from tkinter import *
    import time
    from time import sleep
    import win32gui
    import pytesseract
    import numpy as np
    
    def on_message(message, data):
        if message['type'] == 'send':
            if len(message['payload']) > 0:
                print("[*] {0}".format(message['payload']))
        else:
            print(message)
    
    jscode = """
        const Gdip_BaseAddress = Module.findBaseAddress('gdiplus.dll');
        const GdipDrawLineI = Module.findExportByName('gdiplus.dll', 'GdipDrawLineI');
    
        console.log('[+] gdiplus.dll base address: 0x' + Gdip_BaseAddress.toString(16));
    
        var GdipDrawLineI = new NativeFunction(Module.findExportByName('gdiplus.dll', 'GdipDrawLineI'), 'pointer', ['pointer', 'pointer', 'int', 'int', 'int', 'int']);
        Interceptor.replace(GdipDrawLineI, new NativeCallback(function(graphics, pen, x1, y1, x2, y2) {
            // console.log("GdipDrawLineI(graphics=\\"" + graphics.toString(16) + "\\"" + ", pen=\\"" + pen.toString(16) + "\\"" + ", x1=" + x1.toString() + ", y1=" + y1.toString() + ", x2=" + x2.toString() + ", y2=" + y2.toString());
            if (x2 - x1 <= 100)
            {
                return GdipDrawLineI(graphics, pen, x1, y1, x2, y2);
            }
            else {
                return 0;
            }
        }, 'pointer', ['pointer', 'pointer', 'int', 'int', 'int', 'int']));
    """
    
    if __name__ == "__main__":
        session = frida.attach("Flag.exe")
        script = session.create_script(jscode)
        script.on('message', on_message)
        script.load()
        sys.stdin.read()
    Attached Files
Working...
X