主頁 > 作業系統 > CSAPP - BombLab

CSAPP - BombLab

2022-10-23 06:23:16 作業系統

Bomb Lab

引言:主要任務是“拆炸彈”,所謂炸彈,其實就是一個二進制的可執行檔案,要求輸入六個字串,每個字串對應一個phase,如果字串輸入錯誤,系統就會提示BOOM!!!解決這次實驗需要將二進制檔案反匯編,通過觀察理解匯編語言描述的程式行為來猜測符合條件的字串,可以看出該可執行程式要求從命令列或者檔案以 行 為單位讀入字串,每行字串對應一個phase的輸入,如果phase執行完畢,會呼叫phase_defused 函式表明該 phase 成功搞定,實驗共有6個 phase,難度是逐級提升,考點也不盡相同,首先執行命令objdump -d bomb > bomb.txt得到反匯編代碼,

Phase1

考察點:字串的傳遞方式

查看bomb.txt檔案的反匯編代碼,如下所示,首先堆疊頂指標向下移動了8個位元組,在64位機器下就是一格,然后將0x402400傳遞給了esi暫存器(保存函式引數的暫存器),在0x400ee9處呼叫了string_not_equal函式,呼叫回傳后如果eax暫存器的值為0的話,我們就會跳轉到phase_1 + 0x17 = 400ef7的位置,否則的話呼叫explode_bomb函式就失敗了,顯然,我們需要讓其判斷相等,利用gdb查看0x402400處的字串,

0000000000400ee0 <phase_1>:
  400ee0:       48 83 ec 08             sub    $0x8,%rsp
  400ee4:       be 00 24 40 00          mov    $0x402400,%esi
  400ee9:       e8 4a 04 00 00          callq  401338 <strings_not_equal>
  400eee:       85 c0                   test   %eax,%eax
  400ef0:       74 05                   je     400ef7 <phase_1+0x17>
  400ef2:       e8 43 05 00 00          callq  40143a <explode_bomb>
  400ef7:       48 83 c4 08             add    $0x8,%rsp
  400efb:       c3                      retq

image-20221020190902442

我們按 s 單步執行時也可以看到這個字串

image-20221020191004747

我們gdb bomb時,將上面的字串輸入,可以看到第一關就過了,Border relations with Canada have never been better.

image-20221020191159666

Phase2

考察點:匯編代碼中陣列的表示

還是首先查看匯編代碼

0000000000400efc <phase_2>:
400efc:       55                      push   %rbp                      	# 保存rbp
400efd:       53                      push   %rbx				 	  # 保存rbx
400efe:       48 83 ec 28             sub    $0x28,%rsp				   # 擴大堆疊空間,擴大0x28即40個位元組 
400f02:       48 89 e6                mov    %rsp,%rsi				   # 保存堆疊頂元素到rsi暫存器
# 對應的C語言格式匯編代碼
rsi = rsp;
callq read_six_number;
if (*rsp == 1)
	goto 400f30;
else 
	callq explode_bomb;
goto 400f30;
400f05:       e8 52 05 00 00          callq  40145c <read_six_numbers>     # 讀入六個數字
400f0a:       83 3c 24 01             cmpl   $0x1,(%rsp)				  # 比較rsp必須為1
400f0e:       74 20                   je     400f30 <phase_2+0x34>		# 如果m[rsp] = 1則跳轉到0x400f30
400f10:       e8 25 05 00 00          callq  40143a <explode_bomb>		# 顯然不能執行這條指令
400f15:       eb 19                   jmp    400f30 <phase_2+0x34>  
400f17:       8b 43 fc                mov    -0x4(%rbx),%eax   # 下面是一段回圈  eax = M[rbx - 4]
# 400f17 - 400f25:
eax = *(rbx-4);  # 每次取出M[rbx - 4]的值給eax
eax += eax;      # eax每次都會變為 *(ebx - 4)的二倍
if (eax == *rbx) # rbx為存放的第二個元素的值  即上一個元素的二倍必須等于下一個元素的值
	goto 400f25;
else 
	callq explode_bomb;
400f1a:       01 c0                   add    %eax,%eax         # eax = eax + eax 
400f1c:       39 03                   cmp    %eax,(%rbx)		 # if (eax == m[rbx]) goto 0x400f25
400f1e:       74 05                   je     400f25 <phase_2+0x29> # 跳過下面的bomb  顯然我們需要讓eax = m[rbx]
400f20:       e8 15 05 00 00          callq  40143a <explode_bomb>
400f25:       48 83 c3 04             add    $0x4,%rbx        # rbx = rbx + 4
# 400f25 - 400f2e:
rbx += 4;			# 下一個元素,下一次的 rbx - 4就相當于這一次的rbx了
# 不難看出,下面是一個以rbx為搜索指標,以rbp為結尾信號的回圈
if (rbx != rbp)     # 只要rbx還沒有到rbp   rbx其實就相當于for回圈的i  rbp為6 
	goto 400f17;	# 回圈
else 
	goto 400f3c;
