lab2--01

lab2–01

正式进入lab2之前需要一些铺垫知识

铺垫知识----数据段、代码段、堆栈段、BSS段的区别

linxu中段的管理

在Linux下内存分配是以页为单位的,而页是通过段管理,各个段之间是独立的,方便管理。一个简单的程序被编译成目标文件后的结构如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uhfsog1a-1617863705778)(https://ivanzz1001.github.io/records/assets/img/cplusplus/cpp_segment_example.jpg)]
从上图可以看出,已经初始化的全局变量int global_init_var=84;和已经初始化的局部静态变量static int static_var=85;保存在了.data段,未初始化的全局变量和未初始化的局部变量保存在了.bss段。
目标文件各个段在文件中的布局如下:
2
现在开始说说上图各个段

  • init段

程序初始化的入口代码,在main()之前运行。解释一下,main()是主函数,进入main()就意味着初始化已经结束,开始运行了。

  • bss段

bss段属于静态内存分配。通常用来存放程序中未初始化的全局变量和未初始化的局部静态变量。未初始化的全局变量一般会在函数体(包括main())之外,作为全局的未初始化的变量,默认值是0;未初始化的局部静态变量,一般会有static类型。
在程序运行时,才会给bss段里面的变量分配内存空间。
section table中保存了bss段内存空间大小总和。可以通过objdump -h *.o查看

  • data段

数据段存放已经初始化的全局变量和已经初始化的静态变量的一块内存区域。数据段属于静态内存分配。

  • text段

代码段(code segment / text segment)通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

  • rodata

存放的是只读数据,比如字符串常量,全局const变量和#define定义的常量。本段又称为常量区。例如:

char *p = "123456";

这里的123456 就存放在rodata段中。

  • strtab段

存储的是变量名、函数名等。例如:

char *szPath = "/root";
void func();

上述变量名szPath和函数名func存储在strtab段里。

  • rel.text段

针对text段的重定位表,还有rel.data(针对data段的重定位表)

  • heap堆

堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc()等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free()等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。

  • stack栈

是用户存放程序临时创建的局部变量,也就是说我们函数括弧{}中定义的变量(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把栈看成一个寄存、交换临时数据的内存区。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D834NzbS-1617863705782)(https://ivanzz1001.github.io/records/assets/img/cplusplus/cpp_memory_layout.jpg)]

举例

示例代码
#include <stdio.h>

int a_text;
const int b_test = 2;
static int c_test = 3;
static int d_test;
int e_test = 0;

char *p_test = "hello,world";
const char *q_test = "good";

int main(int argc, char *argv[]){
    static int m_test;
    static int n_test = 0;
    
    char *s_test = "just for test";
    
    printf("a:%d\n", a_test);
    printf("b: %d\n", b_test);
    printf("c: %d\n", c_test);
    printf("d: %d\n", d_test);
    printf("e: %d\n", e_test);

    printf("p: %s\n", p_test);
    printf("q: %s\n", q_test);

    printf("m: %d\n", m_test);
    printf("n: %d\n", n_test);
    printf("s: %s\n", s_test);

    return 0x0;
}

通过下面的命令逐步编译运行上面test.c程序:

# gcc -E -o test.s test.c ; 预处理
# gcc -S -o test.s test.c ;编译为汇编代码
# gcc -c -o test.o test.s ;编译为目标文件test.o
# gcc -o test test.o      ;链接,生成可执行文件test
# ls
test test.c test.o test.s
查看test可执行文件的布局
# objdump -h test

test:     file format elf32-i386

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .interp       00000013  08048154  08048154  00000154  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  08048168  08048168  00000168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  08048188  08048188  00000188  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000020  080481ac  080481ac  000001ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000050  080481cc  080481cc  000001cc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       0000004c  0804821c  0804821c  0000021c  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  0000000a  08048268  08048268  00000268  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000020  08048274  08048274  00000274  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rel.dyn      00000008  08048294  08048294  00000294  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rel.plt      00000010  0804829c  0804829c  0000029c  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         00000023  080482ac  080482ac  000002ac  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000030  080482d0  080482d0  000002d0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .plt.got      00000008  08048300  08048300  00000300  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .text         00000262  08048310  08048310  00000310  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .fini         00000014  08048574  08048574  00000574  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 15 .rodata       00000071  08048588  08048588  00000588  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame_hdr 0000002c  080485fc  080485fc  000005fc  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .eh_frame     000000cc  08048628  08048628  00000628  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 18 .init_array   00000004  08049f08  08049f08  00000f08  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 19 .fini_array   00000004  08049f0c  08049f0c  00000f0c  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 20 .jcr          00000004  08049f10  08049f10  00000f10  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 21 .dynamic      000000e8  08049f14  08049f14  00000f14  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 22 .got          00000004  08049ffc  08049ffc  00000ffc  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 23 .got.plt      00000014  0804a000  0804a000  00001000  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 24 .data         00000014  0804a014  0804a014  00001014  2**2
                  CONTENTS, ALLOC, LOAD, DATA
 25 .bss          00000018  0804a028  0804a028  00001028  2**2
                  ALLOC
 26 .comment      00000035  00000000  00000000  00001028  2**0
                  CONTENTS, READONLY
  • 1) VMA和LMA

我们先简要介绍一下VMALMA这两个字段:

VMA(virtual memory address): 程序区段在执行时期的地址

LMA(load memory address): 某程序区段加载时的地址。因为我们知道程序运行前要经过:编译、链接、装载、运行等过程。装载到哪里呢? 没错,就是LMA对应的地址里。

一般情况下,LMAVMA都是相等的,不等的情况主要发生在一些嵌入式系统上。

  • 2) segment布局

