首页
社区
课程
招聘
[原创] 看雪.京东 2018CTF 第六题 PWN-noheap writeup
发表于: 2018-6-27 21:13 3302

[原创] 看雪.京东 2018CTF 第六题 PWN-noheap writeup

2018-6-27 21:13
3302

1.先检查一下

所有保护全开。
2.反调试
int sub_E87()
{
  puts(
    " _____   _____   _____   _  __    __  _____   _____   ___   _____  \n"
    "|  _  \\ | ____| |  _  \\ | | \\ \\  / / /___  \\ /  _  \\ |_  | /  _  \\ \n"
    "| |_| | | |__   | | | | | |  \\ \\/ /   ___| | | | | |   | | | |_| | \n"
    "|  ___/ |  __|  | | | | | |   \\  /   /  ___/ | |/| |   | | }  _  { \n"
    "| |     | |___  | |_| | | |   / /    | |___  | |_| |   | | | |_| | \n"
    "|_|     |_____| |_____/ |_|  /_/     |_____| \\_____/   |_| \\_____/ \n");
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  alarm(0x3Cu);      //这个影响调试  直接用90全patch掉
  puts("Welcome !");
  return puts("=======================================================================");
}

int sub_E87()
{
  puts(
    " _____   _____   _____   _  __    __  _____   _____   ___   _____  \n"
    "|  _  \\ | ____| |  _  \\ | | \\ \\  / / /___  \\ /  _  \\ |_  | /  _  \\ \n"
    "| |_| | | |__   | | | | | |  \\ \\/ /   ___| | | | | |   | | | |_| | \n"
    "|  ___/ |  __|  | | | | | |   \\  /   /  ___/ | |/| |   | | }  _  { \n"
    "| |     | |___  | |_| | | |   / /    | |___  | |_| |   | | | |_| | \n"
    "|_|     |_____| |_____/ |_|  /_/     |_____| \\_____/   |_| \\_____/ \n");
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  alarm(0x3Cu);      //这个影响调试  直接用90全patch掉
  puts("Welcome !");
  return puts("=======================================================================");
}