400f29:       48 39 eb                cmp    %rbp,%rbx        # if (rbx-rbp!=0) goto 0x400f17,回到回圈開始
400f2c:       75 e9                   jne    400f17 <phase_2+0x1b>
400f2e:       eb 0c                   jmp    400f3c <phase_2+0x40> # rbx==rbp的話就會到這里  0x400f3c
400f30:       48 8d 5c 24 04          lea    0x4(%rsp),%rbx   # rbx = rsp + 4  lea指令傳遞的是暫存器的內容
400f35:       48 8d 6c 24 18          lea    0x18(%rsp),%rbp  # rbp = rsp + 24
400f3a:       eb db                   jmp    400f17 <phase_2+0x1b> # 接著回圈
400f3c:       48 83 c4 28             add    $0x28,%rsp
400f40:       5b                      pop    %rbx
400f41:       5d                      pop    %rbp
400f42:       c3                      retq
# 六個數分別存放到 rsp rsp+0x4  rsp+0x8  rsp+0xc  rsp+0x
# read_six_numbers代碼  需要我們輸入6個數字然后進行比較這里還有如果數字不滿足6個的健壯性判斷  注意rdi和rsi暫存器已經被用來保存read_six_numbers的兩個引數了,
000000000040145c <read_six_numbers>:
40145c:       48 83 ec 18             sub    $0x18,%rsp  # 6個數, 4 * 6 = 24 = 0x18
401460:       48 89 f2                mov    %rsi,%rdx   # 在上面的函式中我們將rsp存盤到了rsi中
401463:       48 8d 4e 04             lea    0x4(%rsi),%rcx  # rsp + 4的地址,存放輸入的第二個數
401467:       48 8d 46 14             lea    0x14(%rsi),%rax # 用rax暫存輸入的第六個數(rsp + 0x14)
40146b:       48 89 44 24 08          mov    %rax,0x8(%rsp)  # rsp + 8 = rax = rsi(之前的rsp) + 0x14  
401470:       48 8d 46 10             lea    0x10(%rsi),%rax # 存放第五個數,存放到了rax暫存器中
401474:       48 89 04 24             mov    %rax,(%rsp) 	   # rsp = rsi(之前的rsp) + 0x10(多的引數存到記憶體)
401478:       4c 8d 4e 0c             lea    0xc(%rsi),%r9   # 存放第四個數   這時候六個暫存器已經用完了
40147c:       4c 8d 46 08             lea    0x8(%rsi),%r8   # 存放第三個數  
401480:       be c3 25 40 00          mov    $0x4025c3,%esi  # 給rsi 賦值為 0x4025c3
401485:       b8 00 00 00 00          mov    $0x0,%eax
40148a:       e8 61 f7 ff ff          callq  400bf0 <__isoc99_sscanf@plt>  # 呼叫 sscanf 函式讀取輸入
40148f:       83 f8 05                cmp    $0x5,%eax # 比較上面函式的回傳值  如果大于5,說明讀取的合法
401492:       7f 05                   jg     401499 <read_six_numbers+0x3d>
401494:       e8 a1 ff ff ff          callq  40143a <explode_bomb>  # 否則執行炸彈bomb
401499:       48 83 c4 18             add    $0x18,%rsp  # 恢復堆疊
40149d:       c3                      retq

這次匯編代碼比較長了,分析的結果都寫在注釋里了,下面通過gdb動態除錯一下,首先b phase_2然后run,可以看到四個暫存器均保存了我們的輸入

image-20221020222529333

查看暫存器的內容i reg或者p $eax,接著查看記憶體中該地址的內容,/s表示以字符形式顯示,可以看到我們輸入的內容都是以字串格式先保存的,然后通過sscanf格式化輸出為了6個整數

image-20221020211142721

image-20221020222606108

執行到呼叫read_six_numbers函式之前,我們可以看到該函式的第一個引數傳遞給了rdi,即我們輸入的字串,然后將rsi暫存器置為0,注意這里的反匯編第一個運算元是目的運算元,rsi保存的是提升堆疊后的rsp的值,用來保存陣列的起始地址,

image-20221020222653690

使用 f 可以查看當前堆疊資訊,利用 bt 指令可以查看函式呼叫堆疊之間的關系

image-20221020214339314

一步步執行下去,直到上面不是很懂的mov $0x4025c3, %esi指令,可以看到該地址的內容如下,其實就是作為sscanf函式的引數,rdi暫存器的內容始終都沒有被修改,這里也可以看出端倪,輸入的字串保存在rdi中,此時作為sscanf函式的第一個引數

image-20221020214013511

image-20221020214546574

呼叫下面這個函式將rax暫存器的值設定為6,從而可以下面可以cmp $0x5, %eax使eax的值大于5,直接跳轉回phase_2函式,回到phase_2函式,之后執行的就是一個回圈判斷了,判斷存進去的數是否滿足后一個數是前一個數的二倍,rbx保存的地址是從2開始的,格式化后的6個數字存盤到了從ebp開始的連續的記憶體空間,查看可見下圖

image-20221020223141926

這里的匯編代碼比較好理解,第一行rbx = rsp + 4,第二行rbp = rsp + 0x18,保存回圈結束位置(6個數,每個數4位元組,共16+8=24位元組),將M[rbx - 4]賦值給eax,此時eax保存的即為輸入的第一個數 1,然后add eax, eaxeax保存的數變為原來的二倍,接著比較eax保存的值與當前記憶體中M[rbx]是否相等,相等的話接下來讓rbx + 4(這里的+4其實就對應陣列元素的+1),看是否滿足回圈終止條件,不滿足就跳到上面phase_2+27繼續執行,

image-20221020220540839

動態執行完后就可以看到過掉了

image-20221020223615620

Phase3

考察點:switch陳述句,索引表的匯編表示

0000000000400f43 <phase_3>:
400f43:       48 83 ec 18             sub    $0x18,%rsp					; 首先提升堆疊	
400f47:       48 8d 4c 24 0c          lea    0xc(%rsp),%rcx				; rcx = rsp + 0xc
400f4c:       48 8d 54 24 08          lea    0x8(%rsp),%rdx				; rdx = rsp + 0x8  保存的是引數 
400f51:       be cf 25 40 00          mov    $0x4025cf,%esi 			; 猜測這里與上面一樣 是sscanf用到的引數 %%
400f56:       b8 00 00 00 00          mov    $0x0,%eax					; 用作回傳值
400f5b:       e8 90 fc ff ff          callq  400bf0 <__isoc99_sscanf@plt> ;這個函式與上面的一樣,先輸入字串再格式化
400f60:       83 f8 01                cmp    $0x1,%eax					; 上面的函式回傳值
400f63:       7f 05                   jg     400f6a <phase_3+0x27>		  ; 0x400f6a 跳過爆炸的函式,回傳值需要大于1
400f65:       e8 d0 04 00 00          callq  40143a <explode_bomb>		  
400f6a:       83 7c 24 08 07          cmpl   $0x7,0x8(%rsp)				# rsp + 8存盤第一個引數,rsp+c存盤第二個
400f6f:       77 3c                   ja     400fad <phase_3+0x6a>;0x400fad 會爆炸,所以rsp+8<7,用ja當a[0]<0是也no
400f71:       8b 44 24 08             mov    0x8(%rsp),%eax				; eax = rsp + 8 < 7  將第一個數存到eax
400f75:       ff 24 c5 70 24 40 00    jmpq   *0x402470(,%rax,8)			; 跳轉到 M[0x402470+rax*8] 處其實就是400fb9
400f7c:       b8 cf 00 00 00          mov    $0xcf,%eax
400f81:       eb 3b                   jmp    400fbe <phase_3+0x7b>
400f83:       b8 c3 02 00 00          mov    $0x2c3,%eax
400f88:       eb 34                   jmp    400fbe <phase_3+0x7b>
400f8a:       b8 00 01 00 00          mov    $0x100,%eax
400f8f:       eb 2d                   jmp    400fbe <phase_3+0x7b>
400f91:       b8 85 01 00 00          mov    $0x185,%eax
400f96:       eb 26                   jmp    400fbe <phase_3+0x7b>
400f98:       b8 ce 00 00 00          mov    $0xce,%eax
400f9d:       eb 1f                   jmp    400fbe <phase_3+0x7b>
400f9f:       b8 aa 02 00 00          mov    $0x2aa,%eax
400fa4:       eb 18                   jmp    400fbe <phase_3+0x7b>
400fa6:       b8 47 01 00 00          mov    $0x147,%eax
400fab:       eb 11                   jmp    400fbe <phase_3+0x7b>
400fad:       e8 88 04 00 00          callq  40143a <explode_bomb>
400fb2:       b8 00 00 00 00          mov    $0x0,%eax
400fb7:       eb 05                   jmp    400fbe <phase_3+0x7b>
400fb9:       b8 37 01 00 00          mov    $0x137,%eax  ; 如果 a[0]=1時會跳轉到這里  eax=0x137
400fbe:       3b 44 24 0c             cmp    0xc(%rsp),%eax ; 比較第二個引數與eax的值是否相同 相同的話就過了
400fc2:       74 05                   je     400fc9 <phase_3+0x86>
400fc4:       e8 71 04 00 00          callq  40143a <explode_bomb>
400fc9:       48 83 c4 18             add    $0x18,%rsp
400fcd:       c3                      retq