如下我们简要的画出各区段的一个布局:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p8zfoQQP-1617863705783)(https://ivanzz1001.github.io/records/assets/img/cplusplus/cpp_lma_vma.jpg)]
其实这里我们看到尽管bss段长度为0x00000018,但是我们看到.bss段和.comment段在文件中的偏移是一样的,这就说明bss段数据是不用包含在可执行程序中的。

汇编文件test.s文件
# cat test.s
        .file   "test.c"
        .comm   a_test,4,4
        .globl  b_test
        .section        .rodata
        .align 4
        .type   b_test, @object
        .size   b_test, 4
b_test:
        .long   2
        .data
        .align 4
        .type   c_test, @object
        .size   c_test, 4
c_test:
        .long   3
        .local  d_test
        .comm   d_test,4,4
        .globl  e_test
        .bss
        .align 4
        .type   e_test, @object
        .size   e_test, 4
e_test:
        .zero   4
        .globl  p_test
        .section        .rodata
.LC0:
        .string "hello,world"
        .data
        .align 4
        .type   p_test, @object
        .size   p_test, 4
p_test:
        .long   .LC0
        .globl  q_test
        .section        .rodata
.LC1:
        .string "good"
        .data
        .align 4
        .type   q_test, @object
        .size   q_test, 4
q_test:
        .long   .LC1
        .section        .rodata
.LC2:
        .string "just for test"
.LC3:
        .string "a: %d\n"
.LC4:
        .string "b: %d\n"
.LC5:
        .string "c: %d\n"
.LC6:
        .string "d: %d\n"
.LC7:
        .string "e: %d\n"
.LC8:
        .string "p: %s\n"
.LC9:
        .string "q: %s\n"
.LC10:
        .string "m: %d\n"
.LC11:
        .string "n: %d\n"
.LC12:
        .string "s: %s\n"
        .text
        .globl  main
        .type   main, @function
main:
.LFB0:
        .cfi_startproc
        leal    4(%esp), %ecx
        .cfi_def_cfa 1, 0
        andl    $-16, %esp
        pushl   -4(%ecx)
        pushl   %ebp
        .cfi_escape 0x10,0x5,0x2,0x75,0
        movl    %esp, %ebp
        pushl   %ecx
        .cfi_escape 0xf,0x3,0x75,0x7c,0x6
        subl    $20, %esp
        movl    $.LC2, -12(%ebp)
        movl    a_test, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC3
        call    printf
        addl    $16, %esp
        movl    $2, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC4
        call    printf
        addl    $16, %esp
        movl    c_test, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC5
        call    printf
        addl    $16, %esp
        movl    d_test, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC6
        call    printf
        addl    $16, %esp
        movl    e_test, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC7
        call    printf
        addl    $16, %esp
        movl    p_test, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC8
        call    printf
        addl    $16, %esp
        movl    q_test, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC9
        call    printf
        addl    $16, %esp
        movl    m_test.1942, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC10
        call    printf
        addl    $16, %esp
        movl    n_test.1943, %eax
        subl    $8, %esp
        pushl   %eax
        pushl   $.LC11
        call    printf
        addl    $16, %esp
        subl    $8, %esp
        pushl   -12(%ebp)
        pushl   $.LC12
        call    printf
        addl    $16, %esp
        movl    $0, %eax
        movl    -4(%ebp), %ecx
        .cfi_def_cfa 1, 0
        leave
        .cfi_restore 5
        leal    -4(%ecx), %esp
        .cfi_def_cfa 4, 4
        ret
        .cfi_endproc
.LFE0:
        .size   main, .-main
        .local  m_test.1942
        .comm   m_test.1942,4,4
        .local  n_test.1943
        .comm   n_test.1943,4,4
        .ident  "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.10) 5.4.0 20160609"
        .section        .note.GNU-stack,"",@progbits

在讲解各变量存放位置之前,我们先简要介绍一下.comm.lcomm:

  • comm: 声明为未初始化的通用内存区域;

  • lcomm: 声明为未初始化的本地内存区域;

其实上述两个均是属于bss段,不过作用域范围有些不同。.lcomm是为不会从本地汇编代码之外进行访问的数据保留的。两者使用的格式为:

.comm symbol, length
.lcomm symbol, length

例如:

.section .bss
.lcomm buffer, 1000

该语句把1000字节的内存地址赋予标签buffer,在声明本地通用内存区域的程序之外的函数是不能访问他们的。

在bss段声明的好处是,数据不包含在可执行文件中。在数据段中定义数据时,它必须被包含在可执行程序中,因为必须使用特定值初始化它。因为不使用数据初始化bss段中声明的数据区域,所以内存区域被保留在运行时使用,并且不必包含在最终的程序中。
致谢:

nasm的使用

创建"hello.asm"文件:

touch hello.asm ;使用touch命令创建一个名为hello.asm的空文件
gedit hello.asm ;使用gedit命令编辑hello.asm

在文件中输入下面的汇编代码

section .data
  hello:     db 'Hello world!',10    ; 'Hello world!' plus a linefeed character
  helloLen:  equ $-hello             ; Length of the 'Hello world!' string
                                     ; (I'll explain soon)
 
section .text
  global _start
 
_start:
  mov eax,4            ; The system call for write (sys_write)
  mov ebx,1            ; File descriptor 1 - standard output
  mov ecx,hello        ; Put the offset of hello in ecx
  mov edx,helloLen     ; helloLen is a constant, so we don't need to say
                       ;  mov edx,[helloLen] to get it's actual value
  int 80h              ; Call the kernel
 
  mov eax,1            ; The system call for exit (sys_exit)
  mov ebx,0            ; Exit with return code of 0 (no error)
  int 80h

保存后退出,编译

nasm -f elf64 hello.asm

如果是32位系统就把elf64改为elf32链接

ld -s -o hello hello.o

运行

./hello

终端输出"hello,world!"就没有问题了

致谢:

shellcode—arm

  • as 汇编程序
  • strace 跟踪系统调用的实用程序
  • objdump 用于检查反汇编中的空字节
  • objcopy 从ELF二进制文件中提取原始shellcode
    基本原则
    1. 让我们的shellcode保持紧凑,不要有空字节;原因:我们编写shellcode的目的是它来利用缓冲区溢出等内存损坏漏洞。例如再一些strcpy函数造成的缓冲区漏洞中,strcpy()的工作是复制数据,在收到空字节后停止复制。当我们使用这个溢出来控制程序流时,如果strcpy命中空字节,它将停止复制shellcode,我们的利用就不会起到作用。
    1. 避免库函数调用和绝对内存地址;原因:为了使我们的shellcode尽可能通用,我们不能依赖需要特定依赖关系的库调用和依赖特定环境的绝对内存地址。
      编写shellcode的过程包含一下步骤:

1、了解要使用的系统调用
2、找出系统调用号码和选择系统调用函数所需的参数
3、使shellcode有效化
4、将shellcode转化为Hex字符串

理解系统调用

在深入研究第一个shellcode之前,让我们来编写一个能输出字符串的简单ARM汇编程序。 第一步是查找我们想要使用的系统调用,这个时候应该使用“write”。

size_t write(int fd, const void *buf, size_t count);

从像C这样的高级编程语言的角度来看,这个系统调用示例如下:

const char string[13] = "Azeria Labs\n";
write(1,string,sizeof(string));

从这个原型中可以看到我们需要的参数:

  • fd 标准输出STDOUT,为1
  • buf 指向字符串的指针
  • count 要写入的字节数,这里是13
  • 要写入的系统调用数-> 0x4
    对于前3个参数,可以使用R0,R1和R2。 对于系统调用,我们需要使用R7并将0x4移入其中。
mov r0, #1     ;fd 1 = STDOUT
ldr r1, string ;loading the string from memory to R1
mov r2, #13    ;write 13 bytes to STDOUT
mov r7, #4     ;Syscall 0x4 = write()
svc #0

通过使用以上的代码片段,ARM组装程序如下:

.data
string: .asciz "Azera Labs\n" 
after_string:
.set size_of_string, after_string - string

.text
.global _start

_start:
    mov r0, #1     ;fd 1 = STDOUT
    ldr r1, string ;loading the string from memory to R1
    mov r2, #13    ;write 13 bytes to STDOUT
    mov r7, #4     ;Syscall 0x4 = write()
    swi #0.        ;invoke syscall
    
_exit:
    mov r7, #1.    ;exit syscall
    swi 0.         ;invoke syscall
    
addr_of_string: .word string

在数据部分,我们通过从字符串后面的地址减去字符串开头的地址来计算字符串的大小。 当然,如果我们可以手动计算字符串大小并将结果直接放入R2中,则无需这样做。另外,使用系统调用号为1的exit()来退出程序。

编译并执行:

azeria@labs:~$ as write.s -o write.o && ld write.o -o write
azeria@labs:~$ ./write
Azeria Labs

酷。 现在我们已经了解了这个过程,接下来让我们更详细地研究它,并在ARM程序集中编写一个简单的shellcode。

跟踪系统调用

对于我们的第一个例子,我们将采用以下简单函数并将其转换为ARM程序集:

#include <stdio.h>

void main(void)
{
    system("/bin/sh");
}

第一步是干什么?第一步是确定此函数需要的系统调用以及系统调用所需的参数。可以使用strace对OS内核的系统调用进行跟踪。
将上面的代码保存在文件中,然后在运行strace命令编辑它

azeria@labs:~$ gcc system.c -o system
azeria@labs:~$ strace -h
-f -- follow forks, -ff -- with output into separate files
-v -- verbose mode: print unabbreviated argv, stat, termio[s], etc. args
--- snip --
azeria@labs:~$ strace -f -v system
--- snip --
[pid 4575] execve("/bin/sh", ["/bin/sh"], ["MAIL=/var/mail/pi", "SSH_CLIENT=192.168.200.1 42616 2"..., "USER=pi", "SHLVL=1", "OLDPWD=/home/azeria", "HOME=/home/azeria", "XDG_SESSION_COOKIE=34069147acf8a"..., "SSH_TTY=/dev/pts/1", "LOGNAME=pi", "_=/usr/bin/strace", "TERM=xterm", "PATH=/usr/local/sbin:/usr/local/"..., "LANG=en_US.UTF-8", "LS_COLORS=rs=0:di=01;34:ln=01;36"..., "SHELL=/bin/bash", "EGG=AAAAAAAAAAAAAAAAAAAAAAAAAAAA"..., "LC_ALL=en_US.UTF-8", "PWD=/home/azeria/", "SSH_CONNECTION=192.168.200.1 426"...]) = 0
--- snip --
[pid 4575] write(2, "$ ", 2$ ) = 2
[pid 4575] read(0, exit
--- snip --
exit_group(0) = ?
+++ exited with 0 +++

结果证明,系统函数execve()正在被调用

系统调用编码和参数

下一步是找出execve()的系统调用编号和所需的参数。 您可以通过w3calls或Linux手册页查找系统调用的概述。 这是我们从execve()的手册页中得到的:

NNME
    execve - execve program
SYNOPSIS
    #include <unostd.h>
    int execve(const char *filename, char *constargv[], char *const envp[]);

execve()要求的参数:
- 指向指定二进制路径的字符串的指针
- argv[] 命令行变量数组
- envp[] 环境变量数组
这些基本上可以转换为:execve(filename,argv[],envp[])->execve(filename,0,0).使用以下命令查找此函数的系统调用编号。

azeria@labs:~$ grep execve /usr/include/arm-linux-gnueabihf/asm/unistd.h
#define __NR_execve (__NR_syscall_BASE +11)

查看输出,可以看到execve()的系统调用编号是11。寄存器R0到R2可用于函数参数,而寄存器R7可以存储系统调用编号。

在x86上调用系统调用的工作方式如下:首先,将参数入栈。 然后,系统调用编号被移入EAX寄存器( MOV EAX,syscall_number)。 最后,使用SYSENTER/INT 80调用系统调用。
在ARM上,系统调用的工作方式略有不同:

1.将参数移动到寄存器 - R0,R1,…

2.将系统调用编号移动到寄存器R7中

mov r7,#<syscall_number></syscall_number>
3.产生一个系统调用

  • SVC#0
  • SVC#1
    4.返回值存入R0
    这是它在ARM Assembly中的样子

代码已上传到azeria-labs的Github上

正如您在上图中所看到的,我们首先使用PC相对寻址将R0指向我们的“/bin/sh”字符串。 然后我们将0移动到R1和R2并将系统调用编号11移动到R7。 看起来很简单吧? 让我们看一下使用objdump的第一次尝试的反汇编:

azeria@labs:~$ as execve1.s -o execve1.o
azeria@labs:~$ objdump -d execve1.o
execve1.o: file format elf32-littlearm

Disassembly of section .text:

00000000 <_start>:
 0: e28f000c add r0, pc, #12
 4: e3a01000 mov r1, #0
 8: e3a02000 mov r2, #0
 c: e3a0700b mov r7, #11
 10: ef000000 svc 0x00000000
 14: 6e69622f .word 0x6e69622f
 18: 0068732f .word 0x0068732f

事实证明我们的shellcode中有很多空字节。 下一步是使shellcode有效化,并替换所有涉及的操作。

使shellcode有效化

Thumb模式是减小空字节出现在我们的shellcode中的几率的可用技术之一。这是因为Thumb指令长度为2个字节而不是4个。如果您完成了ARM Assembly Basics教程,就会知道如何从ARM切换到Thumb模式。 如果还没有,我建议您阅读“条件执行和分支”教程的第6部分中有关分支指令“B / BX / BLX”的章节。

在我们的第二次尝试中,我们使用Thumb模式并将包含#0的操作替换为导致0的操作,具体方法是相互减去寄存器或进行异或操作。 例如,不使用“mov r1,#0”,而是使用“sub r1, r1, r1”(r1 = r1-r1)“eor r1,r1,r1”(r1 = r1 xor r1)。 请记住,由于我们现在使用Thumb模式(2字节指令),我们的代码必须是4字节对齐,并且我们需要在末尾添加NOP(例如mov r5,r5)

反汇编的代码如下:

结果是我们只需要摆脱一个空字节。 我们的代码中导致空字节的部分是以空字符结尾的字符串“/bin/sh\ 0”。 我们可以使用以下技术解决此问题:

  • 将“/bin/sh\0”替换为“/bin/shX”
  • 将指令strb(存储字节)与现有的零填充寄存器结合使用,将X替换为空字节
    如图:

这样就没有空字节了。

将shellcode转换为hex string

现在我们创建的shellcode可以转换为它的十六进制表示。 在此之前,最好检查shellcode是否能独立工作。 但是有一个问题:如果我们像通常那样编译汇编文件,它将无法工作。 因为我们用了strb操作来修改代码段(.text)。 这要求代码段是可写的,可以通过在链接过程中添加-N标志来实现。

azeria@labs:~$ ld --help
--- snip --
-N, --omagic Do not page align data, do not make text readonly.
--- snip -- 
azeria@labs:~$ as execve3.s -o execve3.o && ld -N execve3.o -o execve3
azeria@labs:~$ ./execve3
$ whoami
azeria

有用! 恭喜,您已经在ARM程序集中编写了第一个shellcode。

要将其转换为十六进制,请使用以下指令:

azeria@labs:~$ objcopy -O binary execve3 execve3.bin 
azeria@labs:~$ hexdump -v -e '"\\""x" 1/1 "%02x" ""' execve3.bin 
\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\xa0\x49\x40\x52\x40\xc2\x71\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x78

也可以使用简单的python脚本执行相同操作,而不是使用上面的hexdump指令:

#!/usr/bin/env python

import sys

binary = open(sys.argv[1],'rb')

for byte in binary.read():
 sys.stdout.write("\\x"+byte.encode("hex"))

print ""
azeria@labs:~$ ./shellcode.py execve3.bin
\x01\x30\x8f\xe2\x13\xff\x2f\xe1\x02\xa0\x49\x40\x52\x40\xc2\x71\x0b\x27\x01\xdf\x2f\x62\x69\x6e\x2f\x73\x68\x78

致谢:

如何用x86编写shellcode

致谢:

shellcode编写总结

致谢:

pwntools的简单介绍1

致谢:

from pwn import*

你将在全局空间里引用pwntools的所有函数。现在可以用一些简单函数进行汇编,反汇编,pack,unpack等等操作。
将包导入后,我一般都会设置日志记录级别,方便出现问题的时候排查错误

context.log_level = 'debug'

这样设置后,通过管道发送和接收的数据都会被打印在屏幕上。
然后就是连接了,一般题目都会给你一个ip和一个端口,让你用nc连接访问,也有的题是让你通过ssh连接,这两种方式都可以通过pwntools实现。

# 第一种连接方式,通过ip和port去连接
conn = remote('127.0.0.1', 8888)  
# 第二种连接方式,通过ssh连接
shell = ssh(host='192.168.14.144', user='root', port=2222, password='123456')

在编写exp时,最常见的工作就是在整数之间转换,而且转换后,它们的表现形式就是一个字节序列,pwntools提供了打包函数。

# 比如将0xdeadbeef进行32位的打包,将会得到'\xef\xbe\xad\xde'(小端序)
payload = p32(0xdeadbeef)  #pack 32 bits number
payload = p64(0xdeadbeef)  #pack 64 bits number

打包的时候要指定程序是32位还是64位的,他们之间打包后的长度是不同的。
建立连接后就可以发送和接收数据了。

conn.send(data) #发送数据
conn.sendline(data) #发送一行数据,相当于在数据后面加\n
#接收数据,numb制定接收的字节,timeout指定超时
conn.recv(numb = 2048, timeout = default)
#接受一行数据,keepends为是否保留行尾的\n
conn.recvline(keepends=True)
#接受数据直到我们设置的标志出现
conn.recvuntil("Hello,World\n",drop=fasle) 
conn.recvall()  #一直接收直到 EOF
conn.recvrepeat(timeout = default)  #持续接受直到EOF或timeout
#直接进行交互,相当于回到shell的模式,在取得shell之后使用
conn.interactive()

做过pwn题的人应该知道,如果是通过nc连接的话,一进去就相当于开启了一个进程,你只需要发送数据和程序交互就行了。
如果是通过ssh连接进去的,你需要手动创建一个进程,就跟正常通过ssh连接一样,你需要打开一个程序。
比如:

>>> sh = process('/bin/sh')        # 创建进程,这里开了一个shell
>>> sh.sendline('sleep 3; echo hello world;')  # 发送命令
>>> sh.recvline(timeout=1)         # 接收数据
''
>>> sh.recvline(timeout=5)
'hello world\n'
>>> sh.close()                     # 关闭进程

ELF模块用于获取ELF文件的信息,首先使用ELF()获取这个文件的句柄,然后使用这个句柄调用函数,和IO模块很相似。
下面演示了:获取基地址、获取函数地址(基于符号)、获取函数got地址、获取函数plt地址

>>> e = ELF('/bin/cat')
>>> print hex(e.address)  # 文件装载的基地址
0x400000
>>> print hex(e.symbols['write']) # 函数地址
0x401680
>>> print hex(e.got['write']) # GOT表的地址,GOT表为全局函数表
0x60b070
>>> print hex(e.plt['write']) # PLT的地址,PLT表为内部函数表
0x401680

致谢:GOT表和PLT表知识详解
汇编与反汇编

>>> asm('mov eax, 0')   #汇编
'\xb8\x00\x00\x00\x00'
>>> disasm('\xb8\x0b\x00\x00\x00')  #反汇编
'   0:   b8 0b 00 00 00          mov    eax,0xb'

pwnlib.shellcraft模块包含生成shell代码的函数。
其中的子模块声明结构,比如

  • ARM架构: shellcraft.arm
  • AMD64架构: shellcraft.amd64
  • Intel 80386架构: shellcraft.i386
  • 通用: shellcraft.common
    可以通过context设置架构,然后生成shellcode
context(arch='i386', os='linux')
shellcode = asm(shellcraft.sh())

调用gdb调试在python文件中直接设置断点,当运行到该位置之后就会断下

from pwn import *
p = process('./c')
gdb.attach(p)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值