文章目錄:
1. Demo 效果展示:
2. 進程和端口亂扯淡:
3. 查詢進程和端口的 API 的介紹:
4. 根據進程 ID 獲得該進程所打開的 TCP 和 UDP 端口:
5. 根據端口號來獲得打開該端口號的進程:
6. 小結:
1. Demo 效果展示:
下面的這個 Demo 我只是用最簡單的方式做了個 CUI 界面來演示一下而已,
支持 Windows XP,Windows Server 2003,Vista,Windows 7 ~
並且使用 C++ 來完成的,所以對於博客園里面這么多的 .Net 開發者來說不太友好,
但是有興趣的可以將 Demo 改寫成 DLL,然后 .Net 程序也就可以進行平台調用了,
里面所有的封裝都很工整,所有改寫 DLL 以及平台調用的聲明也會很簡單,有過平台調用經驗的都可以改寫的。
本文的后面我將會把這個 Demo 提供鏈接下載;
本篇博文 Demo 效果演示:
(關於端口的查詢操作,你可以使用 netstat 命令行進行查詢,
也可以使用 TCPView 這個工具來查詢,當然還可以使用 360 的流量防火牆里面的網絡連接來進行查詢)
查詢指定進程所打開的所有的 TCP 端口:
查詢指定進程所打開的所有的 UDP 端口:
查詢指定 TCP 端口所屬的進程 ID:
查詢指定 UDP 端口所屬的進程 ID:
2. 進程和端口亂扯淡:
很久沒寫博文了,以前跟自己說的,每個月要寫那么一兩篇還看得下去的博文,
可是去年春節家里出了些事情給耽擱了 1 個多月,而后人又懶了 1 個多月,所以算下來有 2 個多月沒有寫博文了。
而后今年過來幫一小哥做了個小東西,主要是用來實現偽造端口、偽造遠程地址以及進程內存的保護啊之類的動作。
而后呢就是上面提到的這個小東西中中涉及到了端口的知識,
一開始嘛,我也是放狗一搜索,然后發現關於端口這一塊的東西,
在 2000、XP、Server 2003 下的資料很多,但惟獨到了 Vista 和 7 上面就沒什么資料了,
似乎一下子端口就完了似的,就好比端口的隱藏吧,在 XP 上無數端口隱藏工具,
應用層的,內核層的都有,但是在 Vista 和 7 上呢就都死絕了(主要是指內核層),
沒辦法,只能夠自己去研究了,研究的成果呢,主要分為內核層和應用層的成果,
當然咯,今天我的這篇博文並不會去介紹內核里面關於端口的什么東西,
而是介紹如何在應用層里面完成端口和進程之間的映射。
所謂的進程與端口映射在我來理解,
簡單點就是假如我知道一個進程,那我就要知道這個進程所打開的所有的端口,
又假如我知道一個端口,那我就應該知道是哪一個進程打開了這個端口。
所以要完成端口和進程的映射其實歸根結底是一個根據進程查詢端口和根據端口查詢進程的問題。
然后我稍微解釋一下為何到了 Vista 和 7 以后,狗上面的關於端口的資源就銳減了,
其實原因是這樣的,在 Vista 之前,我這里摘抄一點在別人博客上的東西出來:
Windows Vista 的網絡模塊較從前發生了很大變化,從而導致很多舊的端口隱藏工具無法使用.
事實上在 Vista 下 netstat.exe 調用 Iphlpapi.dll 導出的 InternalGetTcpTable2 函數實現對所有打開端口的列舉.
InternalGetTcpTable2 隨后調用由 nsi.dll 導出的 NsiAllocateAndGetTable 函數,
nsi.dll 則調用 NsiEnumerateObjectsAllParametersEx 向 nsiproxy.sys 發送 Irp 最終轉入內核.
而nsiproxy.sys 又只是對 netio.sy s的簡單封裝,它最終調用了 netio.sys 導出的內核服務例程.
上面的這些文字多說無益,大體的意思就是從 Vista 開始,Windows 的網絡模塊發生了大的變化,
不過話說回來,上面的這種變化在應用層體會的不多,不過如果做到內核的話,
你就能夠體會到網絡層確實變化很大 ~
安全焦點上的一篇關於在 Vista 之前的系統上的進程和端口的很古老的文章
《再談進程與端口的映射》:http://www.xfocus.net/articles/200202/344.html
3. 查詢進程和端口的 API 介紹:
好,上面說的有點離題了,這篇博文的目的在應用層而不在內核層,
所以我們無須去體會 Windows 網絡層的這種變化。
一般來說,要完成進程和端口的映射,我們使用的是由 IPHlpapi.dll 中導出的 API 來完成的,
在 Vista 之前的操作系統中,要查詢進程和端口的信息,容易很多,
你可以通過調用 IPHlpapi.dll 中導出的下面兩個 API 來完成查詢 TCP 和 UDP 端口的信息,
下面的兩個 API 分別用來查詢 TCP 和 UDP 端口信息,
(下面的兩個 API 僅適用於 XP 和 Server 2003,在 Vista 以及 7 中,這兩個 API 被移除)
1: DWORD WINAPI AllocateAndGetTcpExTableFromStack(
2: __out PVOID* ppTcpTable,
3: __in BOOL bOrder,
4: __in HANDLE hHeap,
5: __in DWORD dwFlags,
6: __in DWORD dwFamily
7: );
8:
9: DWORD WINAPI AllocateAndGetUdpExTableFromStack(
10: __out PVOID* ppUDPTable,
11: __in BOOL bOrder,
12: __in HANDLE hHeap,
13: __in DWORD dwFlags,
14: __in DWORD dwFamily
15: );
16:
參數說明:
第一個參數:很顯然,為輸出參數,帶回我們查詢到的 TCP 或者 UDP 信息;
第二個參數:也很顯然,判斷輸出結果是否排序;
第三個參數:指定在哪個 Heap 上分配內存;
第四個參數:指定用來控制 Heap 的標識;
第四個參數:指定 IP_V4 還是 IP_V6,具體看下面的截圖:
不過要完成進程和端口的映射在 Vista 和 7 下確實也沒那么容易,至於原因呢,
就是 IPHlpapi.dll 中關於查詢進程和端口信息的 API 沒有公開,即為 UnDocument API,
不過我們還是可以通過 UnDocument API 來完成這個查詢的工作,具體涉及到下面的兩個 API,
當然,下面的這兩個 API 由於是 UnDocumnet API,自然你查什么 MSDN 啊之類都找不到這個 API 的聲明的,
下面的兩個 API 分別用來查詢 TCP 和 UDP 端口信息,
1: //=====================================================================================//
2: //Name: DWORD InternalGetTcpTable2() //
3: // //
4: //Descripion: 該函數在 Windows Vista 以及 Windows 7 下面效 //
5: // //
6: //=====================================================================================//
7: typedef DWORD (WINAPI *PFNInternalGetTcpTable2)(
8: PMIB_TCPEXTABLE_VISTA *pTcpTable_Vista,
9: HANDLE heap,
10: DWORD flags
11: );
12:
13: //=====================================================================================//
14: //Name: DWORD InternalGetUdpTableWithOwnerPid() //
15: // //
16: //Descripion: 該函數在 Windows Vista 以及 Windows 7 下面效 //
17: // //
18: //=====================================================================================//
19: typedef DWORD (WINAPI *PFNInternalGetUdpTableWithOwnerPid)(
20: PMIB_UDPEXTABLE *pUdpTable,
21: HANDLE heap,
22: DWORD flags
23: );
參數說明:
第一個參數:pTcpTable_Vista 或者 pUdpTable 則不需要多說,其為輸出參數,帶回查詢到的值;
第二個參數:heap 也不需要多說,其指示要從哪個 Heap 上面分配內存;
第三個參數:flags 則未知,一般都設置為 1,不過貌似設置為 2 啊之類的好像又多影響不大;
4. 根據進程 ID 獲得該進程所打開的所有的 TCP 和 UDP 端口:
廢話不多少的,直接上代碼來得更加容易,注意下面函數中的 HeapFree 函數的調用,
因為從函數里面分配了內存,並且函數返回指針帶出了這片內存,所以必須在外部釋放,否則會造成內存泄露。
1: //=======================================================================================//
2: //Name: DWORD GetAllPortByProcessId() //
3: // //
4: //Descripion: 根據進程 ID 來求出該進程所打開的所有的端口號,並且在 dwAllPort 數組中返回所有端口號 //
5: // 其中 dwMaxLen 為數組的長度,函數的返回值為進程所打開的端口的數目 //
6: // (支持 XP,Server 2003,Vista,Win7) //
7: // //
8: //=======================================================================================//
9: DWORD GetAllPortByProcessId(TcpOrUdp type, DWORD dwProcessId, DWORD * dwAllPort, DWORD dwMaxLen)
10: {
11: DWORD dwPortCount = 0;
12: HMODULE hModule = LoadLibraryW(L"iphlpapi.dll");
13: if (hModule == NULL)
14: {
15: return dwPortCount;
16: }
17:
18: if(type == TcpType)
19: {
20: // 表明查詢的是 UDP 信息
21: PFNAllocateAndGetTcpExTableFromStack pAllocateAndGetTcpExTableFromStack;
22: pAllocateAndGetTcpExTableFromStack =
23: (PFNAllocateAndGetTcpExTableFromStack)GetProcAddress(hModule, "AllocateAndGetTcpExTableFromStack");
24: if (pAllocateAndGetTcpExTableFromStack != NULL)
25: {
26: // 表明為 XP 或者 Server 2003 操作系統
27: PMIB_TCPEXTABLE pTcpExTable = NULL;
28: if (pAllocateAndGetTcpExTableFromStack(&pTcpExTable, TRUE, GetProcessHeap(), 0, AF_INET) != 0)
29: {
30: if (pTcpExTable)
31: {
32: HeapFree(GetProcessHeap(), 0, pTcpExTable);
33: }
34:
35: FreeLibrary(hModule);
36: hModule = NULL;
37:
38: return dwPortCount;
39: }
40:
41: for (UINT i = 0; i < pTcpExTable->dwNumEntries; i++)
42: {
43: // 過濾掉數據,只獲取我們要查詢的進程的 Port 信息
44: if(dwProcessId == pTcpExTable->table[i].dwProcessId)
45: {
46: if(dwPortCount < dwMaxLen)
47: {
48: dwAllPort[dwPortCount] = ntohs(0x0000FFFF & pTcpExTable->table[i].dwLocalPort);
49: dwPortCount++;
50: }
51: }
52: }
53:
54: if (pTcpExTable)
55: {
56: HeapFree(GetProcessHeap(), 0, pTcpExTable);
57: }
58:
59: FreeLibrary(hModule);
60: hModule = NULL;
61:
62: return dwPortCount;
63: }
64: else
65: {
66: // 表明為 Vista 或者 7 操作系統
67: PMIB_TCPEXTABLE_VISTA pTcpExTable = NULL;
68: PFNInternalGetTcpTable2 pInternalGetTcpTable2 =
69: (PFNInternalGetTcpTable2)GetProcAddress(hModule, "InternalGetTcpTable2");
70: if (pInternalGetTcpTable2 == NULL)
71: {
72: if (pTcpExTable)
73: {
74: HeapFree(GetProcessHeap(), 0, pTcpExTable);
75: }
76:
77: FreeLibrary(hModule);
78: hModule = NULL;
79:
80: return dwPortCount;
81: }
82:
83: if (pInternalGetTcpTable2(&pTcpExTable, GetProcessHeap(), 1))
84: {
85: if (pTcpExTable)
86: {
87: HeapFree(GetProcessHeap(), 0, pTcpExTable);
88: }
89:
90: FreeLibrary(hModule);
91: hModule = NULL;
92:
93: return dwPortCount;
94: }
95:
96: for (UINT i = 0;i < pTcpExTable->dwNumEntries; i++)
97: {
98: // 過濾掉數據,只獲取我們要查詢的進程的 TCP Port 信息
99: if(dwProcessId == pTcpExTable->table[i].dwProcessId)
100: {
101: if(dwPortCount < dwMaxLen)
102: {
103: dwAllPort[dwPortCount] = ntohs(0x0000FFFF & pTcpExTable->table[i].dwLocalPort);
104: dwPortCount++;
105: }
106: }
107: }
108:
109: if (pTcpExTable)
110: {
111: HeapFree(GetProcessHeap(), 0, pTcpExTable);
112: }
113:
114: FreeLibrary(hModule);
115: hModule = NULL;
116:
117: return dwPortCount;
118: }
119: }
120: else if(type == UdpType)
121: {
122: // 表明查詢的是 UDP 信息
123: PMIB_UDPEXTABLE pUdpExTable = NULL;
124: PFNAllocateAndGetUdpExTableFromStack pAllocateAndGetUdpExTableFromStack;
125: pAllocateAndGetUdpExTableFromStack =
126: (PFNAllocateAndGetUdpExTableFromStack)GetProcAddress(hModule,"AllocateAndGetUdpExTableFromStack");
127: if (pAllocateAndGetUdpExTableFromStack != NULL)
128: {
129: // 表明為 XP 或者 Server 2003 操作系統
130: if (pAllocateAndGetUdpExTableFromStack(&pUdpExTable, TRUE, GetProcessHeap(), 0, AF_INET) != 0)
131: {
132: if (pUdpExTable)
133: {
134: HeapFree(GetProcessHeap(), 0, pUdpExTable);
135: }
136:
137: FreeLibrary(hModule);
138: hModule = NULL;
139:
140: return dwPortCount;
141: }
142:
143: for (UINT i = 0; i < pUdpExTable->dwNumEntries; i++)
144: {
145: // 過濾掉數據,只獲取我們要查詢的進程的 UDP Port信息
146: if(dwProcessId == pUdpExTable->table[i].dwProcessId)
147: {
148: if(dwPortCount < dwMaxLen)
149: {
150: dwAllPort[dwPortCount] = ntohs(0x0000FFFF & pUdpExTable->table[i].dwLocalPort);
151: dwPortCount++;
152: }
153: }
154: }
155:
156: if (pUdpExTable)
157: {
158: HeapFree(GetProcessHeap(), 0, pUdpExTable);
159: }
160:
161: FreeLibrary(hModule);
162: hModule = NULL;
163:
164: return dwPortCount;
165: }
166: else
167: {
168: // 表明為 Vista 或者 7 操作系統
169: PFNInternalGetUdpTableWithOwnerPid pInternalGetUdpTableWithOwnerPid;
170: pInternalGetUdpTableWithOwnerPid =
171: (PFNInternalGetUdpTableWithOwnerPid)GetProcAddress(hModule, "InternalGetUdpTableWithOwnerPid");
172: if (pInternalGetUdpTableWithOwnerPid != NULL)
173: {
174: if (pInternalGetUdpTableWithOwnerPid(&pUdpExTable, GetProcessHeap(), 1))
175: {
176: if (pUdpExTable)
177: {
178: HeapFree(GetProcessHeap(), 0, pUdpExTable);
179: }
180:
181: FreeLibrary(hModule);
182: hModule = NULL;
183:
184: return dwPortCount;
185: }
186:
187: for (UINT i = 0; i < pUdpExTable->dwNumEntries; i++)
188: {
189: // 過濾掉數據,只獲取我們要查詢的進程的 UDP Port信息
190: if(dwProcessId == pUdpExTable->table[i].dwProcessId)
191: {
192: if(dwPortCount < dwMaxLen)
193: {
194: dwAllPort[dwPortCount] = ntohs(0x0000FFFF & pUdpExTable->table[i].dwLocalPort);
195: dwPortCount++;
196: }
197: }
198: }
199: }
200:
201: if (pUdpExTable)
202: {
203: HeapFree(GetProcessHeap(), 0, pUdpExTable);
204: }
205:
206: FreeLibrary(hModule);
207: hModule = NULL;
208:
209: return dwPortCount;
210: }
211: }
212: else
213: {
214: FreeLibrary(hModule);
215: hModule = NULL;
216:
217: return dwPortCount;
218: }
219: }
5. 根據端口號來獲得打開該端口的進程:
同樣是直接上代碼,同樣需要注意下面函數中的 HeapFree 函數的調用,
因為從函數里面分配了內存,並且函數返回指針帶出了這片內存,所以必須在外部釋放,否則會造成內存泄露。
1: //=====================================================================================//
2: //Name: DWORD GetProcessIdByPort() //
3: // //
4: //Descripion: 根據端口號求出打開該端口號的進程 ID(支持 XP,Server 2003,Vista,Win7) //
5: // //
6: //=====================================================================================//
7: DWORD GetProcessIdByPort(TcpOrUdp type, DWORD dwPort)
8: {
9: HMODULE hModule = LoadLibraryW(L"iphlpapi.dll");
10: if (hModule == NULL)
11: {
12: return 0;
13: }
14:
15: if(type == TcpType)
16: {
17: // 表明查詢的是 TCP 信息
18: PFNAllocateAndGetTcpExTableFromStack pAllocateAndGetTcpExTableFromStack;
19: pAllocateAndGetTcpExTableFromStack =
20: (PFNAllocateAndGetTcpExTableFromStack)GetProcAddress(hModule, "AllocateAndGetTcpExTableFromStack");
21: if (pAllocateAndGetTcpExTableFromStack != NULL)
22: {
23: // 表明為 XP 或者 Server 2003 操作系統
24: PMIB_TCPEXTABLE pTcpExTable = NULL;
25: if (pAllocateAndGetTcpExTableFromStack(&pTcpExTable, TRUE, GetProcessHeap(), 0, AF_INET) != 0)
26: {
27: if (pTcpExTable)
28: {
29: HeapFree(GetProcessHeap(), 0, pTcpExTable);
30: }
31:
32: FreeLibrary(hModule);
33: hModule = NULL;
34:
35: return 0;
36: }
37:
38: for (UINT i = 0; i < pTcpExTable->dwNumEntries; i++)
39: {
40: // 過濾掉數據,只查詢我們需要的進程數據
41: if(dwPort == ntohs(0x0000FFFF & pTcpExTable->table[i].dwLocalPort))
42: {
43: DWORD dwProcessId = pTcpExTable->table[i].dwProcessId;
44: if (pTcpExTable)
45: {
46: HeapFree(GetProcessHeap(), 0, pTcpExTable);
47: }
48:
49: FreeLibrary(hModule);
50: hModule = NULL;
51:
52: return dwProcessId;
53: }
54: }
55:
56: if (pTcpExTable)
57: {
58: HeapFree(GetProcessHeap(), 0, pTcpExTable);
59: }
60:
61: FreeLibrary(hModule);
62: hModule = NULL;
63:
64: return 0;
65: }
66: else
67: {
68: // 表明為 Vista 或者 7 操作系統
69: PMIB_TCPEXTABLE_VISTA pTcpExTable = NULL;
70: PFNInternalGetTcpTable2 pInternalGetTcpTable2 =
71: (PFNInternalGetTcpTable2)GetProcAddress(hModule, "InternalGetTcpTable2");
72: if (pInternalGetTcpTable2 == NULL)
73: {
74: if (pTcpExTable)
75: {
76: HeapFree(GetProcessHeap(), 0, pTcpExTable);
77: }
78:
79: FreeLibrary(hModule);
80: hModule = NULL;
81:
82: return 0;
83: }
84:
85: if (pInternalGetTcpTable2(&pTcpExTable, GetProcessHeap(), 1))
86: {
87: if (pTcpExTable)
88: {
89: HeapFree(GetProcessHeap(), 0, pTcpExTable);
90: }
91:
92: FreeLibrary(hModule);
93: hModule = NULL;
94:
95: return 0;
96: }
97:
98: for (UINT i = 0;i < pTcpExTable->dwNumEntries; i++)
99: {
100: // 過濾掉數據,只查詢我們需要的進程數據
101: if(dwPort == ntohs(0x0000FFFF & pTcpExTable->table[i].dwLocalPort))
102: {
103: DWORD dwProcessId = pTcpExTable->table[i].dwProcessId;
104: if (pTcpExTable)
105: {
106: HeapFree(GetProcessHeap(), 0, pTcpExTable);
107: }
108:
109: FreeLibrary(hModule);
110: hModule = NULL;
111:
112: return dwProcessId;
113: }
114: }
115:
116: if (pTcpExTable)
117: {
118: HeapFree(GetProcessHeap(), 0, pTcpExTable);
119: }
120:
121: FreeLibrary(hModule);
122: hModule = NULL;
123:
124: return 0;
125: }
126: }
127: else if(type == UdpType)
128: {
129: // 表明查詢的是 UDP 信息
130: PMIB_UDPEXTABLE pUdpExTable = NULL;
131: PFNAllocateAndGetUdpExTableFromStack pAllocateAndGetUdpExTableFromStack;
132: pAllocateAndGetUdpExTableFromStack =
133: (PFNAllocateAndGetUdpExTableFromStack)GetProcAddress(hModule,"AllocateAndGetUdpExTableFromStack");
134: if (pAllocateAndGetUdpExTableFromStack != NULL)
135: {
136: // 表明為 XP 或者 Server 2003 操作系統
137: if (pAllocateAndGetUdpExTableFromStack(&pUdpExTable, TRUE, GetProcessHeap(), 0, AF_INET) != 0)
138: {
139: if (pUdpExTable)
140: {
141: HeapFree(GetProcessHeap(), 0, pUdpExTable);
142: }
143:
144: FreeLibrary(hModule);
145: hModule = NULL;
146:
147: return 0;
148: }
149:
150: for (UINT i = 0; i < pUdpExTable->dwNumEntries; i++)
151: {
152: // 過濾掉數據,只查詢我們需要的進程數據
153: if (dwPort == ntohs(0x0000FFFF & pUdpExTable->table[i].dwLocalPort))
154: {
155: DWORD dwProcessId = pUdpExTable->table[i].dwProcessId;
156: if (pUdpExTable)
157: {
158: HeapFree(GetProcessHeap(), 0, pUdpExTable);
159: }
160:
161: FreeLibrary(hModule);
162: hModule = NULL;
163:
164: return dwProcessId;
165: }
166: }
167:
168: if (pUdpExTable)
169: {
170: HeapFree(GetProcessHeap(), 0, pUdpExTable);
171: }
172:
173: FreeLibrary(hModule);
174: hModule = NULL;
175:
176: return 0;
177: }
178: else
179: {
180: // 表明為 Vista 或者 7 操作系統
181: PFNInternalGetUdpTableWithOwnerPid pInternalGetUdpTableWithOwnerPid;
182: pInternalGetUdpTableWithOwnerPid =
183: (PFNInternalGetUdpTableWithOwnerPid)GetProcAddress(hModule, "InternalGetUdpTableWithOwnerPid");
184: if (pInternalGetUdpTableWithOwnerPid != NULL)
185: {
186: if (pInternalGetUdpTableWithOwnerPid(&pUdpExTable, GetProcessHeap(), 1))
187: {
188: if (pUdpExTable)
189: {
190: HeapFree(GetProcessHeap(), 0, pUdpExTable);
191: }
192:
193: FreeLibrary(hModule);
194: hModule = NULL;
195:
196: return 0;
197: }
198:
199: for (UINT i = 0; i < pUdpExTable->dwNumEntries; i++)
200: {
201: // 過濾掉數據,只查詢我們需要的進程數據
202: if (dwPort == ntohs(0x0000FFFF & pUdpExTable->table[i].dwLocalPort))
203: {
204: DWORD dwProcessId = pUdpExTable->table[i].dwProcessId;
205: if (pUdpExTable)
206: {
207: HeapFree(GetProcessHeap(), 0, pUdpExTable);
208: }
209:
210: FreeLibrary(hModule);
211: hModule = NULL;
212:
213: return dwProcessId;
214: }
215: }
216: }
217:
218: if (pUdpExTable)
219: {
220: HeapFree(GetProcessHeap(), 0, pUdpExTable);
221: }
222:
223: FreeLibrary(hModule);
224: hModule = NULL;
225:
226: return 0;
227: }
228: }
229: else
230: {
231: FreeLibrary(hModule);
232: hModule = NULL;
233:
234: return -1;
235: }
236: }
6. 小結:
好了,文章就寫到這里了,對於那些對端口有興趣的童鞋,我想這篇文章還是很有幫助的,
因為在網絡上關於這一塊的資料真的不多的,不過到最后了還是要提一下,
就是上面的文章介紹的是在應用層,也就是 Ring3 下的端口的查詢以及使用,
我做過在 Ring0 下的端口隱藏,是通過 Hook NtDeviceIoControlFile 來實現的,
在 Vista 之前的操作系統中都容易搞定,但是在 Vista 后的操作系統中,
由於網絡模塊和之前完全不同了,而且公開的資料又太少,
所以在 Vista 后的操作系統中的端口隱藏以及端口偽造實現效果很不好,
不知有同仁是否研究過這一塊的東西,有的話,我們可以討論討論的 ~
Demo 下載地址: http://files.cnblogs.com/BoyXiao/TestPort.zip
版權所有,歡迎轉載,但轉載請注明: 轉載自 Zachary.XiaoZhen - 夢想的天空