下面利用gdb動態除錯,在進入sscanf函式之前,查看0x4025cf處存盤的要傳入sscanf的字串,所以可以知道sscanf這次要讀取的是兩個整數,不用跟進去猜測sscanf的作用就知道,它將輸入的標準字串格式化為了兩個整數,

image-20221021143641675

一步一步執行下去,知道進入sscanf函式之前,可以看到與該函式有關的資訊如下所示:

image-20221021144749548

退出sscanf函式后,可以查看該函式將格式化后的數字存盤到了哪里,其中0x7fffffffe3d0為堆疊指標rsp的地址,rsp + 4存盤回傳地址,rsp + 8存盤輸入的第一個數,rsp + c存放輸入的第二個數,

image-20221021145501463

讀取堆疊的資料 --兩種方式  入堆疊(edx為堆疊頂,ebx為堆疊底) 
1、base加偏移  堆疊底為高地址
讀第一個壓入的資料:mov esi,dword ptr ds:[ebx-4]
讀第四個壓入的資料:mov esi,dword ptr ds:[ebx-0x10]
2.top加偏移    堆疊頂為低地址
讀第二個壓入的資料:mov edi,dword ptr ds:[edx+8]    
讀第三個壓入的資料:mov edi,dword ptr ds:[edx+4]

rsp和rbp暫存器不用我們指定內容,是由編譯器確定的,接下來是比較rsp + 8 和 0x7的大小,需要滿足rsp + 8 < 0x7,即第一個引數小于7, 注意這里的ja指令可以同時處理輸入的a[0] > 7a[0] < 0的情況,之后會做一個無條件的jmp *0x402470(,%rax,8),根據rax的值去找對應的陳述句,猜測是一個以rax為索引的索引表,類比switch陳述句,對于我們輸入的每一對數,都會根據第一個數的值去確定第二個數的值,查看以地址0x402470為基址的索引表的資訊如下所示,我們輸入的是1,所以取0x400fb9的地址尋找

image-20221021151450464

當我們跳轉到指定地址后可以看到(這里輸入的第一個引數為1),將eax賦值為0x137,然后比較我們輸入的第二個數與這個數是否相等,即我們可以輸入的有1 311或者其他六種其他的數,

image-20221021151834957

對應的C形式的偽代碼就如下所示:

void phase_3(char* output)
{
    int x, y;
    if(sscanf(output, "%d %d", &x, &y) <= 1)
        explode_bomb();
    if(x > 7)
        explode_bomb();
    int num;
    switch(x) {
    case 0:
        num = 207;
    	break;
    case 1:
        num = 311;
        break;
    case 2:
        num = 707;
        break;
    case 3:
        num = 256;
        break;
    case 4:
        num = 389;
        break;
    case 5:
        num = 206;
        break;
    case 6:
        num = 682;
		break;
    case 7:
        num = 327;
    }
    if (num != y)
        explode_bomb();
    return;
}

Phase4

考察點:遞回函式的引數及回傳值

000000000040100c <phase_4>:
40100c:       48 83 ec 18             sub    $0x18,%rsp
401010:       48 8d 4c 24 0c          lea    0xc(%rsp),%rcx
401015:       48 8d 54 24 08          lea    0x8(%rsp),%rdx
40101a:       be cf 25 40 00          mov    $0x4025cf,%esi
40101f:       b8 00 00 00 00          mov    $0x0,%eax
401024:       e8 c7 fb ff ff          callq  400bf0 <__isoc99_sscanf@plt>  # 同樣呼叫了sscanf這個函式
401029:       83 f8 02                cmp    $0x2,%eax		# 如果上面函式的回傳值與2不相等的話就bomb了
40102c:       75 07                   jne    401035 <phase_4+0x29>  # 跳到 0x401035 即bomb函式
40102e:       83 7c 24 08 0e          cmpl   $0xe,0x8(%rsp)		# 這里需要滿足 M[rsp+8] <= 0xe 這樣才能跳過bomb
401033:       76 05                   jbe    40103a <phase_4+0x2e>  # jbe是小于等于 
401035:       e8 00 04 00 00          callq  40143a <explode_bomb>
40103a:       ba 0e 00 00 00          mov    $0xe,%edx   # edx = 0xe   下面三行應該都是 func4函式的引數
40103f:       be 00 00 00 00          mov    $0x0,%esi   # esi = 0x0
401044:       8b 7c 24 08             mov    0x8(%rsp),%edi # edi = a[0](我們輸入的第一個引數的值)
401048:       e8 81 ff ff ff          callq  400fce <func4> # 這里又呼叫了一個函式
40104d:       85 c0                   test   %eax,%eax		# 判斷 eax 是否為0,即func4函式的回傳值是否為0
40104f:       75 07                   jne    401058 <phase_4+0x4c> # 如果不為0的話跳轉到 bomb,所以需要使eax為0
401051:       83 7c 24 0c 00          cmpl   $0x0,0xc(%rsp)  # 比較輸入的第二個數和0是否相等  不相等會bomb
401056:       74 05                   je     40105d <phase_4+0x51>
401058:       e8 dd 03 00 00          callq  40143a <explode_bomb>
40105d:       48 83 c4 18             add    $0x18,%rsp
401061:       c3                      retq