3.第一关
unsigned __int64 sub_F0C()
{
  unsigned __int8 buf; // [rsp+Fh] [rbp-51h]
  unsigned int v2; // [rsp+10h] [rbp-50h]
  int i; // [rsp+14h] [rbp-4Ch]
  int fd; // [rsp+18h] [rbp-48h]
  unsigned int v5; // [rsp+1Ch] [rbp-44h]
  char s1[8]; // [rsp+20h] [rbp-40h]
  char v7; // [rsp+28h] [rbp-38h]
  char s2[8]; // [rsp+30h] [rbp-30h]
  char v9; // [rsp+38h] [rbp-28h]
  unsigned __int64 v10; // [rsp+48h] [rbp-18h]

  v10 = __readfsqword(0x28u);
  *(_QWORD *)s1 = 0LL;
  v7 = 0;
  *(_QWORD *)s2 = 0LL;
  v9 = 0;
  v2 = 0;
  fd = open("/dev/urandom", 0);
  for ( i = 0; i <= 3; ++i )
  {
    read(fd, &buf, 1uLL);
    v2 = (v2 << 8) + buf % 0x2Bu + 48;
  }
  *(_DWORD *)s1 = sub_108A(&v2);
  *(_DWORD *)&s1[4] = sub_108A(&v2);
  v5 = sub_10B2(s1, 8LL);
  printf("Hash:%08x\n", v5);
  printf("Input:");
  sub_14F0(s2, 9LL);
  close(fd);
  if ( strcmp(s1, s2) )
    exit(0);
  puts("=======================================================================");
  return __readfsqword(0x28u) ^ v10;
}
从/dev/urandom 循环读取四字节,通过一定运算组成一个DWORD数值v2.
unsigned __int64 sub_F0C()
{
  unsigned __int8 buf; // [rsp+Fh] [rbp-51h]
  unsigned int v2; // [rsp+10h] [rbp-50h]
  int i; // [rsp+14h] [rbp-4Ch]
  int fd; // [rsp+18h] [rbp-48h]
  unsigned int v5; // [rsp+1Ch] [rbp-44h]
  char s1[8]; // [rsp+20h] [rbp-40h]
  char v7; // [rsp+28h] [rbp-38h]
  char s2[8]; // [rsp+30h] [rbp-30h]
  char v9; // [rsp+38h] [rbp-28h]
  unsigned __int64 v10; // [rsp+48h] [rbp-18h]

  v10 = __readfsqword(0x28u);
  *(_QWORD *)s1 = 0LL;
  v7 = 0;
  *(_QWORD *)s2 = 0LL;
  v9 = 0;
  v2 = 0;
  fd = open("/dev/urandom", 0);
  for ( i = 0; i <= 3; ++i )
  {
    read(fd, &buf, 1uLL);
    v2 = (v2 << 8) + buf % 0x2Bu + 48;
  }
  *(_DWORD *)s1 = sub_108A(&v2);
  *(_DWORD *)&s1[4] = sub_108A(&v2);
  v5 = sub_10B2(s1, 8LL);
  printf("Hash:%08x\n", v5);
  printf("Input:");
  sub_14F0(s2, 9LL);
  close(fd);
  if ( strcmp(s1, s2) )
    exit(0);
  puts("=======================================================================");
  return __readfsqword(0x28u) ^ v10;
}
从/dev/urandom 循环读取四字节,通过一定运算组成一个DWORD数值v2.
通过函数
__int64 __fastcall sub_108A(unsigned int *a1)
{
  *a1 = 214013 * *a1 + 2531011;
  return *a1;
}
构造一个8个字节的字符串。
__int64 __fastcall sub_108A(unsigned int *a1)
{
  *a1 = 214013 * *a1 + 2531011;
  return *a1;
}
构造一个8个字节的字符串。
IDA反编译的有点问题,其实第二次调用sub_108A时传入的事第一次调用的返回值。
接着利用sub_10b2计算一个hash,并输出出来。
__int64 __fastcall sub_10B2(unsigned __int8 *a1, int a2)
{
  unsigned __int8 *v2; // rax
  int v4; // [rsp+0h] [rbp-1Ch]
  unsigned __int8 *v5; // [rsp+4h] [rbp-18h]
  unsigned int v6; // [rsp+14h] [rbp-8h]

  v5 = a1;
  v4 = a2;
  v6 = 0;
  do
  {
    v2 = v5++;
    v6 = 131 * v6 + *v2;
    --v4;
  }
  while ( v4 > 0 );
  return v6;
}
再接着要求我们输入一个8字节的字符串,要求跟上边的s1一致。
__int64 __fastcall sub_10B2(unsigned __int8 *a1, int a2)
{
  unsigned __int8 *v2; // rax
  int v4; // [rsp+0h] [rbp-1Ch]
  unsigned __int8 *v5; // [rsp+4h] [rbp-18h]
  unsigned int v6; // [rsp+14h] [rbp-8h]

  v5 = a1;
  v4 = a2;
  v6 = 0;
  do
  {
    v2 = v5++;
    v6 = 131 * v6 + *v2;
    --v4;
  }
  while ( v4 > 0 );
  return v6;
}
再接着要求我们输入一个8字节的字符串,要求跟上边的s1一致。
这关只能爆破。
从v2的生成代码(v2 = (v2 << 8) + buf % 0x2Bu + 48;)可以看出,v2的每个字节都介于[48+0,48+0x2B)之间。
利用此特性,可获取到hash值之后,爆破出v2的值,进而获取到s1字符串。
爆破代码如下:
import struct
import binascii
import time

def time_me(fn):
    def _wrapper(*args, **kwargs):
        start = time.clock()
        ret=fn(*args, **kwargs)
        print "%s cost %s second"%(fn.__name__, time.clock() - start)
        return ret
    return _wrapper

def calc_1(v2):
    return (214013*v2+2531011)&0xffffffff

def mk_string(v2):
    v2_ret=calc_1(v2)
    #print hex(v2_ret)
    s=""
    s+=struct.pack("I",v2_ret)
    v2_ret=calc_1(v2_ret)
    #print hex(v2_ret)
    s+=struct.pack("I",v2_ret)
    #print binascii.b2a_hex(s)
    return s

    
def calc_2(v2_str):
    #print "fuck"
    v6=0
    for x in v2_str:
        v6=(0x83*v6+ord(x))&0xffffffff
        #print hex(v6)
    #print hex(v6)
    return v6

def calc(v2):
    v2_str=mk_string(v2)
    v2_ret=calc_2(v2_str)
    return v2_ret,v2_str
#print calc(0x5a314f44)

@time_me
def Crack(in_value):
    ret_str=""
    for i1 in range(0,0x2b):
        for i2 in range(0,0x2b):
            for i3 in range(0,0x2b):
                for i4 in range(0,0x2b):
                    v2=((i1+0x30)<<24)+((i2+0x30)<<16)+((i3+0x30)<<8)+((i4+0x30))
                    #print hex(v2)
                    v2_ret,v2_str=calc(v2)
                    #print hex(i),v2_ret,v2_str
                    if v2_ret==in_value:
                        print v2_str,binascii.b2a_hex(v2_str)
                        ret_str=v2_str
                        break
    return ret_str
