软件调试基础
文章目錄
- PE文件格式
- 文件格式
- 加殼
- 虛擬內(nèi)存
- Windows安全模式
- 虛擬內(nèi)存
- 進程空間
- PE文件和虛擬內(nèi)存的映射
- 基本概念
- 映射關(guān)系
- LordPE
- 查看導入表信息
- IAT表信息
- PEView
- 調(diào)試分析工具
- OllyDbg
- 基本調(diào)試方法
- 快捷鍵
- 跟蹤
- IDA PRO
- 反匯編窗口
- 其它窗口
- PE文件代碼注入示例
- 演示內(nèi)容及實驗環(huán)境
- 用OllyDBG打開掃雷程序
- 在空白代碼區(qū)編寫要注入的代碼
- 編輯和注入代碼
- Message函數(shù)
- 構(gòu)造相關(guān)字符串
- 構(gòu)造函數(shù)調(diào)用的代碼
- 掛接代碼及完成跳轉(zhuǎn)
- 掛接代碼
- 保存修改
- 修改程序入口點完成跳轉(zhuǎn)
- 軟件破解示例
- 使用OllyDBG破解
- 破解方式一:修改分支語句
- 破解方式二:修改函數(shù)返回值
- 總結(jié)
PE文件格式
文件格式
可執(zhí)行文件之所以可以被操作系統(tǒng)加載且運行,是因為它們遵循相同的規(guī)范。PE(Portable Executable)是Win32平臺下可執(zhí)行文件遵守的數(shù)據(jù)格式。常見的可執(zhí)行文件(.exe文件和.dll文件)都是典型的PE文件。
一個可執(zhí)行文件不光包含了二進制機器碼,還會自帶許多其他信息,如字符串、菜單、圖標、位圖、字體等。PE文件格式規(guī)定了所有的這些信息在可執(zhí)行文件中如何組織。在程序被執(zhí)行時,操作系統(tǒng)會按照PE文件格式的約定去相應(yīng)的地方準確地定位各種類型的資源,并分別裝入內(nèi)存的不同區(qū)域。
PE文件格式把可執(zhí)行文件分成若干個數(shù)據(jù)節(jié)(section),不同的資源被存放在不同的節(jié)中。一個典型的PE文件中包含的節(jié)如下:
- rsrc
存放程序的資源,如圖標、菜單等。
- text
由編譯器產(chǎn)生,存放著二進制的機器代碼,也是我們反匯編和調(diào)試的對象。
- idata
可執(zhí)行文件所使用的動態(tài)鏈接庫等外來函數(shù)與文件的信息, 即輸入表
- data
初始化的數(shù)據(jù)塊,如宏定義、全局變量、靜態(tài)變量等。
如果是正常編譯出的標準PE文件,其節(jié)信息往往是大致相同的。但這些section的名字只是為了方便人的記憶與使用,使用Microsoft Visual C++中的編譯指示符:
#pragma data_seg()
可以把代碼中的任意部分編譯到PE的任意節(jié)中,節(jié)名也可以自己定義,如果可執(zhí)行文件經(jīng)過了“加殼”處理,PE的節(jié)信息就會變得非常“古怪”。在Crack和反病毒分析中需要經(jīng)常處理這類古怪的PE文件。
加殼
全稱應(yīng)該是可執(zhí)行程序資源壓縮,是保護文件的常用手段。 加殼過的程序可以直接運行,但是不能查看源代碼。要經(jīng)過脫殼才可以查看源代碼。
加殼其實是利用特殊的算法,對EXE、DLL文件里的代碼、資源進行壓縮、加密。類似WINZIP 的效果,只不過這個壓縮之后的文件,可以獨立運行。附加在原程序上的解壓程序通過Windows加載器載入內(nèi)存后,先于原始程序執(zhí)行,得到控制權(quán),執(zhí)行過程中對原始程序進行解密、還原,還原完成后再把控制權(quán)交還給原始程序,執(zhí)行原來的代碼部分。
加上外殼后,原始程序代碼在磁盤文件中一般是以加密后的形式存在,只在執(zhí)行時在內(nèi)存中還原,這樣就可以比較有效地防止對程序文件的非法修改和靜態(tài)反編譯
加殼工具通常分為壓縮殼和加密殼兩類。
- 壓縮殼的特點是減小軟件體積大小,加密保護不是重點。
- 加密殼種類比較多,不同的殼側(cè)重點不同,一些殼單純保護程序,另一些殼提供額外的功能,如提供注冊機制、使用次數(shù)、時間限制等。
虛擬內(nèi)存
Windows安全模式
為了防止用戶程序訪問并篡改操作系統(tǒng)的關(guān)鍵部分,Windows使用了2種處理器存取模式:用戶模式和內(nèi)核模式。用戶程序運行在用戶模式,而操作系統(tǒng)代碼(如系統(tǒng)服務(wù)和設(shè)備驅(qū)動程序)則運行在內(nèi)核模式。在內(nèi)核模式下程序可以訪問所有的內(nèi)存和硬件,并使用所有的處理器指令。操作系統(tǒng)程序比用戶程序有更高的權(quán)限,使得系統(tǒng)設(shè)計者可以確保用戶程序不會意外的破壞系統(tǒng)的穩(wěn)定性。
虛擬內(nèi)存
Windows的內(nèi)存可以被分為兩個層面:物理內(nèi)存和虛擬內(nèi)存。其中,物理內(nèi)存非常復雜,需要進入Windows內(nèi)核級別ring0才能看到。通常,在用戶模式下,用調(diào)試器看到的內(nèi)存地址都是虛擬內(nèi)存。
用戶編制程序時使用的地址稱為虛擬地址或邏輯地址,其對應(yīng)的存儲空間稱為虛擬內(nèi)存或邏輯地址空間;而計算機物理內(nèi)存的訪問地址則稱為實地址或物理地址,其對應(yīng)的存儲空間稱為物理存儲空間或主存空間。程序進行虛地址到實地址轉(zhuǎn)換的過程稱為程序的再定位。
進程空間
在Windows系統(tǒng)中,在運行PE文件時,操作系統(tǒng)會自動加載該文件到內(nèi)存,并為其映射出4GB的虛擬存儲空間,然后繼續(xù)運行,這就形成了所謂的進程空間。用戶的PE文件被操作系統(tǒng)加載進內(nèi)存后,PE對應(yīng)的進程支配了自己獨立的4GB虛擬空間。在這個空間中定位的地址稱為虛擬內(nèi)存地址(Virtual Address,VA)。
到了現(xiàn)在,系統(tǒng)運行在X64架構(gòu)的硬件上,可訪問的內(nèi)存也突破了以前4GB的限制,但是獨立的進程擁有獨立的虛擬地址空間的內(nèi)存管理機制還在沿用。
PE文件和虛擬內(nèi)存的映射
在調(diào)試漏洞時,可能經(jīng)常需要做這樣兩種操作:
- 靜態(tài)反匯編工具看到的PE文件中某條指令的位置是相對于磁盤文件而言的,即所謂的文件偏移,我們可能還需要知道這條指令在內(nèi)存中所處的位置,即虛擬內(nèi)存地址。
- 反之,在調(diào)試時看到的某條指令的地址是虛擬內(nèi)存地址,我們也經(jīng)常需要回到PE文件中找到這條指令對應(yīng)的機器碼。
基本概念
相對虛擬地址(RVA)
相對虛擬地址是內(nèi)存地址相對于映射基址的偏移量。
虛擬內(nèi)存地址(VA)
PE文件中的指令被裝入內(nèi)存后的地址。
文件偏移地址(File Offset)
數(shù)據(jù)在PE文件中的地址叫文件偏移地址,這是文件在磁盤上存放時相對于文件開頭的偏移。
裝載基址(Image Base)
PE裝入內(nèi)存時的基地址。默認情況下,EXE文件在內(nèi)存中的基地址是0x00400000,DLL文件是0x10000000。
這些位置可以通過修改編譯選項更改。
映射關(guān)系
VA=Image Base+RVA
由于文件數(shù)據(jù)的存放單位與內(nèi)存數(shù)據(jù)存放單位不同而造成一些差異:
- PE文件中的數(shù)據(jù)按照磁盤數(shù)據(jù)標準存放,以0x200字節(jié)為基本單位進行組織。當一個數(shù)據(jù)節(jié)(section)不足0x200字節(jié)時,不足的地方將被0x00填充:當一個數(shù)據(jù)節(jié)超過0x200字節(jié)時,下一個0x200塊將分配給這個節(jié)使用。因此PE數(shù)據(jù)節(jié)的大小永遠是0x200的整數(shù)倍。
- 當代碼裝入內(nèi)存后,將按照內(nèi)存數(shù)據(jù)標準存放,并以0x1000字節(jié)為基本單位進行組織。類似的,不足將被補全,若超出將分配下一個0x1000為其所用。因此,內(nèi)存中的節(jié)總是0x1000的整數(shù)倍。
LordPE
LordPE是一款功能強大的PE文件分析、修改、脫殼軟件。LordPE是查看PE格式文件信息的首選工具,并且可以修改相關(guān)信息。
VOffset是RVA(相對虛擬地址),ROffset是文件偏移。也就是,在系統(tǒng)進程中,代碼(.text節(jié))將被加載到0x400000+0x11000=0x411000的虛擬地址中(裝載基址+RVA)。而在文件中,可以使用二進制文件打開,看到對應(yīng)的代碼在0x1000位置處。
查看導入表信息
導入表在文件里的偏移地址ROffset為0x24000,RVA是0x25000。打開目錄表可以看到,可以看到輸入表的RVA確實是0x25000。點左側(cè)按鈕L可以查看具體輸入表里的內(nèi)容。
IAT表信息
IAT(Import Address Table:輸入函數(shù)地址表)
每個API函數(shù)在對應(yīng)的進程空間中都有其相應(yīng)的入口地址。眾所周知,操作系統(tǒng)動態(tài)庫版本的更新,其包含的API函數(shù)入口地址通常也會改變。由于入口地址的不確定性,程序在不同的電腦上很有可能會出錯,為了解決程序的兼容問題,操作系統(tǒng)就必須提供一些措施來確保程序可以在其他版本的Windows操作系統(tǒng),以及DLL版本下也能正常運行。這時IAT表就應(yīng)運而生了。
基于導入表可以定位IAT的具體信息,相關(guān)工具可以幫助直接查看IAT表的相關(guān)內(nèi)容。
PEView
直觀顯示PE文件內(nèi)容
調(diào)試分析工具
OllyDbg
是一種具有可視化界面的 32 位匯編—分析調(diào)試器,適合動態(tài)調(diào)試。 OllyDBG版的發(fā)布版本是個ZIP 壓縮包,解壓就可以使用了。
基本調(diào)試方法
OllyDBG 有兩種方式來載入程序進行調(diào)試
- 一種是點擊菜單文件,打開(快捷鍵是F3)來打開可執(zhí)行文件進行調(diào)試
- 另一種是點擊菜單文件,附加到一個己運行的進程上進行調(diào)試,要附加的程序必須己運行。
快捷鍵
- F2設(shè)置斷點。
- F7單步步入。功能同單步步過(F8)類似,區(qū)別是遇到 CALL 等子程序時會進入其中,進入后首先會停留在子程序的第一條指令上。
- F8單步步過。執(zhí)行一條指令,遇到 CALL 等子程序不進入其代碼。
- F4運行到選定位置。
- F9運行
- CTR+F9執(zhí)行到返回。此命令在執(zhí)行到一個ret(返回指令)指令時暫停,常用于從系統(tǒng)領(lǐng)空返回到我們調(diào)試的程序領(lǐng)空。
- ALT+F9執(zhí)行到用戶代碼。可用于從系統(tǒng)領(lǐng)空快速返回到我們調(diào)試的程序領(lǐng)空。
跟蹤
使用調(diào)試功能時通常會碰到在斷點處無法定位入口的情況,即無法確定前序執(zhí)行指令,通過Trace(跟蹤)功能可以記錄調(diào)試過程中執(zhí)行的指令,用于分析前序執(zhí)行指令。Trace記錄可選擇是否記錄寄存器的值。
IDA PRO
簡稱IDA(Interactive Disassembler),是一個世界頂級的交互式反匯編工具,是逆向分析的主流工具。
IDA使用File菜單中的Open選項,可以打開一個計劃逆向分析的可執(zhí)行文件,打開的過程是需要耗費一些時間的。IDA會對可執(zhí)行文件進行分析。一旦打開成功,會提示你是否進入Proximity view。通常都會點Yes,按默認選項進入。
反匯編窗口
也叫IDA-View窗口,是操作和分析二進制文件的主要工具。
反匯編窗口有三種顯示格式:
- 面向文本的列表視圖(Text view)
- 基于圖形的視圖(Graphic View)
- 優(yōu)化視圖(Proximity view)將顯示函數(shù)及其調(diào)用關(guān)系
視圖間可以切換:在上圖的Proximity view視圖中,點選一個塊,比如_main函數(shù)塊,在其上點右鍵,可以看到Text view和Graph view等選項。通過右鍵可以實現(xiàn)不同視圖的切換。
圖形視圖:將一個函數(shù)分解為許多基本塊,類似程序流程圖類似,生動的顯示該函數(shù)由一個塊到另一個塊的控制流程。
如下圖所示的_main函數(shù)的圖形視圖:
文本視圖:文本視圖則呈現(xiàn)一個程序的完整反匯編代碼清單(而在圖形模式下一次只能顯示一個函數(shù)),用戶只有通過這個窗口才能查看一個二進制文件的數(shù)據(jù)部分。如下圖所示的文本視圖:
通常虛擬地址以[區(qū)域名稱]:[虛擬地址]這種格式顯示,如.txt:0040110C0。
實線箭頭表示非條件跳轉(zhuǎn),虛線箭頭則表示條件跳轉(zhuǎn)。如果一個跳轉(zhuǎn)將控制權(quán)交給程序中的某個地址,這時會使用粗線,出現(xiàn)這類逆向流程,通常表示程序中存在循環(huán)。
其它窗口
通過菜單Views,Open subviews可以打開更多的窗口。
Names窗口:列舉二進制文件的所有全局名稱。名稱是指對一個程序虛擬地址的符號描述。 Names窗口顯示的名稱采用了顏色和字母編碼,其編碼方案如下:
- F常規(guī)函數(shù)
- A字符串數(shù)據(jù)
- L庫函數(shù)
- I導入的名稱,通常為共享庫導入的函數(shù)名稱
- D數(shù)據(jù),已命名數(shù)據(jù)的位置通常表示全局變量
Strings窗口:顯示從二進制文件中提取出的字符串,以及每個字符串所在的地址。與雙擊Names窗口中的名稱得到的結(jié)果類似,雙擊Strings窗口中的任何字符串,反匯編窗口將跳轉(zhuǎn)到該字符串所在的地址。將Strings窗口與交叉引用結(jié)合,可以迅速定義感興趣的字符串,并追蹤到程序中任何引用該字符串的位置。
Function name窗口:該窗口顯示所有的函數(shù)。點擊函數(shù)名稱,可以快速導航到反匯編視圖中的該函數(shù)區(qū)域。該窗口中的條目如下:
這一行信息指出:用戶可以在二進制文件中虛擬地址為00401040的.text部分中找到_main函數(shù),該函數(shù)長度為0x50字節(jié)。
Function call窗口:函數(shù)調(diào)用(Function call)窗口將顯示所有函數(shù)的調(diào)用關(guān)系。如下圖:
反編譯:新版本的IDA增加了反編譯功能,加強了分析能力。
在IDA View窗口下制定匯編代碼,按快捷鍵F5,IDA會將當前所在位置的匯編代碼編譯成C/C++形式的代碼,并在Pseudocode窗口中顯示,如下圖所示。
PE文件代碼注入示例
演示內(nèi)容及實驗環(huán)境
利用PE文件輸入表API實現(xiàn)代碼注入:讓目標程序運行之前,先運行我們注入的代碼,注入的代碼將運行PE文件輸入表里包含的API。
目標PE文件為Windows XP下的掃雷程序,使用的工具包括OllyDBG和LordPE。
掃雷游戲程序位置:在Windows下找到附件里的掃雷游戲,右鍵屬性可以看到具體文件的位置,即C:\WINDOWS\system32\winmine.exe。
用OllyDBG打開掃雷程序
[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-vVPKOYNS-1655188292069)(attachment:48ea5e2f8ed501e9e4e16c59bdde8403)]
程序會停下來,自動停下來的這一行代碼位置就是程序入口點。可以通過LordPE文件來查看,得知程序入口點的RVA是0x00003E21,同時也可以看到裝載基址是0x01000000(掃雷程序是C++語言編寫);也可以通過右側(cè)寄存器EIP的值0x01003E21(VA)可以觀察到注釋信息里,提示是ModuleEntryPoint。
反匯編區(qū)域繼續(xù)往下翻頁,可以看到相關(guān)的導入表動態(tài)鏈接庫及其相關(guān)函數(shù)的信息。
在空白代碼區(qū)編寫要注入的代碼
在代碼區(qū)可以找到大量的空白代碼區(qū)域,如果我們往這里頭植入代碼,直接修改PE文件相關(guān)跳轉(zhuǎn)地址(入口點或特定函數(shù)的IAT的跳轉(zhuǎn)地址),就可以實現(xiàn)相關(guān)的植入代碼的執(zhí)行。
本實驗:演示讓掃雷程序運行之前,先運行我們注入的代碼,注入的代碼將調(diào)用PE文件輸入表里包含的MessageBox函數(shù),彈出對話框,顯示相關(guān)信息。
編輯和注入代碼
Message函數(shù)
int MessageBox(HWND hWnd, // handle to owner windowLPCTSTR lpText, // text in message boxLPCTSTR lpCaption, // message box titleUINT uType // message box style );- hWnd:消息框所屬窗口的句柄,如果為NULL,消息框則不屬于任何窗口。
- lpText:字符串指針,所指字符串會在消息框中顯示。
- lpCaption:字符串指針,所指字符串將成為消息框的標題。
- uType:消息框的風格(單按鈕、多按鈕等),NULL代表默認風格。
系統(tǒng)中并不存在真正的MessageBox函數(shù),調(diào)用最終都將由系統(tǒng)按照參數(shù)中的字符串的類型選擇“A”類函數(shù)(ASCII)或者“W”類函數(shù)(UNICODE)調(diào)用,我們使用MessageBoxA.
構(gòu)造相關(guān)字符串
計劃注入的代碼功能為:彈出對話框,顯示“You are Injected!”。
在代碼空白區(qū)域每一行位置,點鼠標右鍵,選擇 編輯->二進制編輯。
在彈出的編輯界面里,輸入ASCII碼“PE Inject”,將“保持代碼空間大小”去掉選中狀態(tài),狀態(tài)如下:
按快捷鍵CTRL+A(分析),顯示為ASCII碼。再加入另一條語句“You are Injected!” 。
上面的每個語句后面都留了一行00。因為,字符串后面是需要結(jié)束符0x00的。
構(gòu)造函數(shù)調(diào)用的代碼
push 0(默認風格); push 0x01004AA7(標題字符串地址); push 0x01004A9C(內(nèi)容字符串地址); push 0(窗口歸屬); call MessageBoxA //參數(shù)壓入棧中的順序是從右向左注意,直接雙擊要修改的當前行,就進入修改匯編代碼的狀態(tài),如下:
我們輸入的匯編指令call MessageBoxA之所以后面能成功運行,也是因為PE文件的輸入表里已經(jīng)有這個函數(shù)的入口地址了。以上代碼完成輸入后,結(jié)果如下:
掛接代碼及完成跳轉(zhuǎn)
掛接代碼
我們首先繼續(xù)輸入一條指令jmp 0x01003E21。這句話意思是我們運行完我們注入的彈出對話框之后,會跳轉(zhuǎn)到我們原來的這個PE文件的入口點,繼續(xù)運行。結(jié)果如下圖:
保存修改
上述修改是在原始文件副本里修改的,如果要保存修改,需要:
到此,文件修改完畢,但是如果直接運行這個掃雷程序,并沒有發(fā)生任何變化。** 因為,我們只是編輯了一段代碼,只有這些代碼被運行了才算真正被注入。**
修改程序入口點完成跳轉(zhuǎn)
利用LordPE文件,我們更改一下程序入口點,為我們的程序的起始位置,即我們編輯的代碼段的第一個push 0的位置,地址為0x01004ABA,因為只需要更改RVA,就修改為0x00004ABA即可,如下圖:
保存后運行,可以看到彈出右側(cè)對話框,之后出現(xiàn)掃雷程序。
軟件破解示例
本節(jié)將對一個簡單的密碼驗證程序,使用OllyDBG進行破解。具體程序如下:·
#include <iostream> using namespace std; #define password "12345678" bool verifyPwd(char * pwd) {int flag;flag=strcmp(password, pwd);return flag==0; } void main() {bool bFlag;char pwd[1024];printf("please input your password:\n");while (1){scanf("%s",pwd);bFlag=verifyPwd(pwd);if (bFlag){printf("passed\n");break;}else{printf("wrong password, please input again:\n");}} }破解對象是該程序生成的Debug模式的exe程序。
對得到的exe程序(假定不知道上面的源代碼),有多種方式實現(xiàn)破解:
- 使用OllyDBG
通過運行程序,觀察關(guān)鍵信息,通過對關(guān)鍵信息定位,來得到關(guān)鍵分支語句,通過對該分支語句進行修改,達到破解的目的;
- 另一種方式
可以通過IDA Pro來觀察代碼結(jié)構(gòu),確定函數(shù)入口地址,對函數(shù)體返回值進行更改。(給出的程序?qū)嵗惺褂玫拿菜七€是OllyDBG)
運行程序,輸入一個密碼,發(fā)現(xiàn)運行結(jié)果如下:
使用OllyDBG破解
在OllyDBG中,為了盡快定位到分支語句處,在反匯編窗口,點右鍵,選擇“查找→所有引用的字符串”功能:
然后,使用快捷鍵,Ctrl+F打開搜索窗口,輸入wrong,點確定后,將定位出錯信息的哪一行代碼:
雙擊這一行代碼,就會定位反匯編中的相應(yīng)代碼處:
破解方式一:修改分支語句
觀察反匯編語言,可知核心分支判斷在于:
Test eax,eax Jz short 0041364b如果jz條件成立,則跳轉(zhuǎn)到0041364b處,即顯示錯誤密碼分支語句中。如果將jz該指令改為jnz,則程序截然相反。輸入了錯誤密碼,將進入驗證成功的分支中。
雙擊jz密碼一行,對其進行修改:
注意:
此時并沒有真正修改二進制文件中的有關(guān)代碼,如果想要修改二進制文件中的代碼,需要在反匯編窗口,點右鍵,選擇“編輯->復制當前修改到可執(zhí)行文件”。保存后的可執(zhí)行文件,將是破解后的文件。
破解方式二:修改函數(shù)返回值
更改函數(shù)。通過分析匯編語句,可知,驗證命令使用的是verifyPwd函數(shù),點右鍵選擇跟隨,逐步進入該函數(shù):
函數(shù)的返回值通過eax寄存器來完成的,核心語句即sete al。
對于函數(shù)中的代碼:
被解釋成匯編語言:
Mov dword ptr [ebp-8], eax //將strcmp函數(shù)調(diào)用后的返回值(存在eax中)賦值給變量flag Xor eax, eax //將eax的值清空 Cmp dword ptr [ebp-8], 0 //將flag的值與0進行比較,即flag==0; //注意cmp運算的結(jié)果只會影響一些狀態(tài)寄存器的值 Sete al //sete是根據(jù)狀態(tài)寄存器的值,如果相等,則設(shè)置,如果不等,則不設(shè)置要想更改該語句,在cmp dword ptr [ebp-8], 0處開始更改,將其更改為:mov al,01。取消保持代碼空間大小,如果新代碼超長,將無法完成更改。
并將sete al改為NOP。
得到結(jié)果如下:
運行結(jié)果校驗破解正確性。
總結(jié)
本章介紹了軟件安全調(diào)試的基礎(chǔ)知識,包括PE文件格式、虛擬內(nèi)存,以及介紹了LordPE、OllyDBG、IDA Pro等用于逆向分析的工具。還給出了PE文件代碼注入和軟件破解兩個示例,后續(xù)如果有時間會分享這兩個實驗的錄制視頻,演示每一步實驗細節(jié)。
軟件安全系列預期會有8輯:
- 軟件安全預備知識
- 軟件調(diào)試基礎(chǔ)
- 軟件漏洞篇
- 漏洞利用篇
- 漏洞挖掘篇
- 滲透測試篇
- WEB安全基礎(chǔ)
- WEB滲透實戰(zhàn)
內(nèi)容參考自《軟件安全:漏洞利用及滲透測試》,書中對于PE文件結(jié)構(gòu)講述并不到位,后續(xù)會發(fā)一篇博文,講述PE文件結(jié)構(gòu)有關(guān)內(nèi)容。
總結(jié)
- 上一篇: Kmeans 算法 收敛
- 下一篇: 核芯模数转换器新品CL1680,对标AD