; func4函式
0000000000400fce <func4>:
400fce:       48 83 ec 08             sub    $0x8,%rsp   # 堆疊空間擴大8個位元組 這里的 0x1就代表地址空間可以多存盤一個位元組
400fd2:       89 d0                   mov    %edx,%eax   # eax作為sscanf的回傳值一直沒有修改  edx為第三個引數0xe
400fd4:       29 f0                   sub    %esi,%eax   # eax = eax - esi = 0xe - 0 = 0xe
400fd6:       89 c1                   mov    %eax,%ecx   # ecx = eax = 0xe
400fd8:       c1 e9 1f                shr    $0x1f,%ecx  # shr邏輯右移指令 ecx = ecx >> 0x1f = 0
400fdb:       01 c8                   add    %ecx,%eax   # eax = eax + ecx = 0xe
400fdd:       d1 f8                   sar    %eax  # sar 算術右移指令 省略了一個運算元  gdb中顯示為 1 1110>> 1 =111=7
到這里就可以看出端倪了:eax = (eax + eax >> 0x1f) >> 1   其中 eax = edx - esi = 0xe
400fdf:       8d 0c 30                lea    (%rax,%rsi,1),%ecx # ecx = rax + rsi * 1 = 7 + 0 = 7
400fe2:       39 f9                   cmp    %edi,%ecx   # 將我們輸入的第一個引數與 7 比較
400fe4:       7e 0c                   jle    400ff2 <func4+0x24> # 如果7 <= a[0] 跳轉到 0x400ff2 執行
400fe6:       8d 51 ff                lea    -0x1(%rcx),%edx # 否則 a[0] < 7  edx = rcx - 1 = 6
400fe9:       e8 e0 ff ff ff          callq  400fce <func4>  # 遞回呼叫 func4 函式
400fee:       01 c0                   add    %eax,%eax		# 2 * func()
400ff0:       eb 15                   jmp    401007 <func4+0x39>
400ff2:       b8 00 00 00 00          mov    $0x0,%eax  # eax = 0
400ff7:       39 f9                   cmp    %edi,%ecx  # if(ecx(7) >= edi(a[0])) goto 401007; else func4();
400ff9:       7d 0c                   jge    401007 <func4+0x39>
400ffb:       8d 71 01                lea    0x1(%rcx),%esi
400ffe:       e8 cb ff ff ff          callq  400fce <func4> # 這里如果 a[0] > 7的話也會進行遞回  a[0] = 7就是邊界條件
401003:       8d 44 00 01             lea    0x1(%rax,%rax,1),%eax  # eax = rax + rax + 1 遞回呼叫
401007:       48 83 c4 08             add    $0x8,%rsp
40100b:       c3                      retq
遞回函式其實就是
int func(int x, int a, int b)  (edi esi edx)
{
	int c = b - a;(c存盤在 ecx   b在edx里)
	c = (c + c >> 31) >> 1;  這里c又存盤到了 eax 里
	int d = c + a; (rax + rsi(用來傳遞第二個引數))  d 存盤在 ecx
	if (d <= x)  goto 0x400ff2
	{
		if (d >= x) return 0;
		; 遞回呼叫 注意第二個引數變了  lea  0x1(%rcx),%esi  esi = d + 1
		return 2 * func4(x, d + 1, b) + 1
	}
	goto 0x400fe6  lea  -0x1(%rcx),%edx  b = b - 1
	return 2 * func(x, a, b - 1);  只有第三個引數變了  別的都沒變
}

由上面的分析可知,輸入的第二個數一定為0,第一個數作為func4函式的第一個引數進行了運算,需要滿足func4函式的回傳值為0,下面利用gdb動態除錯,可以看到地址0x4025cf處存盤的是% %,所以我們要輸入的引數個數是兩個

image-20221021184432913

由上面匯編的分析可知,我們輸入的第一個引數需要小于等于14,第二個引數一定為0,執行到呼叫func4函式時界面如下,可以看到func4函式有四個引數,第一個引數就是我們輸入的第一個數,分析可知,func4函式是一個遞回函式,遞回終止條件為 a[0] >=7,且下面還有一個判斷如果a[0] > 7也會遞回呼叫函式func4,所以我們令第一個引數為7即可,如第二張圖,

image-20221021185851957

image-20221021192523619

仔細分析后可以得知,func4函式這個遞回函式的代碼如下所示

int func4(int x, int a, int b)
{
    int num = b - a;
    num = (num + num >> 31) / 2;  // 31就是 0x1f
    int c = num + a;
    if (c <= x) {
    	if (c >= x) return 0;
        return 2 * func4(x, num+1, b) + 1;
    }
    return 2 * func4(x, a, num-1);
}

Phase5

考察點:字符陣列,回圈,ASCII,與運算