此算法爆破一个值大约需要7-10s.
import struct
import binascii
import time

def time_me(fn):
    def _wrapper(*args, **kwargs):
        start = time.clock()
        ret=fn(*args, **kwargs)
        print "%s cost %s second"%(fn.__name__, time.clock() - start)
        return ret
    return _wrapper

def calc_1(v2):
    return (214013*v2+2531011)&0xffffffff

def mk_string(v2):
    v2_ret=calc_1(v2)
    #print hex(v2_ret)
    s=""
    s+=struct.pack("I",v2_ret)
    v2_ret=calc_1(v2_ret)
    #print hex(v2_ret)
    s+=struct.pack("I",v2_ret)
    #print binascii.b2a_hex(s)
    return s

    
def calc_2(v2_str):
    #print "fuck"
    v6=0
    for x in v2_str:
        v6=(0x83*v6+ord(x))&0xffffffff
        #print hex(v6)
    #print hex(v6)
    return v6

def calc(v2):
    v2_str=mk_string(v2)
    v2_ret=calc_2(v2_str)
    return v2_ret,v2_str
#print calc(0x5a314f44)

@time_me
def Crack(in_value):
    ret_str=""
    for i1 in range(0,0x2b):
        for i2 in range(0,0x2b):
            for i3 in range(0,0x2b):
                for i4 in range(0,0x2b):
                    v2=((i1+0x30)<<24)+((i2+0x30)<<16)+((i3+0x30)<<8)+((i4+0x30))
                    #print hex(v2)
                    v2_ret,v2_str=calc(v2)
                    #print hex(i),v2_ret,v2_str
                    if v2_ret==in_value:
                        print v2_str,binascii.b2a_hex(v2_str)
                        ret_str=v2_str
                        break
    return ret_str
此算法爆破一个值大约需要7-10s.

4.第二关
__int64 sub_1470()
{
  __int64 result; // rax
  __int64 v1; // ST38_8
  __int64 v2; // ST90_8
  unsigned int v3; // [rsp+74h] [rbp-4h]

  Menu_1591();
  v3 = read_1547();
  result = v3;
  if ( v3 )
  {
    result = v3;
    if ( v3 <= 3 )
    {
      v1 = qword_2030C0[~((unsigned __int8)v3 - 1LL) - 7] ^ qword_2030C0[~((unsigned __int8)v3 - 1LL) - 2];
      v2 = qword_2030C0[-11] ^ qword_2030C0[-6];
      JUMPOUT(__CS__, qword_2030C0[-7] ^ qword_2030C0[-12]);
    }
  }
  return result;
}
显示菜单并读取出来用户输入选项之后就会进入虚拟机。
__int64 sub_1470()
{
  __int64 result; // rax
  __int64 v1; // ST38_8
  __int64 v2; // ST90_8
  unsigned int v3; // [rsp+74h] [rbp-4h]

  Menu_1591();
  v3 = read_1547();
  result = v3;
  if ( v3 )
  {
    result = v3;
    if ( v3 <= 3 )
    {
      v1 = qword_2030C0[~((unsigned __int8)v3 - 1LL) - 7] ^ qword_2030C0[~((unsigned __int8)v3 - 1LL) - 2];
      v2 = qword_2030C0[-11] ^ qword_2030C0[-6];
      JUMPOUT(__CS__, qword_2030C0[-7] ^ qword_2030C0[-12]);
    }
  }
  return result;
}
显示菜单并读取出来用户输入选项之后就会进入虚拟机。
虚拟机先不说,先说三个选项。

1.Malloc函数

主要的漏洞就在这个函数。
2.show

3.free

看完这三个函数,刚开始没有发现读取数据时会覆盖vm_code,还以为是house_of_force。https://ctf-wiki.github.io/ctf-wiki/pwn/heap/house_of_force/
但是此题中qword_203240处只能保存最后一次申请的空间,没办法形成此利用提交。
郁闷了好久想起来了之前的比赛,此题出题思路应该是跟 看雪.Wifi万能钥匙 2017CTF年中赛 第九题 Silence Server

https://ctf.pediy.com/game-fight-39.htm 
是一样的似乎,要利用虚拟机进行操作。
虚拟机代码:

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2018-6-27 21:15 被lacoucou编辑 ,原因:
收藏
免费 1
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//