0000000000401062 <phase_5>:
401062:       53                      push   %rbx
401063:       48 83 ec 20             sub    $0x20,%rsp		# 開辟 32 位元組的堆疊空間
401067:       48 89 fb                mov    %rdi,%rbx		# rbx = rsi
40106a:       64 48 8b 04 25 28 00    mov    %fs:0x28,%rax	# %fs:0x28保存的是 輸入的值 
401071:       00 00
401073:       48 89 44 24 18          mov    %rax,0x18(%rsp) # rsp + 0x18 = rax(存放的是我們輸入的引數)
401078:       31 c0                   xor    %eax,%eax	# eax = 0
40107a:       e8 9c 02 00 00          callq  40131b <string_length> # 獲取輸入的字串長度(包括空格)
40107f:       83 f8 06                cmp    $0x6,%eax	# 如果輸入的字串長度不為6  會爆炸
401082:       74 4e                   je     4010d2 <phase_5+0x70> # 跳轉到 0x4010d2
401084:       e8 b1 03 00 00          callq  40143a <explode_bomb>
401089:       eb 47                   jmp    4010d2 <phase_5+0x70>
; 下面這段指令的含義:遍歷輸入字串的每一個字符,然后逐次將每個字符與0xf與操作,得到的值做為0x4024b0處字串的下標
40108b:       0f b6 0c 03             movzbl (%rbx,%rax,1),%ecx # movzbl零擴展指令 move zero byte to double word
; rax此時為0  ecx = rax + rbx (零擴展后再傳送)  一般用于使用小位元組變數給大位元組變數賦值
40108f:       88 0c 24                mov    %cl,(%rsp)  # M[rsp] = cl(ecx的低8位) = 0x31(1的ASCII碼)
401092:       48 8b 14 24             mov    (%rsp),%rdx # rdx = M[rsp] = 0x31
401096:       83 e2 0f                and    $0xf,%edx   # edx = edx & 1111 = 110001 & 1111 = 1
401099:       0f b6 92 b0 24 40 00    movzbl 0x4024b0(%rdx),%edx # edx = M[rdx + 0x4024b0] 根據上面與的結果去記憶體尋找
4010a0:       88 54 04 10             mov    %dl,0x10(%rsp,%rax,1) # 將edx的第八位存到后面指定的記憶體地址
4010a4:       48 83 c0 01             add    $0x1,%rax	# rax = rax + 1
4010a8:       48 83 f8 06             cmp    $0x6,%rax  # if(rax!=6) goto 40108b; else goto 4010ae  需要執行6次
4010ac:       75 dd                   jne    40108b <phase_5+0x29> # 回到上面繼續回圈
4010ae:       c6 44 24 16 00          movb   $0x0,0x16(%rsp) # 6次回圈結束后  執行到這里 M[rsp+0x16] = 0
4010b3:       be 5e 24 40 00          mov    $0x40245e,%esi  # 函式的引數
4010b8:       48 8d 7c 24 10          lea    0x10(%rsp),%rdi
4010bd:       e8 76 02 00 00          callq  401338 <strings_not_equal>
4010c2:       85 c0                   test   %eax,%eax
4010c4:       74 13                   je     4010d9 <phase_5+0x77>
4010c6:       e8 6f 03 00 00          callq  40143a <explode_bomb>
4010cb:       0f 1f 44 00 00          nopl   0x0(%rax,%rax,1)
4010d0:       eb 07                   jmp    4010d9 <phase_5+0x77>
4010d2:       b8 00 00 00 00          mov    $0x0,%eax  # eax = 0
4010d7:       eb b2                   jmp    40108b <phase_5+0x29> # 又跳轉到上面了
4010d9:       48 8b 44 24 18          mov    0x18(%rsp),%rax
4010de:       64 48 33 04 25 28 00    xor    %fs:0x28,%rax
4010e5:       00 00
4010e7:       74 05                   je     4010ee <phase_5+0x8c>
4010e9:       e8 42 fa ff ff          callq  400b30 <__stack_chk_fail@plt>
4010ee:       48 83 c4 20             add    $0x20,%rsp
4010f2:       5b                      pop    %rbx
4010f3:       c3                      retq

打開gdb進行動態除錯,首先看到我們輸入的長度為6的字串如下所示

image-20221022085546280

前面的指令都很簡單,我們直接看movzbl這條指令,是一個帶零擴展的資料傳送指令,在gdb中查看該指令是如下形式,明確給出了byte型別,此時rax = 0rbx = 0x6038c0(我們輸入的字串的地址),執行完這條指令后rcx = 0x31 = 49(1的ASCII碼)

image-20221022090910626

可以看到0x6038c0處存盤的內容如下,存盤的是我們輸入的123456的ASCII碼,

image-20221022091517491

這里還發現了python中對變數做and運算時的一些有意思的點,python中所有變數的位操作都是通過強制轉換成bool實作的,嚴格遵循短路邏輯,只有and,如果每個運算式都不為假,回傳第二個,只有or,從左往右有一個不為假就回傳這個值,

image-20221022092546230

下一條指令mov %cl,(%rsp)是將ecx暫存器的低八位賦值給M[rsp],存放到堆疊指標指向的地址,0x7f開頭的往往就是堆疊所在地址

image-20221022092742223

中間經過一些處理后此時rdx = 49 & 0xf = 1,然后又是一條零擴展指令movzbl 0x4024b0(%rdx),%edx,根據上面相與的結果取記憶體中尋找對應的值賦值給edx,可以看到這里是0x61,可以看到記憶體中存盤的字串為下面的maduiersnfotvbyl

image-20221022093111895

image-20221022094539847

接下來gdb中的指令更容易理解,mov byte ptr [rsp + rax + 0x10], dl,將0x61存盤到rsp+0x10開始的記憶體地址(即存盤變化后的字符到一個堆疊中新開辟的字符陣列里),rax此時仍為0,先查看未執行前,該地址存盤的數為:0x10,執行之后就變成了了0x61

image-20221022093507081

下面首先rax = rax + 1,然后判斷rax != 6的話回到上面回圈之前的操作,可知這里是一個6次的回圈,下一次回圈,rbx存盤的還是我們輸入的字串的地址,但是rax就變成1了,取到的字符由之前的0x31變為了0x32,直到遍歷完6個長度的字串

image-20221022093726972

總結一下,這一段回圈的意義是遍歷輸入的每一個字符,將每一個字符的ASCII碼與0xf相與,與后的結果作為索引去指定記憶體地址0x4024b0處找對應的字符存盤起來,回圈結束后,我們再往下看,下面就是傳遞引數,然后呼叫了strings_not_equal這個函式

該函式的第一個引數為我們輸入的字串的每一個字符與上0xf后作為索引去記憶體中找到的maduiersnfotvbyl這個字串的子串,第二個引數為記憶體中存盤的正確結果flyers,顯然我們需要讓這兩個字串相等,這樣這個函式才會回傳0,才會跳過下面的explode_bomb下面要做的就很清晰了,找到所有與上0xf后的索引為flyers0x4024b0為起始地址的索引表中的位置即可,索引依次為9 15 14 5 6 7,我們需要找到與上0xf后為以上索引的字符,x & 1111 = 1001 x = 1001001或者111001或者1111001,可以看出后四位即為索引,我們先嘗試第一個1001001(73),對應的輸入為IONEFG,第二種,對應的輸入為9?>567,第三種輸入y(112+15=127)不是可列印字符,

因此,關鍵步驟用C語言來寫就是

const char g_str[16] = "maduiersnfotvbyl";
void phase_5(char* input)
{
    char str[7];
	if (string_length(input) != 6) {
		explode_bomb();
	}
         // x & 0xf =  9 15 14 5 6 7
    	// I O N E F G 或 9 ? > 5 6 7
	for (int i = 0; i != 6; i++) {
        str[i] = g_str[input[i] & 0xf];
	}
    str[7] = '\0';
    if(string_not_equal(str, "flyers") != 0) {
        explode_bomb();
    }
}

至此,第五關也就過了

image-20221022102306862

Phase6

考察點:多重回圈,鏈表,結構體,eax比較數值時只會比較低32位,冗長的匯編

00000000004010f4 <phase_6>:
4010f4:       41 56                   push   %r14		
4010f6:       41 55                   push   %r13
4010f8:       41 54                   push   %r12
4010fa:       55                      push   %rbp
4010fb:       53                      push   %rbx
; 傳入引數  為read_six_numbers做準備
4010fc:       48 83 ec 50             sub    $0x50,%rsp  # 提供80位元組的堆疊空間
401100:       49 89 e5                mov    %rsp,%r13   # r13 = rsp
401103:       48 89 e6                mov    %rsp,%rsi   # rsi = rsp  第二個引數
401106:       e8 51 03 00 00          callq  40145c <read_six_numbers> # 讀取六個數字,這個函式在 p2 見過
40110b:       49 89 e6                mov    %rsp,%r14  # r14 = rsp 此時rsp根進去之前其實還是一樣的  所以r14=r13
40110e:       41 bc 00 00 00 00       mov    $0x0,%r12d # r12d = 0
401114:       4c 89 ed                mov    %r13,%rbp  # rbp = r13 = rsp
401117:       41 8b 45 00             mov    0x0(%r13),%eax # eax = M[r13] = M[rsp]  M[rsp]=0x200000001 因為eax為32位暫存器,只能存盤下來 0x200000001 的低4位元組 即 00000001  所以此時 eax = 0x1
40111b:       83 e8 01                sub    $0x1,%eax # eax = eax - 1
40111e:       83 f8 05                cmp    $0x5,%eax # eax需要 < 5
401121:       76 05                   jbe    401128 <phase_6+0x34>
401123:       e8 12 03 00 00          callq  40143a <explode_bomb>
401128:       41 83 c4 01             add    $0x1,%r12d # r12d += 1  每次回圈加1
40112c:       41 83 fc 06             cmp    $0x6,%r12d # 回圈終止條件 r12d = 6
401130:       74 21                   je     401153 <phase_6+0x5f>
401132:       44 89 e3                mov    %r12d,%ebx # rbx = r12d  回圈變數暫存到rbx中
401135:       48 63 c3                movslq %ebx,%rax # 符號位擴展,l->q 字到雙字, rax = ebx(符號位擴展) 正數用0
401138:       8b 04 84                mov    (%rsp,%rax,4),%eax
40113b:       39 45 00                cmp    %eax,0x0(%rbp)  
40113e:       75 05                   jne    401145 <phase_6+0x51>  # *rbp 不能等于 eax
401140:       e8 f5 02 00 00          callq  40143a <explode_bomb>
401145:       83 c3 01                add    $0x1,%ebx
401148:       83 fb 05                cmp    $0x5,%ebx
40114b:       7e e8                   jle    401135 <phase_6+0x41>  # ebx <= 5 繼續回圈
40114d:       49 83 c5 04             add    $0x4,%r13
401151:       eb c1                   jmp    401114 <phase_6+0x20>
; 上面是一個回圈
phase_6(rdi)  ; 我們輸入的字串傳入到 rdi 中
{
	r13 = rsp;
	rsi = rsp;
	read_six_numbers(rdi, rsi);  rdi 為我們輸入的字串,  rsi為 %%%%%%
	r14 = rsp;
	for (r12 = 0  r12 != 6  r12++) 
	{
		rbp = r13;
		eax = *r13;  去記憶體中找
		eax -= 1;
		if (eax > 5)
			explode_bomb();
		for (ebx = r12 + 1  ebx <= 5  ebx++)
		{
			rax = ebx;  符號位擴展  e->r  ebx為正數用0填充高位   為負數用1填充高位
			eax = *(rsp + rax * 44);
			if (*rbp == eax)
				explode_bomb();
		}
		r13 += 4;
	}
}
401153:       48 8d 74 24 18          lea    0x18(%rsp),%rsi
401158:       4c 89 f0                mov    %r14,%rax
40115b:       b9 07 00 00 00          mov    $0x7,%ecx
401160:       89 ca                   mov    %ecx,%edx
401162:       2b 10                   sub    (%rax),%edx
401164:       89 10                   mov    %edx,(%rax)
401166:       48 83 c0 04             add    $0x4,%rax
40116a:       48 39 f0                cmp    %rsi,%rax  # rax != rsi 的話繼續回圈
40116d:       75 f1                   jne    401160 <phase_6+0x6c>
; 這里也是一個回圈  單獨寫在這里  
rsi = rsp + 0x18;
rax = r14;
ecx = 0x7;
for (rax = r14  rax != rsi  rax += 4)
{
	edx = ecx;
	edx = edx - *rax;
	*rax = edx;
}
40116f:       be 00 00 00 00          mov    $0x0,%esi
401174:       eb 21                   jmp    401197 <phase_6+0xa3>
401176:       48 8b 52 08             mov    0x8(%rdx),%rdx
40117a:       83 c0 01                add    $0x1,%eax
40117d:       39 c8                   cmp    %ecx,%eax
40117f:       75 f5                   jne    401176 <phase_6+0x82>
401181:       eb 05                   jmp    401188 <phase_6+0x94>
401183:       ba d0 32 60 00          mov    $0x6032d0,%edx # ebx = 0x6032d0
401188:       48 89 54 74 20          mov    %rdx,0x20(%rsp,%rsi,2) # *(rsp + rsi*2) = rdx
40118d:       48 83 c6 04             add    $0x4,%rsi  # rsi += 4
401191:       48 83 fe 18             cmp    $0x18,%rsi 
401195:       74 14                   je     4011ab <phase_6+0xb7> # rsi = 0x18的話 就跳到下面了
;	因為這是最外層開始的回圈  所以也可以通過這條陳述句跳轉到的地址確定本次回圈的層數,即最內層回圈的陳述句在哪里結束
401197:       8b 0c 34                mov    (%rsp,%rsi,1),%ecx
40119a:       83 f9 01                cmp    $0x1,%ecx  # ecx <= 1
40119d:       7e e4                   jle    401183 <phase_6+0x8f>
40119f:       b8 01 00 00 00          mov    $0x1,%eax
4011a4:       ba d0 32 60 00          mov    $0x6032d0,%edx
4011a9:       eb cb                   jmp    401176 <phase_6+0x82>
; 小tips  怎么看回圈到哪里結束呢    找下面最遠的跳到上面的指令往往就是最內層回圈
for (esi = 0  rsi != 0x18  rsi += 4)
{
	ecx = *(rsp + rsi);
	if (ecx <= 1)
	{
		edx = 0x6032d0;
		*(rsp + rsi * 2 + 0x20) = rdx;  這句話兩個分支 都會跳轉到那里執行
	}
	else 
	{
		edx = 0x6032d0;
		for (eax = 1  eax != ecx  eax++)
		{
			rdx = *(rdx + 8);
		}
		*(rsp + rsi * 2 + 0x20) = rdx;
	}
}
4011ab:       48 8b 5c 24 20          mov    0x20(%rsp),%rbx
4011b0:       48 8d 44 24 28          lea    0x28(%rsp),%rax
4011b5:       48 8d 74 24 50          lea    0x50(%rsp),%rsi
4011ba:       48 89 d9                mov    %rbx,%rcx
4011bd:       48 8b 10                mov    (%rax),%rdx
4011c0:       48 89 51 08             mov    %rdx,0x8(%rcx)
4011c4:       48 83 c0 08             add    $0x8,%rax
4011c8:       48 39 f0                cmp    %rsi,%rax
4011cb:       74 05                   je     4011d2 <phase_6+0xde>
4011cd:       48 89 d1                mov    %rdx,%rcx
4011d0:       eb eb                   jmp    4011bd <phase_6+0xc9>
; 又是一個回圈
rbx = *(rsp + 0x20);
rsi = rsp + 0x50;
rcx = rbx;
for (rax = rsp + 0x28  rax != rsi  rax += 8)
{
	rdx = *rax;
	*(rcx + 0x8) = rdx;
	rcx = rdx;
}
4011d2:       48 c7 42 08 00 00 00    movq   $0x0,0x8(%rdx)
4011d9:       00
4011da:       bd 05 00 00 00          mov    $0x5,%ebp
4011df:       48 8b 43 08             mov    0x8(%rbx),%rax
4011e3:       8b 00                   mov    (%rax),%eax
4011e5:       39 03                   cmp    %eax,(%rbx)  # *rbx 需要大于 eax 
4011e7:       7d 05                   jge    4011ee <phase_6+0xfa>
4011e9:       e8 4c 02 00 00          callq  40143a <explode_bomb>
4011ee:       48 8b 5b 08             mov    0x8(%rbx),%rbx
4011f2:       83 ed 01                sub    $0x1,%ebp
4011f5:       75 e8                   jne    4011df <phase_6+0xeb>
4011f7:       48 83 c4 50             add    $0x50,%rsp
4011fb:       5b                      pop    %rbx
4011fc:       5d                      pop    %rbp
4011fd:       41 5c                   pop    %r12
4011ff:       41 5d                   pop    %r13
401201:       41 5e                   pop    %r14
401203:       c3                      retq
*(rdx + 0x8) = 0;
for (ebp = 0x5  ebp != 0x1  ebp -= 1)
{
	rax = *(rbx + 0x8);
	eax = *rax;
	if (*rbx < eax)
		explode_bomb();
	rbx = *(rbx + 0x8);
}

代碼太長,這里我考慮直接用gdb分析,前面入堆疊的六個暫存器的值如下圖所示

image-20221022165542351

前面的指令沒什么好說的,注意此時r13rsi中保存的都是堆疊指標rsp的內容,除錯到呼叫read_six之前,這個函式需要兩個引數,第一個是我們輸入的字串,存盤在暫存器rdi中,第二個回傳值的6個int型元素陣列的首地址,存盤在暫存器rsi中,

image-20221022170328819

這個函式內部同樣呼叫了sscanf,在次就不再詳細展開

image-20221022170245086

從該函式退出之后,mov r14, rsprsp的值又賦給了r14,注意rsp進入read_six函式之后又回來堆疊是被平衡了的,所以此時 r13 = r14 = rsp,下一步是mov r12d, 0,很有意思,r12dr12暫存器的??,接著將r13中保存的rsp地址又傳給了rbp,將M[rsp]傳給了eax,這里要注意,M[rsp] = 0x200000001,但是eax暫存器是32位暫存器,只能存盤低四個位元組,即0x00000001,之后eax -= 1 變成了0

整理一下匯編代碼,其對應的C風格如下,分成了以空行間隔的五段代碼,

phase_6(rdi)  //  我們輸入的字串傳入到 rdi 中
{
	r13 = rsp;
	rsi = rsp;
	read_six_numbers(rdi, rsi); //  rdi 為我們輸入的字串,  rsi為 %%%%%%
	r14 = rsp;
    // 總結一下  這個回圈的含義:輸入6個1-6的數,且不能重復
	for (r12 = 0;  r12 != 6;  r12++)   // r12d猜測應該是 r12 的低32位
	{
		rbp = r13; // 這里 rbp = r13 =rsp  rsp 存盤的就是我們輸入的字串格式化后的數字
		eax = *r13;  // 去記憶體中找  第一次 eax = 1  第二次eax= 2
		eax -= 1;  // eax -= 1 = 0   這里限制了輸入的數字必須為 1-6
		if (eax > 5)
			explode_bomb();
		for (ebx = r12 + 1;  ebx <= 5;  ebx++)  // 初始 ebx = r12+1 = 1
		{
			rax = ebx;  // 符號位擴展  e->r  ebx為正數用0填充高位   為負數用1填充高位  rax = ebx 這里是整數 rax = 0x1
			eax = *(rsp + rax * 44);  // eax = *(rsp + i * 4)  依次遍歷 1 2 3 4 5 6  初始rax=1 所以eax = 2
           // 之后 *rbp 是不變的,始終是1  但是 eax 會依次遍歷所有 2 3 4 5 6  都不會相等 所以最終ebx = 5  eax=6退出回圈
			if (*rbp == eax)   // 2 != 1(*rbp)
				explode_bomb();
		}
		r13 += 4; // r13 = rsp + 4   相當于下一次回圈 rbp + 4    取下一個數判斷是否有與它相同的數
	}
    // 下面這段回圈的含義:將 a[i] 變為 7 - a[i] 存盤到原先a[i]所在的位置  即 esp + 4*i
    rsi = rsp + 0x18;  // 剛好是我們輸入的6個字符的下一個位置   24個位元組   其實是我們輸入的字符陣列的 '\0'
    rax = r14;  // rax = r14 = rsp
    ecx = 0x7;
    for (rax = r14;  rax != rsi;  rax += 4)  // 遍歷所有字符陣列
    {
        edx = ecx;               // edx = 7
        edx = edx - *rax;		// edx = 7 - a[i]   同樣也是 1-6 的數
        *rax = edx;				// *rax = rdx  存回記憶體   
    }
    // 下面含義:
    for (esi = 0; rsi != 0x18;  rsi += 4)  // 遍歷所有字符陣列   0x18很明顯 遍歷7次 剛好到'\0'結束回圈
    {
        ecx = *(rsp + rsi);  // 取出對應的字符陣列的值  輸入的是123456  經過上面變換后成了 654321
        if (ecx <= 1) // 只有 輸入的為 6 時才會執行
        {
            edx = 0x6032d0;  // 此地址處是一個結構體
            // 下面的含義:將edx存盤的結構體資訊  存盤到rsp + rsi * 2 + 0x20 處的地址  就是 rsp + 8*i + 0x20
            //*(rsp + rsi * 2 + 0x20) = rdx;  // 這句話  無論進入哪個分支都會跳轉到那里執行  可以寫到外面
        }
        else  // 只要 輸入的 不為6
        {
            edx = 0x6032d0;  // 與上面一樣
            for (eax = 1;  eax != ecx;  eax++) // 第一個 ecx = 6 回圈6次 最終7 - 1存盤到了 node6
            {  // 回圈一次  對應node1   回圈兩次 對應node2  即  node{7-a[i]}
                rdx = *(rdx + 8);  // 從 6032d0(node1) -> 6032e0(node2) 
            }
            //*(rsp + rsi * 2 + 0x20) = rdx;  
        }
        *(rsp + rsi * 2 + 0x20) = rdx; // 第一次  rcx=7-1=6  rdx指向node6  rsp+0x20 = node6
    }
    // 這段好像沒什么用
    rbx = *(rsp + 0x20);  // 距離堆疊指標最近的 node  對應輸入的第一個數  node的編號即為 7-a[i]  node6
    rsi = rsp + 0x50; // node 的結束地址
    rcx = rbx;  // 保存輸入
    for (rax = rsp + 0x28;  rax != rsi;  rax += 8)  // rax 從 第二個node 開始遍歷    node5 
    {
        // 典型的交換操作
        rdx = *rax; // 暫存遍歷到的node  rdx = node5    rdx = node4
        *(rcx + 0x8) = rdx; 		// node5 = node6  node4=node5
        rcx = rdx;				    // node6 = node5
    }
    // 分析: node[7-input[i]]->data >= node[7-input[i+1]]->data
    *(rdx + 0x8) = 0; // 此時 rdx 保存最后一個node 
    for (ebp = 0x5;  ebp != 0x1;  ebp -= 1) // 回圈5次
    {
        rax = *(rbx + 0x8); // rbx 仍指向距離堆疊指標最近的node   rax = node
        eax = *rax; // 取出node的值(注意eax,取得是低32位)   只看低32位 node的大小順序為 3 4 5 6 1 2
        if (*rbx < eax) // 如果第一個node的值小于下一個  就會爆炸  所以需要保證輸入的數對應的node是降序排列在堆疊中的
        // 即 node6 node5 .. node1  只看低32位 node的大小順序為 3 4 5 6 1 2  所以我們輸入的應該為 4 3 2 1 6 5 (7-a)
            explode_bomb();
        rbx = *(rbx + 0x8);
    }
}

首先確認我們輸入的資料的位置,可以看到在rsp rsp + 4處依次存放著格式化后的數字 1 2 ..,然后根據gdb看上面的分析即可

image-20221022203539484

到第三段代碼時,可以看到程式會將edx 設定為0x6032d0,查看該地址處資訊可知,猜測這里應該是一個結構體node1,在gdb中也明確地告訴了我們

image-20221022212435575

image-20221022213252560

執行完mov rdx, qword ptr [rdx + 8]這條指令后,rdx存盤的內容由 node1變成了node2,接著回圈又會變成node3node4一直到node,然后執行qword ptr [rsp + rsi*2 + 0x20], rdx指令,將node6存盤到了堆疊上我們輸入的字串的上面,同樣,回圈6次,找到每一個變化后的 7- a[i] 對應的node,并存盤到堆疊的對應位置,

image-20221022214023503

回圈結束后,可以看到堆疊的情況(右側的數字即為node->data):

image-20221022214652857

查看六個node結構體的資訊

image-20221022221221892

經過分析,得知最后一段代碼的作用是將結構體的data按非升序排列,每一個node的data如上圖所示,注意我們比較時用的是eax來存盤結構體node->data,只能存盤低32位,所以按node->data的低32位排序,可以得到降序排列為node 3,4,5,6,1,2,而7-input[i]剛好與node的編號是一一對應的,所以我們的輸入為4 3 2 1 6 5, 終于完成了??

image-20221022222615380

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/518910.html

標籤:其他

上一篇:痞子衡嵌入式:i.MXRT中FlexSPI外設不常用的讀選通采樣時鐘源 - loopbackFromSckPad

下一篇:docker swarm快速部署redis分布式集群

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more